Skip to content

Commit 6e3cbe0

Browse files
committed
Dq element optimization
1 parent fd6b33f commit 6e3cbe0

File tree

1 file changed

+124
-1
lines changed

1 file changed

+124
-1
lines changed

lib/core/utils/dq-element.js

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
21134
function 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

Comments
 (0)