@percy/dom 1.31.0-alpha.2 → 1.31.0
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 +106 -24
- package/package.json +3 -3
package/dist/bundle.js
CHANGED
|
@@ -314,9 +314,21 @@
|
|
|
314
314
|
/* istanbul ignore next: tested, but coverage is stripped */
|
|
315
315
|
if (clone.constructor.name === 'HTMLDocument' || clone.constructor.name === 'DocumentFragment') {
|
|
316
316
|
// handle document and iframe
|
|
317
|
-
clone.body
|
|
317
|
+
// We are checking if we have multiple stylesheets present for the same clone or clone.body then we add
|
|
318
|
+
// them in the same order in which we receive them.
|
|
319
|
+
const lastLink = clone.body.querySelector('link[data-percy-adopted-stylesheets-serialized]:last-of-type');
|
|
320
|
+
if (lastLink) {
|
|
321
|
+
lastLink.after(styleLink);
|
|
322
|
+
} else {
|
|
323
|
+
clone.body.prepend(styleLink);
|
|
324
|
+
}
|
|
318
325
|
} else if (clone.constructor.name === 'ShadowRoot') {
|
|
319
|
-
clone.
|
|
326
|
+
const lastLink = clone.querySelector('link[data-percy-adopted-stylesheets-serialized]:last-of-type');
|
|
327
|
+
if (lastLink) {
|
|
328
|
+
lastLink.after(styleLink);
|
|
329
|
+
} else {
|
|
330
|
+
clone.prepend(styleLink);
|
|
331
|
+
}
|
|
320
332
|
}
|
|
321
333
|
}
|
|
322
334
|
} else {
|
|
@@ -441,7 +453,7 @@
|
|
|
441
453
|
base64Index += ';base64,'.length;
|
|
442
454
|
return src.substring(base64Index);
|
|
443
455
|
}
|
|
444
|
-
function serializeBase64(node, resources) {
|
|
456
|
+
function serializeBase64(node, resources, cache) {
|
|
445
457
|
let src = node.src;
|
|
446
458
|
let isHrefUsed = false;
|
|
447
459
|
|
|
@@ -455,14 +467,28 @@
|
|
|
455
467
|
let base64String = getBase64Substring(src.toString());
|
|
456
468
|
// skip if src is not base64
|
|
457
469
|
if (base64String == null) return;
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
470
|
+
if (!cache.has(base64String)) {
|
|
471
|
+
// create a resource from the serialized data url
|
|
472
|
+
let resource = resourceFromText(uid(), mimetype, base64String);
|
|
473
|
+
resources.add(resource);
|
|
474
|
+
cache.set(base64String, resource.url);
|
|
475
|
+
}
|
|
462
476
|
if (isHrefUsed === true) {
|
|
463
|
-
node.href
|
|
477
|
+
if (node.hasAttribute('xlink:href')) {
|
|
478
|
+
node.removeAttribute('xlink:href');
|
|
479
|
+
node.setAttribute('data-percy-serialized-attribute-xlink:href', cache.get(base64String));
|
|
480
|
+
} else {
|
|
481
|
+
node.removeAttribute('href');
|
|
482
|
+
node.setAttribute('data-percy-serialized-attribute-href', cache.get(base64String));
|
|
483
|
+
}
|
|
464
484
|
} else {
|
|
465
|
-
|
|
485
|
+
// we use data-percy-serialized-attribute-src here instead of `src`.
|
|
486
|
+
// As soon as src is used the browser will try to load the resource,
|
|
487
|
+
// thus making a network call which would fail as this is a
|
|
488
|
+
// dynamic cached resource and not a resource that backend can serve.
|
|
489
|
+
// we later post converting domtree to html replace this with src
|
|
490
|
+
node.removeAttribute('src');
|
|
491
|
+
node.setAttribute('data-percy-serialized-attribute-src', cache.get(base64String));
|
|
466
492
|
}
|
|
467
493
|
}
|
|
468
494
|
|
|
@@ -478,11 +504,36 @@
|
|
|
478
504
|
*/
|
|
479
505
|
|
|
480
506
|
const ignoreTags = ['NOSCRIPT'];
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* if a custom element has attribute callback then cloneNode calls a callback that can
|
|
510
|
+
* increase CPU load or some other change.
|
|
511
|
+
* So we want to make sure that it is not called when doing serialization.
|
|
512
|
+
*/
|
|
513
|
+
function cloneElementWithoutLifecycle(element) {
|
|
514
|
+
if (!element.attributeChangedCallback || !element.tagName.includes('-')) {
|
|
515
|
+
return element.cloneNode(); // Standard clone for non-custom elements
|
|
516
|
+
}
|
|
517
|
+
const cloned = document.createElement('data-percy-custom-element-' + element.tagName);
|
|
518
|
+
|
|
519
|
+
// Clone attributes without triggering attributeChangedCallback
|
|
520
|
+
for (const attr of element.attributes) {
|
|
521
|
+
// handle src separately
|
|
522
|
+
if (attr.name.toLowerCase() === 'src') {
|
|
523
|
+
cloned.setAttribute('data-percy-serialized-attribute-src', attr.value);
|
|
524
|
+
} else {
|
|
525
|
+
cloned.setAttribute(attr.name, attr.value);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return cloned;
|
|
529
|
+
}
|
|
481
530
|
function cloneNodeAndShadow(ctx) {
|
|
482
531
|
let {
|
|
483
532
|
dom,
|
|
484
533
|
disableShadowDOM,
|
|
485
|
-
resources
|
|
534
|
+
resources,
|
|
535
|
+
cache,
|
|
536
|
+
enableJavaScript
|
|
486
537
|
} = ctx;
|
|
487
538
|
// clones shadow DOM and light DOM for a given node
|
|
488
539
|
let cloneNode = (node, parent) => {
|
|
@@ -498,15 +549,36 @@
|
|
|
498
549
|
|
|
499
550
|
// mark the node before cloning
|
|
500
551
|
markElement(node, disableShadowDOM);
|
|
501
|
-
let clone = node
|
|
552
|
+
let clone = cloneElementWithoutLifecycle(node);
|
|
553
|
+
|
|
554
|
+
// Handle <style> tag specifically for media queries
|
|
555
|
+
if (node.nodeName === 'STYLE' && !enableJavaScript) {
|
|
556
|
+
var _node$textContent;
|
|
557
|
+
let cssText = ((_node$textContent = node.textContent) === null || _node$textContent === void 0 ? void 0 : _node$textContent.trim()) || '';
|
|
558
|
+
if (!cssText && node.sheet) {
|
|
559
|
+
try {
|
|
560
|
+
const cssRules = node.sheet.cssRules;
|
|
561
|
+
if (cssRules && cssRules.length > 0) {
|
|
562
|
+
cssText = Array.from(cssRules).map(rule => rule.cssText).join('\n');
|
|
563
|
+
}
|
|
564
|
+
} catch (_) {
|
|
565
|
+
// ignore errors
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (cssText) {
|
|
569
|
+
clone.textContent = cssText;
|
|
570
|
+
clone.setAttribute('data-percy-cssom-serialized', 'true');
|
|
571
|
+
}
|
|
572
|
+
}
|
|
502
573
|
|
|
503
574
|
// We apply any element transformations here to avoid another treeWalk
|
|
504
575
|
applyElementTransformations(clone);
|
|
505
|
-
serializeBase64(clone, resources);
|
|
576
|
+
serializeBase64(clone, resources, cache);
|
|
506
577
|
parent.appendChild(clone);
|
|
507
578
|
|
|
508
579
|
// shallow clone should not contain children
|
|
509
580
|
if (clone.children) {
|
|
581
|
+
/* istanbul ignore next */
|
|
510
582
|
Array.from(clone.children).forEach(child => clone.removeChild(child));
|
|
511
583
|
}
|
|
512
584
|
|
|
@@ -547,26 +619,27 @@
|
|
|
547
619
|
/**
|
|
548
620
|
* Use `getInnerHTML()` to serialize shadow dom as <template> tags. `innerHTML` and `outerHTML` don't do this. Buzzword: "declarative shadow dom"
|
|
549
621
|
*/
|
|
550
|
-
function getOuterHTML(docElement
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
// old firefox doesn't serialize shadow DOM, we're awaiting API's by firefox to become ready and are not polyfilling it.
|
|
554
|
-
// new firefox from 128 onwards serializes it using getHTML
|
|
555
|
-
/* istanbul ignore if: Only triggered in firefox <= 128 and tests runs on latest */
|
|
556
|
-
if (!docElement.getHTML) {
|
|
557
|
-
return docElement.outerHTML;
|
|
558
|
-
}
|
|
622
|
+
function getOuterHTML(docElement, {
|
|
623
|
+
shadowRootElements
|
|
624
|
+
}) {
|
|
559
625
|
// chromium gives us declarative shadow DOM serialization API
|
|
560
626
|
let innerHTML = '';
|
|
561
627
|
/* istanbul ignore else if: Only triggered in chrome <= 128 and tests runs on latest */
|
|
562
628
|
if (docElement.getHTML) {
|
|
629
|
+
// All major browsers in latest versions supports getHTML API to get serialized DOM
|
|
630
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getHTML
|
|
563
631
|
innerHTML = docElement.getHTML({
|
|
564
|
-
serializableShadowRoots: true
|
|
632
|
+
serializableShadowRoots: true,
|
|
633
|
+
shadowRoots: shadowRootElements
|
|
565
634
|
});
|
|
566
635
|
} else if (docElement.getInnerHTML) {
|
|
567
636
|
innerHTML = docElement.getInnerHTML({
|
|
568
637
|
includeShadowRoots: true
|
|
569
638
|
});
|
|
639
|
+
} else {
|
|
640
|
+
// old firefox doesn't serialize shadow DOM, we're awaiting API's by firefox to become ready and are not polyfilling it.
|
|
641
|
+
// new firefox from 128 onwards serializes it using getHTML
|
|
642
|
+
return docElement.outerHTML;
|
|
570
643
|
}
|
|
571
644
|
docElement.textContent = '';
|
|
572
645
|
// Note: Here we are specifically passing replacer function to avoid any replacements due to
|
|
@@ -594,7 +667,11 @@
|
|
|
594
667
|
|
|
595
668
|
// Serializes and returns the cloned DOM as an HTML string
|
|
596
669
|
function serializeHTML(ctx) {
|
|
597
|
-
let html = getOuterHTML(ctx.clone.documentElement
|
|
670
|
+
let html = getOuterHTML(ctx.clone.documentElement, {
|
|
671
|
+
shadowRootElements: ctx.shadowRootElements
|
|
672
|
+
});
|
|
673
|
+
// this is replacing serialized data tag with real tag
|
|
674
|
+
html = html.replace(/(<\/?)data-percy-custom-element-/g, '$1');
|
|
598
675
|
// replace serialized data attributes with real attributes
|
|
599
676
|
html = html.replace(/ data-percy-serialized-attribute-(\w+?)=/ig, ' $1=');
|
|
600
677
|
// include the doctype with the html string
|
|
@@ -612,6 +689,9 @@
|
|
|
612
689
|
let percyElementId = shadowHost.getAttribute('data-percy-element-id');
|
|
613
690
|
let cloneShadowHost = ctx.clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);
|
|
614
691
|
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
|
|
692
|
+
// getHTML requires shadowRoot to be passed explicitly
|
|
693
|
+
// to serialize the shadow elements properly
|
|
694
|
+
ctx.shadowRootElements.push(cloneShadowHost.shadowRoot);
|
|
615
695
|
serializeElements({
|
|
616
696
|
...ctx,
|
|
617
697
|
dom: shadowHost.shadowRoot,
|
|
@@ -641,6 +721,7 @@
|
|
|
641
721
|
|
|
642
722
|
// Serializes a document and returns the resulting DOM string.
|
|
643
723
|
function serializeDOM(options) {
|
|
724
|
+
var _ctx$clone$body;
|
|
644
725
|
let {
|
|
645
726
|
dom = document,
|
|
646
727
|
// allow snake_case or camelCase
|
|
@@ -657,6 +738,7 @@
|
|
|
657
738
|
warnings: new Set(),
|
|
658
739
|
hints: new Set(),
|
|
659
740
|
cache: new Map(),
|
|
741
|
+
shadowRootElements: [],
|
|
660
742
|
enableJavaScript,
|
|
661
743
|
disableShadowDOM
|
|
662
744
|
};
|
|
@@ -680,7 +762,7 @@
|
|
|
680
762
|
let sibling = clonedBody.nextSibling;
|
|
681
763
|
clonedBody.append(sibling);
|
|
682
764
|
}
|
|
683
|
-
} else if (ctx.clone.body.nextSibling) {
|
|
765
|
+
} else if ((_ctx$clone$body = ctx.clone.body) !== null && _ctx$clone$body !== void 0 && _ctx$clone$body.nextSibling) {
|
|
684
766
|
ctx.hints.add('DOM elements found outside </body>');
|
|
685
767
|
}
|
|
686
768
|
let cookies = '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/dom",
|
|
3
|
-
"version": "1.31.0
|
|
3
|
+
"version": "1.31.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public",
|
|
12
|
-
"tag": "
|
|
12
|
+
"tag": "latest"
|
|
13
13
|
},
|
|
14
14
|
"main": "dist/bundle.js",
|
|
15
15
|
"browser": "dist/bundle.js",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"interactor.js": "^2.0.0-beta.10"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "49895470c0dfa7242881db43e293317d1fb8f8b6"
|
|
39
39
|
}
|