single-file-core 1.1.76 → 1.1.78

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