Socket
Socket
Sign inDemoInstall

@percy/dom

Package Overview
Dependencies
Maintainers
6
Versions
238
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@percy/dom - npm Package Compare versions

Comparing version 1.16.0 to 2.0.0-alpha.0

244

dist/bundle.js

@@ -9,17 +9,2 @@ (function() {

// Returns a mostly random uid.
function uid() {
return `_${Math.random().toString(36).substr(2, 9)}`;
}
// Marks elements that are to be serialized later with a data attribute.
function prepareDOM(dom) {
for (let elem of dom.querySelectorAll('input, textarea, select, iframe, canvas, video, style')) {
if (!elem.getAttribute('data-percy-element-id')) {
elem.setAttribute('data-percy-element-id', uid());
}
}
return dom;
}
// Translates JavaScript properties of inputs into DOM attributes.

@@ -58,2 +43,14 @@ function serializeInputElements(_ref) {

}
// find inputs inside shadow host and recursively serialize them.
for (let shadowHost of dom.querySelectorAll('[data-percy-shadow-host]')) {
let percyElementId = shadowHost.getAttribute('data-percy-element-id');
let cloneShadowHost = clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
serializeInputElements({
dom: shadowHost.shadowRoot,
clone: cloneShadowHost.shadowRoot
});
}
}
}

@@ -80,2 +77,3 @@

for (let frame of dom.querySelectorAll('iframe')) {
var _clone$head;
let percyElementId = frame.getAttribute('data-percy-element-id');

@@ -87,3 +85,3 @@ let cloneEl = clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);

// rerendered and do not effect the visuals of a page
if (clone.head.contains(cloneEl)) {
if ((_clone$head = clone.head) !== null && _clone$head !== void 0 && _clone$head.contains(cloneEl)) {
cloneEl.remove();

@@ -121,2 +119,17 @@

}
// find iframes inside shadow host and recursively serialize them.
for (let shadowHost of dom.querySelectorAll('[data-percy-shadow-host]')) {
let percyElementId = shadowHost.getAttribute('data-percy-element-id');
let cloneShadowHost = clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
serializeFrames({
dom: shadowHost.shadowRoot,
clone: cloneShadowHost.shadowRoot,
warnings,
resources,
enableJavaScript
});
}
}
}

@@ -152,3 +165,3 @@

if (styleSheetsMatch(styleSheet, cloneOwnerNode.sheet)) continue;
let style = clone.createElement('style');
let style = document.createElement('style');
style.type = 'text/css';

@@ -162,2 +175,14 @@ style.setAttribute('data-percy-element-id', styleId);

}
// find stylesheets inside shadow host and recursively serialize them.
for (let shadowHost of dom.querySelectorAll('[data-percy-shadow-host]')) {
let percyElementId = shadowHost.getAttribute('data-percy-element-id');
let cloneShadowHost = clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
serializeCSSOM({
dom: shadowHost.shadowRoot,
clone: cloneShadowHost.shadowRoot
});
}
}
}

@@ -207,3 +232,4 @@

// create an image element in the cloned dom
let img = clone.createElement('img');
// TODO: this works, verify if this is fine?
let img = document.createElement('img');
// use a data attribute to avoid making a real request

@@ -228,5 +254,23 @@ img.setAttribute('data-percy-serialized-attribute-src', resource.url);

let cloneEl = clone.querySelector(`[data-percy-element-id=${percyElementId}]`);
cloneEl.parentElement.insertBefore(img, cloneEl);
// `parentElement` for elements directly under shadow root is `null` -> Incase of Nested Shadow DOM.
if (cloneEl.parentElement) {
cloneEl.parentElement.insertBefore(img, cloneEl);
} else {
clone.insertBefore(img, cloneEl);
}
cloneEl.remove();
}
// find canvas inside shadow host and recursively serialize them.
for (let shadowHost of dom.querySelectorAll('[data-percy-shadow-host]')) {
let percyElementId = shadowHost.getAttribute('data-percy-element-id');
let cloneShadowHost = clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
serializeCanvas({
dom: shadowHost.shadowRoot,
clone: cloneShadowHost.shadowRoot,
resources
});
}
}
}

@@ -239,3 +283,4 @@

clone,
resources
resources,
warnings
} = _ref;

@@ -254,3 +299,5 @@ for (let video of dom.querySelectorAll('video')) {

dataUrl = canvas.toDataURL();
} catch {}
} catch (e) {
warnings.add(`data-percy-element-id="${videoId}" : ${e.toString()}`);
}

