@percy/dom 1.32.8-beta.2 → 1.32.8-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.js +70 -28
- package/package.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -209,7 +209,9 @@
|
|
|
209
209
|
ignoreIframeSelectors,
|
|
210
210
|
forceShadowAsLightDOM,
|
|
211
211
|
maxIframeDepth,
|
|
212
|
-
iframeDepth = 0
|
|
212
|
+
iframeDepth = 0,
|
|
213
|
+
pseudoClassEnabledElements,
|
|
214
|
+
pseudoClassSerialization
|
|
213
215
|
}) {
|
|
214
216
|
maxIframeDepth = clampIframeDepth(maxIframeDepth);
|
|
215
217
|
for (let frame of dom.querySelectorAll('iframe')) {
|
|
@@ -278,7 +280,9 @@
|
|
|
278
280
|
forceShadowAsLightDOM,
|
|
279
281
|
ignoreIframeSelectors,
|
|
280
282
|
maxIframeDepth,
|
|
281
|
-
iframeDepth: iframeDepth + 1
|
|
283
|
+
iframeDepth: iframeDepth + 1,
|
|
284
|
+
pseudoClassEnabledElements,
|
|
285
|
+
enablePseudoClassSerialization: pseudoClassSerialization
|
|
282
286
|
});
|
|
283
287
|
|
|
284
288
|
// append serialized warnings and resources
|
|
@@ -360,12 +364,7 @@
|
|
|
360
364
|
// Custom elements with ElementInternals or closed shadow roots also get
|
|
361
365
|
// stamped so the post-clone state-fallback can locate their clones.
|
|
362
366
|
let tagName = (_domElement$tagName = domElement.tagName) === null || _domElement$tagName === void 0 ? void 0 : _domElement$tagName.toLowerCase();
|
|
363
|
-
|
|
364
|
-
// the sheet's clone element and anchor its rewritten interactive-state rules
|
|
365
|
-
// immediately after it — preserving the sheet's original cascade position
|
|
366
|
-
// instead of appending at the end of <head> (PER-10077).
|
|
367
|
-
let isStylesheetLink = tagName === 'link' && /(^|\s)stylesheet(\s|$)/i.test(domElement.getAttribute('rel') || '');
|
|
368
|
-
if (['input', 'textarea', 'select', 'iframe', 'canvas', 'video', 'style', 'dialog'].includes(tagName) || isStylesheetLink || isCustomElement(domElement)) {
|
|
367
|
+
if (['input', 'textarea', 'select', 'iframe', 'canvas', 'video', 'style', 'dialog'].includes(tagName) || isCustomElement(domElement)) {
|
|
369
368
|
if (!domElement.getAttribute('data-percy-element-id')) {
|
|
370
369
|
domElement.setAttribute('data-percy-element-id', uid());
|
|
371
370
|
}
|
|
@@ -433,7 +432,8 @@
|
|
|
433
432
|
resources,
|
|
434
433
|
cache,
|
|
435
434
|
warnings,
|
|
436
|
-
ignoreStyleSheetSerializationErrors
|
|
435
|
+
ignoreStyleSheetSerializationErrors,
|
|
436
|
+
styleSheetClones
|
|
437
437
|
} = ctx;
|
|
438
438
|
// in-memory CSSOM into their respective DOM nodes.
|
|
439
439
|
let styleSheets = null;
|
|
@@ -462,6 +462,7 @@
|
|
|
462
462
|
style.textContent = Array.from(styleSheet.cssRules).map(cssRule => cssRule.cssText).join('\n');
|
|
463
463
|
cloneOwnerNode.parentNode.insertBefore(style, cloneOwnerNode.nextSibling);
|
|
464
464
|
cloneOwnerNode.remove();
|
|
465
|
+
styleSheetClones === null || styleSheetClones === void 0 || styleSheetClones.set(styleSheet.ownerNode, style);
|
|
465
466
|
} catch (err) {
|
|
466
467
|
handleErrors(err, 'Error serializing stylesheet: ', cloneOwnerNode, {
|
|
467
468
|
styleId: styleId
|
|
@@ -778,6 +779,35 @@
|
|
|
778
779
|
return re.test(selectorText);
|
|
779
780
|
});
|
|
780
781
|
}
|
|
782
|
+
function splitSelectorList(selectorText) {
|
|
783
|
+
const parts = [];
|
|
784
|
+
let depth = 0;
|
|
785
|
+
let quote = null;
|
|
786
|
+
let start = 0;
|
|
787
|
+
for (let i = 0; i < selectorText.length; i++) {
|
|
788
|
+
const char = selectorText[i];
|
|
789
|
+
if (quote) {
|
|
790
|
+
if (char === '\\') i++;else if (char === quote) quote = null;
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (char === '"' || char === "'") quote = char;else if (char === '(' || char === '[') depth++;else if (char === ')' || char === ']') depth--;else if (char === ',' && depth === 0) {
|
|
794
|
+
parts.push(selectorText.slice(start, i));
|
|
795
|
+
start = i + 1;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
parts.push(selectorText.slice(start));
|
|
799
|
+
return parts;
|
|
800
|
+
}
|
|
801
|
+
function rewritePseudoSelectorList(selectorText, pseudoList) {
|
|
802
|
+
const pseudoParts = [];
|
|
803
|
+
for (const part of splitSelectorList(selectorText)) {
|
|
804
|
+
const trimmed = part.trim();
|
|
805
|
+
if (trimmed && selectorContainsPseudo(trimmed, pseudoList)) {
|
|
806
|
+
pseudoParts.push(rewritePseudoSelector(trimmed));
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return pseudoParts.length ? pseudoParts.join(', ') : null;
|
|
810
|
+
}
|
|
781
811
|
function rewritePseudoSelector(selectorText) {
|
|
782
812
|
let out = selectorText;
|
|
783
813
|
for (const [pseudo, re] of PSEUDO_RES) out = out.replace(re, PSEUDO_TO_ATTR[pseudo]);
|
|
@@ -978,8 +1008,9 @@
|
|
|
978
1008
|
// the data-attributes are copied through to the clone via cloneNode.
|
|
979
1009
|
function markPseudoClassElements(ctx, config) {
|
|
980
1010
|
ctx._liveMutations = [];
|
|
981
|
-
markInteractiveStates(ctx);
|
|
982
1011
|
markOpenPopovers(ctx);
|
|
1012
|
+
if (!ctx.pseudoClassSerialization) return;
|
|
1013
|
+
markInteractiveStates(ctx);
|
|
983
1014
|
if (config) getElementsToProcess(ctx, config, true);
|
|
984
1015
|
}
|
|
985
1016
|
|
|
@@ -1118,8 +1149,8 @@
|
|
|
1118
1149
|
// interactive pseudo. Skips most rules on most stylesheets without
|
|
1119
1150
|
// touching the regex bank.
|
|
1120
1151
|
if (!rule.selectorText.includes(':')) continue;
|
|
1121
|
-
|
|
1122
|
-
|
|
1152
|
+
const rewrittenSelector = rewritePseudoSelectorList(rule.selectorText, ALL_INTERACTIVE_PSEUDO);
|
|
1153
|
+
if (!rewrittenSelector) continue;
|
|
1123
1154
|
const cssText = `${rewrittenSelector} { ${rule.style.cssText} }`;
|
|
1124
1155
|
const wrapped = rule.wrapper ? `${rule.wrapper} { ${cssText} }` : cssText;
|
|
1125
1156
|
(rewrittenRules || (rewrittenRules = [])).push(wrapped);
|
|
@@ -1156,11 +1187,11 @@
|
|
|
1156
1187
|
// nosemgrep: javascript.browser.security.insecure-document-method.insecure-document-method
|
|
1157
1188
|
styleElement.textContent = rewrittenRules.join('\n');
|
|
1158
1189
|
if (owner === null) {
|
|
1159
|
-
injectAtSheetPosition(ctx.clone, sheet, styleElement, ctx.clone.head || ctx.clone.querySelector('head'));
|
|
1190
|
+
injectAtSheetPosition(ctx, ctx.clone, sheet, styleElement, ctx.clone.head || ctx.clone.querySelector('head'));
|
|
1160
1191
|
} else {
|
|
1161
1192
|
const cloneHost = cloneByPercyId.get(owner.getAttribute('data-percy-element-id'));
|
|
1162
1193
|
if (cloneHost && cloneHost.shadowRoot) {
|
|
1163
|
-
injectAtSheetPosition(cloneHost.shadowRoot, sheet, styleElement, cloneHost.shadowRoot);
|
|
1194
|
+
injectAtSheetPosition(ctx, cloneHost.shadowRoot, sheet, styleElement, cloneHost.shadowRoot);
|
|
1164
1195
|
}
|
|
1165
1196
|
}
|
|
1166
1197
|
}
|
|
@@ -1169,13 +1200,15 @@
|
|
|
1169
1200
|
// Insert an interactive-states <style> immediately AFTER its source sheet's
|
|
1170
1201
|
// clone element so the copy keeps the sheet's original source-order rank —
|
|
1171
1202
|
// later stylesheets still win equal-specificity ties, exactly as on the live
|
|
1172
|
-
// page (PER-10077).
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1203
|
+
// page (PER-10077).
|
|
1204
|
+
function injectAtSheetPosition(ctx, scopeRoot, sheet, styleElement, fallbackTarget) {
|
|
1205
|
+
var _ctx$styleSheetClones, _anchor;
|
|
1206
|
+
const ownerNode = sheet.ownerNode;
|
|
1207
|
+
let anchor = ownerNode ? (_ctx$styleSheetClones = ctx.styleSheetClones) === null || _ctx$styleSheetClones === void 0 ? void 0 : _ctx$styleSheetClones.get(ownerNode) : null;
|
|
1208
|
+
if (!((_anchor = anchor) !== null && _anchor !== void 0 && _anchor.parentNode) && ownerNode) {
|
|
1209
|
+
const anchorId = ownerNode.getAttribute('data-percy-element-id');
|
|
1210
|
+
anchor = anchorId ? scopeRoot.querySelector(`[data-percy-element-id="${anchorId}"]`) : null;
|
|
1211
|
+
}
|
|
1179
1212
|
const anchorParent = anchor ? anchor.parentNode : null;
|
|
1180
1213
|
if (anchorParent) {
|
|
1181
1214
|
anchorParent.insertBefore(styleElement, anchor.nextSibling);
|
|
@@ -1184,10 +1217,11 @@
|
|
|
1184
1217
|
}
|
|
1185
1218
|
}
|
|
1186
1219
|
function serializePseudoClasses(ctx) {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
//
|
|
1190
|
-
//
|
|
1220
|
+
if (!ctx.pseudoClassSerialization) return;
|
|
1221
|
+
|
|
1222
|
+
// Auto-detect path rewrites `:focus`/`:focus-within` rules whether or not
|
|
1223
|
+
// a pseudoClassEnabledElements list was configured (and regardless of
|
|
1224
|
+
// whether that list matched anything on this page).
|
|
1191
1225
|
extractPseudoClassRules(ctx);
|
|
1192
1226
|
if (!ctx.pseudoClassEnabledElements) return;
|
|
1193
1227
|
const elements = ctx.dom.querySelectorAll(`[${PSEUDO_ELEMENT_MARKER_ATTR}]`);
|
|
@@ -1384,7 +1418,8 @@
|
|
|
1384
1418
|
forceShadowAsLightDOM,
|
|
1385
1419
|
resources,
|
|
1386
1420
|
cache,
|
|
1387
|
-
enableJavaScript
|
|
1421
|
+
enableJavaScript,
|
|
1422
|
+
styleSheetClones
|
|
1388
1423
|
} = ctx;
|
|
1389
1424
|
// clones shadow DOM and light DOM for a given node
|
|
1390
1425
|
let cloneNode = (node, parent) => {
|
|
@@ -1406,6 +1441,9 @@
|
|
|
1406
1441
|
// mark the node before cloning
|
|
1407
1442
|
markElement(node, disableShadowDOM, forceShadowAsLightDOM);
|
|
1408
1443
|
let clone = cloneElementWithoutLifecycle(node);
|
|
1444
|
+
if (styleSheetClones && (node.nodeName === 'LINK' || node.nodeName === 'STYLE')) {
|
|
1445
|
+
styleSheetClones.set(node, clone);
|
|
1446
|
+
}
|
|
1409
1447
|
|
|
1410
1448
|
// Custom-element :state() is captured by the fallback path in
|
|
1411
1449
|
// serialize-custom-states.js (live el.matches against state names
|
|
@@ -1614,8 +1652,10 @@
|
|
|
1614
1652
|
ignoreIframeSelectors = options === null || options === void 0 ? void 0 : options.ignore_iframe_selectors,
|
|
1615
1653
|
maxIframeDepth = options === null || options === void 0 ? void 0 : options.max_iframe_depth,
|
|
1616
1654
|
iframeDepth = (options === null || options === void 0 ? void 0 : options.iframe_depth) ?? 0,
|
|
1617
|
-
pseudoClassEnabledElements = options === null || options === void 0 ? void 0 : options.pseudo_class_enabled_elements
|
|
1655
|
+
pseudoClassEnabledElements = options === null || options === void 0 ? void 0 : options.pseudo_class_enabled_elements,
|
|
1656
|
+
enablePseudoClassSerialization = options === null || options === void 0 ? void 0 : options.enable_pseudo_class_serialization
|
|
1618
1657
|
} = options || {};
|
|
1658
|
+
const pseudoClassSerialization = !!(enablePseudoClassSerialization || pseudoClassEnabledElements);
|
|
1619
1659
|
|
|
1620
1660
|
// keep certain records throughout serialization
|
|
1621
1661
|
let ctx = {
|
|
@@ -1623,6 +1663,7 @@
|
|
|
1623
1663
|
warnings: new Set(),
|
|
1624
1664
|
hints: new Set(),
|
|
1625
1665
|
cache: new Map(),
|
|
1666
|
+
styleSheetClones: new WeakMap(),
|
|
1626
1667
|
shadowRootElements: [],
|
|
1627
1668
|
enableJavaScript,
|
|
1628
1669
|
disableShadowDOM,
|
|
@@ -1632,7 +1673,8 @@
|
|
|
1632
1673
|
ignoreIframeSelectors,
|
|
1633
1674
|
maxIframeDepth,
|
|
1634
1675
|
iframeDepth,
|
|
1635
|
-
pseudoClassEnabledElements
|
|
1676
|
+
pseudoClassEnabledElements,
|
|
1677
|
+
pseudoClassSerialization
|
|
1636
1678
|
};
|
|
1637
1679
|
ctx.dom = dom;
|
|
1638
1680
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/dom",
|
|
3
|
-
"version": "1.32.8-beta.
|
|
3
|
+
"version": "1.32.8-beta.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"interactor.js": "^2.0.0-beta.10"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "8cb91228f629119207fa169f8db8388d66e0c2e0"
|
|
39
39
|
}
|