-
Notifications
You must be signed in to change notification settings - Fork 800
/
FBFindCommands.py
155 lines (125 loc) · 5.94 KB
/
FBFindCommands.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/python
# Copyright (c) 2014, 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 os
import re
import lldb
import fblldbbase as fb
import fblldbobjcruntimehelpers as objc
import fblldbviewcontrollerhelpers as vcHelpers
def lldbcommands():
return [
FBFindViewControllerCommand(),
FBFindViewCommand(),
FBFindViewByAccessibilityLabelCommand(),
FBTapLoggerCommand(),
]
class FBFindViewControllerCommand(fb.FBCommand):
def name(self):
return 'fvc'
def description(self):
return 'Find the view controllers whose class names match classNameRegex and puts the address of first on the clipboard.'
def options(self):
return [
fb.FBCommandArgument(short='-n', long='--name', arg='classNameRegex', type='string', help='The view-controller-class regex to search the view controller hierarchy for.'),
fb.FBCommandArgument(short='-v', long='--view', arg='view', type='UIView', help='This function will print the View Controller that owns this view.')
]
def run(self, arguments, options):
if options.classNameRegex and options.view:
print("Do not set both the --name and --view flags")
elif options.view:
self.findOwningViewController(options.view)
else:
output = vcHelpers.viewControllerRecursiveDescription('(id)[[[UIApplication sharedApplication] keyWindow] rootViewController]')
searchString = options.classNameRegex if options.classNameRegex else arguments[0]
printMatchesInViewOutputStringAndCopyFirstToClipboard(searchString, output)
def findOwningViewController(self, object):
while object:
if self.isViewController(object):
description = fb.evaluateExpressionValue(object).GetObjectDescription()
print("Found the owning view controller.\n{}".format(description))
cmd = 'echo {} | tr -d "\n" | pbcopy'.format(object)
os.system(cmd)
return
else:
object = self.nextResponder(object)
print("Could not find an owning view controller")
@staticmethod
def isViewController(object):
command = '[(id){} isKindOfClass:[UIViewController class]]'.format(object)
isVC = fb.evaluateBooleanExpression(command)
return isVC
@staticmethod
def nextResponder(object):
command = '[((id){}) nextResponder]'.format(object)
nextResponder = fb.evaluateObjectExpression(command)
try:
if int(nextResponder, 0):
return nextResponder
else:
return None
except:
return None
class FBFindViewCommand(fb.FBCommand):
def name(self):
return 'fv'
def description(self):
return 'Find the views whose class names match classNameRegex and puts the address of first on the clipboard.'
def args(self):
return [ fb.FBCommandArgument(arg='classNameRegex', type='string', help='The view-class regex to search the view hierarchy for.') ]
def run(self, arguments, options):
output = fb.evaluateExpressionValue('(id)[[[UIApplication sharedApplication] keyWindow] recursiveDescription]').GetObjectDescription()
printMatchesInViewOutputStringAndCopyFirstToClipboard(arguments[0], output)
def printMatchesInViewOutputStringAndCopyFirstToClipboard(needle, haystack):
first = None
for match in re.finditer('.*<.*(' + needle + ').*: (0x[0-9a-fA-F]*);.*', haystack, re.IGNORECASE):
view = match.groups()[-1]
className = fb.evaluateExpressionValue('(id)[(' + view + ') class]').GetObjectDescription()
print('{} {}'.format(view, className))
if first == None:
first = view
cmd = 'echo %s | tr -d "\n" | pbcopy' % view
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 == None:
first = view
cmd = 'echo %s | tr -d "\n" | pbcopy' % first
os.system(cmd)
class FBTapLoggerCommand(fb.FBCommand):
def name(self):
return 'taplog'
def description(self):
return 'Log tapped view to the console.'
def run(self, arguments, options):
parameterExpr = objc.functionPreambleExpressionForObjectParameterAtIndex(0)
breakpoint = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("-[UIApplication sendEvent:]")
breakpoint.SetCondition('(int)[' + parameterExpr + ' type] == 0 && (int)[[[' + parameterExpr + ' allTouches] anyObject] phase] == 0')
breakpoint.SetOneShot(True)
lldb.debugger.HandleCommand('breakpoint command add -s python -F "sys.modules[\'' + __name__ + '\'].' + self.__class__.__name__ + '.taplog_callback" ' + str(breakpoint.id))
lldb.debugger.SetAsync(True)
lldb.debugger.HandleCommand('continue')
@staticmethod
def taplog_callback(frame, bp_loc, internal_dict):
parameterExpr = objc.functionPreambleExpressionForObjectParameterAtIndex(0)
lldb.debugger.HandleCommand('po [[[%s allTouches] anyObject] view]' % (parameterExpr))
# We don't want to proceed event (click on button for example), so we just skip it
lldb.debugger.HandleCommand('thread return')