Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[[Installation](#installation) • [Commands](#commands) • [Custom Commands](#custom-commands) • [Development Workflow](#development-workflow) [Contributing](#contributing) • [License](#license)]

For a comprehensive overview of LLDB, and how Chisel compliments it, read Ari Grant's [Dancing in the Debugger — A Waltz with LLDB](http://www.objc.io/issue-19/lldb-debugging.html) in issue 19 of [objc.io](http://www.objc.io/).
For a comprehensive overview of LLDB, and how Chisel complements it, read Ari Grant's [Dancing in the Debugger — A Waltz with LLDB](http://www.objc.io/issue-19/lldb-debugging.html) in issue 19 of [objc.io](http://www.objc.io/).

## Installation

Expand Down
106 changes: 106 additions & 0 deletions commands/FBAccessibilityCommands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/python

# Copyright (c) 2015, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.

import re
import os

import lldb
import fblldbbase as fb
import fblldbobjecthelpers as objHelpers

# This is the key corresponding to accessibility label in _accessibilityElementsInContainer:
ACCESSIBILITY_LABEL_KEY = 2001

def lldbcommands():
return [
FBPrintAccessibilityLabels(),
FBFindViewByAccessibilityLabelCommand(),
]

class FBPrintAccessibilityLabels(fb.FBCommand):
def name(self):
return 'pa11y'

def description(self):
return 'Print accessibility labels of all views in hierarchy of <aView>'

def args(self):
return [ fb.FBCommandArgument(arg='aView', type='UIView*', help='The view to print the hierarchy of.', default='(id)[[UIApplication sharedApplication] keyWindow]') ]

def run(self, arguments, options):
forceStartAccessibilityServer();
printAccessibilityHierarchy(arguments[0])

class FBFindViewByAccessibilityLabelCommand(fb.FBCommand):
def name(self):
return 'fa11y'

def description(self):
return 'Find the views whose accessibility labels match labelRegex and puts the address of the first result on the clipboard.'

def args(self):
return [ fb.FBCommandArgument(arg='labelRegex', type='string', help='The accessibility label regex to search the view hierarchy for.') ]

def accessibilityGrepHierarchy(self, view, needle):
a11yLabel = accessibilityLabel(view)
#if we don't have any accessibility string - we should have some children
if int(a11yLabel.GetValue(), 16) == 0:
#We call private method that gives back all visible accessibility children for view
accessibilityElements = fb.evaluateObjectExpression('[[[UIApplication sharedApplication] keyWindow] _accessibilityElementsInContainer:0 topLevel:%s includeKB:0]' % view)
accessibilityElementsCount = fb.evaluateIntegerExpression('[%s count]' % accessibilityElements)
for index in range(0, accessibilityElementsCount):
subview = fb.evaluateObjectExpression('[%s objectAtIndex:%i]' % (accessibilityElements, index))
self.accessibilityGrepHierarchy(subview, needle)
elif re.match(r'.*' + needle + '.*', a11yLabel.GetObjectDescription(), re.IGNORECASE):
classDesc = objHelpers.className(view)
print('({} {}) {}'.format(classDesc, view, a11yLabel.GetObjectDescription()))

#First element that is found is copied to clipboard
if not self.foundElement:
self.foundElement = True
cmd = 'echo %s | tr -d "\n" | pbcopy' % view
os.system(cmd)

def run(self, arguments, options):
forceStartAccessibilityServer()
rootView = fb.evaluateObjectExpression('[[UIApplication sharedApplication] keyWindow]')
self.foundElement = False
self.accessibilityGrepHierarchy(rootView, arguments[0])

def forceStartAccessibilityServer():
#We try to start accessibility server only if we don't have needed method active
if not fb.evaluateBooleanExpression('[UIView instancesRespondToSelector:@selector(_accessibilityElementsInContainer:)]'):
#Starting accessibility server is different for simulator and device
if fb.evaluateExpressionValue('(id)[[UIDevice currentDevice] model]').GetObjectDescription().lower().find('simulator') >= 0:
lldb.debugger.HandleCommand('expr (void)[[UIApplication sharedApplication] accessibilityActivate]')
else:
lldb.debugger.HandleCommand('expr (void)[[[UIApplication sharedApplication] _accessibilityBundlePrincipalClass] _accessibilityStartServer]')

def accessibilityLabel(view):
#using Apple private API to get real value of accessibility string for element.
return fb.evaluateExpressionValue('(id)[%s accessibilityAttributeValue:%i]' % (view, ACCESSIBILITY_LABEL_KEY), False)

def printAccessibilityHierarchy(view, indent = 0):
a11yLabel = accessibilityLabel(view)
classDesc = objHelpers.className(view)
indentString = ' | ' * indent

#if we don't have any accessibility string - we should have some children
if int(a11yLabel.GetValue(), 16) == 0:
print indentString + ('{} {}'.format(classDesc, view))
#We call private method that gives back all visible accessibility children for view
accessibilityElements = fb.evaluateObjectExpression('[[[UIApplication sharedApplication] keyWindow] _accessibilityElementsInContainer:0 topLevel:%s includeKB:0]' % view)
accessibilityElementsCount = int(fb.evaluateExpression('(int)[%s count]' % accessibilityElements))
for index in range(0, accessibilityElementsCount):
subview = fb.evaluateObjectExpression('[%s objectAtIndex:%i]' % (accessibilityElements, index))
printAccessibilityHierarchy(subview, indent + 1)
else:
print indentString + ('({} {}) {}'.format(classDesc, view, a11yLabel.GetObjectDescription()))


28 changes: 0 additions & 28 deletions commands/FBFindCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def lldbcommands():
return [
FBFindViewControllerCommand(),
FBFindViewCommand(),
FBFindViewByAccessibilityLabelCommand(),
FBTapLoggerCommand(),
]

Expand Down Expand Up @@ -104,33 +103,6 @@ def printMatchesInViewOutputStringAndCopyFirstToClipboard(needle, haystack):
os.system(cmd)


class FBFindViewByAccessibilityLabelCommand(fb.FBCommand):
def name(self):
return 'fa11y'

def description(self):
return 'Find the views whose accessibility labels match labelRegex and puts the address of the first result on the clipboard.'

def args(self):
return [ fb.FBCommandArgument(arg='labelRegex', type='string', help='The accessibility label regex to search the view hierarchy for.') ]

def run(self, arguments, options):
first = None
haystack = fb.evaluateExpressionValue('(id)[[[UIApplication sharedApplication] keyWindow] recursiveDescription]').GetObjectDescription()
needle = arguments[0]

allViews = re.findall('.* (0x[0-9a-fA-F]*);.*', haystack)
for view in allViews:
a11yLabel = fb.evaluateExpressionValue('(id)[(' + view + ') accessibilityLabel]').GetObjectDescription()
if re.match(r'.*' + needle + '.*', a11yLabel, re.IGNORECASE):
print('{} {}'.format(view, a11yLabel))

if first is None:
first = view
cmd = 'echo %s | tr -d "\n" | pbcopy' % first
os.system(cmd)


class FBTapLoggerCommand(fb.FBCommand):
def name(self):
return 'taplog'
Expand Down
17 changes: 15 additions & 2 deletions fblldbbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,27 @@ def run(self, arguments, option):
pass


def evaluateExpressionValue(expression, printErrors=True):
def evaluateExpressionValueWithLanguage(expression, language, printErrors):
# lldb.frame is supposed to contain the right frame, but it doesnt :/ so do the dance
frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
value = frame.EvaluateExpression(expression)
expr_options = lldb.SBExpressionOptions()
expr_options.SetLanguage(language) # requires lldb r210874 (2014-06-13) / Xcode 6
value = frame.EvaluateExpression(expression, expr_options)
if printErrors and value.GetError() is not None and str(value.GetError()) != 'success':
print value.GetError()
return value

def evaluateExpressionValueInFrameLanguage(expression, printErrors=True):
# lldb.frame is supposed to contain the right frame, but it doesnt :/ so do the dance
frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
language = frame.GetCompileUnit().GetLanguage() # requires lldb r222189 (2014-11-17)
return evaluateExpressionValueWithLanguage(expression, language, printErrors)

# evaluates expression in Objective-C++ context, so it will work even for
# Swift projects
def evaluateExpressionValue(expression, printErrors=True):
return evaluateExpressionValueWithLanguage(expression, lldb.eLanguageTypeObjC_plus_plus, printErrors)

def evaluateIntegerExpression(expression, printErrors=True):
output = evaluateExpression('(int)(' + expression + ')', printErrors).replace('\'', '')
if output.startswith('\\x'): # Booleans may display as \x01 (Hex)
Expand Down