@@ -555,36 +555,23 @@ class ClientSelectorGenerator {
555555 */
556556 private isMeaningfulElement ( element : HTMLElement ) : boolean {
557557 const tagName = element . tagName . toLowerCase ( ) ;
558-
559- // Fast path for common meaningful elements
560- if ( [ "a" , "img" , "input" , "button" , "select" ] . includes ( tagName ) ) {
561- return true ;
558+
559+ if ( tagName === "img" ) {
560+ return element . hasAttribute ( "src" ) ;
562561 }
563562
564- const text = ( element . textContent || "" ) . trim ( ) ;
565- const hasHref = element . hasAttribute ( "href" ) ;
566- const hasSrc = element . hasAttribute ( "src" ) ;
567-
568- // Quick checks first
569- if ( text . length > 0 || hasHref || hasSrc ) {
563+ if ( tagName === "a" && element . hasAttribute ( "href" ) ) {
570564 return true ;
571565 }
572566
573- const isCustomElement = tagName . includes ( "-" ) ;
567+ if ( element . children . length > 0 ) {
568+ return false ;
569+ }
574570
575- // For custom elements, be more lenient about what's considered meaningful
576- if ( isCustomElement ) {
577- const hasChildren = element . children . length > 0 ;
578- const hasSignificantAttributes = Array . from ( element . attributes ) . some (
579- ( attr ) => ! [ "class" , "style" , "id" ] . includes ( attr . name . toLowerCase ( ) )
580- ) ;
571+ const text = ( element . textContent || "" ) . trim ( ) ;
581572
582- return (
583- hasChildren ||
584- hasSignificantAttributes ||
585- element . hasAttribute ( "role" ) ||
586- element . hasAttribute ( "aria-label" )
587- ) ;
573+ if ( text . length > 0 ) {
574+ return true ;
588575 }
589576
590577 return false ;
@@ -2561,12 +2548,9 @@ class ClientSelectorGenerator {
25612548
25622549 const MAX_MEANINGFUL_ELEMENTS = 300 ;
25632550 const MAX_NODES_TO_CHECK = 1200 ;
2564- const MAX_DEPTH = 12 ;
2551+ const MAX_DEPTH = 20 ;
25652552 let nodesChecked = 0 ;
25662553
2567- let adjustedMaxDepth = MAX_DEPTH ;
2568- const elementDensityThreshold = 50 ;
2569-
25702554 const depths : number [ ] = [ 0 ] ;
25712555 let queueIndex = 0 ;
25722556
@@ -2576,14 +2560,10 @@ class ClientSelectorGenerator {
25762560 queueIndex ++ ;
25772561 nodesChecked ++ ;
25782562
2579- if ( currentDepth <= 3 && meaningfulDescendants . length > elementDensityThreshold ) {
2580- adjustedMaxDepth = Math . max ( 6 , adjustedMaxDepth - 2 ) ;
2581- }
2582-
25832563 if (
25842564 nodesChecked > MAX_NODES_TO_CHECK ||
25852565 meaningfulDescendants . length >= MAX_MEANINGFUL_ELEMENTS ||
2586- currentDepth > adjustedMaxDepth
2566+ currentDepth > MAX_DEPTH
25872567 ) {
25882568 break ;
25892569 }
@@ -2592,7 +2572,7 @@ class ClientSelectorGenerator {
25922572 meaningfulDescendants . push ( element ) ;
25932573 }
25942574
2595- if ( currentDepth >= adjustedMaxDepth ) {
2575+ if ( currentDepth >= MAX_DEPTH ) {
25962576 continue ;
25972577 }
25982578
@@ -2607,7 +2587,7 @@ class ClientSelectorGenerator {
26072587 }
26082588 }
26092589
2610- if ( element . shadowRoot && currentDepth < adjustedMaxDepth - 1 ) {
2590+ if ( element . shadowRoot && currentDepth < MAX_DEPTH - 1 ) {
26112591 const shadowChildren = element . shadowRoot . children ;
26122592 const shadowLimit = Math . min ( shadowChildren . length , 20 ) ;
26132593 for ( let i = 0 ; i < shadowLimit ; i ++ ) {
@@ -2716,22 +2696,46 @@ class ClientSelectorGenerator {
27162696 }
27172697
27182698 if ( ! addPositionToAll ) {
2719- const meaningfulAttrs = [ "role" , "type" , "name" , "src" , "aria-label" ] ;
2699+ const meaningfulAttrs = [ "role" , "type" ] ;
27202700 for ( const attrName of meaningfulAttrs ) {
27212701 if ( element . hasAttribute ( attrName ) ) {
27222702 const value = element . getAttribute ( attrName ) ! . replace ( / ' / g, "\\'" ) ;
2723- return `${ tagName } [@${ attrName } ='${ value } ']` ;
2703+ const isCommonAttribute = this . isAttributeCommonAcrossLists (
2704+ element ,
2705+ attrName ,
2706+ value ,
2707+ otherListElements
2708+ ) ;
2709+ if ( isCommonAttribute ) {
2710+ return `${ tagName } [@${ attrName } ='${ value } ']` ;
2711+ }
27242712 }
27252713 }
27262714 }
27272715
27282716 const testId = element . getAttribute ( "data-testid" ) ;
27292717 if ( testId && ! addPositionToAll ) {
2730- return `${ tagName } [@data-testid='${ testId } ']` ;
2718+ const isCommon = this . isAttributeCommonAcrossLists (
2719+ element ,
2720+ "data-testid" ,
2721+ testId ,
2722+ otherListElements
2723+ ) ;
2724+ if ( isCommon ) {
2725+ return `${ tagName } [@data-testid='${ testId } ']` ;
2726+ }
27312727 }
27322728
27332729 if ( element . id && ! element . id . match ( / ^ \d / ) && ! addPositionToAll ) {
2734- return `${ tagName } [@id='${ element . id } ']` ;
2730+ const isCommon = this . isAttributeCommonAcrossLists (
2731+ element ,
2732+ "id" ,
2733+ element . id ,
2734+ otherListElements
2735+ ) ;
2736+ if ( isCommon ) {
2737+ return `${ tagName } [@id='${ element . id } ']` ;
2738+ }
27352739 }
27362740
27372741 if ( ! addPositionToAll ) {
@@ -2742,7 +2746,15 @@ class ClientSelectorGenerator {
27422746 attr . name !== "data-mx-id" &&
27432747 attr . value
27442748 ) {
2745- return `${ tagName } [@${ attr . name } ='${ attr . value } ']` ;
2749+ const isCommon = this . isAttributeCommonAcrossLists (
2750+ element ,
2751+ attr . name ,
2752+ attr . value ,
2753+ otherListElements
2754+ ) ;
2755+ if ( isCommon ) {
2756+ return `${ tagName } [@${ attr . name } ='${ attr . value } ']` ;
2757+ }
27462758 }
27472759 }
27482760 }
@@ -2906,12 +2918,70 @@ class ClientSelectorGenerator {
29062918 const result = pathParts . length > 0 ? "/" + pathParts . join ( "/" ) : null ;
29072919
29082920 this . pathCache . set ( targetElement , result ) ;
2909-
2921+
29102922 return result ;
29112923 }
29122924
2925+ private isAttributeCommonAcrossLists (
2926+ targetElement : HTMLElement ,
2927+ attrName : string ,
2928+ attrValue : string ,
2929+ otherListElements : HTMLElement [ ]
2930+ ) : boolean {
2931+ if ( otherListElements . length === 0 ) {
2932+ return true ;
2933+ }
2934+
2935+ const targetPath = this . getElementPath ( targetElement ) ;
2936+
2937+ for ( const otherListElement of otherListElements ) {
2938+ const correspondingElement = this . findCorrespondingElement (
2939+ otherListElement ,
2940+ targetPath
2941+ ) ;
2942+ if ( correspondingElement ) {
2943+ const otherValue = correspondingElement . getAttribute ( attrName ) ;
2944+ if ( otherValue !== attrValue ) {
2945+ return false ;
2946+ }
2947+ }
2948+ }
2949+
2950+ return true ;
2951+ }
2952+
2953+ private getElementPath ( element : HTMLElement ) : number [ ] {
2954+ const path : number [ ] = [ ] ;
2955+ let current : HTMLElement | null = element ;
2956+
2957+ while ( current && current . parentElement ) {
2958+ const siblings = Array . from ( current . parentElement . children ) ;
2959+ path . unshift ( siblings . indexOf ( current ) ) ;
2960+ current = current . parentElement ;
2961+ }
2962+
2963+ return path ;
2964+ }
2965+
2966+ private findCorrespondingElement (
2967+ rootElement : HTMLElement ,
2968+ path : number [ ]
2969+ ) : HTMLElement | null {
2970+ let current : HTMLElement = rootElement ;
2971+
2972+ for ( const index of path ) {
2973+ const children = Array . from ( current . children ) ;
2974+ if ( index >= children . length ) {
2975+ return null ;
2976+ }
2977+ current = children [ index ] as HTMLElement ;
2978+ }
2979+
2980+ return current ;
2981+ }
2982+
29132983 private getCommonClassesAcrossLists (
2914- targetElement : HTMLElement ,
2984+ targetElement : HTMLElement ,
29152985 otherListElements : HTMLElement [ ]
29162986 ) : string [ ] {
29172987 if ( otherListElements . length === 0 ) {
@@ -3919,9 +3989,48 @@ class ClientSelectorGenerator {
39193989 ) ;
39203990 if ( ! deepestElement ) return null ;
39213991
3992+ if ( ! this . isMeaningfulElementCached ( deepestElement ) ) {
3993+ const atomicChild = this . findAtomicChildAtPoint ( deepestElement , x , y ) ;
3994+ if ( atomicChild ) {
3995+ return atomicChild ;
3996+ }
3997+ }
3998+
39223999 return deepestElement ;
39234000 }
39244001
4002+ private findAtomicChildAtPoint (
4003+ parent : HTMLElement ,
4004+ x : number ,
4005+ y : number
4006+ ) : HTMLElement | null {
4007+ const stack : HTMLElement [ ] = [ parent ] ;
4008+ const visited = new Set < HTMLElement > ( ) ;
4009+
4010+ while ( stack . length > 0 ) {
4011+ const element = stack . pop ( ) ! ;
4012+ if ( visited . has ( element ) ) continue ;
4013+ visited . add ( element ) ;
4014+
4015+ if ( element !== parent && this . isMeaningfulElementCached ( element ) ) {
4016+ const rect = element . getBoundingClientRect ( ) ;
4017+ if ( x >= rect . left && x <= rect . right && y >= rect . top && y <= rect . bottom ) {
4018+ return element ;
4019+ }
4020+ }
4021+
4022+ for ( let i = element . children . length - 1 ; i >= 0 ; i -- ) {
4023+ const child = element . children [ i ] as HTMLElement ;
4024+ const rect = child . getBoundingClientRect ( ) ;
4025+ if ( x >= rect . left && x <= rect . right && y >= rect . top && y <= rect . bottom ) {
4026+ stack . push ( child ) ;
4027+ }
4028+ }
4029+ }
4030+
4031+ return null ;
4032+ }
4033+
39254034 /**
39264035 * Helper methods used by the unified getDeepestElementFromPoint
39274036 */
0 commit comments