@percy/dom 1.31.0-alpha.3 → 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.
Files changed (2) hide show
  1. package/dist/bundle.js +88 -13
  2. 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.prepend(styleLink);
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.prepend(styleLink);
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
- // create a resource from the serialized data url
460
- let resource = resourceFromText(uid(), mimetype, base64String);
461
- resources.add(resource);
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.baseVal = resource.url;
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
- node.src = resource.url;
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.cloneNode();
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
 
@@ -598,6 +670,8 @@
598
670
  let html = getOuterHTML(ctx.clone.documentElement, {
599
671
  shadowRootElements: ctx.shadowRootElements
600
672
  });
673
+ // this is replacing serialized data tag with real tag
674
+ html = html.replace(/(<\/?)data-percy-custom-element-/g, '$1');
601
675
  // replace serialized data attributes with real attributes
602
676
  html = html.replace(/ data-percy-serialized-attribute-(\w+?)=/ig, ' $1=');
603
677
  // include the doctype with the html string
@@ -647,6 +721,7 @@
647
721
 
648
722
  // Serializes a document and returns the resulting DOM string.
649
723
  function serializeDOM(options) {
724
+ var _ctx$clone$body;
650
725
  let {
651
726
  dom = document,
652
727
  // allow snake_case or camelCase
@@ -687,7 +762,7 @@
687
762
  let sibling = clonedBody.nextSibling;
688
763
  clonedBody.append(sibling);
689
764
  }
690
- } 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) {
691
766
  ctx.hints.add('DOM elements found outside </body>');
692
767
  }
693
768
  let cookies = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/dom",
3
- "version": "1.31.0-alpha.3",
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": "alpha"
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": "14ae0b4319c1c2ae28729d56cba3b159e80386a8"
38
+ "gitHead": "49895470c0dfa7242881db43e293317d1fb8f8b6"
39
39
  }