single-file-core 1.0.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.
@@ -0,0 +1 @@
1
+ import "./processors/frame-tree/content/content-frame-tree.js";
@@ -0,0 +1,557 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global globalThis, CustomEvent */
25
+
26
+ import * as cssUnescape from "./vendor/css-unescape.js";
27
+ import * as hooksFrames from "./processors/hooks/content/content-hooks-frames";
28
+
29
+ const ON_BEFORE_CAPTURE_EVENT_NAME = "single-file-on-before-capture";
30
+ const ON_AFTER_CAPTURE_EVENT_NAME = "single-file-on-after-capture";
31
+ const REMOVED_CONTENT_ATTRIBUTE_NAME = "data-single-file-removed-content";
32
+ const HIDDEN_CONTENT_ATTRIBUTE_NAME = "data-single-file-hidden-content";
33
+ const KEPT_CONTENT_ATTRIBUTE_NAME = "data-single-file-kept-content";
34
+ const HIDDEN_FRAME_ATTRIBUTE_NAME = "data-single-file-hidden-frame";
35
+ const PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME = "data-single-file-preserved-space-element";
36
+ const SHADOW_ROOT_ATTRIBUTE_NAME = "data-single-file-shadow-root-element";
37
+ const WIN_ID_ATTRIBUTE_NAME = "data-single-file-win-id";
38
+ const IMAGE_ATTRIBUTE_NAME = "data-single-file-image";
39
+ const POSTER_ATTRIBUTE_NAME = "data-single-file-poster";
40
+ const VIDEO_ATTRIBUTE_NAME = "data-single-file-video";
41
+ const CANVAS_ATTRIBUTE_NAME = "data-single-file-canvas";
42
+ const HTML_IMPORT_ATTRIBUTE_NAME = "data-single-file-import";
43
+ const STYLE_ATTRIBUTE_NAME = "data-single-file-movable-style";
44
+ const INPUT_VALUE_ATTRIBUTE_NAME = "data-single-file-input-value";
45
+ const LAZY_SRC_ATTRIBUTE_NAME = "data-single-file-lazy-loaded-src";
46
+ const STYLESHEET_ATTRIBUTE_NAME = "data-single-file-stylesheet";
47
+ const DISABLED_NOSCRIPT_ATTRIBUTE_NAME = "data-single-file-disabled-noscript";
48
+ const SELECTED_CONTENT_ATTRIBUTE_NAME = "data-single-file-selected-content";
49
+ const ASYNC_SCRIPT_ATTRIBUTE_NAME = "data-single-file-async-script";
50
+ const FLOW_ELEMENTS_SELECTOR = "*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)";
51
+ const KEPT_TAG_NAMES = ["NOSCRIPT", "DISABLED-NOSCRIPT", "META", "LINK", "STYLE", "TITLE", "TEMPLATE", "SOURCE", "OBJECT", "SCRIPT", "HEAD"];
52
+ const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
53
+ const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
54
+ const FONT_WEIGHTS = {
55
+ regular: "400",
56
+ normal: "400",
57
+ bold: "700",
58
+ bolder: "700",
59
+ lighter: "100"
60
+ };
61
+ const COMMENT_HEADER = "Page saved with SingleFile";
62
+ const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
63
+ const SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
64
+ const EMPTY_RESOURCE = "data:,";
65
+ const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
66
+ const dispatchEvent = event => { try { globalThis.dispatchEvent(event); } catch (error) { /* ignored */ } };
67
+
68
+ export {
69
+ initUserScriptHandler,
70
+ initDoc,
71
+ preProcessDoc,
72
+ postProcessDoc,
73
+ serialize,
74
+ removeQuotes,
75
+ flatten,
76
+ getFontWeight,
77
+ normalizeFontFamily,
78
+ getShadowRoot,
79
+ ON_BEFORE_CAPTURE_EVENT_NAME,
80
+ ON_AFTER_CAPTURE_EVENT_NAME,
81
+ WIN_ID_ATTRIBUTE_NAME,
82
+ PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
83
+ REMOVED_CONTENT_ATTRIBUTE_NAME,
84
+ HIDDEN_CONTENT_ATTRIBUTE_NAME,
85
+ HIDDEN_FRAME_ATTRIBUTE_NAME,
86
+ IMAGE_ATTRIBUTE_NAME,
87
+ POSTER_ATTRIBUTE_NAME,
88
+ VIDEO_ATTRIBUTE_NAME,
89
+ CANVAS_ATTRIBUTE_NAME,
90
+ INPUT_VALUE_ATTRIBUTE_NAME,
91
+ SHADOW_ROOT_ATTRIBUTE_NAME,
92
+ HTML_IMPORT_ATTRIBUTE_NAME,
93
+ STYLE_ATTRIBUTE_NAME,
94
+ LAZY_SRC_ATTRIBUTE_NAME,
95
+ STYLESHEET_ATTRIBUTE_NAME,
96
+ SELECTED_CONTENT_ATTRIBUTE_NAME,
97
+ ASYNC_SCRIPT_ATTRIBUTE_NAME,
98
+ COMMENT_HEADER,
99
+ COMMENT_HEADER_LEGACY,
100
+ SINGLE_FILE_UI_ELEMENT_CLASS,
101
+ EMPTY_RESOURCE
102
+ };
103
+
104
+ function initUserScriptHandler() {
105
+ addEventListener("single-file-user-script-init", () => globalThis._singleFile_waitForUserScript = async eventPrefixName => {
106
+ const event = new CustomEvent(eventPrefixName + "-request", { cancelable: true });
107
+ const promiseResponse = new Promise(resolve => addEventListener(eventPrefixName + "-response", resolve));
108
+ dispatchEvent(event);
109
+ if (event.defaultPrevented) {
110
+ await promiseResponse;
111
+ }
112
+ });
113
+ }
114
+
115
+ function initDoc(doc) {
116
+ doc.querySelectorAll("meta[http-equiv=refresh]").forEach(element => {
117
+ element.removeAttribute("http-equiv");
118
+ element.setAttribute("disabled-http-equiv", "refresh");
119
+ });
120
+ }
121
+
122
+ function preProcessDoc(doc, win, options) {
123
+ doc.querySelectorAll("noscript:not([" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "])").forEach(element => {
124
+ element.setAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME, element.textContent);
125
+ element.textContent = "";
126
+ });
127
+ initDoc(doc);
128
+ if (doc.head) {
129
+ doc.head.querySelectorAll(FLOW_ELEMENTS_SELECTOR).forEach(element => element.hidden = true);
130
+ }
131
+ doc.querySelectorAll("svg foreignObject").forEach(element => {
132
+ const flowElements = element.querySelectorAll("html > head > " + FLOW_ELEMENTS_SELECTOR + ", html > body > " + FLOW_ELEMENTS_SELECTOR);
133
+ if (flowElements.length) {
134
+ Array.from(element.childNodes).forEach(node => node.remove());
135
+ flowElements.forEach(flowElement => element.appendChild(flowElement));
136
+ }
137
+ });
138
+ const invalidElements = new Map();
139
+ let elementsInfo;
140
+ if (win && doc.documentElement) {
141
+ doc.querySelectorAll("button button").forEach(element => {
142
+ const placeHolderElement = doc.createComment("");
143
+ invalidElements.set(element, placeHolderElement);
144
+ element.replaceWith(placeHolderElement);
145
+ });
146
+ elementsInfo = getElementsInfo(win, doc, doc.documentElement, options);
147
+ if (options.moveStylesInHead) {
148
+ doc.querySelectorAll("body style, body ~ style").forEach(element => {
149
+ const computedStyle = win.getComputedStyle(element);
150
+ if (computedStyle && testHiddenElement(element, computedStyle)) {
151
+ element.setAttribute(STYLE_ATTRIBUTE_NAME, "");
152
+ elementsInfo.markedElements.push(element);
153
+ }
154
+ });
155
+ }
156
+ } else {
157
+ elementsInfo = {
158
+ canvases: [],
159
+ images: [],
160
+ posters: [],
161
+ videos: [],
162
+ usedFonts: [],
163
+ shadowRoots: [],
164
+ imports: [],
165
+ markedElements: []
166
+ };
167
+ }
168
+ return {
169
+ canvases: elementsInfo.canvases,
170
+ fonts: getFontsData(doc),
171
+ stylesheets: getStylesheetsData(doc),
172
+ images: elementsInfo.images,
173
+ posters: elementsInfo.posters,
174
+ videos: elementsInfo.videos,
175
+ usedFonts: Array.from(elementsInfo.usedFonts.values()),
176
+ shadowRoots: elementsInfo.shadowRoots,
177
+ imports: elementsInfo.imports,
178
+ referrer: doc.referrer,
179
+ markedElements: elementsInfo.markedElements,
180
+ invalidElements
181
+ };
182
+ }
183
+
184
+ function getElementsInfo(win, doc, element, options, data = { usedFonts: new Map(), canvases: [], images: [], posters: [], videos: [], shadowRoots: [], imports: [], markedElements: [] }, ascendantHidden) {
185
+ const elements = Array.from(element.childNodes).filter(node => (node instanceof win.HTMLElement) || (node instanceof win.SVGElement));
186
+ elements.forEach(element => {
187
+ let elementHidden, elementKept, computedStyle;
188
+ if (!options.autoSaveExternalSave && (options.removeHiddenElements || options.removeUnusedFonts || options.compressHTML)) {
189
+ computedStyle = win.getComputedStyle(element);
190
+ if (element instanceof win.HTMLElement) {
191
+ if (options.removeHiddenElements) {
192
+ elementKept = ((ascendantHidden || element.closest("html > head")) && KEPT_TAG_NAMES.includes(element.tagName)) || element.closest("details");
193
+ if (!elementKept) {
194
+ elementHidden = ascendantHidden || testHiddenElement(element, computedStyle);
195
+ if (elementHidden) {
196
+ element.setAttribute(HIDDEN_CONTENT_ATTRIBUTE_NAME, "");
197
+ data.markedElements.push(element);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ if (!elementHidden) {
203
+ if (options.compressHTML && computedStyle) {
204
+ const whiteSpace = computedStyle.getPropertyValue("white-space");
205
+ if (whiteSpace && whiteSpace.startsWith("pre")) {
206
+ element.setAttribute(PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, "");
207
+ data.markedElements.push(element);
208
+ }
209
+ }
210
+ if (options.removeUnusedFonts) {
211
+ getUsedFont(computedStyle, options, data.usedFonts);
212
+ getUsedFont(win.getComputedStyle(element, ":first-letter"), options, data.usedFonts);
213
+ getUsedFont(win.getComputedStyle(element, ":before"), options, data.usedFonts);
214
+ getUsedFont(win.getComputedStyle(element, ":after"), options, data.usedFonts);
215
+ }
216
+ }
217
+ }
218
+ getResourcesInfo(win, doc, element, options, data, elementHidden, computedStyle);
219
+ const shadowRoot = !(element instanceof win.SVGElement) && getShadowRoot(element);
220
+ if (shadowRoot && !element.classList.contains(SINGLE_FILE_UI_ELEMENT_CLASS)) {
221
+ const shadowRootInfo = {};
222
+ element.setAttribute(SHADOW_ROOT_ATTRIBUTE_NAME, data.shadowRoots.length);
223
+ data.markedElements.push(element);
224
+ data.shadowRoots.push(shadowRootInfo);
225
+ getElementsInfo(win, doc, shadowRoot, options, data, elementHidden);
226
+ shadowRootInfo.content = shadowRoot.innerHTML;
227
+ shadowRootInfo.mode = shadowRoot.mode;
228
+ try {
229
+ if (shadowRoot.adoptedStyleSheets && shadowRoot.adoptedStyleSheets.length) {
230
+ shadowRootInfo.adoptedStyleSheets = Array.from(shadowRoot.adoptedStyleSheets).map(stylesheet => Array.from(stylesheet.cssRules).map(cssRule => cssRule.cssText).join("\n"));
231
+ }
232
+ } catch (error) {
233
+ // ignored
234
+ }
235
+ }
236
+ getElementsInfo(win, doc, element, options, data, elementHidden);
237
+ if (!options.autoSaveExternalSave && options.removeHiddenElements && ascendantHidden) {
238
+ if (elementKept || element.getAttribute(KEPT_CONTENT_ATTRIBUTE_NAME) == "") {
239
+ if (element.parentElement) {
240
+ element.parentElement.setAttribute(KEPT_CONTENT_ATTRIBUTE_NAME, "");
241
+ data.markedElements.push(element.parentElement);
242
+ }
243
+ } else if (elementHidden) {
244
+ element.setAttribute(REMOVED_CONTENT_ATTRIBUTE_NAME, "");
245
+ data.markedElements.push(element);
246
+ }
247
+ }
248
+ });
249
+ return data;
250
+ }
251
+
252
+ function getResourcesInfo(win, doc, element, options, data, elementHidden, computedStyle) {
253
+ if (element.tagName == "CANVAS") {
254
+ try {
255
+ data.canvases.push({ dataURI: element.toDataURL("image/png", "") });
256
+ element.setAttribute(CANVAS_ATTRIBUTE_NAME, data.canvases.length - 1);
257
+ data.markedElements.push(element);
258
+ } catch (error) {
259
+ // ignored
260
+ }
261
+ }
262
+ if (element.tagName == "IMG") {
263
+ const imageData = {
264
+ currentSrc: elementHidden ?
265
+ EMPTY_RESOURCE :
266
+ (options.loadDeferredImages && element.getAttribute(LAZY_SRC_ATTRIBUTE_NAME)) || element.currentSrc
267
+ };
268
+ data.images.push(imageData);
269
+ element.setAttribute(IMAGE_ATTRIBUTE_NAME, data.images.length - 1);
270
+ data.markedElements.push(element);
271
+ element.removeAttribute(LAZY_SRC_ATTRIBUTE_NAME);
272
+ computedStyle = computedStyle || win.getComputedStyle(element);
273
+ if (computedStyle) {
274
+ imageData.size = getSize(win, element, computedStyle);
275
+ const boxShadow = computedStyle.getPropertyValue("box-shadow");
276
+ const backgroundImage = computedStyle.getPropertyValue("background-image");
277
+ if ((!boxShadow || boxShadow == "none") &&
278
+ (!backgroundImage || backgroundImage == "none") &&
279
+ (imageData.size.pxWidth > 1 || imageData.size.pxHeight > 1)) {
280
+ imageData.replaceable = true;
281
+ imageData.backgroundColor = computedStyle.getPropertyValue("background-color");
282
+ imageData.objectFit = computedStyle.getPropertyValue("object-fit");
283
+ imageData.boxSizing = computedStyle.getPropertyValue("box-sizing");
284
+ imageData.objectPosition = computedStyle.getPropertyValue("object-position");
285
+ }
286
+ }
287
+ }
288
+ if (element.tagName == "VIDEO") {
289
+ const src = element.currentSrc;
290
+ if (src && !src.startsWith("blob:") && !src.startsWith("data:")) {
291
+ const positionParent = win.getComputedStyle(element.parentNode).getPropertyValue("position");
292
+ data.videos.push({
293
+ positionParent,
294
+ src,
295
+ size: {
296
+ pxWidth: element.clientWidth,
297
+ pxHeight: element.clientHeight
298
+ },
299
+ currentTime: element.currentTime
300
+ });
301
+ element.setAttribute(VIDEO_ATTRIBUTE_NAME, data.videos.length - 1);
302
+ }
303
+ if (!element.poster) {
304
+ const canvasElement = doc.createElement("canvas");
305
+ const context = canvasElement.getContext("2d");
306
+ canvasElement.width = element.clientWidth;
307
+ canvasElement.height = element.clientHeight;
308
+ try {
309
+ context.drawImage(element, 0, 0, canvasElement.width, canvasElement.height);
310
+ data.posters.push(canvasElement.toDataURL("image/png", ""));
311
+ element.setAttribute(POSTER_ATTRIBUTE_NAME, data.posters.length - 1);
312
+ data.markedElements.push(element);
313
+ } catch (error) {
314
+ // ignored
315
+ }
316
+ }
317
+ }
318
+ if (element.tagName == "IFRAME") {
319
+ if (elementHidden && options.removeHiddenElements) {
320
+ element.setAttribute(HIDDEN_FRAME_ATTRIBUTE_NAME, "");
321
+ data.markedElements.push(element);
322
+ }
323
+ }
324
+ if (element.tagName == "LINK") {
325
+ if (element.import && element.import.documentElement) {
326
+ data.imports.push({ content: serialize(element.import) });
327
+ element.setAttribute(HTML_IMPORT_ATTRIBUTE_NAME, data.imports.length - 1);
328
+ data.markedElements.push(element);
329
+ }
330
+ }
331
+ if (element.tagName == "INPUT") {
332
+ if (element.type != "password") {
333
+ element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.value);
334
+ data.markedElements.push(element);
335
+ }
336
+ if (element.type == "radio" || element.type == "checkbox") {
337
+ element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.checked);
338
+ data.markedElements.push(element);
339
+ }
340
+ }
341
+ if (element.tagName == "TEXTAREA") {
342
+ element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.value);
343
+ data.markedElements.push(element);
344
+ }
345
+ if (element.tagName == "SELECT") {
346
+ element.querySelectorAll("option").forEach(option => {
347
+ if (option.selected) {
348
+ option.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, "");
349
+ data.markedElements.push(option);
350
+ }
351
+ });
352
+ }
353
+ if (element.tagName == "SCRIPT") {
354
+ if (element.async && element.getAttribute("async") != "" && element.getAttribute("async") != "async") {
355
+ element.setAttribute(ASYNC_SCRIPT_ATTRIBUTE_NAME, "");
356
+ data.markedElements.push(element);
357
+ }
358
+ element.textContent = element.textContent.replace(/<\/script>/gi, "<\\/script>");
359
+ }
360
+ }
361
+
362
+ function getUsedFont(computedStyle, options, usedFonts) {
363
+ if (computedStyle) {
364
+ const fontStyle = computedStyle.getPropertyValue("font-style") || "normal";
365
+ computedStyle.getPropertyValue("font-family").split(",").forEach(fontFamilyName => {
366
+ fontFamilyName = normalizeFontFamily(fontFamilyName);
367
+ if (!options.loadedFonts || options.loadedFonts.find(font => normalizeFontFamily(font.family) == fontFamilyName && font.style == fontStyle)) {
368
+ const fontWeight = getFontWeight(computedStyle.getPropertyValue("font-weight"));
369
+ const fontVariant = computedStyle.getPropertyValue("font-variant") || "normal";
370
+ const value = [fontFamilyName, fontWeight, fontStyle, fontVariant];
371
+ usedFonts.set(JSON.stringify(value), [fontFamilyName, fontWeight, fontStyle, fontVariant]);
372
+ }
373
+ });
374
+ }
375
+ }
376
+
377
+ function getShadowRoot(element) {
378
+ const chrome = globalThis.chrome;
379
+ if (element.openOrClosedShadowRoot) {
380
+ return element.openOrClosedShadowRoot;
381
+ } else if (chrome && chrome.dom && chrome.dom.openOrClosedShadowRoot) {
382
+ try {
383
+ return chrome.dom.openOrClosedShadowRoot(element);
384
+ } catch (error) {
385
+ return element.shadowRoot;
386
+ }
387
+ } else {
388
+ return element.shadowRoot;
389
+ }
390
+ }
391
+
392
+ function normalizeFontFamily(fontFamilyName = "") {
393
+ return removeQuotes(cssUnescape.process(fontFamilyName.trim())).toLowerCase();
394
+ }
395
+
396
+ function testHiddenElement(element, computedStyle) {
397
+ let hidden = false;
398
+ if (computedStyle) {
399
+ const display = computedStyle.getPropertyValue("display");
400
+ const opacity = computedStyle.getPropertyValue("opacity");
401
+ const visibility = computedStyle.getPropertyValue("visibility");
402
+ hidden = display == "none";
403
+ if (!hidden && (opacity == "0" || visibility == "hidden") && element.getBoundingClientRect) {
404
+ const boundingRect = element.getBoundingClientRect();
405
+ hidden = !boundingRect.width && !boundingRect.height;
406
+ }
407
+ }
408
+ return Boolean(hidden);
409
+ }
410
+
411
+ function postProcessDoc(doc, markedElements, invalidElements) {
412
+ doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
413
+ element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
414
+ element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
415
+ });
416
+ doc.querySelectorAll("meta[disabled-http-equiv]").forEach(element => {
417
+ element.setAttribute("http-equiv", element.getAttribute("disabled-http-equiv"));
418
+ element.removeAttribute("disabled-http-equiv");
419
+ });
420
+ if (doc.head) {
421
+ doc.head.querySelectorAll("*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)").forEach(element => element.removeAttribute("hidden"));
422
+ }
423
+ if (!markedElements) {
424
+ const singleFileAttributes = [REMOVED_CONTENT_ATTRIBUTE_NAME, HIDDEN_FRAME_ATTRIBUTE_NAME, HIDDEN_CONTENT_ATTRIBUTE_NAME, PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, IMAGE_ATTRIBUTE_NAME, POSTER_ATTRIBUTE_NAME, VIDEO_ATTRIBUTE_NAME, CANVAS_ATTRIBUTE_NAME, INPUT_VALUE_ATTRIBUTE_NAME, SHADOW_ROOT_ATTRIBUTE_NAME, HTML_IMPORT_ATTRIBUTE_NAME, STYLESHEET_ATTRIBUTE_NAME, ASYNC_SCRIPT_ATTRIBUTE_NAME];
425
+ markedElements = doc.querySelectorAll(singleFileAttributes.map(name => "[" + name + "]").join(","));
426
+ }
427
+ markedElements.forEach(element => {
428
+ element.removeAttribute(REMOVED_CONTENT_ATTRIBUTE_NAME);
429
+ element.removeAttribute(HIDDEN_CONTENT_ATTRIBUTE_NAME);
430
+ element.removeAttribute(KEPT_CONTENT_ATTRIBUTE_NAME);
431
+ element.removeAttribute(HIDDEN_FRAME_ATTRIBUTE_NAME);
432
+ element.removeAttribute(PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME);
433
+ element.removeAttribute(IMAGE_ATTRIBUTE_NAME);
434
+ element.removeAttribute(POSTER_ATTRIBUTE_NAME);
435
+ element.removeAttribute(VIDEO_ATTRIBUTE_NAME);
436
+ element.removeAttribute(CANVAS_ATTRIBUTE_NAME);
437
+ element.removeAttribute(INPUT_VALUE_ATTRIBUTE_NAME);
438
+ element.removeAttribute(SHADOW_ROOT_ATTRIBUTE_NAME);
439
+ element.removeAttribute(HTML_IMPORT_ATTRIBUTE_NAME);
440
+ element.removeAttribute(STYLESHEET_ATTRIBUTE_NAME);
441
+ element.removeAttribute(ASYNC_SCRIPT_ATTRIBUTE_NAME);
442
+ element.removeAttribute(STYLE_ATTRIBUTE_NAME);
443
+ });
444
+ if (invalidElements) {
445
+ Array.from(invalidElements.entries()).forEach(([element, placeholderElement]) => placeholderElement.replaceWith(element));
446
+ }
447
+ }
448
+
449
+ function getStylesheetsData(doc) {
450
+ if (doc) {
451
+ const contents = [];
452
+ doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
453
+ try {
454
+ const tempStyleElement = doc.createElement("style");
455
+ tempStyleElement.textContent = styleElement.textContent;
456
+ doc.body.appendChild(tempStyleElement);
457
+ const stylesheet = tempStyleElement.sheet;
458
+ tempStyleElement.remove();
459
+ if (!stylesheet || stylesheet.cssRules.length != styleElement.sheet.cssRules.length) {
460
+ styleElement.setAttribute(STYLESHEET_ATTRIBUTE_NAME, styleIndex);
461
+ contents[styleIndex] = Array.from(styleElement.sheet.cssRules).map(cssRule => cssRule.cssText).join("\n");
462
+ }
463
+ } catch (error) {
464
+ // ignored
465
+ }
466
+ });
467
+ return contents;
468
+ }
469
+ }
470
+
471
+ function getSize(win, imageElement, computedStyle) {
472
+ let pxWidth = imageElement.naturalWidth;
473
+ let pxHeight = imageElement.naturalHeight;
474
+ if (!pxWidth && !pxHeight) {
475
+ const noStyleAttribute = imageElement.getAttribute("style") == null;
476
+ computedStyle = computedStyle || win.getComputedStyle(imageElement);
477
+ let removeBorderWidth = false;
478
+ if (computedStyle.getPropertyValue("box-sizing") == "content-box") {
479
+ const boxSizingValue = imageElement.style.getPropertyValue("box-sizing");
480
+ const boxSizingPriority = imageElement.style.getPropertyPriority("box-sizing");
481
+ const clientWidth = imageElement.clientWidth;
482
+ imageElement.style.setProperty("box-sizing", "border-box", "important");
483
+ removeBorderWidth = imageElement.clientWidth != clientWidth;
484
+ if (boxSizingValue) {
485
+ imageElement.style.setProperty("box-sizing", boxSizingValue, boxSizingPriority);
486
+ } else {
487
+ imageElement.style.removeProperty("box-sizing");
488
+ }
489
+ }
490
+ let paddingLeft, paddingRight, paddingTop, paddingBottom, borderLeft, borderRight, borderTop, borderBottom;
491
+ paddingLeft = getWidth("padding-left", computedStyle);
492
+ paddingRight = getWidth("padding-right", computedStyle);
493
+ paddingTop = getWidth("padding-top", computedStyle);
494
+ paddingBottom = getWidth("padding-bottom", computedStyle);
495
+ if (removeBorderWidth) {
496
+ borderLeft = getWidth("border-left-width", computedStyle);
497
+ borderRight = getWidth("border-right-width", computedStyle);
498
+ borderTop = getWidth("border-top-width", computedStyle);
499
+ borderBottom = getWidth("border-bottom-width", computedStyle);
500
+ } else {
501
+ borderLeft = borderRight = borderTop = borderBottom = 0;
502
+ }
503
+ pxWidth = Math.max(0, imageElement.clientWidth - paddingLeft - paddingRight - borderLeft - borderRight);
504
+ pxHeight = Math.max(0, imageElement.clientHeight - paddingTop - paddingBottom - borderTop - borderBottom);
505
+ if (noStyleAttribute) {
506
+ imageElement.removeAttribute("style");
507
+ }
508
+ }
509
+ return { pxWidth, pxHeight };
510
+ }
511
+
512
+ function getWidth(styleName, computedStyle) {
513
+ if (computedStyle.getPropertyValue(styleName).endsWith("px")) {
514
+ return parseFloat(computedStyle.getPropertyValue(styleName));
515
+ }
516
+ }
517
+
518
+ function getFontsData() {
519
+ return hooksFrames.getFontsData();
520
+ }
521
+
522
+ function serialize(doc) {
523
+ const docType = doc.doctype;
524
+ let docTypeString = "";
525
+ if (docType) {
526
+ docTypeString = "<!DOCTYPE " + docType.nodeName;
527
+ if (docType.publicId) {
528
+ docTypeString += " PUBLIC \"" + docType.publicId + "\"";
529
+ if (docType.systemId) {
530
+ docTypeString += " \"" + docType.systemId + "\"";
531
+ }
532
+ } else if (docType.systemId) {
533
+ docTypeString += " SYSTEM \"" + docType.systemId + "\"";
534
+ } if (docType.internalSubset) {
535
+ docTypeString += " [" + docType.internalSubset + "]";
536
+ }
537
+ docTypeString += "> ";
538
+ }
539
+ return docTypeString + doc.documentElement.outerHTML;
540
+ }
541
+
542
+ function removeQuotes(string) {
543
+ if (string.match(REGEXP_SIMPLE_QUOTES_STRING)) {
544
+ string = string.replace(REGEXP_SIMPLE_QUOTES_STRING, "$1");
545
+ } else {
546
+ string = string.replace(REGEXP_DOUBLE_QUOTES_STRING, "$1");
547
+ }
548
+ return string.trim();
549
+ }
550
+
551
+ function getFontWeight(weight) {
552
+ return FONT_WEIGHTS[weight.toLowerCase().trim()] || weight;
553
+ }
554
+
555
+ function flatten(array) {
556
+ return array.flat ? array.flat() : array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
557
+ }