@lambdatest/smartui-cli 2.0.1 → 2.0.3
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/dom-serializer.js +569 -559
- package/dist/index.cjs +210 -84
- package/package.json +7 -2
package/dist/dom-serializer.js
CHANGED
|
@@ -1,599 +1,609 @@
|
|
|
1
1
|
(function() {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
cloneEl.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
2
|
+
(function (exports) {
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const process = (typeof globalThis !== "undefined" && globalThis.process) || {};
|
|
6
|
+
process.env = process.env || {};
|
|
7
|
+
process.env.__SMARTUI_BROWSERIFIED__ = true;
|
|
8
|
+
|
|
9
|
+
// Translates JavaScript properties of inputs into DOM attributes.
|
|
10
|
+
function serializeInputElements(_ref) {
|
|
11
|
+
let {
|
|
12
|
+
dom,
|
|
13
|
+
clone,
|
|
14
|
+
warnings
|
|
15
|
+
} = _ref;
|
|
16
|
+
for (let elem of dom.querySelectorAll('input, textarea, select')) {
|
|
17
|
+
let inputId = elem.getAttribute('data-smartui-element-id');
|
|
18
|
+
let cloneEl = clone.querySelector(`[data-smartui-element-id="${inputId}"]`);
|
|
19
|
+
switch (elem.type) {
|
|
20
|
+
case 'checkbox':
|
|
21
|
+
case 'radio':
|
|
22
|
+
if (elem.checked) {
|
|
23
|
+
cloneEl.setAttribute('checked', '');
|
|
24
|
+
}
|
|
25
|
+
break;
|
|
26
|
+
case 'select-one':
|
|
27
|
+
if (elem.selectedIndex !== -1) {
|
|
28
|
+
cloneEl.options[elem.selectedIndex].setAttribute('selected', 'true');
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
case 'select-multiple':
|
|
32
|
+
for (let option of elem.selectedOptions) {
|
|
33
|
+
cloneEl.options[option.index].setAttribute('selected', 'true');
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
case 'textarea':
|
|
37
|
+
cloneEl.innerHTML = elem.value;
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
cloneEl.setAttribute('value', elem.value);
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Adds a `<base>` element to the serialized iframe's `<head>`. This is necessary when
|
|
46
|
+
// embedded documents are serialized and their contents become root-relative.
|
|
47
|
+
function setBaseURI(dom) {
|
|
48
|
+
/* istanbul ignore if: sanity check */
|
|
49
|
+
if (!new URL(dom.baseURI).hostname) return;
|
|
50
|
+
let $base = document.createElement('base');
|
|
51
|
+
$base.href = dom.baseURI;
|
|
52
|
+
dom.querySelector('head').prepend($base);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Recursively serializes iframe documents into srcdoc attributes.
|
|
56
|
+
function serializeFrames(_ref) {
|
|
57
|
+
let {
|
|
58
|
+
dom,
|
|
59
|
+
clone,
|
|
60
|
+
warnings,
|
|
61
|
+
resources,
|
|
62
|
+
enableJavaScript,
|
|
63
|
+
disableShadowDOM
|
|
64
|
+
} = _ref;
|
|
65
|
+
for (let frame of dom.querySelectorAll('iframe')) {
|
|
66
|
+
var _clone$head;
|
|
67
|
+
let smartuiElementId = frame.getAttribute('data-smartui-element-id');
|
|
68
|
+
let cloneEl = clone.querySelector(`[data-smartui-element-id="${smartuiElementId}"]`);
|
|
69
|
+
let builtWithJs = !frame.srcdoc && (!frame.src || frame.src.split(':')[0] === 'javascript');
|
|
70
|
+
|
|
71
|
+
// delete frames within the head since they usually break pages when
|
|
72
|
+
// rerendered and do not effect the visuals of a page
|
|
73
|
+
if ((_clone$head = clone.head) !== null && _clone$head !== void 0 && _clone$head.contains(cloneEl)) {
|
|
74
|
+
cloneEl.remove();
|
|
75
|
+
|
|
76
|
+
// if the frame document is accessible and not empty, we can serialize it
|
|
77
|
+
} else if (frame.contentDocument && frame.contentDocument.documentElement) {
|
|
78
|
+
// js is enabled and this frame was built with js, don't serialize it
|
|
79
|
+
if (enableJavaScript && builtWithJs) continue;
|
|
80
|
+
|
|
81
|
+
// the frame has yet to load and wasn't built with js, it is unsafe to serialize
|
|
82
|
+
if (!builtWithJs && !frame.contentWindow.performance.timing.loadEventEnd) continue;
|
|
83
|
+
|
|
84
|
+
// recersively serialize contents
|
|
85
|
+
let serialized = serializeDOM({
|
|
86
|
+
domTransformation: setBaseURI,
|
|
87
|
+
dom: frame.contentDocument,
|
|
88
|
+
enableJavaScript,
|
|
89
|
+
disableShadowDOM
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// append serialized warnings and resources
|
|
93
|
+
/* istanbul ignore next: warnings not implemented yet */
|
|
94
|
+
// for (let w of serialized.warnings) warnings.add(w);
|
|
95
|
+
// for (let r of serialized.resources) resources.add(r);
|
|
96
|
+
|
|
97
|
+
// assign serialized html to srcdoc and remove src
|
|
98
|
+
cloneEl.setAttribute('srcdoc', serialized.html);
|
|
99
|
+
cloneEl.removeAttribute('src');
|
|
100
|
+
|
|
101
|
+
// delete inaccessible frames built with js when js is disabled because they
|
|
102
|
+
// break asset discovery by creating non-captured requests that hang
|
|
103
|
+
} else if (!enableJavaScript && builtWithJs) {
|
|
104
|
+
cloneEl.remove();
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Creates a resource object from an element's unique ID and data URL
|
|
110
|
+
function resourceFromDataURL(uid, dataURL) {
|
|
111
|
+
// split dataURL into desired parts
|
|
112
|
+
let [data, content] = dataURL.split(',');
|
|
113
|
+
let [, mimetype] = data.split(':');
|
|
114
|
+
[mimetype] = mimetype.split(';');
|
|
115
|
+
|
|
116
|
+
// build a URL for the serialized asset
|
|
117
|
+
let [, ext] = mimetype.split('/');
|
|
118
|
+
let path = `/__serialized__/${uid}.${ext}`;
|
|
119
|
+
let url = rewriteLocalhostURL(new URL(path, document.URL).toString());
|
|
120
|
+
|
|
121
|
+
// return the url, base64 content, and mimetype
|
|
122
|
+
return {
|
|
123
|
+
url,
|
|
124
|
+
content,
|
|
125
|
+
mimetype
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function resourceFromText(uid, mimetype, data) {
|
|
129
|
+
// build a URL for the serialized asset
|
|
130
|
+
let [, ext] = mimetype.split('/');
|
|
131
|
+
let path = `/__serialized__/${uid}.${ext}`;
|
|
132
|
+
let url = rewriteLocalhostURL(new URL(path, document.URL).toString());
|
|
133
|
+
// return the url, text content, and mimetype
|
|
134
|
+
return {
|
|
135
|
+
url,
|
|
136
|
+
content: data,
|
|
137
|
+
mimetype
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function styleSheetFromNode(node) {
|
|
141
|
+
/* istanbul ignore if: sanity check */
|
|
142
|
+
if (node.sheet) return node.sheet;
|
|
143
|
+
|
|
144
|
+
// Cloned style nodes don't have a sheet instance unless they are within
|
|
145
|
+
// a document; we get it by temporarily adding the rules to DOM
|
|
146
|
+
const tempStyle = node.cloneNode();
|
|
147
|
+
tempStyle.setAttribute('data-smartui-style-helper', '');
|
|
148
|
+
tempStyle.innerHTML = node.innerHTML;
|
|
149
|
+
const clone = document.cloneNode();
|
|
150
|
+
clone.appendChild(tempStyle);
|
|
151
|
+
const sheet = tempStyle.sheet;
|
|
152
|
+
// Cleanup node
|
|
153
|
+
tempStyle.remove();
|
|
154
|
+
return sheet;
|
|
155
|
+
}
|
|
156
|
+
function rewriteLocalhostURL(url) {
|
|
157
|
+
return url.replace(/(http[s]{0,1}:\/\/)localhost[:\d+]*/, '$1render.smartui.local');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Returns a mostly random uid.
|
|
161
|
+
function uid() {
|
|
162
|
+
return `_${Math.random().toString(36).substr(2, 9)}`;
|
|
163
|
+
}
|
|
164
|
+
function markElement(domElement, disableShadowDOM) {
|
|
165
|
+
var _domElement$tagName;
|
|
166
|
+
// Mark elements that are to be serialized later with a data attribute.
|
|
167
|
+
if (['input', 'textarea', 'select', 'iframe', 'canvas', 'video', 'style'].includes((_domElement$tagName = domElement.tagName) === null || _domElement$tagName === void 0 ? void 0 : _domElement$tagName.toLowerCase())) {
|
|
168
|
+
if (!domElement.getAttribute('data-smartui-element-id')) {
|
|
169
|
+
domElement.setAttribute('data-smartui-element-id', uid());
|
|
170
|
+
}
|
|
151
171
|
}
|
|
152
|
-
|
|
153
|
-
|
|
172
|
+
|
|
173
|
+
// add special marker for shadow host
|
|
174
|
+
if (!disableShadowDOM && domElement.shadowRoot) {
|
|
175
|
+
domElement.setAttribute('data-smartui-shadow-host', '');
|
|
176
|
+
if (!domElement.getAttribute('data-smartui-element-id')) {
|
|
177
|
+
domElement.setAttribute('data-smartui-element-id', uid());
|
|
178
|
+
}
|
|
154
179
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Returns true if a stylesheet is a CSSOM-based stylesheet.
|
|
183
|
+
function isCSSOM(styleSheet) {
|
|
184
|
+
// no href, has a rulesheet, and has an owner node
|
|
185
|
+
return !styleSheet.href && styleSheet.cssRules && styleSheet.ownerNode;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Returns false if any stylesheet rules do not match between two stylesheets
|
|
189
|
+
function styleSheetsMatch(sheetA, sheetB) {
|
|
190
|
+
for (let i = 0; i < sheetA.cssRules.length; i++) {
|
|
191
|
+
var _sheetB$cssRules$i;
|
|
192
|
+
let ruleA = sheetA.cssRules[i].cssText;
|
|
193
|
+
let ruleB = (_sheetB$cssRules$i = sheetB.cssRules[i]) === null || _sheetB$cssRules$i === void 0 ? void 0 : _sheetB$cssRules$i.cssText;
|
|
194
|
+
if (ruleA !== ruleB) return false;
|
|
159
195
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function createStyleResource(styleSheet) {
|
|
199
|
+
const styles = Array.from(styleSheet.cssRules).map(cssRule => cssRule.cssText).join('\n');
|
|
200
|
+
let resource = resourceFromText(uid(), 'text/css', styles);
|
|
201
|
+
return resource;
|
|
202
|
+
}
|
|
203
|
+
function serializeCSSOM(_ref) {
|
|
204
|
+
let {
|
|
205
|
+
dom,
|
|
206
|
+
clone,
|
|
207
|
+
resources,
|
|
208
|
+
cache,
|
|
209
|
+
warnings
|
|
210
|
+
} = _ref;
|
|
211
|
+
// in-memory CSSOM into their respective DOM nodes.
|
|
212
|
+
for (let styleSheet of dom.styleSheets) {
|
|
213
|
+
var _styleSheet$href;
|
|
214
|
+
if (isCSSOM(styleSheet)) {
|
|
215
|
+
let styleId = styleSheet.ownerNode.getAttribute('data-smartui-element-id');
|
|
216
|
+
let cloneOwnerNode = clone.querySelector(`[data-smartui-element-id="${styleId}"]`);
|
|
217
|
+
if (styleSheetsMatch(styleSheet, styleSheetFromNode(cloneOwnerNode))) continue;
|
|
218
|
+
let style = document.createElement('style');
|
|
219
|
+
style.type = 'text/css';
|
|
220
|
+
style.setAttribute('data-smartui-element-id', styleId);
|
|
221
|
+
style.setAttribute('data-smartui-cssom-serialized', 'true');
|
|
222
|
+
style.innerHTML = Array.from(styleSheet.cssRules).map(cssRule => cssRule.cssText).join('\n');
|
|
223
|
+
cloneOwnerNode.parentNode.insertBefore(style, cloneOwnerNode.nextSibling);
|
|
224
|
+
cloneOwnerNode.remove();
|
|
225
|
+
} else if ((_styleSheet$href = styleSheet.href) !== null && _styleSheet$href !== void 0 && _styleSheet$href.startsWith('blob:')) {
|
|
226
|
+
const styleLink = document.createElement('link');
|
|
227
|
+
styleLink.setAttribute('rel', 'stylesheet');
|
|
228
|
+
let resource = createStyleResource(styleSheet);
|
|
229
|
+
resources.add(resource);
|
|
230
|
+
styleLink.setAttribute('data-smartui-blob-stylesheets-serialized', 'true');
|
|
231
|
+
styleLink.setAttribute('data-smartui-serialized-attribute-href', resource.url);
|
|
232
|
+
|
|
233
|
+
/* istanbul ignore next: tested, but coverage is stripped */
|
|
234
|
+
if (clone.constructor.name === 'HTMLDocument' || clone.constructor.name === 'DocumentFragment') {
|
|
235
|
+
// handle document and iframe
|
|
236
|
+
clone.body.prepend(styleLink);
|
|
237
|
+
} else if (clone.constructor.name === 'ShadowRoot') {
|
|
238
|
+
clone.prepend(styleLink);
|
|
166
239
|
}
|
|
167
240
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// clone Adopted Stylesheets
|
|
244
|
+
// Regarding ordering of the adopted stylesheets - https://github.com/WICG/construct-stylesheets/issues/93
|
|
245
|
+
/* istanbul ignore next: tested, but coverage is stripped */
|
|
246
|
+
if (dom.adoptedStyleSheets) {
|
|
247
|
+
for (let sheet of dom.adoptedStyleSheets) {
|
|
248
|
+
const styleLink = document.createElement('link');
|
|
249
|
+
styleLink.setAttribute('rel', 'stylesheet');
|
|
250
|
+
if (!cache.has(sheet)) {
|
|
251
|
+
let resource = createStyleResource(sheet);
|
|
252
|
+
resources.add(resource);
|
|
253
|
+
cache.set(sheet, resource.url);
|
|
254
|
+
}
|
|
255
|
+
styleLink.setAttribute('data-smartui-adopted-stylesheets-serialized', 'true');
|
|
256
|
+
styleLink.setAttribute('data-smartui-serialized-attribute-href', cache.get(sheet));
|
|
257
|
+
|
|
258
|
+
/* istanbul ignore next: tested, but coverage is stripped */
|
|
259
|
+
if (clone.constructor.name === 'HTMLDocument' || clone.constructor.name === 'DocumentFragment') {
|
|
260
|
+
// handle document and iframe
|
|
261
|
+
clone.body.prepend(styleLink);
|
|
262
|
+
} else if (clone.constructor.name === 'ShadowRoot') {
|
|
263
|
+
clone.prepend(styleLink);
|
|
174
264
|
}
|
|
175
265
|
}
|
|
266
|
+
} else {
|
|
267
|
+
warnings.add('Skipping `adoptedStyleSheets` as it is not supported.');
|
|
176
268
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Serialize in-memory canvas elements into images.
|
|
272
|
+
function serializeCanvas(_ref) {
|
|
273
|
+
let {
|
|
274
|
+
dom,
|
|
275
|
+
clone,
|
|
276
|
+
resources
|
|
277
|
+
} = _ref;
|
|
278
|
+
for (let canvas of dom.querySelectorAll('canvas')) {
|
|
279
|
+
// Note: the `.toDataURL` API requires WebGL canvas elements to use
|
|
280
|
+
// `preserveDrawingBuffer: true`. This is because `.toDataURL` uses the
|
|
281
|
+
// drawing buffer, which is cleared after each render for WebGL by default.
|
|
282
|
+
let dataUrl = canvas.toDataURL();
|
|
283
|
+
|
|
284
|
+
// skip empty canvases
|
|
285
|
+
if (!dataUrl || dataUrl === 'data:,') continue;
|
|
286
|
+
|
|
287
|
+
// get the element's smartui id and create a resource for it
|
|
288
|
+
let smartuiElementId = canvas.getAttribute('data-smartui-element-id');
|
|
289
|
+
// let resource = resourceFromDataURL(smartuiElementId, dataUrl);
|
|
290
|
+
// resources.add(resource);
|
|
291
|
+
|
|
292
|
+
// create an image element in the cloned dom
|
|
293
|
+
let img = document.createElement('img');
|
|
294
|
+
// use a data attribute to avoid making a real request
|
|
295
|
+
// img.setAttribute('data-smartui-serialized-attribute-src', resource.url);
|
|
296
|
+
img.setAttribute('src', dataUrl);
|
|
297
|
+
|
|
298
|
+
// copy canvas element attributes to the image element such as style, class,
|
|
299
|
+
// or data attributes that may be targeted by CSS
|
|
300
|
+
for (let {
|
|
301
|
+
name,
|
|
302
|
+
value
|
|
303
|
+
} of canvas.attributes) {
|
|
304
|
+
img.setAttribute(name, value);
|
|
191
305
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
306
|
+
|
|
307
|
+
// mark the image as serialized (can be targeted by CSS)
|
|
308
|
+
img.setAttribute('data-smartui-canvas-serialized', 'test');
|
|
309
|
+
// set a default max width to account for canvases that might resize with JS
|
|
310
|
+
img.style.maxWidth = img.style.maxWidth || '100%';
|
|
311
|
+
|
|
312
|
+
// insert the image into the cloned DOM and remove the cloned canvas element
|
|
313
|
+
let cloneEl = clone.querySelector(`[data-smartui-element-id=${smartuiElementId}]`);
|
|
314
|
+
// `parentElement` for elements directly under shadow root is `null` -> Incase of Nested Shadow DOM.
|
|
315
|
+
if (cloneEl.parentElement) {
|
|
316
|
+
cloneEl.parentElement.insertBefore(img, cloneEl);
|
|
317
|
+
} else {
|
|
318
|
+
clone.insertBefore(img, cloneEl);
|
|
319
|
+
}
|
|
320
|
+
cloneEl.remove();
|
|
198
321
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
style.setAttribute('data-smartui-element-id', styleId);
|
|
217
|
-
style.setAttribute('data-smartui-cssom-serialized', 'true');
|
|
218
|
-
style.innerHTML = Array.from(styleSheet.cssRules).map(cssRule => cssRule.cssText).join('\n');
|
|
219
|
-
cloneOwnerNode.parentNode.insertBefore(style, cloneOwnerNode.nextSibling);
|
|
220
|
-
cloneOwnerNode.remove();
|
|
221
|
-
} else if ((_styleSheet$href = styleSheet.href) !== null && _styleSheet$href !== void 0 && _styleSheet$href.startsWith('blob:')) {
|
|
222
|
-
const styleLink = document.createElement('link');
|
|
223
|
-
styleLink.setAttribute('rel', 'stylesheet');
|
|
224
|
-
let resource = createStyleResource(styleSheet);
|
|
225
|
-
resources.add(resource);
|
|
226
|
-
styleLink.setAttribute('data-smartui-blob-stylesheets-serialized', 'true');
|
|
227
|
-
styleLink.setAttribute('data-smartui-serialized-attribute-href', resource.url);
|
|
228
|
-
|
|
229
|
-
/* istanbul ignore next: tested, but coverage is stripped */
|
|
230
|
-
if (clone.constructor.name === 'HTMLDocument' || clone.constructor.name === 'DocumentFragment') {
|
|
231
|
-
// handle document and iframe
|
|
232
|
-
clone.body.prepend(styleLink);
|
|
233
|
-
} else if (clone.constructor.name === 'ShadowRoot') {
|
|
234
|
-
clone.prepend(styleLink);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Captures the current frame of videos and sets the poster image
|
|
325
|
+
function serializeVideos(_ref) {
|
|
326
|
+
let {
|
|
327
|
+
dom,
|
|
328
|
+
clone,
|
|
329
|
+
resources,
|
|
330
|
+
warnings
|
|
331
|
+
} = _ref;
|
|
332
|
+
for (let video of dom.querySelectorAll('video')) {
|
|
333
|
+
let videoId = video.getAttribute('data-smartui-element-id');
|
|
334
|
+
let cloneEl = clone.querySelector(`[data-smartui-element-id="${videoId}"]`);
|
|
335
|
+
// if the video already has a poster image, no work for us to do
|
|
336
|
+
if (video.getAttribute('poster')) {
|
|
337
|
+
cloneEl.removeAttribute('src');
|
|
338
|
+
continue;
|
|
237
339
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
let resource = createStyleResource(sheet);
|
|
248
|
-
resources.add(resource);
|
|
249
|
-
cache.set(sheet, resource.url);
|
|
250
|
-
}
|
|
251
|
-
styleLink.setAttribute('data-smartui-adopted-stylesheets-serialized', 'true');
|
|
252
|
-
styleLink.setAttribute('data-smartui-serialized-attribute-href', cache.get(sheet));
|
|
253
|
-
|
|
254
|
-
/* istanbul ignore next: tested, but coverage is stripped */
|
|
255
|
-
if (clone.constructor.name === 'HTMLDocument' || clone.constructor.name === 'DocumentFragment') {
|
|
256
|
-
// handle document and iframe
|
|
257
|
-
clone.body.prepend(styleLink);
|
|
258
|
-
} else if (clone.constructor.name === 'ShadowRoot') {
|
|
259
|
-
clone.prepend(styleLink);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
} else {
|
|
263
|
-
warnings.add('Skipping `adoptedStyleSheets` as it is not supported.');
|
|
340
|
+
let canvas = document.createElement('canvas');
|
|
341
|
+
let width = canvas.width = video.videoWidth;
|
|
342
|
+
let height = canvas.height = video.videoHeight;
|
|
343
|
+
let dataUrl;
|
|
344
|
+
canvas.getContext('2d').drawImage(video, 0, 0, width, height);
|
|
345
|
+
try {
|
|
346
|
+
dataUrl = canvas.toDataURL();
|
|
347
|
+
} catch (e) {
|
|
348
|
+
warnings.add(`data-smartui-element-id="${videoId}" : ${e.toString()}`);
|
|
264
349
|
}
|
|
350
|
+
|
|
351
|
+
// if the canvas produces a blank image, skip
|
|
352
|
+
if (!dataUrl || dataUrl === 'data:,') continue;
|
|
353
|
+
|
|
354
|
+
// create a resource from the serialized data url
|
|
355
|
+
// let resource = resourceFromDataURL(videoId, dataUrl);
|
|
356
|
+
// resources.add(resource);
|
|
357
|
+
|
|
358
|
+
// use a data attribute to avoid making a real request
|
|
359
|
+
cloneEl.removeAttribute('src');
|
|
360
|
+
cloneEl.setAttribute('poster', dataUrl);
|
|
265
361
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Drop loading attribute. We do not scroll page in discovery stage but we want to make sure that
|
|
365
|
+
// all resources are requested, so we drop loading attribute [as it can be set to lazy]
|
|
366
|
+
function dropLoadingAttribute(domElement) {
|
|
367
|
+
var _domElement$tagName;
|
|
368
|
+
if (!['img', 'iframe'].includes((_domElement$tagName = domElement.tagName) === null || _domElement$tagName === void 0 ? void 0 : _domElement$tagName.toLowerCase())) return;
|
|
369
|
+
domElement.removeAttribute('loading');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// All transformations that we need to apply for a successful discovery and stable render
|
|
373
|
+
function applyElementTransformations(domElement) {
|
|
374
|
+
dropLoadingAttribute(domElement);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Custom deep clone function that replaces SmartUI's current clone behavior.
|
|
379
|
+
* This enables us to capture shadow DOM in snapshots. It takes advantage of `attachShadow`'s mode option set to open
|
|
380
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#parameters
|
|
381
|
+
*/
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Deep clone a document while also preserving shadow roots
|
|
385
|
+
* returns document fragment
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
const ignoreTags = ['NOSCRIPT'];
|
|
389
|
+
function cloneNodeAndShadow(_ref) {
|
|
390
|
+
let {
|
|
391
|
+
dom,
|
|
392
|
+
disableShadowDOM
|
|
393
|
+
} = _ref;
|
|
394
|
+
// clones shadow DOM and light DOM for a given node
|
|
395
|
+
let cloneNode = (node, parent) => {
|
|
396
|
+
let walkTree = (nextn, nextp) => {
|
|
397
|
+
while (nextn) {
|
|
398
|
+
if (!ignoreTags.includes(nextn.nodeName)) {
|
|
399
|
+
cloneNode(nextn, nextp);
|
|
400
|
+
}
|
|
401
|
+
nextn = nextn.nextSibling;
|
|
300
402
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// mark the node before cloning
|
|
406
|
+
markElement(node, disableShadowDOM);
|
|
407
|
+
let clone = node.cloneNode();
|
|
408
|
+
|
|
409
|
+
// We apply any element transformations here to avoid another treeWalk
|
|
410
|
+
applyElementTransformations(clone);
|
|
411
|
+
parent.appendChild(clone);
|
|
412
|
+
|
|
413
|
+
// shallow clone should not contain children
|
|
414
|
+
if (clone.children) {
|
|
415
|
+
Array.from(clone.children).forEach(child => clone.removeChild(child));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// clone shadow DOM
|
|
419
|
+
if (node.shadowRoot && !disableShadowDOM) {
|
|
420
|
+
// create shadowRoot
|
|
421
|
+
if (clone.shadowRoot) {
|
|
422
|
+
// it may be set up in a custom element's constructor
|
|
423
|
+
clone.shadowRoot.innerHTML = '';
|
|
312
424
|
} else {
|
|
313
|
-
clone.
|
|
425
|
+
clone.attachShadow({
|
|
426
|
+
mode: 'open'
|
|
427
|
+
});
|
|
314
428
|
}
|
|
315
|
-
|
|
429
|
+
// clone dom elements
|
|
430
|
+
walkTree(node.shadowRoot.firstChild, clone.shadowRoot);
|
|
316
431
|
}
|
|
432
|
+
|
|
433
|
+
// clone light DOM
|
|
434
|
+
walkTree(node.firstChild, clone);
|
|
435
|
+
};
|
|
436
|
+
let fragment = dom.createDocumentFragment();
|
|
437
|
+
cloneNode(dom.documentElement, fragment);
|
|
438
|
+
fragment.documentElement = fragment.firstChild;
|
|
439
|
+
fragment.head = fragment.querySelector('head');
|
|
440
|
+
fragment.body = fragment.querySelector('body');
|
|
441
|
+
return fragment;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Use `getInnerHTML()` to serialize shadow dom as <template> tags. `innerHTML` and `outerHTML` don't do this. Buzzword: "declarative shadow dom"
|
|
446
|
+
*/
|
|
447
|
+
function getOuterHTML(docElement) {
|
|
448
|
+
// firefox doesn't serialize shadow DOM, we're awaiting API's by firefox to become ready and are not polyfilling it.
|
|
449
|
+
if (!docElement.getInnerHTML) {
|
|
450
|
+
return docElement.outerHTML;
|
|
317
451
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
// create a resource from the serialized data url
|
|
347
|
-
let resource = resourceFromDataURL(videoId, dataUrl);
|
|
348
|
-
resources.add(resource);
|
|
349
|
-
|
|
350
|
-
// use a data attribute to avoid making a real request
|
|
351
|
-
cloneEl.setAttribute('data-smartui-serialized-attribute-poster', resource.url);
|
|
352
|
-
}
|
|
452
|
+
// chromium gives us declarative shadow DOM serialization API
|
|
453
|
+
let innerHTML = docElement.getInnerHTML({
|
|
454
|
+
includeShadowRoots: true
|
|
455
|
+
});
|
|
456
|
+
docElement.textContent = '';
|
|
457
|
+
// Note: Here we are specifically passing replacer function to avoid any replacements due to
|
|
458
|
+
// special characters in client's dom like $&
|
|
459
|
+
return docElement.outerHTML.replace('</html>', () => `${innerHTML}</html>`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// we inject declarative shadow dom polyfill to allow shadow dom to load in non chromium infrastructure browsers
|
|
463
|
+
// Since only chromium currently supports declarative shadow DOM - https://caniuse.com/declarative-shadow-dom
|
|
464
|
+
function injectDeclarativeShadowDOMPolyfill(ctx) {
|
|
465
|
+
let clone = ctx.clone;
|
|
466
|
+
let scriptEl = document.createElement('script');
|
|
467
|
+
scriptEl.setAttribute('id', '__smartui_shadowdom_helper');
|
|
468
|
+
scriptEl.setAttribute('data-smartui-injected', true);
|
|
469
|
+
scriptEl.innerHTML = `
|
|
470
|
+
function reversePolyFill(root=document){
|
|
471
|
+
root.querySelectorAll('template[shadowroot]').forEach(template => {
|
|
472
|
+
const mode = template.getAttribute('shadowroot');
|
|
473
|
+
const shadowRoot = template.parentNode.attachShadow({ mode });
|
|
474
|
+
shadowRoot.appendChild(template.content);
|
|
475
|
+
template.remove();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
root.querySelectorAll('[data-smartui-shadow-host]').forEach(shadowHost => reversePolyFill(shadowHost.shadowRoot));
|
|
353
479
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (!['img', 'iframe'].includes((_domElement$tagName = domElement.tagName) === null || _domElement$tagName === void 0 ? void 0 : _domElement$tagName.toLowerCase())) return;
|
|
360
|
-
domElement.removeAttribute('loading');
|
|
480
|
+
|
|
481
|
+
if (["interactive", "complete"].includes(document.readyState)) {
|
|
482
|
+
reversePolyFill();
|
|
483
|
+
} else {
|
|
484
|
+
document.addEventListener("DOMContentLoaded", () => reversePolyFill());
|
|
361
485
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
486
|
+
`.replace(/(\n|\s{2}|\t)/g, '');
|
|
487
|
+
|
|
488
|
+
// run polyfill as first thing post dom content is loaded
|
|
489
|
+
clone.head.prepend(scriptEl);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Returns a copy or new doctype for a document.
|
|
493
|
+
function doctype(dom) {
|
|
494
|
+
let {
|
|
495
|
+
name = 'html',
|
|
496
|
+
publicId = '',
|
|
497
|
+
systemId = ''
|
|
498
|
+
} = (dom === null || dom === void 0 ? void 0 : dom.doctype) ?? {};
|
|
499
|
+
let deprecated = '';
|
|
500
|
+
if (publicId && systemId) {
|
|
501
|
+
deprecated = ` PUBLIC "${publicId}" "${systemId}"`;
|
|
502
|
+
} else if (publicId) {
|
|
503
|
+
deprecated = ` PUBLIC "${publicId}"`;
|
|
504
|
+
} else if (systemId) {
|
|
505
|
+
deprecated = ` SYSTEM "${systemId}"`;
|
|
366
506
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
// clones shadow DOM and light DOM for a given node
|
|
386
|
-
let cloneNode = (node, parent) => {
|
|
387
|
-
let walkTree = (nextn, nextp) => {
|
|
388
|
-
while (nextn) {
|
|
389
|
-
if (!ignoreTags.includes(nextn.nodeName)) {
|
|
390
|
-
cloneNode(nextn, nextp);
|
|
391
|
-
}
|
|
392
|
-
nextn = nextn.nextSibling;
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
// mark the node before cloning
|
|
397
|
-
markElement(node, disableShadowDOM);
|
|
398
|
-
let clone = node.cloneNode();
|
|
399
|
-
|
|
400
|
-
// We apply any element transformations here to avoid another treeWalk
|
|
401
|
-
applyElementTransformations(clone);
|
|
402
|
-
parent.appendChild(clone);
|
|
403
|
-
|
|
404
|
-
// shallow clone should not contain children
|
|
405
|
-
if (clone.children) {
|
|
406
|
-
Array.from(clone.children).forEach(child => clone.removeChild(child));
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// clone shadow DOM
|
|
410
|
-
if (node.shadowRoot && !disableShadowDOM) {
|
|
411
|
-
// create shadowRoot
|
|
412
|
-
if (clone.shadowRoot) {
|
|
413
|
-
// it may be set up in a custom element's constructor
|
|
414
|
-
clone.shadowRoot.innerHTML = '';
|
|
415
|
-
} else {
|
|
416
|
-
clone.attachShadow({
|
|
417
|
-
mode: 'open'
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
// clone dom elements
|
|
421
|
-
walkTree(node.shadowRoot.firstChild, clone.shadowRoot);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// clone light DOM
|
|
425
|
-
walkTree(node.firstChild, clone);
|
|
426
|
-
};
|
|
427
|
-
let fragment = dom.createDocumentFragment();
|
|
428
|
-
cloneNode(dom.documentElement, fragment);
|
|
429
|
-
fragment.documentElement = fragment.firstChild;
|
|
430
|
-
fragment.head = fragment.querySelector('head');
|
|
431
|
-
fragment.body = fragment.querySelector('body');
|
|
432
|
-
return fragment;
|
|
507
|
+
return `<!DOCTYPE ${name}${deprecated}>`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Serializes and returns the cloned DOM as an HTML string
|
|
511
|
+
function serializeHTML(ctx) {
|
|
512
|
+
let html = getOuterHTML(ctx.clone.documentElement);
|
|
513
|
+
// replace serialized data attributes with real attributes
|
|
514
|
+
html = html.replace(/ data-smartui-serialized-attribute-(\w+?)=/ig, ' $1=');
|
|
515
|
+
// include the doctype with the html string
|
|
516
|
+
return doctype(ctx.dom) + html;
|
|
517
|
+
}
|
|
518
|
+
function serializeElements(ctx) {
|
|
519
|
+
serializeInputElements(ctx);
|
|
520
|
+
serializeFrames(ctx);
|
|
521
|
+
serializeVideos(ctx);
|
|
522
|
+
if (!ctx.enableJavaScript) {
|
|
523
|
+
serializeCSSOM(ctx);
|
|
524
|
+
serializeCanvas(ctx);
|
|
433
525
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
// chromium gives us declarative shadow DOM serialization API
|
|
444
|
-
let innerHTML = docElement.getInnerHTML({
|
|
445
|
-
includeShadowRoots: true
|
|
446
|
-
});
|
|
447
|
-
docElement.textContent = '';
|
|
448
|
-
// Note: Here we are specifically passing replacer function to avoid any replacements due to
|
|
449
|
-
// special characters in client's dom like $&
|
|
450
|
-
return docElement.outerHTML.replace('</html>', () => `${innerHTML}</html>`);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// we inject declarative shadow dom polyfill to allow shadow dom to load in non chromium infrastructure browsers
|
|
454
|
-
// Since only chromium currently supports declarative shadow DOM - https://caniuse.com/declarative-shadow-dom
|
|
455
|
-
function injectDeclarativeShadowDOMPolyfill(ctx) {
|
|
456
|
-
let clone = ctx.clone;
|
|
457
|
-
let scriptEl = document.createElement('script');
|
|
458
|
-
scriptEl.setAttribute('id', '__smartui_shadowdom_helper');
|
|
459
|
-
scriptEl.setAttribute('data-smartui-injected', true);
|
|
460
|
-
scriptEl.innerHTML = `
|
|
461
|
-
function reversePolyFill(root=document){
|
|
462
|
-
root.querySelectorAll('template[shadowroot]').forEach(template => {
|
|
463
|
-
const mode = template.getAttribute('shadowroot');
|
|
464
|
-
const shadowRoot = template.parentNode.attachShadow({ mode });
|
|
465
|
-
shadowRoot.appendChild(template.content);
|
|
466
|
-
template.remove();
|
|
526
|
+
for (const shadowHost of ctx.dom.querySelectorAll('[data-smartui-shadow-host]')) {
|
|
527
|
+
let smartuiElementId = shadowHost.getAttribute('data-smartui-element-id');
|
|
528
|
+
let cloneShadowHost = ctx.clone.querySelector(`[data-smartui-element-id="${smartuiElementId}"]`);
|
|
529
|
+
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
|
|
530
|
+
serializeElements({
|
|
531
|
+
...ctx,
|
|
532
|
+
dom: shadowHost.shadowRoot,
|
|
533
|
+
clone: cloneShadowHost.shadowRoot
|
|
467
534
|
});
|
|
468
|
-
|
|
469
|
-
root.querySelectorAll('[data-smartui-shadow-host]').forEach(shadowHost => reversePolyFill(shadowHost.shadowRoot));
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (["interactive", "complete"].includes(document.readyState)) {
|
|
473
|
-
reversePolyFill();
|
|
474
535
|
} else {
|
|
475
|
-
|
|
536
|
+
ctx.warnings.add('data-smartui-shadow-host does not have shadowRoot');
|
|
476
537
|
}
|
|
477
|
-
`.replace(/(\n|\s{2}|\t)/g, '');
|
|
478
|
-
|
|
479
|
-
// run polyfill as first thing post dom content is loaded
|
|
480
|
-
clone.head.prepend(scriptEl);
|
|
481
538
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Serializes a document and returns the resulting DOM string.
|
|
542
|
+
function serializeDOM(options) {
|
|
543
|
+
let {
|
|
544
|
+
dom = document,
|
|
545
|
+
// allow snake_case or camelCase
|
|
546
|
+
enableJavaScript = options === null || options === void 0 ? void 0 : options.enable_javascript,
|
|
547
|
+
domTransformation = options === null || options === void 0 ? void 0 : options.dom_transformation,
|
|
548
|
+
stringifyResponse = options === null || options === void 0 ? void 0 : options.stringify_response,
|
|
549
|
+
disableShadowDOM = options === null || options === void 0 ? void 0 : options.disable_shadow_dom,
|
|
550
|
+
reshuffleInvalidTags = options === null || options === void 0 ? void 0 : options.reshuffle_invalid_tags
|
|
551
|
+
} = options || {};
|
|
552
|
+
|
|
553
|
+
// keep certain records throughout serialization
|
|
554
|
+
let ctx = {
|
|
555
|
+
resources: new Set(),
|
|
556
|
+
warnings: new Set(),
|
|
557
|
+
hints: new Set(),
|
|
558
|
+
cache: new Map(),
|
|
559
|
+
enableJavaScript,
|
|
560
|
+
disableShadowDOM
|
|
561
|
+
};
|
|
562
|
+
ctx.dom = dom;
|
|
563
|
+
ctx.clone = cloneNodeAndShadow(ctx);
|
|
564
|
+
serializeElements(ctx);
|
|
565
|
+
setBaseURI(ctx.clone.documentElement);
|
|
566
|
+
if (domTransformation) {
|
|
567
|
+
try {
|
|
568
|
+
// eslint-disable-next-line no-eval
|
|
569
|
+
if (typeof domTransformation === 'string') domTransformation = window.eval(domTransformation);
|
|
570
|
+
domTransformation(ctx.clone.documentElement);
|
|
571
|
+
} catch (err) {
|
|
572
|
+
let errorMessage = `Could not transform the dom: ${err.message}`;
|
|
573
|
+
ctx.warnings.add(errorMessage);
|
|
574
|
+
console.error(errorMessage);
|
|
497
575
|
}
|
|
498
|
-
return `<!DOCTYPE ${name}${deprecated}>`;
|
|
499
576
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
function serializeHTML(ctx) {
|
|
503
|
-
let html = getOuterHTML(ctx.clone.documentElement);
|
|
504
|
-
// replace serialized data attributes with real attributes
|
|
505
|
-
html = html.replace(/ data-smartui-serialized-attribute-(\w+?)=/ig, ' $1=');
|
|
506
|
-
// include the doctype with the html string
|
|
507
|
-
return doctype(ctx.dom) + html;
|
|
577
|
+
if (!disableShadowDOM) {
|
|
578
|
+
injectDeclarativeShadowDOMPolyfill(ctx);
|
|
508
579
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
serializeCSSOM(ctx);
|
|
515
|
-
serializeCanvas(ctx);
|
|
516
|
-
}
|
|
517
|
-
for (const shadowHost of ctx.dom.querySelectorAll('[data-smartui-shadow-host]')) {
|
|
518
|
-
let smartuiElementId = shadowHost.getAttribute('data-smartui-element-id');
|
|
519
|
-
let cloneShadowHost = ctx.clone.querySelector(`[data-smartui-element-id="${smartuiElementId}"]`);
|
|
520
|
-
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
|
|
521
|
-
serializeElements({
|
|
522
|
-
...ctx,
|
|
523
|
-
dom: shadowHost.shadowRoot,
|
|
524
|
-
clone: cloneShadowHost.shadowRoot
|
|
525
|
-
});
|
|
526
|
-
} else {
|
|
527
|
-
ctx.warnings.add('data-smartui-shadow-host does not have shadowRoot');
|
|
528
|
-
}
|
|
580
|
+
if (reshuffleInvalidTags) {
|
|
581
|
+
let clonedBody = ctx.clone.body;
|
|
582
|
+
while (clonedBody.nextSibling) {
|
|
583
|
+
let sibling = clonedBody.nextSibling;
|
|
584
|
+
clonedBody.append(sibling);
|
|
529
585
|
}
|
|
586
|
+
} else if (ctx.clone.body.nextSibling) {
|
|
587
|
+
ctx.hints.add('DOM elements found outside </body>');
|
|
530
588
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
warnings: new Set(),
|
|
548
|
-
hints: new Set(),
|
|
549
|
-
cache: new Map(),
|
|
550
|
-
enableJavaScript,
|
|
551
|
-
disableShadowDOM
|
|
552
|
-
};
|
|
553
|
-
ctx.dom = dom;
|
|
554
|
-
ctx.clone = cloneNodeAndShadow(ctx);
|
|
555
|
-
serializeElements(ctx);
|
|
556
|
-
if (domTransformation) {
|
|
557
|
-
try {
|
|
558
|
-
// eslint-disable-next-line no-eval
|
|
559
|
-
if (typeof domTransformation === 'string') domTransformation = window.eval(domTransformation);
|
|
560
|
-
domTransformation(ctx.clone.documentElement);
|
|
561
|
-
} catch (err) {
|
|
562
|
-
let errorMessage = `Could not transform the dom: ${err.message}`;
|
|
563
|
-
ctx.warnings.add(errorMessage);
|
|
564
|
-
console.error(errorMessage);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
if (!disableShadowDOM) {
|
|
568
|
-
injectDeclarativeShadowDOMPolyfill(ctx);
|
|
569
|
-
}
|
|
570
|
-
if (reshuffleInvalidTags) {
|
|
571
|
-
let clonedBody = ctx.clone.body;
|
|
572
|
-
while (clonedBody.nextSibling) {
|
|
573
|
-
let sibling = clonedBody.nextSibling;
|
|
574
|
-
clonedBody.append(sibling);
|
|
575
|
-
}
|
|
576
|
-
} else if (ctx.clone.body.nextSibling) {
|
|
577
|
-
ctx.hints.add('DOM elements found outside </body>');
|
|
578
|
-
}
|
|
579
|
-
let result = {
|
|
580
|
-
html: serializeHTML(ctx),
|
|
581
|
-
warnings: Array.from(ctx.warnings),
|
|
582
|
-
resources: Array.from(ctx.resources),
|
|
583
|
-
hints: Array.from(ctx.hints)
|
|
584
|
-
};
|
|
585
|
-
return stringifyResponse ? JSON.stringify(result) : result;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
exports["default"] = serializeDOM;
|
|
589
|
-
exports.serialize = serializeDOM;
|
|
590
|
-
exports.serializeDOM = serializeDOM;
|
|
591
|
-
|
|
592
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
593
|
-
|
|
594
|
-
})(this.SmartUIDOM = this.SmartUIDOM || {});
|
|
589
|
+
let result = {
|
|
590
|
+
html: serializeHTML(ctx),
|
|
591
|
+
warnings: Array.from(ctx.warnings),
|
|
592
|
+
resources: Array.from(ctx.resources),
|
|
593
|
+
hints: Array.from(ctx.hints)
|
|
594
|
+
};
|
|
595
|
+
return stringifyResponse ? JSON.stringify(result) : result;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
exports["default"] = serializeDOM;
|
|
599
|
+
exports.serialize = serializeDOM;
|
|
600
|
+
exports.serializeDOM = serializeDOM;
|
|
601
|
+
|
|
602
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
603
|
+
|
|
604
|
+
})(this.SmartUIDOM = this.SmartUIDOM || {});
|
|
595
605
|
}).call(window);
|
|
596
|
-
|
|
606
|
+
|
|
597
607
|
if (typeof define === "function" && define.amd) {
|
|
598
608
|
define("@smartui/dom", [], () => window.SmartUIDOM);
|
|
599
609
|
} else if (typeof module === "object" && module.exports) {
|