single-file-core 1.0.45 → 1.0.47

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.45",
3
+ "version": "1.0.47",
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;
@@ -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,
@@ -1479,7 +1465,7 @@ class Processor {
1479
1465
  publisher: publisherElement && publisherElement.content ? publisherElement.content.trim() : "",
1480
1466
  heading: headingElement && headingElement.textContent ? headingElement.textContent.trim() : ""
1481
1467
  };
1482
- this.options.infobarContent = await ProcessorHelper.evalTemplate(this.options.infobarTemplate, this.options, null, true);
1468
+ this.options.infobarContent = await util.evalTemplate(this.options.infobarTemplate, this.options, null, true);
1483
1469
  }
1484
1470
  }
1485
1471
 
@@ -1493,86 +1479,6 @@ const SINGLE_FILE_VARIABLE_NAME_PREFIX = "--sf-img-";
1493
1479
  const SINGLE_FILE_VARIABLE_MAX_SIZE = 512 * 1024;
1494
1480
 
1495
1481
  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
1482
  static setBackgroundImage(element, url, style) {
1577
1483
  element.style.setProperty("background-blend-mode", "normal", "important");
1578
1484
  element.style.setProperty("background-clip", "content-box", "important");
@@ -2102,65 +2008,6 @@ function getOnEventAttributeNames(doc) {
2102
2008
  return attributeNames;
2103
2009
  }
2104
2010
 
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
2011
  function getUrlFunctions(declarationList) {
2165
2012
  return cssTree.findAll(declarationList, node => node.type == "Url");
2166
2013
  }
@@ -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);
@@ -325,6 +316,7 @@ async function getFetchResponse(resourceURL, options, data, charset, contentType
325
316
  charset = "utf-8";
326
317
  data = new TextDecoder(charset).decode(data);
327
318
  }
319
+ data = data.replace(/\ufeff/gi, "");
328
320
  }
329
321
  } else {
330
322
  data = options.asBinary ? helper.EMPTY_RESOURCE : "";