@@ -555,36 +555,24 @@ 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" ) ;
561+ }
562+
563+ if ( element . children . length > 0 ) {
564+ return false ;
562565 }
563566
564567 const text = ( element . textContent || "" ) . trim ( ) ;
565568 const hasHref = element . hasAttribute ( "href" ) ;
566- const hasSrc = element . hasAttribute ( "src" ) ;
567-
568- // Quick checks first
569- if ( text . length > 0 || hasHref || hasSrc ) {
569+
570+ if ( text . length > 0 ) {
570571 return true ;
571572 }
572573
573- const isCustomElement = tagName . includes ( "-" ) ;
574-
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- ) ;
581-
582- return (
583- hasChildren ||
584- hasSignificantAttributes ||
585- element . hasAttribute ( "role" ) ||
586- element . hasAttribute ( "aria-label" )
587- ) ;
574+ if ( tagName === "a" && hasHref ) {
575+ return true ;
588576 }
589577
590578 return false ;
@@ -2561,12 +2549,9 @@ class ClientSelectorGenerator {
25612549
25622550 const MAX_MEANINGFUL_ELEMENTS = 300 ;
25632551 const MAX_NODES_TO_CHECK = 1200 ;
2564- const MAX_DEPTH = 12 ;
2552+ const MAX_DEPTH = 20 ;
25652553 let nodesChecked = 0 ;
25662554
2567- let adjustedMaxDepth = MAX_DEPTH ;
2568- const elementDensityThreshold = 50 ;
2569-
25702555 const depths : number [ ] = [ 0 ] ;
25712556 let queueIndex = 0 ;
25722557
@@ -2576,14 +2561,10 @@ class ClientSelectorGenerator {
25762561 queueIndex ++ ;
25772562 nodesChecked ++ ;
25782563
2579- if ( currentDepth <= 3 && meaningfulDescendants . length > elementDensityThreshold ) {
2580- adjustedMaxDepth = Math . max ( 6 , adjustedMaxDepth - 2 ) ;
2581- }
2582-
25832564 if (
25842565 nodesChecked > MAX_NODES_TO_CHECK ||
25852566 meaningfulDescendants . length >= MAX_MEANINGFUL_ELEMENTS ||
2586- currentDepth > adjustedMaxDepth
2567+ currentDepth > MAX_DEPTH
25872568 ) {
25882569 break ;
25892570 }
@@ -2592,7 +2573,7 @@ class ClientSelectorGenerator {
25922573 meaningfulDescendants . push ( element ) ;
25932574 }
25942575
2595- if ( currentDepth >= adjustedMaxDepth ) {
2576+ if ( currentDepth >= MAX_DEPTH ) {
25962577 continue ;
25972578 }
25982579
@@ -2607,7 +2588,7 @@ class ClientSelectorGenerator {
26072588 }
26082589 }
26092590
2610- if ( element . shadowRoot && currentDepth < adjustedMaxDepth - 1 ) {
2591+ if ( element . shadowRoot && currentDepth < MAX_DEPTH - 1 ) {
26112592 const shadowChildren = element . shadowRoot . children ;
26122593 const shadowLimit = Math . min ( shadowChildren . length , 20 ) ;
26132594 for ( let i = 0 ; i < shadowLimit ; i ++ ) {
@@ -2716,22 +2697,46 @@ class ClientSelectorGenerator {
27162697 }
27172698
27182699 if ( ! addPositionToAll ) {
2719- const meaningfulAttrs = [ "role" , "type" , "name" , "src" , "aria-label" ] ;
2700+ const meaningfulAttrs = [ "role" , "type" ] ;
27202701 for ( const attrName of meaningfulAttrs ) {
27212702 if ( element . hasAttribute ( attrName ) ) {
27222703 const value = element . getAttribute ( attrName ) ! . replace ( / ' / g, "\\'" ) ;
2723- return `${ tagName } [@${ attrName } ='${ value } ']` ;
2704+ const isCommonAttribute = this . isAttributeCommonAcrossLists (
2705+ element ,
2706+ attrName ,
2707+ value ,
2708+ otherListElements
2709+ ) ;
2710+ if ( isCommonAttribute ) {
2711+ return `${ tagName } [@${ attrName } ='${ value } ']` ;
2712+ }
27242713 }
27252714 }
27262715 }
27272716
27282717 const testId = element . getAttribute ( "data-testid" ) ;
27292718 if ( testId && ! addPositionToAll ) {
2730- return `${ tagName } [@data-testid='${ testId } ']` ;
2719+ const isCommon = this . isAttributeCommonAcrossLists (
2720+ element ,
2721+ "data-testid" ,
2722+ testId ,
2723+ otherListElements
2724+ ) ;
2725+ if ( isCommon ) {
2726+ return `${ tagName } [@data-testid='${ testId } ']` ;
2727+ }
27312728 }
27322729
27332730 if ( element . id && ! element . id . match ( / ^ \d / ) && ! addPositionToAll ) {
2734- return `${ tagName } [@id='${ element . id } ']` ;
2731+ const isCommon = this . isAttributeCommonAcrossLists (
2732+ element ,
2733+ "id" ,
2734+ element . id ,
2735+ otherListElements
2736+ ) ;
2737+ if ( isCommon ) {
2738+ return `${ tagName } [@id='${ element . id } ']` ;
2739+ }
27352740 }
27362741
27372742 if ( ! addPositionToAll ) {
@@ -2742,7 +2747,15 @@ class ClientSelectorGenerator {
27422747 attr . name !== "data-mx-id" &&
27432748 attr . value
27442749 ) {
2745- return `${ tagName } [@${ attr . name } ='${ attr . value } ']` ;
2750+ const isCommon = this . isAttributeCommonAcrossLists (
2751+ element ,
2752+ attr . name ,
2753+ attr . value ,
2754+ otherListElements
2755+ ) ;
2756+ if ( isCommon ) {
2757+ return `${ tagName } [@${ attr . name } ='${ attr . value } ']` ;
2758+ }
27462759 }
27472760 }
27482761 }
@@ -2906,12 +2919,70 @@ class ClientSelectorGenerator {
29062919 const result = pathParts . length > 0 ? "/" + pathParts . join ( "/" ) : null ;
29072920
29082921 this . pathCache . set ( targetElement , result ) ;
2909-
2922+
29102923 return result ;
29112924 }
29122925
2926+ private isAttributeCommonAcrossLists (
2927+ targetElement : HTMLElement ,
2928+ attrName : string ,
2929+ attrValue : string ,
2930+ otherListElements : HTMLElement [ ]
2931+ ) : boolean {
2932+ if ( otherListElements . length === 0 ) {
2933+ return true ;
2934+ }
2935+
2936+ const targetPath = this . getElementPath ( targetElement ) ;
2937+
2938+ for ( const otherListElement of otherListElements ) {
2939+ const correspondingElement = this . findCorrespondingElement (
2940+ otherListElement ,
2941+ targetPath
2942+ ) ;
2943+ if ( correspondingElement ) {
2944+ const otherValue = correspondingElement . getAttribute ( attrName ) ;
2945+ if ( otherValue !== attrValue ) {
2946+ return false ;
2947+ }
2948+ }
2949+ }
2950+
2951+ return true ;
2952+ }
2953+
2954+ private getElementPath ( element : HTMLElement ) : number [ ] {
2955+ const path : number [ ] = [ ] ;
2956+ let current : HTMLElement | null = element ;
2957+
2958+ while ( current && current . parentElement ) {
2959+ const siblings = Array . from ( current . parentElement . children ) ;
2960+ path . unshift ( siblings . indexOf ( current ) ) ;
2961+ current = current . parentElement ;
2962+ }
2963+
2964+ return path ;
2965+ }
2966+
2967+ private findCorrespondingElement (
2968+ rootElement : HTMLElement ,
2969+ path : number [ ]
2970+ ) : HTMLElement | null {
2971+ let current : HTMLElement = rootElement ;
2972+
2973+ for ( const index of path ) {
2974+ const children = Array . from ( current . children ) ;
2975+ if ( index >= children . length ) {
2976+ return null ;
2977+ }
2978+ current = children [ index ] as HTMLElement ;
2979+ }
2980+
2981+ return current ;
2982+ }
2983+
29132984 private getCommonClassesAcrossLists (
2914- targetElement : HTMLElement ,
2985+ targetElement : HTMLElement ,
29152986 otherListElements : HTMLElement [ ]
29162987 ) : string [ ] {
29172988 if ( otherListElements . length === 0 ) {
@@ -3919,9 +3990,48 @@ class ClientSelectorGenerator {
39193990 ) ;
39203991 if ( ! deepestElement ) return null ;
39213992
3993+ if ( ! this . isMeaningfulElementCached ( deepestElement ) ) {
3994+ const atomicChild = this . findAtomicChildAtPoint ( deepestElement , x , y ) ;
3995+ if ( atomicChild ) {
3996+ return atomicChild ;
3997+ }
3998+ }
3999+
39224000 return deepestElement ;
39234001 }
39244002
4003+ private findAtomicChildAtPoint (
4004+ parent : HTMLElement ,
4005+ x : number ,
4006+ y : number
4007+ ) : HTMLElement | null {
4008+ const stack : HTMLElement [ ] = [ parent ] ;
4009+ const visited = new Set < HTMLElement > ( ) ;
4010+
4011+ while ( stack . length > 0 ) {
4012+ const element = stack . pop ( ) ! ;
4013+ if ( visited . has ( element ) ) continue ;
4014+ visited . add ( element ) ;
4015+
4016+ if ( element !== parent && this . isMeaningfulElementCached ( element ) ) {
4017+ const rect = element . getBoundingClientRect ( ) ;
4018+ if ( x >= rect . left && x <= rect . right && y >= rect . top && y <= rect . bottom ) {
4019+ return element ;
4020+ }
4021+ }
4022+
4023+ for ( let i = element . children . length - 1 ; i >= 0 ; i -- ) {
4024+ const child = element . children [ i ] as HTMLElement ;
4025+ const rect = child . getBoundingClientRect ( ) ;
4026+ if ( x >= rect . left && x <= rect . right && y >= rect . top && y <= rect . bottom ) {
4027+ stack . push ( child ) ;
4028+ }
4029+ }
4030+ }
4031+
4032+ return null ;
4033+ }
4034+
39254035 /**
39264036 * Helper methods used by the unified getDeepestElementFromPoint
39274037 */
0 commit comments