@percy/dom 1.12.0 → 1.14.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 +42 -47
- package/package.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// Returns a mostly random uid.
|
|
10
10
|
function uid() {
|
|
11
11
|
return `_${Math.random().toString(36).substr(2, 9)}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
12
|
+
}
|
|
14
13
|
|
|
14
|
+
// Marks elements that are to be serialized later with a data attribute.
|
|
15
15
|
function prepareDOM(dom) {
|
|
16
16
|
for (let elem of dom.querySelectorAll('input, textarea, select, iframe, canvas, video, style')) {
|
|
17
17
|
if (!elem.getAttribute('data-percy-element-id')) {
|
|
@@ -25,77 +25,76 @@
|
|
|
25
25
|
for (let elem of dom.querySelectorAll('input, textarea, select')) {
|
|
26
26
|
let inputId = elem.getAttribute('data-percy-element-id');
|
|
27
27
|
let cloneEl = clone.querySelector(`[data-percy-element-id="${inputId}"]`);
|
|
28
|
-
|
|
29
28
|
switch (elem.type) {
|
|
30
29
|
case 'checkbox':
|
|
31
30
|
case 'radio':
|
|
32
31
|
if (elem.checked) {
|
|
33
32
|
cloneEl.setAttribute('checked', '');
|
|
34
33
|
}
|
|
35
|
-
|
|
36
34
|
break;
|
|
37
|
-
|
|
38
35
|
case 'select-one':
|
|
39
36
|
if (elem.selectedIndex !== -1) {
|
|
40
37
|
cloneEl.options[elem.selectedIndex].setAttribute('selected', 'true');
|
|
41
38
|
}
|
|
42
|
-
|
|
43
39
|
break;
|
|
44
|
-
|
|
45
40
|
case 'select-multiple':
|
|
46
41
|
for (let option of elem.selectedOptions) {
|
|
47
42
|
cloneEl.options[option.index].setAttribute('selected', 'true');
|
|
48
43
|
}
|
|
49
|
-
|
|
50
44
|
break;
|
|
51
|
-
|
|
52
45
|
case 'textarea':
|
|
53
46
|
cloneEl.innerHTML = elem.value;
|
|
54
47
|
break;
|
|
55
|
-
|
|
56
48
|
default:
|
|
57
49
|
cloneEl.setAttribute('value', elem.value);
|
|
58
50
|
}
|
|
59
51
|
}
|
|
60
52
|
}
|
|
61
53
|
|
|
54
|
+
// Adds a `<base>` element to the serialized iframe's `<head>`. This is necessary when
|
|
62
55
|
// embedded documents are serialized and their contents become root-relative.
|
|
63
|
-
|
|
64
56
|
function setBaseURI(dom) {
|
|
65
57
|
if (!new URL(dom.baseURI).hostname) return;
|
|
66
58
|
let $base = document.createElement('base');
|
|
67
59
|
$base.href = dom.baseURI;
|
|
68
60
|
dom.querySelector('head').prepend($base);
|
|
69
|
-
}
|
|
70
|
-
|
|
61
|
+
}
|
|
71
62
|
|
|
63
|
+
// Recursively serializes iframe documents into srcdoc attributes.
|
|
72
64
|
function serializeFrames(dom, clone, _ref) {
|
|
73
65
|
let {
|
|
74
66
|
enableJavaScript
|
|
75
67
|
} = _ref;
|
|
76
|
-
|
|
77
68
|
for (let frame of dom.querySelectorAll('iframe')) {
|
|
78
69
|
let percyElementId = frame.getAttribute('data-percy-element-id');
|
|
79
70
|
let cloneEl = clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);
|
|
80
|
-
let builtWithJs = !frame.srcdoc && (!frame.src || frame.src.split(':')[0] === 'javascript');
|
|
81
|
-
// rerendered and do not effect the visuals of a page
|
|
71
|
+
let builtWithJs = !frame.srcdoc && (!frame.src || frame.src.split(':')[0] === 'javascript');
|
|
82
72
|
|
|
73
|
+
// delete frames within the head since they usually break pages when
|
|
74
|
+
// rerendered and do not effect the visuals of a page
|
|
83
75
|
if (clone.head.contains(cloneEl)) {
|
|
84
|
-
cloneEl.remove();
|
|
76
|
+
cloneEl.remove();
|
|
77
|
+
|
|
78
|
+
// if the frame document is accessible and not empty, we can serialize it
|
|
85
79
|
} else if (frame.contentDocument && frame.contentDocument.documentElement) {
|
|
86
80
|
// js is enabled and this frame was built with js, don't serialize it
|
|
87
|
-
if (enableJavaScript && builtWithJs) continue;
|
|
81
|
+
if (enableJavaScript && builtWithJs) continue;
|
|
88
82
|
|
|
89
|
-
|
|
83
|
+
// the frame has yet to load and wasn't built with js, it is unsafe to serialize
|
|
84
|
+
if (!builtWithJs && !frame.contentWindow.performance.timing.loadEventEnd) continue;
|
|
90
85
|
|
|
86
|
+
// recersively serialize contents
|
|
91
87
|
let serialized = serializeDOM({
|
|
92
88
|
domTransformation: setBaseURI,
|
|
93
89
|
dom: frame.contentDocument,
|
|
94
90
|
enableJavaScript
|
|
95
|
-
});
|
|
91
|
+
});
|
|
96
92
|
|
|
93
|
+
// assign to srcdoc and remove src
|
|
97
94
|
cloneEl.setAttribute('srcdoc', serialized);
|
|
98
|
-
cloneEl.removeAttribute('src');
|
|
95
|
+
cloneEl.removeAttribute('src');
|
|
96
|
+
|
|
97
|
+
// delete inaccessible frames built with js when js is disabled because they
|
|
99
98
|
// break asset discovery by creating non-captured requests that hang
|
|
100
99
|
} else if (!enableJavaScript && builtWithJs) {
|
|
101
100
|
cloneEl.remove();
|
|
@@ -107,22 +106,20 @@
|
|
|
107
106
|
function isCSSOM(styleSheet) {
|
|
108
107
|
// no href, has a rulesheet, and has an owner node
|
|
109
108
|
return !styleSheet.href && styleSheet.cssRules && styleSheet.ownerNode;
|
|
110
|
-
}
|
|
111
|
-
|
|
109
|
+
}
|
|
112
110
|
|
|
111
|
+
// Returns false if any stylesheet rules do not match between two stylesheets
|
|
113
112
|
function styleSheetsMatch(sheetA, sheetB) {
|
|
114
113
|
for (let i = 0; i < sheetA.cssRules.length; i++) {
|
|
115
114
|
var _sheetB$cssRules$i;
|
|
116
|
-
|
|
117
115
|
let ruleA = sheetA.cssRules[i].cssText;
|
|
118
116
|
let ruleB = (_sheetB$cssRules$i = sheetB.cssRules[i]) === null || _sheetB$cssRules$i === void 0 ? void 0 : _sheetB$cssRules$i.cssText;
|
|
119
117
|
if (ruleA !== ruleB) return false;
|
|
120
118
|
}
|
|
121
|
-
|
|
122
119
|
return true;
|
|
123
|
-
}
|
|
124
|
-
|
|
120
|
+
}
|
|
125
121
|
|
|
122
|
+
// Outputs in-memory CSSOM into their respective DOM nodes.
|
|
126
123
|
function serializeCSSOM(dom, clone) {
|
|
127
124
|
for (let styleSheet of dom.styleSheets) {
|
|
128
125
|
if (isCSSOM(styleSheet)) {
|
|
@@ -146,26 +143,30 @@
|
|
|
146
143
|
// Note: the `.toDataURL` API requires WebGL canvas elements to use
|
|
147
144
|
// `preserveDrawingBuffer: true`. This is because `.toDataURL` uses the
|
|
148
145
|
// drawing buffer, which is cleared after each render for WebGL by default.
|
|
149
|
-
let dataUrl = canvas.toDataURL();
|
|
146
|
+
let dataUrl = canvas.toDataURL();
|
|
150
147
|
|
|
151
|
-
|
|
148
|
+
// skip empty canvases
|
|
149
|
+
if (!dataUrl || dataUrl === 'data:,') continue;
|
|
152
150
|
|
|
151
|
+
// create an image element in the cloned dom
|
|
153
152
|
let img = clone.createElement('img');
|
|
154
|
-
img.src = dataUrl;
|
|
155
|
-
// or data attributes that may be targeted by CSS
|
|
153
|
+
img.src = dataUrl;
|
|
156
154
|
|
|
155
|
+
// copy canvas element attributes to the image element such as style, class,
|
|
156
|
+
// or data attributes that may be targeted by CSS
|
|
157
157
|
for (let {
|
|
158
158
|
name,
|
|
159
159
|
value
|
|
160
160
|
} of canvas.attributes) {
|
|
161
161
|
img.setAttribute(name, value);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
img.setAttribute('data-percy-canvas-serialized', ''); // set a default max width to account for canvases that might resize with JS
|
|
162
|
+
}
|
|
166
163
|
|
|
167
|
-
|
|
164
|
+
// mark the image as serialized (can be targeted by CSS)
|
|
165
|
+
img.setAttribute('data-percy-canvas-serialized', '');
|
|
166
|
+
// set a default max width to account for canvases that might resize with JS
|
|
167
|
+
img.style.maxWidth = img.style.maxWidth || '100%';
|
|
168
168
|
|
|
169
|
+
// insert the image into the cloned DOM and remove the cloned canvas element
|
|
169
170
|
let percyElementId = canvas.getAttribute('data-percy-element-id');
|
|
170
171
|
let cloneEl = clone.querySelector(`[data-percy-element-id=${percyElementId}]`);
|
|
171
172
|
cloneEl.parentElement.insertBefore(img, cloneEl);
|
|
@@ -185,17 +186,17 @@
|
|
|
185
186
|
let height = canvas.height = video.videoHeight;
|
|
186
187
|
let dataUrl;
|
|
187
188
|
canvas.getContext('2d').drawImage(video, 0, 0, width, height);
|
|
188
|
-
|
|
189
189
|
try {
|
|
190
190
|
dataUrl = canvas.toDataURL();
|
|
191
|
-
} catch {}
|
|
192
|
-
|
|
191
|
+
} catch {}
|
|
193
192
|
|
|
193
|
+
// If the canvas produces a blank image, skip
|
|
194
194
|
if (!dataUrl || dataUrl === 'data:,') continue;
|
|
195
195
|
cloneEl.setAttribute('poster', dataUrl);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// Returns a copy or new doctype for a document.
|
|
199
200
|
function doctype(dom) {
|
|
200
201
|
let {
|
|
201
202
|
name = 'html',
|
|
@@ -203,7 +204,6 @@
|
|
|
203
204
|
systemId = ''
|
|
204
205
|
} = (dom === null || dom === void 0 ? void 0 : dom.doctype) ?? {};
|
|
205
206
|
let deprecated = '';
|
|
206
|
-
|
|
207
207
|
if (publicId && systemId) {
|
|
208
208
|
deprecated = ` PUBLIC "${publicId}" "${systemId}"`;
|
|
209
209
|
} else if (publicId) {
|
|
@@ -211,11 +211,10 @@
|
|
|
211
211
|
} else if (systemId) {
|
|
212
212
|
deprecated = ` SYSTEM "${systemId}"`;
|
|
213
213
|
}
|
|
214
|
-
|
|
215
214
|
return `<!DOCTYPE ${name}${deprecated}>`;
|
|
216
|
-
}
|
|
217
|
-
|
|
215
|
+
}
|
|
218
216
|
|
|
217
|
+
// Serializes a document and returns the resulting DOM string.
|
|
219
218
|
function serializeDOM(options) {
|
|
220
219
|
let {
|
|
221
220
|
dom = document,
|
|
@@ -230,14 +229,11 @@
|
|
|
230
229
|
enableJavaScript
|
|
231
230
|
});
|
|
232
231
|
serializeVideos(dom, clone);
|
|
233
|
-
|
|
234
232
|
if (!enableJavaScript) {
|
|
235
233
|
serializeCSSOM(dom, clone);
|
|
236
234
|
serializeCanvas(dom, clone);
|
|
237
235
|
}
|
|
238
|
-
|
|
239
236
|
let doc = clone.documentElement;
|
|
240
|
-
|
|
241
237
|
if (domTransformation) {
|
|
242
238
|
try {
|
|
243
239
|
domTransformation(doc);
|
|
@@ -245,7 +241,6 @@
|
|
|
245
241
|
console.error('Could not transform the dom:', err.message);
|
|
246
242
|
}
|
|
247
243
|
}
|
|
248
|
-
|
|
249
244
|
return doctype(dom) + doc.outerHTML;
|
|
250
245
|
}
|
|
251
246
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/dom",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,5 +34,5 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"interactor.js": "^2.0.0-beta.10"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "fd72688e449d6dd3eafd346fc07879cb3bb01a4e"
|
|
38
38
|
}
|