single-file-bootstrap.js 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.singlefileBootstrap = {}));
  5. })(this, (function (exports) { 'use strict';
  6. /*
  7. * Copyright 2010-2022 Gildas Lormeau
  8. * contact : gildas.lormeau <at> gmail.com
  9. *
  10. * This file is part of SingleFile.
  11. *
  12. * The code in this file is free software: you can redistribute it and/or
  13. * modify it under the terms of the GNU Affero General Public License
  14. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  15. * of the License, or (at your option) any later version.
  16. *
  17. * The code in this file is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  20. * General Public License for more details.
  21. *
  22. * As additional permission under GNU AGPL version 3 section 7, you may
  23. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  24. * AGPL normally required by section 4, provided you include this license
  25. * notice and a URL through which recipients can access the Corresponding
  26. * Source.
  27. */
  28. /* global globalThis, window */
  29. const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
  30. const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
  31. const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-images-keep-zoom-level-start";
  32. const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-end";
  33. const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
  34. const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
  35. const DISPATCH_SCROLL_START_EVENT = "single-file-dispatch-scroll-event-start";
  36. const DISPATCH_SCROLL_END_EVENT = "single-file-dispatch-scroll-event-end";
  37. const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
  38. const BLOCK_STORAGE_END_EVENT = "single-file-block-storage-end";
  39. const LOAD_IMAGE_EVENT = "single-file-load-image";
  40. const IMAGE_LOADED_EVENT = "single-file-image-loaded";
  41. const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
  42. const DELETE_FONT_EVENT = "single-file-delete-font";
  43. const CLEAR_FONTS_EVENT = "single-file-clear-fonts";
  44. const FONT_FACE_PROPERTY_NAME = "_singleFile_fontFaces";
  45. const addEventListener$3 = (type, listener, options) => globalThis.addEventListener(type, listener, options);
  46. const dispatchEvent$1 = event => { try { globalThis.dispatchEvent(event); } catch (error) { /* ignored */ } };
  47. const CustomEvent$1 = globalThis.CustomEvent;
  48. const document$2 = globalThis.document;
  49. const Document = globalThis.Document;
  50. const JSON$2 = globalThis.JSON;
  51. let fontFaces;
  52. if (window[FONT_FACE_PROPERTY_NAME]) {
  53. fontFaces = window[FONT_FACE_PROPERTY_NAME];
  54. } else {
  55. fontFaces = window[FONT_FACE_PROPERTY_NAME] = new Map();
  56. }
  57. if (document$2 instanceof Document) {
  58. addEventListener$3(NEW_FONT_FACE_EVENT, event => {
  59. const detail = event.detail;
  60. const key = Object.assign({}, detail);
  61. delete key.src;
  62. fontFaces.set(JSON$2.stringify(key), detail);
  63. });
  64. addEventListener$3(DELETE_FONT_EVENT, event => {
  65. const detail = event.detail;
  66. const key = Object.assign({}, detail);
  67. delete key.src;
  68. fontFaces.delete(JSON$2.stringify(key));
  69. });
  70. addEventListener$3(CLEAR_FONTS_EVENT, () => fontFaces = new Map());
  71. }
  72. function getFontsData$1() {
  73. return Array.from(fontFaces.values());
  74. }
  75. function loadDeferredImagesStart(options) {
  76. if (options.loadDeferredImagesBlockCookies) {
  77. dispatchEvent$1(new CustomEvent$1(BLOCK_COOKIES_START_EVENT));
  78. }
  79. if (options.loadDeferredImagesBlockStorage) {
  80. dispatchEvent$1(new CustomEvent$1(BLOCK_STORAGE_START_EVENT));
  81. }
  82. if (options.loadDeferredImagesDispatchScrollEvent) {
  83. dispatchEvent$1(new CustomEvent$1(DISPATCH_SCROLL_START_EVENT));
  84. }
  85. if (options.loadDeferredImagesKeepZoomLevel) {
  86. dispatchEvent$1(new CustomEvent$1(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT));
  87. } else {
  88. dispatchEvent$1(new CustomEvent$1(LOAD_DEFERRED_IMAGES_START_EVENT));
  89. }
  90. }
  91. function loadDeferredImagesEnd(options) {
  92. if (options.loadDeferredImagesBlockCookies) {
  93. dispatchEvent$1(new CustomEvent$1(BLOCK_COOKIES_END_EVENT));
  94. }
  95. if (options.loadDeferredImagesBlockStorage) {
  96. dispatchEvent$1(new CustomEvent$1(BLOCK_STORAGE_END_EVENT));
  97. }
  98. if (options.loadDeferredImagesDispatchScrollEvent) {
  99. dispatchEvent$1(new CustomEvent$1(DISPATCH_SCROLL_END_EVENT));
  100. }
  101. if (options.loadDeferredImagesKeepZoomLevel) {
  102. dispatchEvent$1(new CustomEvent$1(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT));
  103. } else {
  104. dispatchEvent$1(new CustomEvent$1(LOAD_DEFERRED_IMAGES_END_EVENT));
  105. }
  106. }
  107. /*
  108. * The MIT License (MIT)
  109. *
  110. * Author: Gildas Lormeau
  111. *
  112. * Permission is hereby granted, free of charge, to any person obtaining a copy
  113. * of this software and associated documentation files (the "Software"), to deal
  114. * in the Software without restriction, including without limitation the rights
  115. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  116. * copies of the Software, and to permit persons to whom the Software is
  117. * furnished to do so, subject to the following conditions:
  118. *
  119. * The above copyright notice and this permission notice shall be included in all
  120. * copies or substantial portions of the Software.
  121. *
  122. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  123. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  124. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  125. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  126. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  127. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  128. * SOFTWARE.
  129. */
  130. // derived from https://github.com/postcss/postcss-selector-parser/blob/master/src/util/unesc.js
  131. /*
  132. * The MIT License (MIT)
  133. * Copyright (c) Ben Briggs <beneb.info@gmail.com> (http://beneb.info)
  134. *
  135. * Permission is hereby granted, free of charge, to any person obtaining a copy
  136. * of this software and associated documentation files (the "Software"), to deal
  137. * in the Software without restriction, including without limitation the rights
  138. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  139. * copies of the Software, and to permit persons to whom the Software is
  140. * furnished to do so, subject to the following conditions:
  141. *
  142. * The above copyright notice and this permission notice shall be included in
  143. * all copies or substantial portions of the Software.
  144. *
  145. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  146. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  147. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  148. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  149. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  150. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  151. * THE SOFTWARE.
  152. */
  153. const whitespace = "[\\x20\\t\\r\\n\\f]";
  154. const unescapeRegExp = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig");
  155. function process$2(str) {
  156. return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => {
  157. const high = "0x" + escaped - 0x10000;
  158. // NaN means non-codepoint
  159. // Workaround erroneous numeric interpretation of +"0x"
  160. // eslint-disable-next-line no-self-compare
  161. return high !== high || escapedWhitespace
  162. ? escaped
  163. : high < 0
  164. ? // BMP codepoint
  165. String.fromCharCode(high + 0x10000)
  166. : // Supplemental Plane codepoint (surrogate pair)
  167. String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
  168. });
  169. }
  170. const SINGLE_FILE_PREFIX = "single-file-";
  171. const COMMENT_HEADER = "Page saved with SingleFile";
  172. const WAIT_FOR_USERSCRIPT_PROPERTY_NAME = "_singleFile_waitForUserScript";
  173. const MESSAGE_PREFIX = "__frameTree__::";
  174. /*
  175. * Copyright 2010-2022 Gildas Lormeau
  176. * contact : gildas.lormeau <at> gmail.com
  177. *
  178. * This file is part of SingleFile.
  179. *
  180. * The code in this file is free software: you can redistribute it and/or
  181. * modify it under the terms of the GNU Affero General Public License
  182. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  183. * of the License, or (at your option) any later version.
  184. *
  185. * The code in this file is distributed in the hope that it will be useful,
  186. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  187. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  188. * General Public License for more details.
  189. *
  190. * As additional permission under GNU AGPL version 3 section 7, you may
  191. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  192. * AGPL normally required by section 4, provided you include this license
  193. * notice and a URL through which recipients can access the Corresponding
  194. * Source.
  195. */
  196. const ON_BEFORE_CAPTURE_EVENT_NAME = SINGLE_FILE_PREFIX + "on-before-capture";
  197. const ON_AFTER_CAPTURE_EVENT_NAME = SINGLE_FILE_PREFIX + "on-after-capture";
  198. const GET_ADOPTED_STYLESHEETS_REQUEST_EVENT = SINGLE_FILE_PREFIX + "request-get-adopted-stylesheets";
  199. const GET_ADOPTED_STYLESHEETS_RESPONSE_EVENT = SINGLE_FILE_PREFIX + "response-get-adopted-stylesheets";
  200. const UNREGISTER_GET_ADOPTED_STYLESHEETS_REQUEST_EVENT = SINGLE_FILE_PREFIX + "unregister-request-get-adopted-stylesheets";
  201. const ON_INIT_USERSCRIPT_EVENT = SINGLE_FILE_PREFIX + "user-script-init";
  202. const REMOVED_CONTENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "removed-content";
  203. const HIDDEN_CONTENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "hidden-content";
  204. const KEPT_CONTENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "kept-content";
  205. const HIDDEN_FRAME_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "hidden-frame";
  206. const PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "preserved-space-element";
  207. const SHADOW_ROOT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "shadow-root-element";
  208. const WIN_ID_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "win-id";
  209. const IMAGE_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "image";
  210. const POSTER_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "poster";
  211. const VIDEO_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "video";
  212. const CANVAS_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "canvas";
  213. const STYLE_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "movable-style";
  214. const INPUT_VALUE_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "input-value";
  215. const LAZY_SRC_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "lazy-loaded-src";
  216. const STYLESHEET_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "stylesheet";
  217. const DISABLED_NOSCRIPT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "disabled-noscript";
  218. const INVALID_ELEMENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "invalid-element";
  219. const ASYNC_SCRIPT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "async-script";
  220. const FLOW_ELEMENTS_SELECTOR = "*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)";
  221. const KEPT_TAG_NAMES = ["NOSCRIPT", "DISABLED-NOSCRIPT", "META", "LINK", "STYLE", "TITLE", "TEMPLATE", "SOURCE", "OBJECT", "SCRIPT", "HEAD", "BODY"];
  222. const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
  223. const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
  224. const FONT_WEIGHTS = {
  225. regular: "400",
  226. normal: "400",
  227. bold: "700",
  228. bolder: "700",
  229. lighter: "100"
  230. };
  231. const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
  232. const SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
  233. const EMPTY_RESOURCE = "data:,";
  234. const addEventListener$2 = (type, listener, options) => globalThis.addEventListener(type, listener, options);
  235. const dispatchEvent = event => { try { globalThis.dispatchEvent(event); } catch (error) { /* ignored */ } };
  236. const JSON$1 = globalThis.JSON;
  237. function initUserScriptHandler() {
  238. addEventListener$2(ON_INIT_USERSCRIPT_EVENT, () => globalThis[WAIT_FOR_USERSCRIPT_PROPERTY_NAME] = async eventPrefixName => {
  239. const event = new CustomEvent(eventPrefixName + "-request", { cancelable: true });
  240. const promiseResponse = new Promise(resolve => addEventListener$2(eventPrefixName + "-response", resolve));
  241. dispatchEvent(event);
  242. if (event.defaultPrevented) {
  243. await promiseResponse;
  244. }
  245. });
  246. }
  247. function initDoc(doc) {
  248. doc.querySelectorAll("meta[http-equiv=refresh]").forEach(element => {
  249. element.removeAttribute("http-equiv");
  250. element.setAttribute("disabled-http-equiv", "refresh");
  251. });
  252. }
  253. function preProcessDoc(doc, win, options) {
  254. doc.querySelectorAll("noscript:not([" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "])").forEach(element => {
  255. element.setAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME, element.textContent);
  256. element.textContent = "";
  257. });
  258. initDoc(doc);
  259. if (doc.head) {
  260. doc.head.querySelectorAll(FLOW_ELEMENTS_SELECTOR).forEach(element => element.hidden = true);
  261. }
  262. doc.querySelectorAll("svg foreignObject").forEach(element => {
  263. const flowElements = element.querySelectorAll("html > head > " + FLOW_ELEMENTS_SELECTOR + ", html > body > " + FLOW_ELEMENTS_SELECTOR);
  264. if (flowElements.length) {
  265. Array.from(element.childNodes).forEach(node => node.remove());
  266. flowElements.forEach(flowElement => element.appendChild(flowElement));
  267. }
  268. });
  269. const invalidElements = new Map();
  270. let elementsInfo;
  271. if (win && doc.documentElement) {
  272. doc.querySelectorAll("button button, a a").forEach(element => {
  273. const placeHolderElement = doc.createElement("template");
  274. placeHolderElement.setAttribute(INVALID_ELEMENT_ATTRIBUTE_NAME, "");
  275. placeHolderElement.content.appendChild(element.cloneNode(true));
  276. invalidElements.set(element, placeHolderElement);
  277. element.replaceWith(placeHolderElement);
  278. });
  279. elementsInfo = getElementsInfo(win, doc, doc.documentElement, options);
  280. if (options.moveStylesInHead) {
  281. doc.querySelectorAll("body style, body ~ style").forEach(element => {
  282. const computedStyle = getComputedStyle(win, element);
  283. if (computedStyle && testHiddenElement(element, computedStyle)) {
  284. element.setAttribute(STYLE_ATTRIBUTE_NAME, "");
  285. elementsInfo.markedElements.push(element);
  286. }
  287. });
  288. }
  289. } else {
  290. elementsInfo = {
  291. canvases: [],
  292. images: [],
  293. posters: [],
  294. videos: [],
  295. usedFonts: [],
  296. shadowRoots: [],
  297. markedElements: []
  298. };
  299. }
  300. return {
  301. canvases: elementsInfo.canvases,
  302. fonts: getFontsData(),
  303. stylesheets: getStylesheetsData(doc),
  304. images: elementsInfo.images,
  305. posters: elementsInfo.posters,
  306. videos: elementsInfo.videos,
  307. usedFonts: Array.from(elementsInfo.usedFonts.values()),
  308. shadowRoots: elementsInfo.shadowRoots,
  309. referrer: doc.referrer,
  310. markedElements: elementsInfo.markedElements,
  311. invalidElements,
  312. scrollPosition: { x: win.scrollX, y: win.scrollY },
  313. adoptedStyleSheets: getStylesheetsContent(doc.adoptedStyleSheets)
  314. };
  315. }
  316. function getElementsInfo(win, doc, element, options, data = { usedFonts: new Map(), canvases: [], images: [], posters: [], videos: [], shadowRoots: [], markedElements: [] }, ascendantHidden) {
  317. if (element.childNodes) {
  318. const elements = Array.from(element.childNodes).filter(node => (node instanceof win.HTMLElement) || (node instanceof win.SVGElement));
  319. elements.forEach(element => {
  320. let elementHidden, elementKept, computedStyle;
  321. if (!options.autoSaveExternalSave && (options.removeHiddenElements || options.removeUnusedFonts || options.compressHTML)) {
  322. computedStyle = getComputedStyle(win, element);
  323. if (element instanceof win.HTMLElement) {
  324. if (options.removeHiddenElements) {
  325. elementKept = ((ascendantHidden || element.closest("html > head")) && KEPT_TAG_NAMES.includes(element.tagName.toUpperCase())) || element.closest("details");
  326. if (!elementKept) {
  327. elementHidden = ascendantHidden || testHiddenElement(element, computedStyle);
  328. if (elementHidden) {
  329. element.setAttribute(HIDDEN_CONTENT_ATTRIBUTE_NAME, "");
  330. data.markedElements.push(element);
  331. }
  332. }
  333. }
  334. }
  335. if (!elementHidden) {
  336. if (options.compressHTML && computedStyle) {
  337. const whiteSpace = computedStyle.getPropertyValue("white-space");
  338. if (whiteSpace && whiteSpace.startsWith("pre")) {
  339. element.setAttribute(PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, "");
  340. data.markedElements.push(element);
  341. }
  342. }
  343. if (options.removeUnusedFonts) {
  344. getUsedFont(computedStyle, options, data.usedFonts);
  345. getUsedFont(getComputedStyle(win, element, ":first-letter"), options, data.usedFonts);
  346. getUsedFont(getComputedStyle(win, element, ":before"), options, data.usedFonts);
  347. getUsedFont(getComputedStyle(win, element, ":after"), options, data.usedFonts);
  348. }
  349. }
  350. }
  351. getResourcesInfo(win, doc, element, options, data, elementHidden, computedStyle);
  352. const shadowRoot = !(element instanceof win.SVGElement) && getShadowRoot(element);
  353. if (shadowRoot && !element.classList.contains(SINGLE_FILE_UI_ELEMENT_CLASS)) {
  354. const shadowRootInfo = {};
  355. element.setAttribute(SHADOW_ROOT_ATTRIBUTE_NAME, data.shadowRoots.length);
  356. data.markedElements.push(element);
  357. data.shadowRoots.push(shadowRootInfo);
  358. try {
  359. if (shadowRoot.adoptedStyleSheets) {
  360. if (shadowRoot.adoptedStyleSheets.length) {
  361. shadowRootInfo.adoptedStyleSheets = getStylesheetsContent(shadowRoot.adoptedStyleSheets);
  362. } else if (shadowRoot.adoptedStyleSheets.length === undefined) {
  363. const listener = event => shadowRootInfo.adoptedStyleSheets = event.detail.adoptedStyleSheets;
  364. element.addEventListener(GET_ADOPTED_STYLESHEETS_RESPONSE_EVENT, listener);
  365. element.dispatchEvent(new CustomEvent(GET_ADOPTED_STYLESHEETS_REQUEST_EVENT, { bubbles: true }));
  366. element.removeEventListener(GET_ADOPTED_STYLESHEETS_RESPONSE_EVENT, listener);
  367. }
  368. }
  369. } catch (error) {
  370. // ignored
  371. }
  372. getElementsInfo(win, doc, shadowRoot, options, data, elementHidden);
  373. shadowRootInfo.content = shadowRoot.innerHTML;
  374. shadowRootInfo.mode = shadowRoot.mode;
  375. try {
  376. if (shadowRoot.adoptedStyleSheets && shadowRoot.adoptedStyleSheets.length === undefined) {
  377. element.dispatchEvent(new CustomEvent(UNREGISTER_GET_ADOPTED_STYLESHEETS_REQUEST_EVENT, { bubbles: true }));
  378. }
  379. } catch (error) {
  380. // ignored
  381. }
  382. }
  383. getElementsInfo(win, doc, element, options, data, elementHidden);
  384. if (!options.autoSaveExternalSave && options.removeHiddenElements && ascendantHidden) {
  385. if (elementKept || element.getAttribute(KEPT_CONTENT_ATTRIBUTE_NAME) == "") {
  386. if (element.parentElement) {
  387. element.parentElement.setAttribute(KEPT_CONTENT_ATTRIBUTE_NAME, "");
  388. data.markedElements.push(element.parentElement);
  389. }
  390. } else if (elementHidden) {
  391. element.setAttribute(REMOVED_CONTENT_ATTRIBUTE_NAME, "");
  392. data.markedElements.push(element);
  393. }
  394. }
  395. });
  396. }
  397. return data;
  398. }
  399. function getStylesheetsContent(styleSheets) {
  400. return styleSheets ? Array.from(styleSheets).map(stylesheet => Array.from(stylesheet.cssRules).map(cssRule => cssRule.cssText).join("\n")) : [];
  401. }
  402. function getResourcesInfo(win, doc, element, options, data, elementHidden, computedStyle) {
  403. const tagName = element.tagName && element.tagName.toUpperCase();
  404. if (tagName == "CANVAS") {
  405. try {
  406. data.canvases.push({
  407. dataURI: element.toDataURL("image/png", ""),
  408. backgroundColor: computedStyle.getPropertyValue("background-color")
  409. });
  410. element.setAttribute(CANVAS_ATTRIBUTE_NAME, data.canvases.length - 1);
  411. data.markedElements.push(element);
  412. } catch (error) {
  413. // ignored
  414. }
  415. }
  416. if (tagName == "IMG") {
  417. const imageData = {
  418. currentSrc: elementHidden ?
  419. EMPTY_RESOURCE :
  420. (options.loadDeferredImages && element.getAttribute(LAZY_SRC_ATTRIBUTE_NAME)) || element.currentSrc
  421. };
  422. data.images.push(imageData);
  423. element.setAttribute(IMAGE_ATTRIBUTE_NAME, data.images.length - 1);
  424. data.markedElements.push(element);
  425. element.removeAttribute(LAZY_SRC_ATTRIBUTE_NAME);
  426. computedStyle = computedStyle || getComputedStyle(win, element);
  427. if (computedStyle) {
  428. imageData.size = getSize(win, element, computedStyle);
  429. const boxShadow = computedStyle.getPropertyValue("box-shadow");
  430. const backgroundImage = computedStyle.getPropertyValue("background-image");
  431. if ((!boxShadow || boxShadow == "none") &&
  432. (!backgroundImage || backgroundImage == "none") &&
  433. (imageData.size.pxWidth > 1 || imageData.size.pxHeight > 1)) {
  434. imageData.replaceable = true;
  435. imageData.backgroundColor = computedStyle.getPropertyValue("background-color");
  436. imageData.objectFit = computedStyle.getPropertyValue("object-fit");
  437. imageData.boxSizing = computedStyle.getPropertyValue("box-sizing");
  438. imageData.objectPosition = computedStyle.getPropertyValue("object-position");
  439. }
  440. }
  441. }
  442. if (tagName == "VIDEO") {
  443. const src = element.currentSrc;
  444. if (src && !src.startsWith("blob:") && !src.startsWith("data:")) {
  445. const computedStyle = getComputedStyle(win, element.parentNode);
  446. data.videos.push({
  447. positionParent: computedStyle && computedStyle.getPropertyValue("position"),
  448. src,
  449. size: {
  450. pxWidth: element.clientWidth,
  451. pxHeight: element.clientHeight
  452. },
  453. currentTime: element.currentTime
  454. });
  455. element.setAttribute(VIDEO_ATTRIBUTE_NAME, data.videos.length - 1);
  456. }
  457. if (!element.getAttribute("poster")) {
  458. const canvasElement = doc.createElement("canvas");
  459. const context = canvasElement.getContext("2d");
  460. canvasElement.width = element.clientWidth;
  461. canvasElement.height = element.clientHeight;
  462. try {
  463. context.drawImage(element, 0, 0, canvasElement.width, canvasElement.height);
  464. data.posters.push(canvasElement.toDataURL("image/png", ""));
  465. element.setAttribute(POSTER_ATTRIBUTE_NAME, data.posters.length - 1);
  466. data.markedElements.push(element);
  467. } catch (error) {
  468. // ignored
  469. }
  470. }
  471. }
  472. if (tagName == "IFRAME") {
  473. if (elementHidden && options.removeHiddenElements) {
  474. element.setAttribute(HIDDEN_FRAME_ATTRIBUTE_NAME, "");
  475. data.markedElements.push(element);
  476. }
  477. }
  478. if (tagName == "INPUT") {
  479. if (element.type != "password") {
  480. element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.value);
  481. data.markedElements.push(element);
  482. }
  483. if (element.type == "radio" || element.type == "checkbox") {
  484. element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.checked);
  485. data.markedElements.push(element);
  486. }
  487. }
  488. if (tagName == "TEXTAREA") {
  489. element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.value);
  490. data.markedElements.push(element);
  491. }
  492. if (tagName == "SELECT") {
  493. element.querySelectorAll("option").forEach(option => {
  494. if (option.selected) {
  495. option.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, "");
  496. data.markedElements.push(option);
  497. }
  498. });
  499. }
  500. if (tagName == "SCRIPT") {
  501. if (element.async && element.getAttribute("async") != "" && element.getAttribute("async") != "async") {
  502. element.setAttribute(ASYNC_SCRIPT_ATTRIBUTE_NAME, "");
  503. data.markedElements.push(element);
  504. }
  505. element.textContent = element.textContent.replace(/<\/script>/gi, "<\\/script>");
  506. }
  507. }
  508. function getUsedFont(computedStyle, options, usedFonts) {
  509. if (computedStyle) {
  510. const fontStyle = computedStyle.getPropertyValue("font-style") || "normal";
  511. computedStyle.getPropertyValue("font-family").split(",").forEach(fontFamilyName => {
  512. fontFamilyName = normalizeFontFamily(fontFamilyName);
  513. if (!options.loadedFonts || options.loadedFonts.find(font => normalizeFontFamily(font.family) == fontFamilyName && font.style == fontStyle)) {
  514. const fontWeight = getFontWeight(computedStyle.getPropertyValue("font-weight"));
  515. const fontVariant = computedStyle.getPropertyValue("font-variant") || "normal";
  516. const value = [fontFamilyName, fontWeight, fontStyle, fontVariant];
  517. usedFonts.set(JSON$1.stringify(value), [fontFamilyName, fontWeight, fontStyle, fontVariant]);
  518. }
  519. });
  520. }
  521. }
  522. function getShadowRoot(element) {
  523. const chrome = globalThis.chrome;
  524. if (element.openOrClosedShadowRoot) {
  525. return element.openOrClosedShadowRoot;
  526. } else if (chrome && chrome.dom && chrome.dom.openOrClosedShadowRoot) {
  527. try {
  528. return chrome.dom.openOrClosedShadowRoot(element);
  529. } catch (error) {
  530. return element.shadowRoot;
  531. }
  532. } else {
  533. return element.shadowRoot;
  534. }
  535. }
  536. function normalizeFontFamily(fontFamilyName = "") {
  537. return removeQuotes(process$2(fontFamilyName.trim())).toLowerCase();
  538. }
  539. function testHiddenElement(element, computedStyle) {
  540. let hidden = false;
  541. if (computedStyle) {
  542. const display = computedStyle.getPropertyValue("display");
  543. const opacity = computedStyle.getPropertyValue("opacity");
  544. const visibility = computedStyle.getPropertyValue("visibility");
  545. hidden = display == "none";
  546. if (!hidden && (opacity == "0" || visibility == "hidden") && element.getBoundingClientRect) {
  547. const boundingRect = element.getBoundingClientRect();
  548. hidden = !boundingRect.width && !boundingRect.height;
  549. }
  550. }
  551. return Boolean(hidden);
  552. }
  553. function postProcessDoc(doc, markedElements, invalidElements) {
  554. doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
  555. element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
  556. element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
  557. if (doc.body.firstChild) {
  558. doc.body.insertBefore(element, doc.body.firstChild);
  559. } else {
  560. doc.body.appendChild(element);
  561. }
  562. });
  563. doc.querySelectorAll("meta[disabled-http-equiv]").forEach(element => {
  564. element.setAttribute("http-equiv", element.getAttribute("disabled-http-equiv"));
  565. element.removeAttribute("disabled-http-equiv");
  566. });
  567. if (doc.head) {
  568. doc.head.querySelectorAll("*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)").forEach(element => element.removeAttribute("hidden"));
  569. }
  570. if (!markedElements) {
  571. 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];
  572. markedElements = doc.querySelectorAll(singleFileAttributes.map(name => "[" + name + "]").join(","));
  573. }
  574. markedElements.forEach(element => {
  575. element.removeAttribute(REMOVED_CONTENT_ATTRIBUTE_NAME);
  576. element.removeAttribute(HIDDEN_CONTENT_ATTRIBUTE_NAME);
  577. element.removeAttribute(KEPT_CONTENT_ATTRIBUTE_NAME);
  578. element.removeAttribute(HIDDEN_FRAME_ATTRIBUTE_NAME);
  579. element.removeAttribute(PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME);
  580. element.removeAttribute(IMAGE_ATTRIBUTE_NAME);
  581. element.removeAttribute(POSTER_ATTRIBUTE_NAME);
  582. element.removeAttribute(VIDEO_ATTRIBUTE_NAME);
  583. element.removeAttribute(CANVAS_ATTRIBUTE_NAME);
  584. element.removeAttribute(INPUT_VALUE_ATTRIBUTE_NAME);
  585. element.removeAttribute(SHADOW_ROOT_ATTRIBUTE_NAME);
  586. element.removeAttribute(STYLESHEET_ATTRIBUTE_NAME);
  587. element.removeAttribute(ASYNC_SCRIPT_ATTRIBUTE_NAME);
  588. element.removeAttribute(STYLE_ATTRIBUTE_NAME);
  589. });
  590. if (invalidElements) {
  591. invalidElements.forEach((placeholderElement, element) => placeholderElement.replaceWith(element));
  592. }
  593. }
  594. function getStylesheetsData(doc) {
  595. if (doc) {
  596. const contents = [];
  597. doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
  598. try {
  599. const tempStyleElement = doc.createElement("style");
  600. tempStyleElement.textContent = styleElement.textContent;
  601. doc.body.appendChild(tempStyleElement);
  602. const stylesheet = tempStyleElement.sheet;
  603. tempStyleElement.remove();
  604. if (!stylesheet || stylesheet.cssRules.length != styleElement.sheet.cssRules.length) {
  605. styleElement.setAttribute(STYLESHEET_ATTRIBUTE_NAME, styleIndex);
  606. contents[styleIndex] = Array.from(styleElement.sheet.cssRules).map(cssRule => cssRule.cssText).join("\n");
  607. }
  608. } catch (error) {
  609. // ignored
  610. }
  611. });
  612. return contents;
  613. }
  614. }
  615. function getSize(win, imageElement, computedStyle) {
  616. let pxWidth = imageElement.naturalWidth;
  617. let pxHeight = imageElement.naturalHeight;
  618. if (!pxWidth && !pxHeight) {
  619. const noStyleAttribute = imageElement.getAttribute("style") == null;
  620. computedStyle = computedStyle || getComputedStyle(win, imageElement);
  621. if (computedStyle) {
  622. let removeBorderWidth = false;
  623. if (computedStyle.getPropertyValue("box-sizing") == "content-box") {
  624. const boxSizingValue = imageElement.style.getPropertyValue("box-sizing");
  625. const boxSizingPriority = imageElement.style.getPropertyPriority("box-sizing");
  626. const clientWidth = imageElement.clientWidth;
  627. imageElement.style.setProperty("box-sizing", "border-box", "important");
  628. removeBorderWidth = imageElement.clientWidth != clientWidth;
  629. if (boxSizingValue) {
  630. imageElement.style.setProperty("box-sizing", boxSizingValue, boxSizingPriority);
  631. } else {
  632. imageElement.style.removeProperty("box-sizing");
  633. }
  634. }
  635. let paddingLeft, paddingRight, paddingTop, paddingBottom, borderLeft, borderRight, borderTop, borderBottom;
  636. paddingLeft = getWidth("padding-left", computedStyle);
  637. paddingRight = getWidth("padding-right", computedStyle);
  638. paddingTop = getWidth("padding-top", computedStyle);
  639. paddingBottom = getWidth("padding-bottom", computedStyle);
  640. if (removeBorderWidth) {
  641. borderLeft = getWidth("border-left-width", computedStyle);
  642. borderRight = getWidth("border-right-width", computedStyle);
  643. borderTop = getWidth("border-top-width", computedStyle);
  644. borderBottom = getWidth("border-bottom-width", computedStyle);
  645. } else {
  646. borderLeft = borderRight = borderTop = borderBottom = 0;
  647. }
  648. pxWidth = Math.max(0, imageElement.clientWidth - paddingLeft - paddingRight - borderLeft - borderRight);
  649. pxHeight = Math.max(0, imageElement.clientHeight - paddingTop - paddingBottom - borderTop - borderBottom);
  650. if (noStyleAttribute) {
  651. imageElement.removeAttribute("style");
  652. }
  653. }
  654. }
  655. return { pxWidth, pxHeight };
  656. }
  657. function getWidth(styleName, computedStyle) {
  658. if (computedStyle.getPropertyValue(styleName).endsWith("px")) {
  659. return parseFloat(computedStyle.getPropertyValue(styleName));
  660. }
  661. }
  662. function getFontsData() {
  663. return getFontsData$1();
  664. }
  665. function serialize$1(doc) {
  666. const docType = doc.doctype;
  667. let docTypeString = "";
  668. if (docType) {
  669. docTypeString = "<!DOCTYPE " + docType.nodeName;
  670. if (docType.publicId) {
  671. docTypeString += " PUBLIC \"" + docType.publicId + "\"";
  672. if (docType.systemId) {
  673. docTypeString += " \"" + docType.systemId + "\"";
  674. }
  675. } else if (docType.systemId) {
  676. docTypeString += " SYSTEM \"" + docType.systemId + "\"";
  677. } if (docType.internalSubset) {
  678. docTypeString += " [" + docType.internalSubset + "]";
  679. }
  680. docTypeString += "> ";
  681. }
  682. return docTypeString + doc.documentElement.outerHTML;
  683. }
  684. function removeQuotes(string) {
  685. if (string.match(REGEXP_SIMPLE_QUOTES_STRING)) {
  686. string = string.replace(REGEXP_SIMPLE_QUOTES_STRING, "$1");
  687. } else {
  688. string = string.replace(REGEXP_DOUBLE_QUOTES_STRING, "$1");
  689. }
  690. return string.trim();
  691. }
  692. function getFontWeight(weight) {
  693. return FONT_WEIGHTS[weight.toLowerCase().trim()] || weight;
  694. }
  695. function getComputedStyle(win, element, pseudoElement) {
  696. try {
  697. return win.getComputedStyle(element, pseudoElement);
  698. } catch (error) {
  699. // ignored
  700. }
  701. }
  702. /*
  703. * Copyright 2010-2022 Gildas Lormeau
  704. * contact : gildas.lormeau <at> gmail.com
  705. *
  706. * This file is part of SingleFile.
  707. *
  708. * The code in this file is free software: you can redistribute it and/or
  709. * modify it under the terms of the GNU Affero General Public License
  710. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  711. * of the License, or (at your option) any later version.
  712. *
  713. * The code in this file is distributed in the hope that it will be useful,
  714. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  715. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  716. * General Public License for more details.
  717. *
  718. * As additional permission under GNU AGPL version 3 section 7, you may
  719. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  720. * AGPL normally required by section 4, provided you include this license
  721. * notice and a URL through which recipients can access the Corresponding
  722. * Source.
  723. */
  724. const helper$2 = {
  725. LAZY_SRC_ATTRIBUTE_NAME,
  726. SINGLE_FILE_UI_ELEMENT_CLASS
  727. };
  728. const MAX_IDLE_TIMEOUT_CALLS = 10;
  729. const ATTRIBUTES_MUTATION_TYPE = "attributes";
  730. const browser$1 = globalThis.browser;
  731. const document$1 = globalThis.document;
  732. const MutationObserver = globalThis.MutationObserver;
  733. const addEventListener$1 = (type, listener, options) => globalThis.addEventListener(type, listener, options);
  734. const removeEventListener = (type, listener, options) => globalThis.removeEventListener(type, listener, options);
  735. const timeouts = new Map();
  736. let idleTimeoutCalls;
  737. if (browser$1 && browser$1.runtime && browser$1.runtime.onMessage && browser$1.runtime.onMessage.addListener) {
  738. browser$1.runtime.onMessage.addListener(message => {
  739. if (message.method == "singlefile.lazyTimeout.onTimeout") {
  740. const timeoutData = timeouts.get(message.type);
  741. if (timeoutData) {
  742. timeouts.delete(message.type);
  743. try {
  744. timeoutData.callback();
  745. } catch (error) {
  746. clearRegularTimeout(message.type);
  747. }
  748. }
  749. }
  750. });
  751. }
  752. async function process$1(options) {
  753. if (document$1.documentElement) {
  754. timeouts.clear();
  755. const bodyHeight = document$1.body ? Math.max(document$1.body.scrollHeight, document$1.documentElement.scrollHeight) : document$1.documentElement.scrollHeight;
  756. const bodyWidth = document$1.body ? Math.max(document$1.body.scrollWidth, document$1.documentElement.scrollWidth) : document$1.documentElement.scrollWidth;
  757. if (bodyHeight > globalThis.innerHeight || bodyWidth > globalThis.innerWidth) {
  758. const maxScrollY = Math.max(bodyHeight - (globalThis.innerHeight * 1.5), 0);
  759. const maxScrollX = Math.max(bodyWidth - (globalThis.innerWidth * 1.5), 0);
  760. if (globalThis.scrollY < maxScrollY || globalThis.scrollX < maxScrollX) {
  761. return triggerLazyLoading(options);
  762. }
  763. }
  764. }
  765. }
  766. function triggerLazyLoading(options) {
  767. idleTimeoutCalls = 0;
  768. return new Promise(async resolve => { // eslint-disable-line no-async-promise-executor
  769. let loadingImages;
  770. const pendingImages = new Set();
  771. const observer = new MutationObserver(async mutations => {
  772. mutations = mutations.filter(mutation => mutation.type == ATTRIBUTES_MUTATION_TYPE);
  773. if (mutations.length) {
  774. const updated = mutations.filter(mutation => {
  775. if (mutation.attributeName == "src") {
  776. mutation.target.setAttribute(helper$2.LAZY_SRC_ATTRIBUTE_NAME, mutation.target.src);
  777. mutation.target.addEventListener("load", onResourceLoad);
  778. }
  779. if (mutation.attributeName == "src" || mutation.attributeName == "srcset" ||
  780. (mutation.target.tagName && mutation.target.tagName.toUpperCase() == "SOURCE")) {
  781. return !mutation.target.classList || !mutation.target.classList.contains(helper$2.SINGLE_FILE_UI_ELEMENT_CLASS);
  782. }
  783. });
  784. if (updated.length) {
  785. loadingImages = true;
  786. await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
  787. if (!pendingImages.size) {
  788. await deferLazyLoadEnd(observer, options, cleanupAndResolve);
  789. }
  790. }
  791. }
  792. });
  793. await setIdleTimeout(options.loadDeferredImagesMaxIdleTime * 2);
  794. await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
  795. observer.observe(document$1, { subtree: true, childList: true, attributes: true });
  796. addEventListener$1(LOAD_IMAGE_EVENT, onImageLoadEvent);
  797. addEventListener$1(IMAGE_LOADED_EVENT, onImageLoadedEvent);
  798. loadDeferredImagesStart(options);
  799. async function setIdleTimeout(delay) {
  800. await setAsyncTimeout("idleTimeout", async () => {
  801. if (!loadingImages) {
  802. clearAsyncTimeout("loadTimeout");
  803. clearAsyncTimeout("maxTimeout");
  804. lazyLoadEnd(observer, options, cleanupAndResolve);
  805. } else if (idleTimeoutCalls < MAX_IDLE_TIMEOUT_CALLS) {
  806. idleTimeoutCalls++;
  807. clearAsyncTimeout("idleTimeout");
  808. await setIdleTimeout(Math.max(500, delay / 2));
  809. }
  810. }, delay, options.loadDeferredImagesNativeTimeout);
  811. }
  812. function onResourceLoad(event) {
  813. const element = event.target;
  814. element.removeAttribute(helper$2.LAZY_SRC_ATTRIBUTE_NAME);
  815. element.removeEventListener("load", onResourceLoad);
  816. }
  817. async function onImageLoadEvent(event) {
  818. loadingImages = true;
  819. await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
  820. await deferLazyLoadEnd(observer, options, cleanupAndResolve);
  821. if (event.detail) {
  822. pendingImages.add(event.detail);
  823. }
  824. }
  825. async function onImageLoadedEvent(event) {
  826. await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
  827. await deferLazyLoadEnd(observer, options, cleanupAndResolve);
  828. pendingImages.delete(event.detail);
  829. if (!pendingImages.size) {
  830. await deferLazyLoadEnd(observer, options, cleanupAndResolve);
  831. }
  832. }
  833. function cleanupAndResolve(value) {
  834. observer.disconnect();
  835. removeEventListener(LOAD_IMAGE_EVENT, onImageLoadEvent);
  836. removeEventListener(IMAGE_LOADED_EVENT, onImageLoadedEvent);
  837. resolve(value);
  838. }
  839. });
  840. }
  841. async function deferLazyLoadEnd(observer, options, resolve) {
  842. await setAsyncTimeout("loadTimeout", () => lazyLoadEnd(observer, options, resolve), options.loadDeferredImagesMaxIdleTime, options.loadDeferredImagesNativeTimeout);
  843. }
  844. async function deferForceLazyLoadEnd(observer, options, resolve) {
  845. await setAsyncTimeout("maxTimeout", async () => {
  846. await clearAsyncTimeout("loadTimeout");
  847. await lazyLoadEnd(observer, options, resolve);
  848. }, options.loadDeferredImagesMaxIdleTime * 10, options.loadDeferredImagesNativeTimeout);
  849. }
  850. async function lazyLoadEnd(observer, options, resolve) {
  851. await clearAsyncTimeout("idleTimeout");
  852. loadDeferredImagesEnd(options);
  853. await setAsyncTimeout("endTimeout", async () => {
  854. await clearAsyncTimeout("maxTimeout");
  855. resolve();
  856. }, options.loadDeferredImagesMaxIdleTime / 2, options.loadDeferredImagesNativeTimeout);
  857. observer.disconnect();
  858. }
  859. async function setAsyncTimeout(type, callback, delay, forceNativeTimeout) {
  860. if (browser$1 && browser$1.runtime && browser$1.runtime.sendMessage && !forceNativeTimeout) {
  861. if (!timeouts.get(type) || !timeouts.get(type).pending) {
  862. const timeoutData = { callback, pending: true };
  863. timeouts.set(type, timeoutData);
  864. try {
  865. await browser$1.runtime.sendMessage({ method: "singlefile.lazyTimeout.setTimeout", type, delay });
  866. } catch (error) {
  867. setRegularTimeout(type, callback, delay);
  868. }
  869. timeoutData.pending = false;
  870. }
  871. } else {
  872. setRegularTimeout(type, callback, delay);
  873. }
  874. }
  875. function setRegularTimeout(type, callback, delay) {
  876. const timeoutId = timeouts.get(type);
  877. if (timeoutId) {
  878. globalThis.clearTimeout(timeoutId);
  879. }
  880. timeouts.set(type, callback);
  881. globalThis.setTimeout(callback, delay);
  882. }
  883. async function clearAsyncTimeout(type) {
  884. if (browser$1 && browser$1.runtime && browser$1.runtime.sendMessage) {
  885. try {
  886. await browser$1.runtime.sendMessage({ method: "singlefile.lazyTimeout.clearTimeout", type });
  887. } catch (error) {
  888. clearRegularTimeout(type);
  889. }
  890. } else {
  891. clearRegularTimeout(type);
  892. }
  893. }
  894. function clearRegularTimeout(type) {
  895. const previousTimeoutId = timeouts.get(type);
  896. timeouts.delete(type);
  897. if (previousTimeoutId) {
  898. globalThis.clearTimeout(previousTimeoutId);
  899. }
  900. }
  901. /*
  902. * Copyright 2010-2022 Gildas Lormeau
  903. * contact : gildas.lormeau <at> gmail.com
  904. *
  905. * This file is part of SingleFile.
  906. *
  907. * The code in this file is free software: you can redistribute it and/or
  908. * modify it under the terms of the GNU Affero General Public License
  909. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  910. * of the License, or (at your option) any later version.
  911. *
  912. * The code in this file is distributed in the hope that it will be useful,
  913. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  914. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  915. * General Public License for more details.
  916. *
  917. * As additional permission under GNU AGPL version 3 section 7, you may
  918. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  919. * AGPL normally required by section 4, provided you include this license
  920. * notice and a URL through which recipients can access the Corresponding
  921. * Source.
  922. */
  923. const helper$1 = {
  924. ON_BEFORE_CAPTURE_EVENT_NAME,
  925. ON_AFTER_CAPTURE_EVENT_NAME,
  926. WIN_ID_ATTRIBUTE_NAME,
  927. WAIT_FOR_USERSCRIPT_PROPERTY_NAME,
  928. preProcessDoc,
  929. serialize: serialize$1,
  930. postProcessDoc,
  931. getShadowRoot
  932. };
  933. const FRAMES_CSS_SELECTOR = "iframe, frame, object[type=\"text/html\"][data]";
  934. const ALL_ELEMENTS_CSS_SELECTOR = "*";
  935. const INIT_REQUEST_MESSAGE = "singlefile.frameTree.initRequest";
  936. const ACK_INIT_REQUEST_MESSAGE = "singlefile.frameTree.ackInitRequest";
  937. const CLEANUP_REQUEST_MESSAGE = "singlefile.frameTree.cleanupRequest";
  938. const INIT_RESPONSE_MESSAGE = "singlefile.frameTree.initResponse";
  939. const TARGET_ORIGIN = "*";
  940. const TIMEOUT_INIT_REQUEST_MESSAGE = 5000;
  941. const TIMEOUT_INIT_RESPONSE_MESSAGE = 10000;
  942. const TOP_WINDOW_ID = "0";
  943. const WINDOW_ID_SEPARATOR = ".";
  944. const TOP_WINDOW = globalThis.window == globalThis.top;
  945. const browser = globalThis.browser;
  946. const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
  947. const top = globalThis.top;
  948. const MessageChannel = globalThis.MessageChannel;
  949. const document = globalThis.document;
  950. const JSON = globalThis.JSON;
  951. let sessions = globalThis.sessions;
  952. if (!sessions) {
  953. sessions = globalThis.sessions = new Map();
  954. }
  955. let windowId;
  956. if (TOP_WINDOW) {
  957. windowId = TOP_WINDOW_ID;
  958. if (browser && browser.runtime && browser.runtime.onMessage && browser.runtime.onMessage.addListener) {
  959. browser.runtime.onMessage.addListener(message => {
  960. if (message.method == INIT_RESPONSE_MESSAGE) {
  961. initResponse(message);
  962. return Promise.resolve({});
  963. } else if (message.method == ACK_INIT_REQUEST_MESSAGE) {
  964. clearFrameTimeout("requestTimeouts", message.sessionId, message.windowId);
  965. createFrameResponseTimeout(message.sessionId, message.windowId);
  966. return Promise.resolve({});
  967. }
  968. });
  969. }
  970. }
  971. addEventListener("message", async event => {
  972. if (typeof event.data == "string" && event.data.startsWith(MESSAGE_PREFIX)) {
  973. event.preventDefault();
  974. event.stopPropagation();
  975. const message = JSON.parse(event.data.substring(MESSAGE_PREFIX.length));
  976. if (message.method == INIT_REQUEST_MESSAGE) {
  977. if (event.source) {
  978. sendMessage(event.source, { method: ACK_INIT_REQUEST_MESSAGE, windowId: message.windowId, sessionId: message.sessionId });
  979. }
  980. if (!TOP_WINDOW) {
  981. globalThis.stop();
  982. if (message.options.loadDeferredImages) {
  983. process$1(message.options);
  984. }
  985. await initRequestAsync(message);
  986. }
  987. } else if (message.method == ACK_INIT_REQUEST_MESSAGE) {
  988. clearFrameTimeout("requestTimeouts", message.sessionId, message.windowId);
  989. createFrameResponseTimeout(message.sessionId, message.windowId);
  990. } else if (message.method == CLEANUP_REQUEST_MESSAGE) {
  991. cleanupRequest(message);
  992. } else if (message.method == INIT_RESPONSE_MESSAGE && sessions.get(message.sessionId)) {
  993. const port = event.ports[0];
  994. port.onmessage = event => initResponse(event.data);
  995. }
  996. }
  997. }, true);
  998. function getAsync(options) {
  999. const sessionId = getNewSessionId();
  1000. options = JSON.parse(JSON.stringify(options));
  1001. return new Promise(resolve => {
  1002. sessions.set(sessionId, {
  1003. frames: [],
  1004. requestTimeouts: {},
  1005. responseTimeouts: {},
  1006. resolve: frames => {
  1007. frames.sessionId = sessionId;
  1008. resolve(frames);
  1009. }
  1010. });
  1011. initRequestAsync({ windowId, sessionId, options });
  1012. });
  1013. }
  1014. function getSync(options) {
  1015. const sessionId = getNewSessionId();
  1016. options = JSON.parse(JSON.stringify(options));
  1017. sessions.set(sessionId, {
  1018. frames: [],
  1019. requestTimeouts: {},
  1020. responseTimeouts: {}
  1021. });
  1022. initRequestSync({ windowId, sessionId, options });
  1023. const frames = sessions.get(sessionId).frames;
  1024. frames.sessionId = sessionId;
  1025. return frames;
  1026. }
  1027. function cleanup(sessionId) {
  1028. sessions.delete(sessionId);
  1029. cleanupRequest({ windowId, sessionId, options: { sessionId } });
  1030. }
  1031. function getNewSessionId() {
  1032. return globalThis.crypto.getRandomValues(new Uint32Array(32)).join("");
  1033. }
  1034. function initRequestSync(message) {
  1035. const sessionId = message.sessionId;
  1036. const waitForUserScript = globalThis[helper$1.WAIT_FOR_USERSCRIPT_PROPERTY_NAME];
  1037. delete globalThis._singleFile_cleaningUp;
  1038. if (!TOP_WINDOW) {
  1039. windowId = globalThis.frameId = message.windowId;
  1040. }
  1041. processFrames(document, message.options, windowId, sessionId);
  1042. if (!TOP_WINDOW) {
  1043. if (message.options.userScriptEnabled && waitForUserScript) {
  1044. waitForUserScript(helper$1.ON_BEFORE_CAPTURE_EVENT_NAME);
  1045. }
  1046. sendInitResponse({ frames: [getFrameData(document, globalThis, windowId, message.options, message.scrolling)], sessionId, requestedFrameId: document.documentElement.dataset.requestedFrameId && windowId });
  1047. if (message.options.userScriptEnabled && waitForUserScript) {
  1048. waitForUserScript(helper$1.ON_AFTER_CAPTURE_EVENT_NAME);
  1049. }
  1050. delete document.documentElement.dataset.requestedFrameId;
  1051. }
  1052. }
  1053. async function initRequestAsync(message) {
  1054. const sessionId = message.sessionId;
  1055. const waitForUserScript = globalThis[helper$1.WAIT_FOR_USERSCRIPT_PROPERTY_NAME];
  1056. delete globalThis._singleFile_cleaningUp;
  1057. if (!TOP_WINDOW) {
  1058. windowId = globalThis.frameId = message.windowId;
  1059. }
  1060. processFrames(document, message.options, windowId, sessionId);
  1061. if (!TOP_WINDOW) {
  1062. if (message.options.userScriptEnabled && waitForUserScript) {
  1063. await waitForUserScript(helper$1.ON_BEFORE_CAPTURE_EVENT_NAME);
  1064. }
  1065. sendInitResponse({ frames: [getFrameData(document, globalThis, windowId, message.options, message.scrolling)], sessionId, requestedFrameId: document.documentElement.dataset.requestedFrameId && windowId });
  1066. if (message.options.userScriptEnabled && waitForUserScript) {
  1067. await waitForUserScript(helper$1.ON_AFTER_CAPTURE_EVENT_NAME);
  1068. }
  1069. delete document.documentElement.dataset.requestedFrameId;
  1070. }
  1071. }
  1072. function cleanupRequest(message) {
  1073. if (!globalThis._singleFile_cleaningUp) {
  1074. globalThis._singleFile_cleaningUp = true;
  1075. const sessionId = message.sessionId;
  1076. cleanupFrames(getFrames(document), message.windowId, sessionId);
  1077. }
  1078. }
  1079. function initResponse(message) {
  1080. message.frames.forEach(frameData => clearFrameTimeout("responseTimeouts", message.sessionId, frameData.windowId));
  1081. const windowData = sessions.get(message.sessionId);
  1082. if (windowData) {
  1083. if (message.requestedFrameId) {
  1084. windowData.requestedFrameId = message.requestedFrameId;
  1085. }
  1086. message.frames.forEach(messageFrameData => {
  1087. let frameData = windowData.frames.find(frameData => messageFrameData.windowId == frameData.windowId);
  1088. if (!frameData) {
  1089. frameData = { windowId: messageFrameData.windowId };
  1090. windowData.frames.push(frameData);
  1091. }
  1092. if (!frameData.processed) {
  1093. frameData.content = messageFrameData.content;
  1094. frameData.baseURI = messageFrameData.baseURI;
  1095. frameData.title = messageFrameData.title;
  1096. frameData.url = messageFrameData.url;
  1097. frameData.canvases = messageFrameData.canvases;
  1098. frameData.fonts = messageFrameData.fonts;
  1099. frameData.stylesheets = messageFrameData.stylesheets;
  1100. frameData.images = messageFrameData.images;
  1101. frameData.posters = messageFrameData.posters;
  1102. frameData.videos = messageFrameData.videos;
  1103. frameData.usedFonts = messageFrameData.usedFonts;
  1104. frameData.shadowRoots = messageFrameData.shadowRoots;
  1105. frameData.processed = messageFrameData.processed;
  1106. frameData.scrollPosition = messageFrameData.scrollPosition;
  1107. frameData.scrolling = messageFrameData.scrolling;
  1108. frameData.adoptedStyleSheets = messageFrameData.adoptedStyleSheets;
  1109. }
  1110. });
  1111. const remainingFrames = windowData.frames.filter(frameData => !frameData.processed).length;
  1112. if (!remainingFrames) {
  1113. windowData.frames = windowData.frames.sort((frame1, frame2) => frame2.windowId.split(WINDOW_ID_SEPARATOR).length - frame1.windowId.split(WINDOW_ID_SEPARATOR).length);
  1114. if (windowData.resolve) {
  1115. if (windowData.requestedFrameId) {
  1116. windowData.frames.forEach(frameData => {
  1117. if (frameData.windowId == windowData.requestedFrameId) {
  1118. frameData.requestedFrame = true;
  1119. }
  1120. });
  1121. }
  1122. windowData.resolve(windowData.frames);
  1123. }
  1124. }
  1125. }
  1126. }
  1127. function processFrames(doc, options, parentWindowId, sessionId) {
  1128. const frameElements = getFrames(doc);
  1129. processFramesAsync(doc, frameElements, options, parentWindowId, sessionId);
  1130. if (frameElements.length) {
  1131. processFramesSync(doc, frameElements, options, parentWindowId, sessionId);
  1132. }
  1133. }
  1134. function processFramesAsync(doc, frameElements, options, parentWindowId, sessionId) {
  1135. const frames = [];
  1136. let requestTimeouts;
  1137. if (sessions.get(sessionId)) {
  1138. requestTimeouts = sessions.get(sessionId).requestTimeouts;
  1139. } else {
  1140. requestTimeouts = {};
  1141. sessions.set(sessionId, { requestTimeouts });
  1142. }
  1143. frameElements.forEach((frameElement, frameIndex) => {
  1144. const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
  1145. frameElement.setAttribute(helper$1.WIN_ID_ATTRIBUTE_NAME, windowId);
  1146. frames.push({ windowId });
  1147. });
  1148. sendInitResponse({ frames, sessionId, requestedFrameId: doc.documentElement.dataset.requestedFrameId && parentWindowId });
  1149. frameElements.forEach((frameElement, frameIndex) => {
  1150. const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
  1151. try {
  1152. sendMessage(frameElement.contentWindow, { method: INIT_REQUEST_MESSAGE, windowId, sessionId, options, scrolling: frameElement.scrolling });
  1153. } catch (error) {
  1154. // ignored
  1155. }
  1156. requestTimeouts[windowId] = globalThis.setTimeout(() => sendInitResponse({ frames: [{ windowId, processed: true }], sessionId }), TIMEOUT_INIT_REQUEST_MESSAGE);
  1157. });
  1158. delete doc.documentElement.dataset.requestedFrameId;
  1159. }
  1160. function processFramesSync(doc, frameElements, options, parentWindowId, sessionId) {
  1161. const frames = [];
  1162. frameElements.forEach((frameElement, frameIndex) => {
  1163. const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
  1164. let frameDoc;
  1165. try {
  1166. frameDoc = frameElement.contentDocument;
  1167. } catch (error) {
  1168. // ignored
  1169. }
  1170. if (frameDoc) {
  1171. try {
  1172. const frameWindow = frameElement.contentWindow;
  1173. frameWindow.stop();
  1174. clearFrameTimeout("requestTimeouts", sessionId, windowId);
  1175. processFrames(frameDoc, options, windowId, sessionId);
  1176. frames.push(getFrameData(frameDoc, frameWindow, windowId, options, frameElement.scrolling));
  1177. } catch (error) {
  1178. frames.push({ windowId, processed: true });
  1179. }
  1180. }
  1181. });
  1182. sendInitResponse({ frames, sessionId, requestedFrameId: doc.documentElement.dataset.requestedFrameId && parentWindowId });
  1183. delete doc.documentElement.dataset.requestedFrameId;
  1184. }
  1185. function clearFrameTimeout(type, sessionId, windowId) {
  1186. const session = sessions.get(sessionId);
  1187. if (session && session[type]) {
  1188. const timeout = session[type][windowId];
  1189. if (timeout) {
  1190. globalThis.clearTimeout(timeout);
  1191. delete session[type][windowId];
  1192. }
  1193. }
  1194. }
  1195. function createFrameResponseTimeout(sessionId, windowId) {
  1196. const session = sessions.get(sessionId);
  1197. if (session && session.responseTimeouts) {
  1198. session.responseTimeouts[windowId] = globalThis.setTimeout(() => sendInitResponse({ frames: [{ windowId: windowId, processed: true }], sessionId: sessionId }), TIMEOUT_INIT_RESPONSE_MESSAGE);
  1199. }
  1200. }
  1201. function cleanupFrames(frameElements, parentWindowId, sessionId) {
  1202. frameElements.forEach((frameElement, frameIndex) => {
  1203. const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
  1204. frameElement.removeAttribute(helper$1.WIN_ID_ATTRIBUTE_NAME);
  1205. try {
  1206. sendMessage(frameElement.contentWindow, { method: CLEANUP_REQUEST_MESSAGE, windowId, sessionId });
  1207. } catch (error) {
  1208. // ignored
  1209. }
  1210. });
  1211. frameElements.forEach((frameElement, frameIndex) => {
  1212. const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
  1213. let frameDoc;
  1214. try {
  1215. frameDoc = frameElement.contentDocument;
  1216. } catch (error) {
  1217. // ignored
  1218. }
  1219. if (frameDoc) {
  1220. try {
  1221. cleanupFrames(getFrames(frameDoc), windowId, sessionId);
  1222. } catch (error) {
  1223. // ignored
  1224. }
  1225. }
  1226. });
  1227. }
  1228. function sendInitResponse(message) {
  1229. message.method = INIT_RESPONSE_MESSAGE;
  1230. try {
  1231. top.singlefile.processors.frameTree.initResponse(message);
  1232. } catch (error) {
  1233. sendMessage(top, message, true);
  1234. }
  1235. }
  1236. function sendMessage(targetWindow, message, useChannel) {
  1237. if (targetWindow == top && browser && browser.runtime && browser.runtime.sendMessage) {
  1238. browser.runtime.sendMessage(message);
  1239. } else {
  1240. if (useChannel) {
  1241. const channel = new MessageChannel();
  1242. targetWindow.postMessage(MESSAGE_PREFIX + JSON.stringify({ method: message.method, sessionId: message.sessionId }), TARGET_ORIGIN, [channel.port2]);
  1243. channel.port1.postMessage(message);
  1244. } else {
  1245. targetWindow.postMessage(MESSAGE_PREFIX + JSON.stringify(message), TARGET_ORIGIN);
  1246. }
  1247. }
  1248. }
  1249. function getFrameData(document, globalThis, windowId, options, scrolling) {
  1250. const docData = helper$1.preProcessDoc(document, globalThis, options);
  1251. const content = helper$1.serialize(document);
  1252. helper$1.postProcessDoc(document, docData.markedElements, docData.invalidElements);
  1253. const baseURI = document.baseURI.split("#")[0];
  1254. return {
  1255. windowId,
  1256. content,
  1257. baseURI,
  1258. url: document.location.href,
  1259. title: document.title,
  1260. canvases: docData.canvases,
  1261. fonts: docData.fonts,
  1262. stylesheets: docData.stylesheets,
  1263. images: docData.images,
  1264. posters: docData.posters,
  1265. videos: docData.videos,
  1266. usedFonts: docData.usedFonts,
  1267. shadowRoots: docData.shadowRoots,
  1268. scrollPosition: docData.scrollPosition,
  1269. scrolling,
  1270. adoptedStyleSheets: docData.adoptedStyleSheets,
  1271. processed: true
  1272. };
  1273. }
  1274. function getFrames(document) {
  1275. let frames = Array.from(document.querySelectorAll(FRAMES_CSS_SELECTOR));
  1276. document.querySelectorAll(ALL_ELEMENTS_CSS_SELECTOR).forEach(element => {
  1277. const shadowRoot = helper$1.getShadowRoot(element);
  1278. if (shadowRoot) {
  1279. frames = frames.concat(...shadowRoot.querySelectorAll(FRAMES_CSS_SELECTOR));
  1280. }
  1281. });
  1282. return frames;
  1283. }
  1284. var frameTree = /*#__PURE__*/Object.freeze({
  1285. __proto__: null,
  1286. getAsync: getAsync,
  1287. getSync: getSync,
  1288. cleanup: cleanup,
  1289. initResponse: initResponse,
  1290. TIMEOUT_INIT_REQUEST_MESSAGE: TIMEOUT_INIT_REQUEST_MESSAGE
  1291. });
  1292. /*
  1293. * Copyright 2010-2022 Gildas Lormeau
  1294. * contact : gildas.lormeau <at> gmail.com
  1295. *
  1296. * This file is part of SingleFile.
  1297. *
  1298. * The code in this file is free software: you can redistribute it and/or
  1299. * modify it under the terms of the GNU Affero General Public License
  1300. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1301. * of the License, or (at your option) any later version.
  1302. *
  1303. * The code in this file is distributed in the hope that it will be useful,
  1304. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1305. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1306. * General Public License for more details.
  1307. *
  1308. * As additional permission under GNU AGPL version 3 section 7, you may
  1309. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1310. * AGPL normally required by section 4, provided you include this license
  1311. * notice and a URL through which recipients can access the Corresponding
  1312. * Source.
  1313. */
  1314. const SELF_CLOSED_TAG_NAMES = ["AREA", "BASE", "BR", "COL", "COMMAND", "EMBED", "HR", "IMG", "INPUT", "KEYGEN", "LINK", "META", "PARAM", "SOURCE", "TRACK", "WBR"];
  1315. const Node_ELEMENT_NODE = 1;
  1316. const Node_TEXT_NODE = 3;
  1317. const Node_COMMENT_NODE = 8;
  1318. // see https://www.w3.org/TR/html5/syntax.html#optional-tags
  1319. const OMITTED_START_TAGS = [
  1320. { tagName: "HEAD", accept: element => !element.childNodes.length || element.childNodes[0].nodeType == Node_ELEMENT_NODE },
  1321. { tagName: "BODY", accept: element => !element.childNodes.length }
  1322. ];
  1323. const OMITTED_END_TAGS = [
  1324. { tagName: "HTML", accept: next => !next || next.nodeType != Node_COMMENT_NODE },
  1325. { tagName: "HEAD", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
  1326. { tagName: "BODY", accept: next => !next || next.nodeType != Node_COMMENT_NODE },
  1327. { tagName: "LI", accept: (next, element) => (!next && element.parentElement && (getTagName(element.parentElement) == "UL" || getTagName(element.parentElement) == "OL")) || (next && ["LI"].includes(getTagName(next))) },
  1328. { tagName: "DT", accept: next => !next || ["DT", "DD"].includes(getTagName(next)) },
  1329. { tagName: "P", accept: next => next && ["ADDRESS", "ARTICLE", "ASIDE", "BLOCKQUOTE", "DETAILS", "DIV", "DL", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", "HR", "MAIN", "NAV", "OL", "P", "PRE", "SECTION", "TABLE", "UL"].includes(getTagName(next)) },
  1330. { tagName: "DD", accept: next => !next || ["DT", "DD"].includes(getTagName(next)) },
  1331. { tagName: "RT", accept: next => !next || ["RT", "RP"].includes(getTagName(next)) },
  1332. { tagName: "RP", accept: next => !next || ["RT", "RP"].includes(getTagName(next)) },
  1333. { tagName: "OPTGROUP", accept: next => !next || ["OPTGROUP"].includes(getTagName(next)) },
  1334. { tagName: "OPTION", accept: next => !next || ["OPTION", "OPTGROUP"].includes(getTagName(next)) },
  1335. { tagName: "COLGROUP", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
  1336. { tagName: "CAPTION", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
  1337. { tagName: "THEAD", accept: next => !next || ["TBODY", "TFOOT"].includes(getTagName(next)) },
  1338. { tagName: "TBODY", accept: next => !next || ["TBODY", "TFOOT"].includes(getTagName(next)) },
  1339. { tagName: "TFOOT", accept: next => !next },
  1340. { tagName: "TR", accept: next => !next || ["TR"].includes(getTagName(next)) },
  1341. { tagName: "TD", accept: next => !next || ["TD", "TH"].includes(getTagName(next)) },
  1342. { tagName: "TH", accept: next => !next || ["TD", "TH"].includes(getTagName(next)) }
  1343. ];
  1344. const TEXT_NODE_TAGS = ["STYLE", "SCRIPT", "XMP", "IFRAME", "NOEMBED", "NOFRAMES", "PLAINTEXT", "NOSCRIPT"];
  1345. function process(doc, compressHTML) {
  1346. const docType = doc.doctype;
  1347. let docTypeString = "";
  1348. if (docType) {
  1349. docTypeString = "<!DOCTYPE " + docType.nodeName;
  1350. if (docType.publicId) {
  1351. docTypeString += " PUBLIC \"" + docType.publicId + "\"";
  1352. if (docType.systemId)
  1353. docTypeString += " \"" + docType.systemId + "\"";
  1354. } else if (docType.systemId)
  1355. docTypeString += " SYSTEM \"" + docType.systemId + "\"";
  1356. if (docType.internalSubset)
  1357. docTypeString += " [" + docType.internalSubset + "]";
  1358. docTypeString += "> ";
  1359. }
  1360. return docTypeString + serialize(doc.documentElement, compressHTML);
  1361. }
  1362. function serialize(node, compressHTML, isSVG) {
  1363. if (node.nodeType == Node_TEXT_NODE) {
  1364. return serializeTextNode(node);
  1365. } else if (node.nodeType == Node_COMMENT_NODE) {
  1366. return serializeCommentNode(node);
  1367. } else if (node.nodeType == Node_ELEMENT_NODE) {
  1368. return serializeElement(node, compressHTML, isSVG);
  1369. }
  1370. }
  1371. function serializeTextNode(textNode) {
  1372. const parentNode = textNode.parentNode;
  1373. let parentTagName;
  1374. if (parentNode && parentNode.nodeType == Node_ELEMENT_NODE) {
  1375. parentTagName = getTagName(parentNode);
  1376. }
  1377. if (!parentTagName || TEXT_NODE_TAGS.includes(parentTagName)) {
  1378. if (parentTagName == "SCRIPT" || parentTagName == "STYLE") {
  1379. return textNode.textContent.replace(/<\//gi, "<\\/").replace(/\/>/gi, "\\/>");
  1380. }
  1381. return textNode.textContent;
  1382. } else {
  1383. return textNode.textContent.replace(/&/g, "&amp;").replace(/\u00a0/g, "&nbsp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  1384. }
  1385. }
  1386. function serializeCommentNode(commentNode) {
  1387. return "<!--" + commentNode.textContent + "-->";
  1388. }
  1389. function serializeElement(element, compressHTML, isSVG) {
  1390. const tagName = getTagName(element);
  1391. const omittedStartTag = compressHTML && OMITTED_START_TAGS.find(omittedStartTag => tagName == getTagName(omittedStartTag) && omittedStartTag.accept(element));
  1392. let content = "";
  1393. if (!omittedStartTag || element.attributes.length) {
  1394. content = "<" + tagName.toLowerCase();
  1395. Array.from(element.attributes).forEach(attribute => content += serializeAttribute(attribute, element, compressHTML));
  1396. content += ">";
  1397. }
  1398. if (tagName == "TEMPLATE" && !element.childNodes.length) {
  1399. content += element.innerHTML;
  1400. } else {
  1401. Array.from(element.childNodes).forEach(childNode => content += serialize(childNode, compressHTML, isSVG || tagName == "svg"));
  1402. }
  1403. const omittedEndTag = compressHTML && OMITTED_END_TAGS.find(omittedEndTag => tagName == getTagName(omittedEndTag) && omittedEndTag.accept(element.nextSibling, element));
  1404. if (isSVG || (!omittedEndTag && !SELF_CLOSED_TAG_NAMES.includes(tagName))) {
  1405. content += "</" + tagName.toLowerCase() + ">";
  1406. }
  1407. return content;
  1408. }
  1409. function serializeAttribute(attribute, element, compressHTML) {
  1410. const name = attribute.name;
  1411. let content = "";
  1412. if (!name.match(/["'>/=]/)) {
  1413. let value = attribute.value;
  1414. if (compressHTML && name == "class") {
  1415. value = Array.from(element.classList).map(className => className.trim()).join(" ");
  1416. }
  1417. let simpleQuotesValue;
  1418. value = value.replace(/&/g, "&amp;").replace(/\u00a0/g, "&nbsp;");
  1419. if (value.includes("\"")) {
  1420. if (value.includes("'") || !compressHTML) {
  1421. value = value.replace(/"/g, "&quot;");
  1422. } else {
  1423. simpleQuotesValue = true;
  1424. }
  1425. }
  1426. const invalidUnquotedValue = !compressHTML || value.match(/[ \t\n\f\r'"`=<>]/);
  1427. content += " ";
  1428. if (!attribute.namespace) {
  1429. content += name;
  1430. } else if (attribute.namespaceURI == "http://www.w3.org/XML/1998/namespace") {
  1431. content += "xml:" + name;
  1432. } else if (attribute.namespaceURI == "http://www.w3.org/2000/xmlns/") {
  1433. if (name !== "xmlns") {
  1434. content += "xmlns:";
  1435. }
  1436. content += name;
  1437. } else if (attribute.namespaceURI == "http://www.w3.org/1999/xlink") {
  1438. content += "xlink:" + name;
  1439. } else {
  1440. content += name;
  1441. }
  1442. if (value != "") {
  1443. content += "=";
  1444. if (invalidUnquotedValue) {
  1445. content += simpleQuotesValue ? "'" : "\"";
  1446. }
  1447. content += value;
  1448. if (invalidUnquotedValue) {
  1449. content += simpleQuotesValue ? "'" : "\"";
  1450. }
  1451. }
  1452. }
  1453. return content;
  1454. }
  1455. function startsWithSpaceChar(textContent) {
  1456. return Boolean(textContent.match(/^[ \t\n\f\r]/));
  1457. }
  1458. function getTagName(element) {
  1459. return element.tagName && element.tagName.toUpperCase();
  1460. }
  1461. /*
  1462. * Copyright 2010-2022 Gildas Lormeau
  1463. * contact : gildas.lormeau <at> gmail.com
  1464. *
  1465. * This file is part of SingleFile.
  1466. *
  1467. * The code in this file is free software: you can redistribute it and/or
  1468. * modify it under the terms of the GNU Affero General Public License
  1469. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1470. * of the License, or (at your option) any later version.
  1471. *
  1472. * The code in this file is distributed in the hope that it will be useful,
  1473. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1474. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1475. * General Public License for more details.
  1476. *
  1477. * As additional permission under GNU AGPL version 3 section 7, you may
  1478. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1479. * AGPL normally required by section 4, provided you include this license
  1480. * notice and a URL through which recipients can access the Corresponding
  1481. * Source.
  1482. */
  1483. const processors = { frameTree };
  1484. const helper = {
  1485. COMMENT_HEADER,
  1486. COMMENT_HEADER_LEGACY,
  1487. ON_BEFORE_CAPTURE_EVENT_NAME,
  1488. ON_AFTER_CAPTURE_EVENT_NAME,
  1489. preProcessDoc,
  1490. postProcessDoc,
  1491. serialize(doc, compressHTML) {
  1492. return process(doc, compressHTML);
  1493. },
  1494. getShadowRoot
  1495. };
  1496. initUserScriptHandler();
  1497. exports.helper = helper;
  1498. exports.processors = processors;
  1499. Object.defineProperty(exports, '__esModule', { value: true });
  1500. }));