@@ -267,4 +314,152 @@ // if the canvas produces a blank image, skip

}
// find video inside shadow host and recursively serialize them.
for (let shadowHost of dom.querySelectorAll('[data-percy-shadow-host]')) {
let percyElementId = shadowHost.getAttribute('data-percy-element-id');
let cloneShadowHost = clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
serializeVideos({
dom: shadowHost.shadowRoot,
clone: cloneShadowHost.shadowRoot,
resources,
warnings
});
}
}
}
// Returns a mostly random uid.
function uid() {
return `_${Math.random().toString(36).substr(2, 9)}`;
}
function markElement(domElement) {
var _domElement$tagName;
// Mark elements that are to be serialized later with a data attribute.
if (['input', 'textarea', 'select', 'iframe', 'canvas', 'video', 'style'].includes((_domElement$tagName = domElement.tagName) === null || _domElement$tagName === void 0 ? void 0 : _domElement$tagName.toLowerCase())) {
if (!domElement.getAttribute('data-percy-element-id')) {
domElement.setAttribute('data-percy-element-id', uid());
}
}
// add special marker for shadow host
if (domElement.shadowRoot) {
if (!domElement.getAttribute('data-percy-shadow-host')) {
domElement.setAttribute('data-percy-shadow-host', '');
}
if (!domElement.getAttribute('data-percy-element-id')) {
domElement.setAttribute('data-percy-element-id', uid());
}
}
}
/**
* Custom deep clone function that replaces Percy's current clone behavior.
* This enables us to capture shadow DOM in snapshots. It takes advantage of `attachShadow`'s mode option set to open
* https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#parameters
*/
// returns document fragment
const deepClone = host => {
// clones shadow DOM and light DOM for a given node
let cloneNode = (node, parent) => {
let walkTree = (nextn, nextp) => {
while (nextn) {
cloneNode(nextn, nextp);
nextn = nextn.nextSibling;
}
};
// mark the node before cloning
markElement(node);
let clone = node.cloneNode();
parent.appendChild(clone);
// clone shadow DOM
if (node.shadowRoot) {
// create shadowRoot
if (clone.shadowRoot) {
// it may be set up in a custom element's constructor
clone.shadowRoot.innerHTML = '';
} else {
clone.attachShadow({
mode: 'open'
});
}
// clone stylesheets in shadowRoot
for (let sheet of node.shadowRoot.adoptedStyleSheets) {
let cssText = Array.from(sheet.rules).map(rule => rule.cssText).join('\n');
let style = document.createElement('style');
style.appendChild(document.createTextNode(cssText));
clone.shadowRoot.prepend(style);
}
// clone dom elements
walkTree(node.shadowRoot.firstChild, clone.shadowRoot);
}
// clone light DOM
walkTree(node.firstChild, clone);
};
let fragment = document.createDocumentFragment();
cloneNode(host, fragment);
return fragment;
};
/**
* Deep clone a document while also preserving shadow roots and converting adoptedStylesheets to <style> tags.
*/
const cloneNodeAndShadow = doc => {
let mockDocumentFragment = deepClone(doc.documentElement);
// convert document fragment to document object
let cloneDocument = doc.cloneNode();
// dissolve document fragment in clone document
cloneDocument.appendChild(mockDocumentFragment);
return cloneDocument;
};
/**
* Use `getInnerHTML()` to serialize shadow dom as <template> tags. `innerHTML` and `outerHTML` don't do this. Buzzword: "declarative shadow dom"
*/
const getOuterHTML = docElement => {
// firefox doesn't serialize shadow DOM, we're awaiting API's by firefox to become ready and are not polyfilling it.
if (!docElement.getInnerHTML) {
return docElement.outerHTML;
}
// chromium gives us declarative shadow DOM serialization API
let innerHTML = docElement.getInnerHTML({
includeShadowRoots: true
});
docElement.textContent = '';
return docElement.outerHTML.replace('</html>', `${innerHTML}</html>`);
};
// we inject declarative shadow dom polyfill to allow shadow dom to load in non chromium infrastructure browsers
// Since only chromium currently supports declarative shadow DOM - https://caniuse.com/declarative-shadow-dom
function injectDeclarativeShadowDOMPolyfill(ctx) {
let clone = ctx.clone;
let scriptEl = clone.createElement('script');
scriptEl.setAttribute('id', '__percy_shadowdom_helper');
scriptEl.setAttribute('data-percy-injected', true);
scriptEl.innerHTML = `
function reversePolyFill(root=document){
root.querySelectorAll('template[shadowroot]').forEach(template => {
const mode = template.getAttribute('shadowroot');
const shadowRoot = template.parentNode.attachShadow({ mode });
shadowRoot.appendChild(template.content);
template.remove();
});
root.querySelectorAll('[data-percy-shadow-host]').forEach(shadowHost => reversePolyFill(shadowHost.shadowRoot));
}
if (["interactive", "complete"].includes(document.readyState)) {
reversePolyFill();
} else {
document.addEventListener("DOMContentLoaded", () => reversePolyFill());
}
`.replace(/(\n|\s{2}|\t)/g, '');
clone.body.appendChild(scriptEl);
}
// Returns a copy or new doctype for a document.

@@ -290,3 +485,3 @@ function doctype(dom) {

function serializeHTML(ctx) {
let html = ctx.clone.documentElement.outerHTML;
let html = getOuterHTML(ctx.clone.documentElement);
// replace serialized data attributes with real attributes

@@ -314,4 +509,4 @@ html = html.replace(/ data-percy-serialized-attribute-(\w+?)=/ig, ' $1=');

};
ctx.dom = prepareDOM(dom);
ctx.clone = ctx.dom.cloneNode(true);
ctx.dom = dom;
ctx.clone = cloneNodeAndShadow(ctx.dom);
serializeInputElements(ctx);

@@ -331,2 +526,3 @@ serializeFrames(ctx);

}
injectDeclarativeShadowDOMPolyfill(ctx);
let result = {

@@ -333,0 +529,0 @@ html: serializeHTML(ctx),

4

package.json
{
"name": "@percy/dom",
"version": "1.16.0",
"version": "2.0.0-alpha.0",
"license": "MIT",

@@ -37,3 +37,3 @@ "repository": {

},
"gitHead": "147d3a6f249771252ee6cf03c9af44e4e21e1c55"
"gitHead": "6ebc3d59194d6cd25fceb5366ab9f230849a4f41"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc