@percy/dom
Advanced tools
Comparing version 1.19.1-alpha.0 to 1.19.2-alpha.0
@@ -72,3 +72,4 @@ (function() { | ||
resources, | ||
enableJavaScript | ||
enableJavaScript, | ||
disableShadowDOM | ||
} = _ref; | ||
@@ -98,3 +99,4 @@ for (let frame of dom.querySelectorAll('iframe')) { | ||
dom: frame.contentDocument, | ||
enableJavaScript | ||
enableJavaScript, | ||
disableShadowDOM | ||
}); | ||
@@ -134,2 +136,61 @@ | ||
// Creates a resource object from an element's unique ID and data URL | ||
function resourceFromDataURL(uid, dataURL) { | ||
// split dataURL into desired parts | ||
let [data, content] = dataURL.split(','); | ||
let [, mimetype] = data.split(':'); | ||
[mimetype] = mimetype.split(';'); | ||
// build a URL for the serialized asset | ||
let [, ext] = mimetype.split('/'); | ||
let path = `/__serialized__/${uid}.${ext}`; | ||
let url = new URL(path, document.URL).toString(); | ||
// return the url, base64 content, and mimetype | ||
return { | ||
url, | ||
content, | ||
mimetype | ||
}; | ||
} | ||
function resourceFromText(uid, mimetype, data) { | ||
// build a URL for the serialized asset | ||
let [, ext] = mimetype.split('/'); | ||
let path = `/__serialized__/${uid}.${ext}`; | ||
let url = new URL(path, document.URL).toString(); | ||
// converts text to base64 | ||
let content = window.btoa(data); | ||
// return the url, base64 content, and mimetype | ||
return { | ||
url, | ||
content, | ||
mimetype | ||
}; | ||
} | ||
// Returns a mostly random uid. | ||
function uid() { | ||
return `_${Math.random().toString(36).substr(2, 9)}`; | ||
} | ||
function markElement(domElement, disableShadowDOM) { | ||
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 (!disableShadowDOM && 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()); | ||
} | ||
} | ||
} | ||
// Returns true if a stylesheet is a CSSOM-based stylesheet. | ||
@@ -157,4 +218,6 @@ function isCSSOM(styleSheet) { | ||
clone, | ||
warnings | ||
warnings, | ||
resources | ||
} = _ref; | ||
let adoptedStylesMap = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
for (let styleSheet of dom.styleSheets) { | ||
@@ -180,2 +243,25 @@ if (isCSSOM(styleSheet)) { | ||
// clone stylesheets in shadowRoot | ||
// https://github.com/WICG/construct-stylesheets/issues/93 | ||
if (!adoptedStylesMap) { | ||
adoptedStylesMap = new Map(); | ||
} | ||
for (let sheet of dom.adoptedStyleSheets) { | ||
const styleLink = document.createElement('link'); | ||
styleLink.setAttribute('rel', 'stylesheet'); | ||
if (!adoptedStylesMap.has(sheet)) { | ||
const styles = Array.from(sheet.cssRules).map(cssRule => cssRule.cssText).join('\n'); | ||
let resource = resourceFromText(uid(), 'text/css', styles); | ||
resources.add(resource); | ||
adoptedStylesMap.set(sheet, resource.url); | ||
} | ||
styleLink.setAttribute('data-percy-serialized-attribute-href', adoptedStylesMap.get(sheet)); | ||
if (clone.constructor.name === 'HTMLDocument') { | ||
// handle document and iframe | ||
clone.body.prepend(styleLink); | ||
} else if (clone.constructor.name === 'ShadowRoot') { | ||
clone.prepend(styleLink); | ||
} | ||
} | ||
// find stylesheets inside shadow host and recursively serialize them. | ||
@@ -188,4 +274,5 @@ for (let shadowHost of dom.querySelectorAll('[data-percy-shadow-host]')) { | ||
dom: shadowHost.shadowRoot, | ||
clone: cloneShadowHost.shadowRoot | ||
}); | ||
clone: cloneShadowHost.shadowRoot, | ||
resources | ||
}, adoptedStylesMap); | ||
} | ||
@@ -195,22 +282,2 @@ } | ||
// Creates a resource object from an element's unique ID and data URL | ||
function resourceFromDataURL(uid, dataURL) { | ||
// split dataURL into desired parts | ||
let [data, content] = dataURL.split(','); | ||
let [, mimetype] = data.split(':'); | ||
[mimetype] = mimetype.split(';'); | ||
// build a URL for the serialized asset | ||
let [, ext] = mimetype.split('/'); | ||
let path = `/__serialized__/${uid}.${ext}`; | ||
let url = new URL(path, document.URL).toString(); | ||
// return the url, base64 content, and mimetype | ||
return { | ||
url, | ||
content, | ||
mimetype | ||
}; | ||
} | ||
// Serialize in-memory canvas elements into images. | ||
@@ -238,3 +305,2 @@ function serializeCanvas(_ref) { | ||
// create an image element in the cloned dom | ||
// TODO: this works, verify if this is fine? | ||
let img = document.createElement('img'); | ||
@@ -333,26 +399,2 @@ // use a data attribute to avoid making a real request | ||
// 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()); | ||
} | ||
} | ||
} | ||
/** | ||
@@ -365,3 +407,3 @@ * Custom deep clone function that replaces Percy's current clone behavior. | ||
// returns document fragment | ||
const deepClone = host => { | ||
const deepClone = (host, disableShadowDOM) => { | ||
// clones shadow DOM and light DOM for a given node | ||
@@ -377,3 +419,3 @@ let cloneNode = (node, parent) => { | ||
// mark the node before cloning | ||
markElement(node); | ||
markElement(node, disableShadowDOM); | ||
let clone = node.cloneNode(); | ||
@@ -383,3 +425,3 @@ parent.appendChild(clone); | ||
// clone shadow DOM | ||
if (node.shadowRoot) { | ||
if (node.shadowRoot && !disableShadowDOM) { | ||
// create shadowRoot | ||
@@ -394,11 +436,2 @@ if (clone.shadowRoot) { | ||
} | ||
// 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 | ||
@@ -419,8 +452,9 @@ walkTree(node.shadowRoot.firstChild, clone.shadowRoot); | ||
*/ | ||
const cloneNodeAndShadow = doc => { | ||
let mockDocumentFragment = deepClone(doc.documentElement); | ||
const cloneNodeAndShadow = ctx => { | ||
let cloneDocumentElement = deepClone(ctx.dom.documentElement, ctx.disableShadowDOM); | ||
// TODO: we're not properly appending documentElement (html node) in the clone document, this can cause side effects in original document. | ||
// convert document fragment to document object | ||
let cloneDocument = doc.cloneNode(); | ||
let cloneDocument = ctx.dom.cloneNode(); | ||
// dissolve document fragment in clone document | ||
cloneDocument.appendChild(mockDocumentFragment); | ||
cloneDocument.appendChild(cloneDocumentElement); | ||
return cloneDocument; | ||
@@ -470,3 +504,5 @@ }; | ||
`.replace(/(\n|\s{2}|\t)/g, ''); | ||
clone.body.appendChild(scriptEl); | ||
// run polyfill as first thing post dom content is loaded | ||
clone.head.prepend(scriptEl); | ||
} | ||
@@ -508,3 +544,4 @@ | ||
domTransformation = options === null || options === void 0 ? void 0 : options.dom_transformation, | ||
stringifyResponse = options === null || options === void 0 ? void 0 : options.stringify_response | ||
stringifyResponse = options === null || options === void 0 ? void 0 : options.stringify_response, | ||
disableShadowDOM = options === null || options === void 0 ? void 0 : options.disable_shadow_dom | ||
} = options || {}; | ||
@@ -516,6 +553,7 @@ | ||
warnings: new Set(), | ||
enableJavaScript | ||
enableJavaScript, | ||
disableShadowDOM | ||
}; | ||
ctx.dom = dom; | ||
ctx.clone = cloneNodeAndShadow(ctx.dom); | ||
ctx.clone = cloneNodeAndShadow(ctx); | ||
serializeInputElements(ctx); | ||
@@ -535,3 +573,5 @@ serializeFrames(ctx); | ||
} | ||
injectDeclarativeShadowDOMPolyfill(ctx); | ||
if (!disableShadowDOM) { | ||
injectDeclarativeShadowDOMPolyfill(ctx); | ||
} | ||
let result = { | ||
@@ -538,0 +578,0 @@ html: serializeHTML(ctx), |
{ | ||
"name": "@percy/dom", | ||
"version": "1.19.1-alpha.0", | ||
"version": "1.19.2-alpha.0", | ||
"license": "MIT", | ||
@@ -37,3 +37,3 @@ "repository": { | ||
}, | ||
"gitHead": "1a95e827ef2fb7e6f2c7a7a5a90d265185f31fc2" | ||
"gitHead": "d9bf82889731f5cdc3f7fa4fe836cc5232ee8fb6" | ||
} |
@@ -40,2 +40,3 @@ # @percy/dom | ||
- `domTransformation` — Function to transform the DOM after serialization | ||
- `disableShadowDOM` — disable shadow DOM capturing, this option can be passed to `percySnapshot` its part of per-snapshot config. | ||
@@ -42,0 +43,0 @@ ## Serialized Content |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
52168
5
524
116