single-file-core 1.0.46 → 1.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -67,9 +67,7 @@
67
67
  const Event = globalThis.Event;
68
68
  const FileReader = globalThis.FileReader;
69
69
  const Blob = globalThis.Blob;
70
- const console = globalThis.console;
71
70
  const JSON = globalThis.JSON;
72
- const warn = (console && console.warn && ((...args) => console.warn(...args))) || (() => { });
73
71
 
74
72
  const observers = new Map();
75
73
  const observedElements = new Map();
@@ -314,12 +312,7 @@
314
312
 
315
313
  if (globalThis.FontFace) {
316
314
  const FontFace = globalThis.FontFace;
317
- let warningFontFaceDisplayed;
318
315
  globalThis.FontFace = function () {
319
- if (!warningFontFaceDisplayed) {
320
- warn("SingleFile is hooking the FontFace constructor, document.fonts.delete and document.fonts.clear to handle dynamically loaded fonts.");
321
- warningFontFaceDisplayed = true;
322
- }
323
316
  getDetailObject(...arguments).then(detail => dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail })));
324
317
  return new FontFace(...arguments);
325
318
  };
@@ -340,12 +333,7 @@
340
333
 
341
334
  if (globalThis.IntersectionObserver) {
342
335
  const IntersectionObserver = globalThis.IntersectionObserver;
343
- let warningIntersectionObserverDisplayed;
344
336
  globalThis.IntersectionObserver = function () {
345
- if (!warningIntersectionObserverDisplayed) {
346
- warn("SingleFile is hooking the IntersectionObserver API to detect and load deferred images.");
347
- warningIntersectionObserverDisplayed = true;
348
- }
349
337
  const intersectionObserver = new IntersectionObserver(...arguments);
350
338
  const observeIntersection = IntersectionObserver.prototype.observe || intersectionObserver.observe;
351
339
  const unobserveIntersection = IntersectionObserver.prototype.unobserve || intersectionObserver.unobserve;
@@ -100,9 +100,9 @@ const STAGES = [
100
100
  { action: "replaceInvalidElements" },
101
101
  { action: "resetCharsetMeta" },
102
102
  { option: "saveFavicon", action: "saveFavicon" },
103
- { action: "replaceCanvasElements" },
104
103
  { action: "insertFonts" },
105
104
  { action: "insertShadowRootContents" },
105
+ { action: "replaceCanvasElements" },
106
106
  { action: "setInputValues" },
107
107
  { option: "moveStylesInHead", action: "moveStylesInHead" },
108
108
  { option: "blockScripts", action: "removeEmbedScripts" },
@@ -517,21 +517,7 @@ class Processor {
517
517
  this.stats.set("processed", "HTML bytes", contentSize);
518
518
  this.stats.add("discarded", "HTML bytes", size - contentSize);
519
519
  }
520
- let filename = (await ProcessorHelper.evalTemplate(this.options.filenameTemplate, this.options, content)) || "";
521
- const replacementCharacter = this.options.filenameReplacementCharacter;
522
- filename = util.getValidFilename(filename, this.options.filenameReplacedCharacters, replacementCharacter);
523
- if (!this.options.backgroundSave) {
524
- filename = filename.replace(/\//g, replacementCharacter);
525
- }
526
- if (!this.options.keepFilename && ((this.options.filenameMaxLengthUnit == "bytes" && util.getContentSize(filename) > this.options.filenameMaxLength) || filename.length > this.options.filenameMaxLength)) {
527
- const extensionMatch = filename.match(/(\.[^.]{3,4})$/);
528
- const extension = extensionMatch && extensionMatch[0] && extensionMatch[0].length > 1 ? extensionMatch[0] : "";
529
- filename = this.options.filenameMaxLengthUnit == "bytes" ? await util.truncateText(filename, this.options.filenameMaxLength - extension.length) : filename.substring(0, this.options.filenameMaxLength - extension.length);
530
- filename = filename + "…" + extension;
531
- }
532
- if (!filename) {
533
- filename = "Unnamed page";
534
- }
520
+ const filename = await util.formatFilename(content, this.options);
535
521
  const matchTitle = this.baseURI.match(/([^/]*)\/?(\.html?.*)$/) || this.baseURI.match(/\/\/([^/]*)\/?$/);
536
522
  const pageData = {
537
523
  stats: this.stats.data,
@@ -634,6 +620,9 @@ class Processor {
634
620
  function hideNode(node) {
635
621
  if (canHideNode(node)) {
636
622
  node.style.setProperty("display", "none", "important");
623
+ node.removeAttribute("src");
624
+ node.removeAttribute("srcset");
625
+ node.removeAttribute("srcdoc");
637
626
  Array.from(node.childNodes).forEach(removeNode);
638
627
  }
639
628
  }
@@ -1479,7 +1468,7 @@ class Processor {
1479
1468
  publisher: publisherElement && publisherElement.content ? publisherElement.content.trim() : "",
1480
1469
  heading: headingElement && headingElement.textContent ? headingElement.textContent.trim() : ""
1481
1470
  };
1482
- this.options.infobarContent = await ProcessorHelper.evalTemplate(this.options.infobarTemplate, this.options, null, true);
1471
+ this.options.infobarContent = await util.evalTemplate(this.options.infobarTemplate, this.options, null, true);
1483
1472
  }
1484
1473
  }
1485
1474
 
@@ -1493,86 +1482,6 @@ const SINGLE_FILE_VARIABLE_NAME_PREFIX = "--sf-img-";
1493
1482
  const SINGLE_FILE_VARIABLE_MAX_SIZE = 512 * 1024;
1494
1483
 
1495
1484
  class ProcessorHelper {
1496
- static async evalTemplate(template = "", options, content, dontReplaceSlash) {
1497
- const url = util.parseURL(options.saveUrl);
1498
- template = await evalTemplateVariable(template, "page-title", () => options.title || "No title", dontReplaceSlash, options.filenameReplacementCharacter);
1499
- template = await evalTemplateVariable(template, "page-heading", () => options.info.heading || "No heading", dontReplaceSlash, options.filenameReplacementCharacter);
1500
- template = await evalTemplateVariable(template, "page-language", () => options.info.lang || "No language", dontReplaceSlash, options.filenameReplacementCharacter);
1501
- template = await evalTemplateVariable(template, "page-description", () => options.info.description || "No description", dontReplaceSlash, options.filenameReplacementCharacter);
1502
- template = await evalTemplateVariable(template, "page-author", () => options.info.author || "No author", dontReplaceSlash, options.filenameReplacementCharacter);
1503
- template = await evalTemplateVariable(template, "page-creator", () => options.info.creator || "No creator", dontReplaceSlash, options.filenameReplacementCharacter);
1504
- template = await evalTemplateVariable(template, "page-publisher", () => options.info.publisher || "No publisher", dontReplaceSlash, options.filenameReplacementCharacter);
1505
- await evalDate(options.saveDate);
1506
- await evalDate(options.visitDate, "visit-");
1507
- template = await evalTemplateVariable(template, "url-hash", () => url.hash.substring(1) || "No hash", dontReplaceSlash, options.filenameReplacementCharacter);
1508
- template = await evalTemplateVariable(template, "url-host", () => url.host.replace(/\/$/, "") || "No host", dontReplaceSlash, options.filenameReplacementCharacter);
1509
- template = await evalTemplateVariable(template, "url-hostname", () => url.hostname.replace(/\/$/, "") || "No hostname", dontReplaceSlash, options.filenameReplacementCharacter);
1510
- const urlHref = decode(url.href);
1511
- template = await evalTemplateVariable(template, "url-href", () => urlHref || "No href", dontReplaceSlash === undefined ? true : dontReplaceSlash, options.filenameReplacementCharacter);
1512
- template = await evalTemplateVariable(template, "url-href-digest-sha-1", urlHref ? async () => util.digest("SHA-1", urlHref) : "No href", dontReplaceSlash, options.filenameReplacementCharacter);
1513
- template = await evalTemplateVariable(template, "url-href-flat", () => decode(url.href) || "No href", false, options.filenameReplacementCharacter);
1514
- template = await evalTemplateVariable(template, "url-referrer", () => decode(options.referrer) || "No referrer", dontReplaceSlash === undefined ? true : dontReplaceSlash, options.filenameReplacementCharacter);
1515
- template = await evalTemplateVariable(template, "url-referrer-flat", () => decode(options.referrer) || "No referrer", false, options.filenameReplacementCharacter);
1516
- template = await evalTemplateVariable(template, "url-password", () => url.password || "No password", dontReplaceSlash, options.filenameReplacementCharacter);
1517
- template = await evalTemplateVariable(template, "url-pathname", () => decode(url.pathname).replace(/^\//, "").replace(/\/$/, "") || "No pathname", dontReplaceSlash === undefined ? true : dontReplaceSlash, options.filenameReplacementCharacter);
1518
- template = await evalTemplateVariable(template, "url-pathname-flat", () => decode(url.pathname) || "No pathname", false, options.filenameReplacementCharacter);
1519
- template = await evalTemplateVariable(template, "url-port", () => url.port || "No port", dontReplaceSlash, options.filenameReplacementCharacter);
1520
- template = await evalTemplateVariable(template, "url-protocol", () => url.protocol || "No protocol", dontReplaceSlash, options.filenameReplacementCharacter);
1521
- template = await evalTemplateVariable(template, "url-search", () => url.search.substring(1) || "No search", dontReplaceSlash, options.filenameReplacementCharacter);
1522
- const params = util.getSearchParams(url.search);
1523
- for (const [name, value] of params) {
1524
- template = await evalTemplateVariable(template, "url-search-" + name, () => value || "", dontReplaceSlash, options.filenameReplacementCharacter);
1525
- }
1526
- template = template.replace(/{\s*url-search-[^}\s]*\s*}/gi, "");
1527
- template = await evalTemplateVariable(template, "url-username", () => url.username || "No username", dontReplaceSlash, options.filenameReplacementCharacter);
1528
- template = await evalTemplateVariable(template, "tab-id", () => String(options.tabId || "No tab id"), dontReplaceSlash, options.filenameReplacementCharacter);
1529
- template = await evalTemplateVariable(template, "tab-index", () => String(options.tabIndex || "No tab index"), dontReplaceSlash, options.filenameReplacementCharacter);
1530
- template = await evalTemplateVariable(template, "url-last-segment", () => decode(getLastSegment(url, options.filenameReplacementCharacter)) || "No last segment", dontReplaceSlash, options.filenameReplacementCharacter);
1531
- if (content) {
1532
- template = await evalTemplateVariable(template, "digest-sha-256", async () => util.digest("SHA-256", content), dontReplaceSlash, options.filenameReplacementCharacter);
1533
- template = await evalTemplateVariable(template, "digest-sha-384", async () => util.digest("SHA-384", content), dontReplaceSlash, options.filenameReplacementCharacter);
1534
- template = await evalTemplateVariable(template, "digest-sha-512", async () => util.digest("SHA-512", content), dontReplaceSlash, options.filenameReplacementCharacter);
1535
- }
1536
- const bookmarkFolder = (options.bookmarkFolders && options.bookmarkFolders.join("/")) || "";
1537
- template = await evalTemplateVariable(template, "bookmark-pathname", () => bookmarkFolder, dontReplaceSlash === undefined ? true : dontReplaceSlash, options.filenameReplacementCharacter);
1538
- template = await evalTemplateVariable(template, "bookmark-pathname-flat", () => bookmarkFolder, false, options.filenameReplacementCharacter);
1539
- template = await evalTemplateVariable(template, "profile-name", () => options.profileName, dontReplaceSlash, options.filenameReplacementCharacter);
1540
- return template.trim();
1541
-
1542
- function decode(value) {
1543
- try {
1544
- return decodeURI(value);
1545
- } catch (error) {
1546
- return value;
1547
- }
1548
- }
1549
-
1550
- async function evalDate(date, prefix = "") {
1551
- if (date) {
1552
- template = await evalTemplateVariable(template, prefix + "datetime-iso", () => date.toISOString(), dontReplaceSlash, options.filenameReplacementCharacter);
1553
- template = await evalTemplateVariable(template, prefix + "date-iso", () => date.toISOString().split("T")[0], dontReplaceSlash, options.filenameReplacementCharacter);
1554
- template = await evalTemplateVariable(template, prefix + "time-iso", () => date.toISOString().split("T")[1].split("Z")[0], dontReplaceSlash, options.filenameReplacementCharacter);
1555
- template = await evalTemplateVariable(template, prefix + "date-locale", () => date.toLocaleDateString(), dontReplaceSlash, options.filenameReplacementCharacter);
1556
- template = await evalTemplateVariable(template, prefix + "time-locale", () => date.toLocaleTimeString(), dontReplaceSlash, options.filenameReplacementCharacter);
1557
- template = await evalTemplateVariable(template, prefix + "day-locale", () => String(date.getDate()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1558
- template = await evalTemplateVariable(template, prefix + "month-locale", () => String(date.getMonth() + 1).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1559
- template = await evalTemplateVariable(template, prefix + "year-locale", () => String(date.getFullYear()), dontReplaceSlash, options.filenameReplacementCharacter);
1560
- template = await evalTemplateVariable(template, prefix + "datetime-locale", () => date.toLocaleString(), dontReplaceSlash, options.filenameReplacementCharacter);
1561
- template = await evalTemplateVariable(template, prefix + "datetime-utc", () => date.toUTCString(), dontReplaceSlash, options.filenameReplacementCharacter);
1562
- template = await evalTemplateVariable(template, prefix + "day-utc", () => String(date.getUTCDate()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1563
- template = await evalTemplateVariable(template, prefix + "month-utc", () => String(date.getUTCMonth() + 1).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1564
- template = await evalTemplateVariable(template, prefix + "year-utc", () => String(date.getUTCFullYear()), dontReplaceSlash, options.filenameReplacementCharacter);
1565
- template = await evalTemplateVariable(template, prefix + "hours-locale", () => String(date.getHours()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1566
- template = await evalTemplateVariable(template, prefix + "minutes-locale", () => String(date.getMinutes()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1567
- template = await evalTemplateVariable(template, prefix + "seconds-locale", () => String(date.getSeconds()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1568
- template = await evalTemplateVariable(template, prefix + "hours-utc", () => String(date.getUTCHours()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1569
- template = await evalTemplateVariable(template, prefix + "minutes-utc", () => String(date.getUTCMinutes()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1570
- template = await evalTemplateVariable(template, prefix + "seconds-utc", () => String(date.getUTCSeconds()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
1571
- template = await evalTemplateVariable(template, prefix + "time-ms", () => String(date.getTime()), dontReplaceSlash, options.filenameReplacementCharacter);
1572
- }
1573
- }
1574
- }
1575
-
1576
1485
  static setBackgroundImage(element, url, style) {
1577
1486
  element.style.setProperty("background-blend-mode", "normal", "important");
1578
1487
  element.style.setProperty("background-clip", "content-box", "important");
@@ -2102,65 +2011,6 @@ function getOnEventAttributeNames(doc) {
2102
2011
  return attributeNames;
2103
2012
  }
2104
2013
 
2105
- async function evalTemplateVariable(template, variableName, valueGetter, dontReplaceSlash, replacementCharacter) {
2106
- let maxLength, maxCharLength;
2107
- if (template) {
2108
- const regExpVariable = "{\\s*" + variableName.replace(/\W|_/g, "[$&]") + "\\s*}";
2109
- let replaceRegExp = new RegExp(regExpVariable + "\\[\\d+(ch)?\\]", "g");
2110
- if (template.match(replaceRegExp)) {
2111
- const matchedLength = template.match(replaceRegExp)[0];
2112
- if (matchedLength.match(/\[(\d+)\]$/)) {
2113
- maxLength = Number(matchedLength.match(/\[(\d+)\]$/)[1]);
2114
- if (isNaN(maxLength) || maxLength <= 0) {
2115
- maxLength = null;
2116
- }
2117
- } else {
2118
- maxCharLength = Number(matchedLength.match(/\[(\d+)ch\]$/)[1]);
2119
- if (isNaN(maxCharLength) || maxCharLength <= 0) {
2120
- maxCharLength = null;
2121
- }
2122
- }
2123
- } else {
2124
- replaceRegExp = new RegExp(regExpVariable, "g");
2125
- }
2126
- if (template.match(replaceRegExp)) {
2127
- let value = await valueGetter();
2128
- if (!dontReplaceSlash) {
2129
- value = value.replace(/\/+/g, replacementCharacter);
2130
- }
2131
- if (maxLength) {
2132
- value = await util.truncateText(value, maxLength);
2133
- } else if (maxCharLength) {
2134
- value = value.substring(0, maxCharLength);
2135
- }
2136
- return template.replace(replaceRegExp, value);
2137
- }
2138
- }
2139
- return template;
2140
- }
2141
-
2142
- function getLastSegment(url, replacementCharacter) {
2143
- let lastSegmentMatch = url.pathname.match(/\/([^/]+)$/),
2144
- lastSegment = lastSegmentMatch && lastSegmentMatch[0];
2145
- if (!lastSegment) {
2146
- lastSegmentMatch = url.href.match(/([^/]+)\/?$/);
2147
- lastSegment = lastSegmentMatch && lastSegmentMatch[0];
2148
- }
2149
- if (!lastSegment) {
2150
- lastSegmentMatch = lastSegment.match(/(.*)\.[^.]+$/);
2151
- lastSegment = lastSegmentMatch && lastSegmentMatch[0];
2152
- }
2153
- if (!lastSegment) {
2154
- lastSegment = url.hostname.replace(/\/+/g, replacementCharacter).replace(/\/$/, "");
2155
- }
2156
- lastSegmentMatch = lastSegment.match(/(.*)\.[^.]+$/);
2157
- if (lastSegmentMatch && lastSegmentMatch[1]) {
2158
- lastSegment = lastSegmentMatch[1];
2159
- }
2160
- lastSegment = lastSegment.replace(/\/$/, "").replace(/^\//, "");
2161
- return lastSegment;
2162
- }
2163
-
2164
2014
  function getUrlFunctions(declarationList) {
2165
2015
  return cssTree.findAll(declarationList, node => node.type == "Url");
2166
2016
  }
@@ -183,70 +183,72 @@ function preProcessDoc(doc, win, options) {
183
183
  }
184
184
 
185
185
  function getElementsInfo(win, doc, element, options, data = { usedFonts: new Map(), canvases: [], images: [], posters: [], videos: [], shadowRoots: [], markedElements: [] }, ascendantHidden) {
186
- const elements = Array.from(element.childNodes).filter(node => (node instanceof win.HTMLElement) || (node instanceof win.SVGElement));
187
- elements.forEach(element => {
188
- let elementHidden, elementKept, computedStyle;
189
- if (!options.autoSaveExternalSave && (options.removeHiddenElements || options.removeUnusedFonts || options.compressHTML)) {
190
- computedStyle = getComputedStyle(win, element);
191
- if (element instanceof win.HTMLElement) {
192
- if (options.removeHiddenElements) {
193
- elementKept = ((ascendantHidden || element.closest("html > head")) && KEPT_TAG_NAMES.includes(element.tagName)) || element.closest("details");
194
- if (!elementKept) {
195
- elementHidden = ascendantHidden || testHiddenElement(element, computedStyle);
196
- if (elementHidden) {
197
- element.setAttribute(HIDDEN_CONTENT_ATTRIBUTE_NAME, "");
198
- data.markedElements.push(element);
186
+ if (element.childNodes) {
187
+ const elements = Array.from(element.childNodes).filter(node => (node instanceof win.HTMLElement) || (node instanceof win.SVGElement));
188
+ elements.forEach(element => {
189
+ let elementHidden, elementKept, computedStyle;
190
+ if (!options.autoSaveExternalSave && (options.removeHiddenElements || options.removeUnusedFonts || options.compressHTML)) {
191
+ computedStyle = getComputedStyle(win, element);
192
+ if (element instanceof win.HTMLElement) {
193
+ if (options.removeHiddenElements) {
194
+ elementKept = ((ascendantHidden || element.closest("html > head")) && KEPT_TAG_NAMES.includes(element.tagName)) || element.closest("details");
195
+ if (!elementKept) {
196
+ elementHidden = ascendantHidden || testHiddenElement(element, computedStyle);
197
+ if (elementHidden) {
198
+ element.setAttribute(HIDDEN_CONTENT_ATTRIBUTE_NAME, "");
199
+ data.markedElements.push(element);
200
+ }
199
201
  }
200
202
  }
201
203
  }
202
- }
203
- if (!elementHidden) {
204
- if (options.compressHTML && computedStyle) {
205
- const whiteSpace = computedStyle.getPropertyValue("white-space");
206
- if (whiteSpace && whiteSpace.startsWith("pre")) {
207
- element.setAttribute(PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, "");
208
- data.markedElements.push(element);
204
+ if (!elementHidden) {
205
+ if (options.compressHTML && computedStyle) {
206
+ const whiteSpace = computedStyle.getPropertyValue("white-space");
207
+ if (whiteSpace && whiteSpace.startsWith("pre")) {
208
+ element.setAttribute(PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, "");
209
+ data.markedElements.push(element);
210
+ }
211
+ }
212
+ if (options.removeUnusedFonts) {
213
+ getUsedFont(computedStyle, options, data.usedFonts);
214
+ getUsedFont(getComputedStyle(win, element, ":first-letter"), options, data.usedFonts);
215
+ getUsedFont(getComputedStyle(win, element, ":before"), options, data.usedFonts);
216
+ getUsedFont(getComputedStyle(win, element, ":after"), options, data.usedFonts);
209
217
  }
210
- }
211
- if (options.removeUnusedFonts) {
212
- getUsedFont(computedStyle, options, data.usedFonts);
213
- getUsedFont(getComputedStyle(win, element, ":first-letter"), options, data.usedFonts);
214
- getUsedFont(getComputedStyle(win, element, ":before"), options, data.usedFonts);
215
- getUsedFont(getComputedStyle(win, element, ":after"), options, data.usedFonts);
216
218
  }
217
219
  }
218
- }
219
- getResourcesInfo(win, doc, element, options, data, elementHidden, computedStyle);
220
- const shadowRoot = !(element instanceof win.SVGElement) && getShadowRoot(element);
221
- if (shadowRoot && !element.classList.contains(SINGLE_FILE_UI_ELEMENT_CLASS)) {
222
- const shadowRootInfo = {};
223
- element.setAttribute(SHADOW_ROOT_ATTRIBUTE_NAME, data.shadowRoots.length);
224
- data.markedElements.push(element);
225
- data.shadowRoots.push(shadowRootInfo);
226
- getElementsInfo(win, doc, shadowRoot, options, data, elementHidden);
227
- shadowRootInfo.content = shadowRoot.innerHTML;
228
- shadowRootInfo.mode = shadowRoot.mode;
229
- try {
230
- if (shadowRoot.adoptedStyleSheets && shadowRoot.adoptedStyleSheets.length) {
231
- shadowRootInfo.adoptedStyleSheets = Array.from(shadowRoot.adoptedStyleSheets).map(stylesheet => Array.from(stylesheet.cssRules).map(cssRule => cssRule.cssText).join("\n"));
220
+ getResourcesInfo(win, doc, element, options, data, elementHidden, computedStyle);
221
+ const shadowRoot = !(element instanceof win.SVGElement) && getShadowRoot(element);
222
+ if (shadowRoot && !element.classList.contains(SINGLE_FILE_UI_ELEMENT_CLASS)) {
223
+ const shadowRootInfo = {};
224
+ element.setAttribute(SHADOW_ROOT_ATTRIBUTE_NAME, data.shadowRoots.length);
225
+ data.markedElements.push(element);
226
+ data.shadowRoots.push(shadowRootInfo);
227
+ getElementsInfo(win, doc, shadowRoot, options, data, elementHidden);
228
+ shadowRootInfo.content = shadowRoot.innerHTML;
229
+ shadowRootInfo.mode = shadowRoot.mode;
230
+ try {
231
+ if (shadowRoot.adoptedStyleSheets && shadowRoot.adoptedStyleSheets.length) {
232
+ shadowRootInfo.adoptedStyleSheets = Array.from(shadowRoot.adoptedStyleSheets).map(stylesheet => Array.from(stylesheet.cssRules).map(cssRule => cssRule.cssText).join("\n"));
233
+ }
234
+ } catch (error) {
235
+ // ignored
232
236
  }
233
- } catch (error) {
234
- // ignored
235
237
  }
236
- }
237
- getElementsInfo(win, doc, element, options, data, elementHidden);
238
- if (!options.autoSaveExternalSave && options.removeHiddenElements && ascendantHidden) {
239
- if (elementKept || element.getAttribute(KEPT_CONTENT_ATTRIBUTE_NAME) == "") {
240
- if (element.parentElement) {
241
- element.parentElement.setAttribute(KEPT_CONTENT_ATTRIBUTE_NAME, "");
242
- data.markedElements.push(element.parentElement);
238
+ getElementsInfo(win, doc, element, options, data, elementHidden);
239
+ if (!options.autoSaveExternalSave && options.removeHiddenElements && ascendantHidden) {
240
+ if (elementKept || element.getAttribute(KEPT_CONTENT_ATTRIBUTE_NAME) == "") {
241
+ if (element.parentElement) {
242
+ element.parentElement.setAttribute(KEPT_CONTENT_ATTRIBUTE_NAME, "");
243
+ data.markedElements.push(element.parentElement);
244
+ }
245
+ } else if (elementHidden) {
246
+ element.setAttribute(REMOVED_CONTENT_ATTRIBUTE_NAME, "");
247
+ data.markedElements.push(element);
243
248
  }
244
- } else if (elementHidden) {
245
- element.setAttribute(REMOVED_CONTENT_ATTRIBUTE_NAME, "");
246
- data.markedElements.push(element);
247
249
  }
248
- }
249
- });
250
+ });
251
+ }
250
252
  return data;
251
253
  }
252
254
 
@@ -115,20 +115,11 @@ function getInstance(utilOptions) {
115
115
  getContentSize(content) {
116
116
  return new Blob([content]).size;
117
117
  },
118
- truncateText(content, maxSize) {
119
- const blob = new Blob([content]);
120
- const reader = new FileReader();
121
- reader.readAsText(blob.slice(0, maxSize));
122
- return new Promise((resolve, reject) => {
123
- reader.addEventListener("load", () => {
124
- if (content.startsWith(reader.result)) {
125
- resolve(reader.result);
126
- } else {
127
- this.truncateText(content, maxSize - 1).then(resolve).catch(reject);
128
- }
129
- }, false);
130
- reader.addEventListener("error", reject, false);
131
- });
118
+ formatFilename(content, options) {
119
+ return modules.templateFormatter.formatFilename(content, options, this);
120
+ },
121
+ evalTemplate(template, options, content, dontReplaceSlash) {
122
+ return modules.templateFormatter.evalTemplate(template, options, this, content, dontReplaceSlash);
132
123
  },
133
124
  minifyHTML(doc, options) {
134
125
  return modules.htmlMinifier.process(doc, options);