@@ -84,11 +84,11 @@ public extension AXUIElement {
8484 var isHidden : Bool {
8585 ( try ? copyValue ( key: kAXHiddenAttribute) ) ?? false
8686 }
87-
87+
8888 var debugDescription : String {
8989 " < \( title) > < \( description) > < \( label) > ( \( role) : \( roleDescription) ) [ \( identifier) ] \( rect ?? . zero) \( children. count) children "
9090 }
91-
91+
9292 var debugEnumerateChildren : String {
9393 var result = " > " + debugDescription + " \n "
9494 result += children. map {
@@ -98,7 +98,7 @@ public extension AXUIElement {
9898 } . joined ( separator: " \n " )
9999 return result
100100 }
101-
101+
102102 var debugEnumerateParents : String {
103103 var chain : [ String ] = [ ]
104104 chain. append ( " * " + debugDescription)
@@ -166,7 +166,7 @@ public extension AXUIElement {
166166 var isFullScreen : Bool {
167167 ( try ? copyValue ( key: " AXFullScreen " ) ) ?? false
168168 }
169-
169+
170170 var windowID : CGWindowID ? {
171171 var identifier : CGWindowID = 0
172172 let error = AXUIElementGetWindow ( self , & identifier)
@@ -265,7 +265,7 @@ public extension AXUIElement {
265265 fatalError ( " AXUIElement.children: Exceeding recommended depth. " )
266266 }
267267 #endif
268-
268+
269269 var all = [ AXUIElement] ( )
270270 for child in children {
271271 if match ( child) { all. append ( child) }
@@ -282,11 +282,19 @@ public extension AXUIElement {
282282 return parent. firstParent ( where: match)
283283 }
284284
285- func firstChild( depth: Int = 0 , where match: ( AXUIElement ) -> Bool ) -> AXUIElement ? {
285+ func firstChild(
286+ depth: Int = 0 ,
287+ maxDepth: Int = 50 ,
288+ where match: ( AXUIElement ) -> Bool
289+ ) -> AXUIElement ? {
286290 #if DEBUG
287- if depth >= 50 {
291+ if depth > maxDepth {
288292 fatalError ( " AXUIElement.firstChild: Exceeding recommended depth. " )
289293 }
294+ #else
295+ if depth > maxDepth {
296+ return nil
297+ }
290298 #endif
291299 for child in children {
292300 if match ( child) { return child }
@@ -313,11 +321,11 @@ public extension AXUIElement {
313321}
314322
315323public extension AXUIElement {
316- enum SearchNextStep {
324+ enum SearchNextStep < Info > {
317325 case skipDescendants
318- case skipSiblings
326+ case skipSiblings( Info )
319327 case skipDescendantsAndSiblings
320- case continueSearching
328+ case continueSearching( Info )
321329 case stopSearching
322330 }
323331
@@ -327,34 +335,79 @@ public extension AXUIElement {
327335 /// **performance of Xcode**. Please make sure to skip as much as possible.
328336 ///
329337 /// - todo: Make it not recursive.
330- func traverse( _ handle: ( _ element: AXUIElement , _ level: Int ) -> SearchNextStep ) {
338+ func traverse< Info> (
339+ access: ( AXUIElement ) -> [ AXUIElement ] = { $0. children } ,
340+ info: Info ,
341+ file: StaticString = #file,
342+ line: UInt = #line,
343+ function: StaticString = #function,
344+ _ handle: ( _ element: AXUIElement , _ level: Int , _ info: Info ) -> SearchNextStep < Info >
345+ ) {
346+ #if DEBUG
347+ var count = 0
348+ let startDate = Date ( )
349+ #endif
331350 func _traverse(
332351 element: AXUIElement ,
333352 level: Int ,
334- handle: ( AXUIElement , Int ) -> SearchNextStep
335- ) -> SearchNextStep {
336- let nextStep = handle ( element, level)
353+ info: Info ,
354+ handle: ( AXUIElement , Int , Info ) -> SearchNextStep < Info >
355+ ) -> SearchNextStep < Info > {
356+ #if DEBUG
357+ count += 1
358+ #endif
359+ let nextStep = handle ( element, level, info)
337360 switch nextStep {
338361 case . stopSearching: return . stopSearching
339- case . skipDescendants: return . continueSearching
340- case . skipDescendantsAndSiblings: return . skipSiblings
341- case . continueSearching, . skipSiblings:
342- for child in element. children {
343- switch _traverse ( element: child, level: level + 1 , handle: handle) {
362+ case . skipDescendants: return . continueSearching( info )
363+ case . skipDescendantsAndSiblings: return . skipSiblings( info )
364+ case let . continueSearching( info ) , let . skipSiblings( info ) :
365+ loop: for child in access ( element) {
366+ switch _traverse ( element: child, level: level + 1 , info : info , handle: handle) {
344367 case . skipSiblings, . skipDescendantsAndSiblings:
345- break
368+ break loop
346369 case . stopSearching:
347370 return . stopSearching
348371 case . continueSearching, . skipDescendants:
349- continue
372+ continue loop
350373 }
351374 }
352375
353376 return nextStep
354377 }
355378 }
356379
357- _ = _traverse ( element: self , level: 0 , handle: handle)
380+ _ = _traverse ( element: self , level: 0 , info: info, handle: handle)
381+
382+ #if DEBUG
383+ let duration = Date ( ) . timeIntervalSince ( startDate)
384+ . formatted ( . number. precision ( . fractionLength( 0 ... 4 ) ) )
385+ Logger . service. debug (
386+ " AXUIElement.traverse count: \( count) , took \( duration) seconds " ,
387+ file: file,
388+ line: line,
389+ function: function
390+ )
391+ #endif
392+ }
393+
394+ /// Traversing the element tree.
395+ ///
396+ /// - important: Traversing the element tree is resource consuming and will affect the
397+ /// **performance of Xcode**. Please make sure to skip as much as possible.
398+ ///
399+ /// - todo: Make it not recursive.
400+ func traverse(
401+ access: ( AXUIElement ) -> [ AXUIElement ] = { $0. children } ,
402+ file: StaticString = #file,
403+ line: UInt = #line,
404+ function: StaticString = #function,
405+ _ handle: ( _ element: AXUIElement , _ level: Int ) -> SearchNextStep < Void >
406+ ) {
407+ traverse ( access: access, info: ( ) , file: file, line: line, function: function) {
408+ element, level, _ in
409+ handle ( element, level)
410+ }
358411 }
359412}
360413
0 commit comments