@@ -18,7 +18,121 @@ function truncate(str, maxLength) {
1818 return str ;
1919}
2020
21+ /**
22+ * Escapes a string for use in CSS selectors
23+ * @param {String } str - The string to escape
24+ * @returns {String } The escaped string
25+ */
26+ function escapeCSSSelector ( str ) {
27+ // Use the CSS.escape method if available
28+ if ( window . CSS && window . CSS . escape ) {
29+ return window . CSS . escape ( str ) ;
30+ }
31+ // Simple fallback for browsers that don't support CSS.escape
32+ return str
33+ . replace ( / [ ! " # $ % & ' ( ) * + , . / : ; < = > ? @ [ \\ \] ^ ` { | } ~ ] / g, '\\$&' )
34+ . replace ( / ^ \d / , '\\3$& ' ) ;
35+ }
36+
37+ function getFullPathSelector ( elm ) {
38+ console . log ( 'Get Full Path Selector Optimization' ) ;
39+ if ( elm . nodeName === 'HTML' || elm . nodeName === 'BODY' ) {
40+ return elm . nodeName . toLowerCase ( ) ;
41+ }
42+
43+ if ( cache . get ( 'getFullPathSelector' ) === undefined ) {
44+ cache . set ( 'getFullPathSelector' , new WeakMap ( ) ) ;
45+ }
46+
47+ // Check cache first
48+ const sourceCache = cache . get ( 'getFullPathSelector' ) ;
49+ if ( sourceCache . has ( elm ) ) {
50+ return sourceCache . get ( elm ) ;
51+ }
52+
53+ const element = elm ;
54+ const names = [ ] ;
55+ while ( elm . parentElement && elm . nodeName !== 'BODY' ) {
56+ if ( sourceCache . has ( elm ) ) {
57+ names . unshift ( sourceCache . get ( elm ) ) ;
58+ break ;
59+ } else if ( elm . id ) {
60+ // Check if the ID is unique in the document before using it
61+ const escapedId = escapeCSSSelector ( elm . getAttribute ( 'id' ) ) ;
62+ const elementsWithSameId = document . querySelectorAll ( `#${ escapedId } ` ) ;
63+ if ( elementsWithSameId . length === 1 ) {
64+ // ID is unique, safe to use
65+ names . unshift ( '#' + escapedId ) ;
66+ break ;
67+ } else {
68+ // ID is not unique, fallback to position-based selector
69+ let c = 1 ;
70+ let e = elm ;
71+ for ( ; e . previousElementSibling ; e = e . previousElementSibling , c ++ ) {
72+ // Increment counter for each previous sibling
73+ }
74+ names . unshift ( `${ elm . nodeName . toLowerCase ( ) } :nth-child(${ c } )` ) ;
75+ }
76+ } else {
77+ let c = 1 ;
78+ let e = elm ;
79+ for ( ; e . previousElementSibling ; e = e . previousElementSibling , c ++ ) {
80+ // Increment counter for each previous sibling
81+ }
82+ names . unshift ( `${ elm . nodeName . toLowerCase ( ) } :nth-child(${ c } )` ) ;
83+ }
84+ elm = elm . parentElement ;
85+ }
86+
87+ const selector = names . join ( '>' ) ;
88+ sourceCache . set ( element , selector ) ;
89+ return selector ;
90+ }
91+
92+ function getSourceOpt ( element ) {
93+ console . log ( 'Get Source optimization' ) ;
94+ if ( ! element ) {
95+ return '' ;
96+ }
97+
98+ // Initialize cache if needed
99+ if ( cache . get ( 'getSourceEfficient' ) === undefined ) {
100+ cache . set ( 'getSourceEfficient' , new WeakMap ( ) ) ;
101+ }
102+
103+ // Check cache first
104+ const sourceCache = cache . get ( 'getSourceEfficient' ) ;
105+ if ( sourceCache . has ( element ) ) {
106+ return sourceCache . get ( element ) ;
107+ }
108+
109+ // Compute value if not cached
110+ const tagName = element . nodeName ?. toLowerCase ( ) ;
111+ if ( ! tagName ) {
112+ return '' ;
113+ }
114+
115+ let result ;
116+ try {
117+ const attributes = Array . from ( element . attributes || [ ] )
118+ . filter ( attr => ! attr . name . startsWith ( 'data-percy-' ) )
119+ . map ( attr => `${ attr . name } ="${ attr . value } "` )
120+ . join ( ' ' ) ;
121+
122+ result = attributes ? `<${ tagName } ${ attributes } >` : `<${ tagName } >` ;
123+ result = truncate ( result , 300 ) ; // Truncate to 300 characters
124+ // Store in cache
125+ sourceCache . set ( element , result ) ;
126+ } catch ( e ) {
127+ // Handle potential errors (like accessing attributes on non-element nodes)
128+ result = `<${ tagName || 'unknown' } >` ;
129+ }
130+
131+ return result ;
132+ }
133+
21134function getSource ( element ) {
135+ console . log ( 'Get Source' ) ;
22136 if ( ! element ?. outerHTML ) {
23137 return '' ;
24138 }
@@ -84,7 +198,11 @@ function DqElement(elm, options = null, spec = {}) {
84198 this . source = null ;
85199 // TODO: es-modules_audit
86200 if ( ! axe . _audit . noHtml ) {
87- this . source = this . spec . source ?? getSource ( this . _element ) ;
201+ if ( axe . _cache . get ( 'runTypeAOpt' ) ) {
202+ this . source = this . spec . source ?? getSourceOpt ( this . _element ) ;
203+ } else {
204+ this . source = this . spec . source ?? getSource ( this . _element ) ;
205+ }
88206 }
89207}
90208
@@ -94,6 +212,11 @@ DqElement.prototype = {
94212 * @return {String }
95213 */
96214 get selector ( ) {
215+ if ( axe . _cache . get ( 'runTypeAOpt' ) ) {
216+ return (
217+ this . spec . selector || [ getFullPathSelector ( this . element , this . _options ) ]
218+ ) ;
219+ }
97220 return this . spec . selector || [ getSelector ( this . element , this . _options ) ] ;
98221 } ,
99222
0 commit comments