single-file-core 1.5.129 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core/helper.js CHANGED
@@ -82,6 +82,15 @@ const DEFAULT_REPLACEMENT_CHARACTER = "_";
82
82
  const DEFAULT_REPLACEMENT_CHARACTERS = ["~", "+", "?", "%", "*", ":", "|", """, "<", ">", "\"];
83
83
  const CHARACTER_CLASS_SPECIAL_CHARACTERS = ["[", "]", "^", "-", "\\"];
84
84
  const NESTING_TRACK_ID_ATTRIBUTE_NAME = "data-sf-nesting-track-id";
85
+ const DEPRECATED_OPTION_NAMES = {
86
+ loadDeferredImages: "loadDeferredContent",
87
+ loadDeferredImagesMaxIdleTime: "loadDeferredContentMaxIdleTime",
88
+ loadDeferredImagesBlockCookies: "loadDeferredContentBlockCookies",
89
+ loadDeferredImagesBlockStorage: "loadDeferredContentBlockStorage",
90
+ loadDeferredImagesKeepZoomLevel: "loadDeferredContentKeepZoomLevel",
91
+ loadDeferredImagesDispatchScrollEvent: "loadDeferredContentDispatchScrollEvent",
92
+ loadDeferredImagesBeforeFrames: "loadDeferredContentBeforeFrames"
93
+ };
85
94
  const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
86
95
  const removeEventListener = (type, listener, options) => globalThis.removeEventListener(type, listener, options);
87
96
  // eslint-disable-next-line no-unused-vars
@@ -117,6 +126,7 @@ export {
117
126
  parseDocContent,
118
127
  markInvalidNesting,
119
128
  fixInvalidNesting,
129
+ normalizeOptions,
120
130
  ON_BEFORE_CAPTURE_EVENT_NAME,
121
131
  ON_AFTER_CAPTURE_EVENT_NAME,
122
132
  WIN_ID_ATTRIBUTE_NAME,
@@ -153,6 +163,22 @@ export {
153
163
  // lossy format: the same frame costs an order of magnitude less than as a PNG.
154
164
  // toDataURL falls back to PNG without reporting it when it does not support the
155
165
  // format, so the type it returned is tested instead of assumed
166
+ function normalizeOptions(options) {
167
+ let normalizedOptions = options;
168
+ if (options) {
169
+ Object.keys(DEPRECATED_OPTION_NAMES).forEach(deprecatedName => {
170
+ const optionName = DEPRECATED_OPTION_NAMES[deprecatedName];
171
+ if (options[deprecatedName] !== undefined && options[optionName] === undefined) {
172
+ if (normalizedOptions == options) {
173
+ normalizedOptions = Object.assign({}, options);
174
+ }
175
+ normalizedOptions[optionName] = options[deprecatedName];
176
+ }
177
+ });
178
+ }
179
+ return normalizedOptions;
180
+ }
181
+
156
182
  function getPosterDataURI(canvasElement) {
157
183
  for (const contentType of POSTER_CONTENT_TYPES) {
158
184
  const dataURI = canvasElement.toDataURL(contentType, POSTER_QUALITY);
@@ -535,7 +561,7 @@ function getResourcesInfo(win, doc, element, options, data, elementHidden, compu
535
561
  const imageData = {
536
562
  currentSrc: elementHidden ?
537
563
  EMPTY_RESOURCE :
538
- (options.loadDeferredImages && element.getAttribute(LAZY_SRC_ATTRIBUTE_NAME)) || element.currentSrc
564
+ (options.loadDeferredContent && element.getAttribute(LAZY_SRC_ATTRIBUTE_NAME)) || element.currentSrc
539
565
  };
540
566
  data.images.push(imageData);
541
567
  element.setAttribute(IMAGE_ATTRIBUTE_NAME, data.images.length - 1);
package/core/index.js CHANGED
@@ -31,6 +31,7 @@ const DEBUG = false;
31
31
  const Set = globalThis.Set;
32
32
  const Map = globalThis.Map;
33
33
  const JSON = globalThis.JSON;
34
+ const URL = globalThis.URL;
34
35
 
35
36
  let util;
36
37
 
@@ -102,7 +103,7 @@ const FINALIZE_STAGE = 4;
102
103
  const STAGES = [{
103
104
  sequential: [
104
105
  { action: "preProcessPage" },
105
- { option: "loadDeferredImagesKeepZoomLevel", action: "resetZoomLevel" },
106
+ { option: "loadDeferredContentKeepZoomLevel", action: "resetZoomLevel" },
106
107
  { action: "replaceStyleContents" },
107
108
  { action: "replaceInvalidElements" },
108
109
  { action: "resetCharsetMeta" },
@@ -160,7 +161,7 @@ const STAGES = [{
160
161
  { action: "cleanupPage" }
161
162
  ],
162
163
  parallel: [
163
- { option: "enableMaff", action: "insertMAFFMetaData" },
164
+ { option: "readMaffMetadata", action: "readMAFFMetaData" },
164
165
  { action: "setDocInfo" }
165
166
  ]
166
167
  }, {
@@ -421,6 +422,9 @@ const SHADOWROOT_DELEGATES_FOCUS = "shadowrootdelegatesfocus";
421
422
  const SHADOWROOT_CLONABLE = "shadowrootclonable";
422
423
  const SHADOWROOT_SERIALIZABLE = "shadowrootserializable";
423
424
  const SCRIPT_OPTIONS = "data-single-file-options";
425
+ const JAVASCRIPT_URI_PROTOCOL = "javascript:";
426
+ const DISABLED_SCRIPT_URI = "javascript:void(0)";
427
+ const SCRIPT_URI_ATTRIBUTE_NAMES = ["href", "src", "action", "formaction", "data"];
424
428
  const UTF8_CHARSET = "utf-8";
425
429
  const TAINTED_CANVAS_WARNING_MESSAGE = "SingleFile: canvas elements tainted by a cross-origin resource, dropped from the page:";
426
430
 
@@ -448,7 +452,7 @@ class Processor {
448
452
  initialize() {
449
453
  this.options.saveDate = new Date();
450
454
  this.options.saveUrl = this.options.url;
451
- if (this.options.enableMaff) {
455
+ if (this.options.readMaffMetadata) {
452
456
  this.maffMetaDataPromise = this.batchRequest.addURL(util.resolveURL("index.rdf", this.options.baseURI || this.options.url), { expectedType: "document" });
453
457
  }
454
458
  this.maxResources = this.batchRequest.getMaxResources();
@@ -497,7 +501,7 @@ class Processor {
497
501
  }
498
502
  this.workStyleElement = this.doc.createElement("style");
499
503
  this.doc.body.appendChild(this.workStyleElement);
500
- this.onEventAttributeNames = getOnEventAttributeNames(this.doc);
504
+ this.onEventAttributeNames = new Set(getOnEventAttributeNames(this.doc));
501
505
  }
502
506
 
503
507
  finalize() {
@@ -637,7 +641,7 @@ class Processor {
637
641
  imgElement.dataset.singleFileOriginURL = imgElement.getAttribute("src");
638
642
  imgElement.setAttribute("src", imageData.currentSrc);
639
643
  }
640
- if (this.options.loadDeferredImages) {
644
+ if (this.options.loadDeferredContent) {
641
645
  if ((!imgElement.getAttribute("src") || imgElement.getAttribute("src") == util.EMPTY_RESOURCE) && imgElement.getAttribute("data-src")) {
642
646
  imageData.src = imgElement.dataset.src;
643
647
  imgElement.setAttribute("src", imgElement.dataset.src);
@@ -648,7 +652,7 @@ class Processor {
648
652
  }
649
653
  }
650
654
  });
651
- if (this.options.loadDeferredImages) {
655
+ if (this.options.loadDeferredContent) {
652
656
  this.doc.querySelectorAll("img[data-srcset]").forEach(imgElement => {
653
657
  if (!imgElement.getAttribute("srcset") && imgElement.getAttribute("data-srcset")) {
654
658
  imgElement.setAttribute("srcset", imgElement.dataset.srcset);
@@ -676,7 +680,7 @@ class Processor {
676
680
  optionsElement.type = "application/json";
677
681
  optionsElement.setAttribute(SCRIPT_OPTIONS, "");
678
682
  optionsElement.textContent = JSON.stringify({
679
- saveUrl: this.options.url,
683
+ saveUrl: this.options.saveUrl,
680
684
  saveDate: this.options.saveDate.getTime(),
681
685
  visitDate: (this.options.visitDate || this.options.saveDate).getTime(),
682
686
  filenameTemplate: this.options.filenameTemplate,
@@ -852,18 +856,16 @@ class Processor {
852
856
  }
853
857
 
854
858
  removeEmbedScripts() {
855
- const JAVASCRIPT_URI_PREFIX = "javascript:";
856
- const DISABLED_SCRIPT = "javascript:void(0)";
857
- this.onEventAttributeNames.forEach(attributeName => this.doc.querySelectorAll("[" + attributeName + "]").forEach(element => element.removeAttribute(attributeName)));
858
- this.doc.querySelectorAll("[href]").forEach(element => {
859
- if (element.href && element.href.match && element.href.trim().startsWith(JAVASCRIPT_URI_PREFIX)) {
860
- element.setAttribute("href", DISABLED_SCRIPT);
861
- }
862
- });
863
- this.doc.querySelectorAll("[src]").forEach(element => {
864
- if (element.src && element.src.trim().startsWith(JAVASCRIPT_URI_PREFIX)) {
865
- element.setAttribute("src", DISABLED_SCRIPT);
866
- }
859
+ this.doc.querySelectorAll("*").forEach(element => {
860
+ Array.from(element.attributes).forEach(attribute => {
861
+ const localName = attribute.localName || attribute.name;
862
+ const attributeName = localName.toLowerCase();
863
+ if (this.onEventAttributeNames.has(attributeName)) {
864
+ element.removeAttributeNS(attribute.namespaceURI, localName);
865
+ } else if (SCRIPT_URI_ATTRIBUTE_NAMES.includes(attributeName) && isScriptURI(attribute.value)) {
866
+ element.setAttributeNS(attribute.namespaceURI, attribute.name, DISABLED_SCRIPT_URI);
867
+ }
868
+ });
867
869
  });
868
870
  const scriptElements = this.doc.querySelectorAll("script:not([type=\"application/ld+json\"]):not([" + SCRIPT_OPTIONS + "])");
869
871
  this.stats.set("discarded", "scripts", scriptElements.length);
@@ -1647,7 +1649,7 @@ class Processor {
1647
1649
  this.doc.documentElement.style.removeProperty("-sf-min-height");
1648
1650
  }
1649
1651
 
1650
- async insertMAFFMetaData() {
1652
+ async readMAFFMetaData() {
1651
1653
  const maffMetaData = await this.maffMetaDataPromise;
1652
1654
  if (maffMetaData && maffMetaData.content) {
1653
1655
  const NAMESPACE_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
@@ -1655,7 +1657,10 @@ class Processor {
1655
1657
  const originalURLElement = maffDoc.querySelector("RDF > Description > originalurl");
1656
1658
  const archiveTimeElement = maffDoc.querySelector("RDF > Description > archivetime");
1657
1659
  if (originalURLElement) {
1658
- this.options.saveUrl = originalURLElement.getAttributeNS(NAMESPACE_RDF, "resource");
1660
+ const value = originalURLElement.getAttributeNS(NAMESPACE_RDF, "resource");
1661
+ if (value) {
1662
+ this.options.saveUrl = value;
1663
+ }
1659
1664
  }
1660
1665
  if (archiveTimeElement) {
1661
1666
  const value = archiveTimeElement.getAttributeNS(NAMESPACE_RDF, "resource");
@@ -1709,6 +1714,14 @@ function normalizeURL(url) {
1709
1714
  }
1710
1715
  }
1711
1716
 
1717
+ function isScriptURI(value) {
1718
+ try {
1719
+ return new URL(value).protocol == JAVASCRIPT_URI_PROTOCOL;
1720
+ } catch {
1721
+ return false;
1722
+ }
1723
+ }
1724
+
1712
1725
  function getOnEventAttributeNames(doc) {
1713
1726
  const element = doc.body || doc.createElement("div");
1714
1727
  const attributeNames = [];
package/core/infobar.js CHANGED
@@ -141,28 +141,28 @@ const INFOBAR_STYLES = `
141
141
 
142
142
  @keyframes flash {
143
143
  0%, 100% {
144
- background-color: #737373;
144
+ background-color: #737373;
145
145
  }
146
146
  50% {
147
- background-color: #dd6a00;
147
+ background-color: #dd6a00;
148
148
  }
149
149
  }
150
150
 
151
151
  @keyframes ripple {
152
152
  0% {
153
- transform: scale(1);
154
- opacity: 1;
153
+ transform: scale(1);
154
+ opacity: 1;
155
155
  }
156
156
  45%, 100% {
157
- transform: scale(2);
158
- opacity: 0;
157
+ transform: scale(2);
158
+ opacity: 0;
159
159
  }
160
160
  }
161
161
 
162
162
  @media (prefers-reduced-motion: reduce) {
163
163
  .infobar,
164
164
  .infobar:not(:focus-within):not(.infobar-focus)::after {
165
- animation-name: none;
165
+ animation-name: none;
166
166
  }
167
167
  }
168
168
 
@@ -562,7 +562,7 @@ function getProcessorHelperClass(utilInstance) {
562
562
  }
563
563
 
564
564
  setMetaCSP(metaElement) {
565
- metaElement.content = "default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:; object-src 'self' data:; frame-src 'self' data:;";
565
+ metaElement.content = "default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:; object-src 'self' data:; frame-src 'self' data:; form-action 'none'; base-uri 'none';";
566
566
  }
567
567
 
568
568
  removeUnusedStylesheets(doc) {
@@ -488,7 +488,7 @@ function getProcessorHelperClass(utilInstance) {
488
488
  }
489
489
 
490
490
  setMetaCSP(metaElement) {
491
- metaElement.content = "default-src 'none'; connect-src 'self' data: blob:; font-src 'self' data: blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline' data: blob:; frame-src 'self' data: blob:; media-src 'self' data: blob:; script-src 'self' 'unsafe-inline' data: blob:; object-src 'self' data: blob:;";
491
+ metaElement.content = "default-src 'none'; connect-src 'self' data: blob:; font-src 'self' data: blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline' data: blob:; frame-src 'self' data: blob:; media-src 'self' data: blob:; script-src 'self' 'unsafe-inline' data: blob:; object-src 'self' data: blob:; form-action 'none'; base-uri 'none';";
492
492
  }
493
493
 
494
494
  removeUnusedStylesheets() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.5.129",
3
+ "version": "1.6.0",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -132,7 +132,8 @@ export {
132
132
  process,
133
133
  createArchive,
134
134
  escapeHTML,
135
- PROCESS_OPTION_NAMES
135
+ PROCESS_OPTION_NAMES,
136
+ DEFAULT_MAX_APPENDED_DATA_LENGTH
136
137
  };
137
138
 
138
139
  async function process(pageData, options, lastModDate = new Date()) {
@@ -114,7 +114,7 @@ async function onMessage(event) {
114
114
  }
115
115
  if (!TOP_WINDOW) {
116
116
  globalThis.stop();
117
- if (message.options.loadDeferredImages) {
117
+ if (message.options.loadDeferredContent) {
118
118
  lazy.process(message.options);
119
119
  }
120
120
  await initRequestAsync(message);
@@ -23,12 +23,12 @@
23
23
 
24
24
  (() => {
25
25
 
26
- const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
27
- const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
28
- const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-images-keep-zoom-level-start";
29
- const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-end";
30
- const LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT = "single-file-load-deferred-images-keep-zoom-level-reset";
31
- const LOAD_DEFERRED_IMAGES_RESET_EVENT = "single-file-load-deferred-images-reset";
26
+ const LOAD_DEFERRED_CONTENT_START_EVENT = "single-file-load-deferred-content-start";
27
+ const LOAD_DEFERRED_CONTENT_END_EVENT = "single-file-load-deferred-content-end";
28
+ const LOAD_DEFERRED_CONTENT_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-content-keep-zoom-level-start";
29
+ const LOAD_DEFERRED_CONTENT_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-content-keep-zoom-level-end";
30
+ const LOAD_DEFERRED_CONTENT_RESET_ZOOM_LEVEL_EVENT = "single-file-load-deferred-content-keep-zoom-level-reset";
31
+ const LOAD_DEFERRED_CONTENT_RESET_EVENT = "single-file-load-deferred-content-reset";
32
32
  const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
33
33
  const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
34
34
  const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
@@ -89,12 +89,12 @@
89
89
  new MutationObserver(init).observe(document, { childList: true });
90
90
 
91
91
  function init() {
92
- document.addEventListener(LOAD_DEFERRED_IMAGES_START_EVENT, onLoadDeferredImagesStart);
93
- document.addEventListener(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT, onLoadDeferredImagesKeepZoomLevelStart);
94
- document.addEventListener(LOAD_DEFERRED_IMAGES_END_EVENT, onLoadDeferredImagesEnd);
95
- document.addEventListener(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT, onLoadDeferredImagesKeepZoomLevelEnd);
96
- document.addEventListener(LOAD_DEFERRED_IMAGES_RESET_EVENT, resetScreenSize);
97
- document.addEventListener(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT, onLoadDeferredImagesResetZoomLevel);
92
+ document.addEventListener(LOAD_DEFERRED_CONTENT_START_EVENT, onLoadDeferredContentStart);
93
+ document.addEventListener(LOAD_DEFERRED_CONTENT_KEEP_ZOOM_LEVEL_START_EVENT, onLoadDeferredContentKeepZoomLevelStart);
94
+ document.addEventListener(LOAD_DEFERRED_CONTENT_END_EVENT, onLoadDeferredContentEnd);
95
+ document.addEventListener(LOAD_DEFERRED_CONTENT_KEEP_ZOOM_LEVEL_END_EVENT, onLoadDeferredContentKeepZoomLevelEnd);
96
+ document.addEventListener(LOAD_DEFERRED_CONTENT_RESET_EVENT, resetScreenSize);
97
+ document.addEventListener(LOAD_DEFERRED_CONTENT_RESET_ZOOM_LEVEL_EVENT, onLoadDeferredContentResetZoomLevel);
98
98
  document.addEventListener(DISPATCH_SCROLL_START_EVENT, onDispatchScrollStart);
99
99
  document.addEventListener(DISPATCH_SCROLL_END_EVENT, onDispatchScrollEnd);
100
100
  document.addEventListener(BLOCK_COOKIES_START_EVENT, onBlockCookiesStart);
@@ -107,23 +107,33 @@
107
107
  document.addEventListener(BOOTSTRAP_EVENT, onBootstrap);
108
108
  }
109
109
 
110
- function onLoadDeferredImagesStart() {
111
- loadDeferredImagesStart();
110
+ function onLoadDeferredContentStart(event) {
111
+ loadDeferredContentStart(false, getMinZoomFactor(event));
112
112
  }
113
113
 
114
- function onLoadDeferredImagesKeepZoomLevelStart() {
115
- loadDeferredImagesStart(true);
114
+ function onLoadDeferredContentKeepZoomLevelStart(event) {
115
+ loadDeferredContentStart(true, getMinZoomFactor(event));
116
116
  }
117
117
 
118
- function onLoadDeferredImagesEnd() {
119
- loadDeferredImagesEnd();
118
+ function getMinZoomFactor(event) {
119
+ try {
120
+ const { minZoomFactor } = JSON.parse(event.detail);
121
+ return minZoomFactor > 0 && minZoomFactor <= 1 ? minZoomFactor : 0;
122
+ // eslint-disable-next-line no-unused-vars
123
+ } catch (error) {
124
+ return 0;
125
+ }
126
+ }
127
+
128
+ function onLoadDeferredContentEnd() {
129
+ loadDeferredContentEnd();
120
130
  }
121
131
 
122
- function onLoadDeferredImagesKeepZoomLevelEnd() {
123
- loadDeferredImagesEnd(true);
132
+ function onLoadDeferredContentKeepZoomLevelEnd() {
133
+ loadDeferredContentEnd(true);
124
134
  }
125
135
 
126
- function onLoadDeferredImagesResetZoomLevel() {
136
+ function onLoadDeferredContentResetZoomLevel() {
127
137
  const transform = document.documentElement.style.getPropertyValue("-sf-transform");
128
138
  const transformPriority = document.documentElement.style.getPropertyPriority("-sf-transform");
129
139
  const transformOrigin = document.documentElement.style.getPropertyValue("-sf-transform-origin");
@@ -211,34 +221,34 @@
211
221
  }
212
222
  }
213
223
 
214
- function loadDeferredImagesStart(keepZoomLevel) {
224
+ function loadDeferredContentStart(keepZoomLevel, minZoomFactor) {
215
225
  const scrollingElement = document.scrollingElement || document.documentElement;
216
226
  const clientHeight = scrollingElement.clientHeight;
217
227
  const clientWidth = scrollingElement.clientWidth;
218
- const scrollHeight = Math.max(scrollingElement.scrollHeight - clientHeight, clientHeight);
219
- const scrollWidth = Math.max(scrollingElement.scrollWidth - clientWidth, clientWidth);
228
+ const maxScrollY = Math.max(scrollingElement.scrollHeight - clientHeight, clientHeight);
229
+ const maxScrollX = Math.max(scrollingElement.scrollWidth - clientWidth, clientWidth);
220
230
  document.querySelectorAll("[loading=lazy]").forEach(element => {
221
231
  element.loading = "eager";
222
232
  element.setAttribute(LAZY_LOAD_ATTRIBUTE, "");
223
233
  });
224
- scrollingElement.__defineGetter__("clientHeight", () => scrollHeight);
225
- scrollingElement.__defineGetter__("clientWidth", () => scrollWidth);
226
- screen.__defineGetter__("height", () => scrollHeight);
227
- screen.__defineGetter__("width", () => scrollWidth);
234
+ scrollingElement.__defineGetter__("clientHeight", () => maxScrollY);
235
+ scrollingElement.__defineGetter__("clientWidth", () => maxScrollX);
236
+ screen.__defineGetter__("height", () => maxScrollY);
237
+ screen.__defineGetter__("width", () => maxScrollX);
228
238
  globalThis._singleFile_innerHeight = globalThis.innerHeight;
229
239
  globalThis._singleFile_innerWidth = globalThis.innerWidth;
230
- globalThis.__defineGetter__("innerHeight", () => scrollHeight);
231
- globalThis.__defineGetter__("innerWidth", () => scrollWidth);
240
+ globalThis.__defineGetter__("innerHeight", () => maxScrollY);
241
+ globalThis.__defineGetter__("innerWidth", () => maxScrollX);
232
242
  if (!keepZoomLevel) {
233
243
  if (!globalThis._singleFile_getBoundingClientRect) {
234
244
  globalThis._singleFile_getBoundingClientRect = Element.prototype.getBoundingClientRect;
235
245
  Element.prototype.getBoundingClientRect = function () {
236
246
  const boundingRect = globalThis._singleFile_getBoundingClientRect.call(this);
237
247
  if (this == scrollingElement) {
238
- boundingRect.__defineGetter__("height", () => scrollHeight);
239
- boundingRect.__defineGetter__("bottom", () => scrollHeight + boundingRect.top);
240
- boundingRect.__defineGetter__("width", () => scrollWidth);
241
- boundingRect.__defineGetter__("right", () => scrollWidth + boundingRect.left);
248
+ boundingRect.__defineGetter__("height", () => maxScrollY);
249
+ boundingRect.__defineGetter__("bottom", () => maxScrollY + boundingRect.top);
250
+ boundingRect.__defineGetter__("width", () => maxScrollX);
251
+ boundingRect.__defineGetter__("right", () => maxScrollX + boundingRect.left);
242
252
  }
243
253
  return boundingRect;
244
254
  };
@@ -283,9 +293,9 @@
283
293
  setFunctionName(ImageWrapper, "Image");
284
294
  globalThis.__defineGetter__("Image", () => ImageWrapper);
285
295
  }
286
- const verticalZoomFactor = clientHeight / scrollHeight;
287
- const horizontalZoomFactor = clientWidth / scrollWidth;
288
- const zoomFactor = Math.min(verticalZoomFactor, horizontalZoomFactor);
296
+ const verticalZoomFactor = clientHeight / maxScrollY;
297
+ const horizontalZoomFactor = clientWidth / maxScrollX;
298
+ const zoomFactor = Math.max(Math.min(verticalZoomFactor, horizontalZoomFactor), minZoomFactor || 0);
289
299
  const scrollPosition = { x: globalThis.scrollX, y: globalThis.scrollY };
290
300
  if (zoomFactor < 1) {
291
301
  const transform = document.documentElement.style.getPropertyValue("transform");
@@ -336,7 +346,7 @@
336
346
  }
337
347
  }
338
348
 
339
- function loadDeferredImagesEnd(keepZoomLevel) {
349
+ function loadDeferredContentEnd(keepZoomLevel) {
340
350
  document.querySelectorAll("[" + LAZY_LOAD_ATTRIBUTE + "]").forEach(element => {
341
351
  element.loading = "lazy";
342
352
  element.removeAttribute(LAZY_LOAD_ATTRIBUTE);
@@ -21,12 +21,12 @@
21
21
  * Source.
22
22
  */
23
23
 
24
- const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
25
- const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
26
- const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-images-keep-zoom-level-start";
27
- const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-end";
28
- const LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT = "single-file-load-deferred-images-keep-zoom-level-reset";
29
- const LOAD_DEFERRED_IMAGES_RESET_EVENT = "single-file-load-deferred-images-reset";
24
+ const LOAD_DEFERRED_CONTENT_START_EVENT = "single-file-load-deferred-content-start";
25
+ const LOAD_DEFERRED_CONTENT_END_EVENT = "single-file-load-deferred-content-end";
26
+ const LOAD_DEFERRED_CONTENT_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-content-keep-zoom-level-start";
27
+ const LOAD_DEFERRED_CONTENT_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-content-keep-zoom-level-end";
28
+ const LOAD_DEFERRED_CONTENT_RESET_ZOOM_LEVEL_EVENT = "single-file-load-deferred-content-keep-zoom-level-reset";
29
+ const LOAD_DEFERRED_CONTENT_RESET_EVENT = "single-file-load-deferred-content-reset";
30
30
  const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
31
31
  const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
32
32
  const DISPATCH_SCROLL_START_EVENT = "single-file-dispatch-scroll-event-start";
@@ -98,9 +98,9 @@ function onNewWorklet(event) {
98
98
  export {
99
99
  getFontsData,
100
100
  getWorkletsData,
101
- loadDeferredImagesStart,
102
- loadDeferredImagesEnd,
103
- loadDeferredImagesResetZoomLevel,
101
+ loadDeferredContentStart,
102
+ loadDeferredContentEnd,
103
+ loadDeferredContentResetZoomLevel,
104
104
  LOAD_IMAGE_EVENT,
105
105
  IMAGE_LOADED_EVENT
106
106
  };
@@ -113,44 +113,45 @@ function getWorkletsData() {
113
113
  return Array.from(worklets.values());
114
114
  }
115
115
 
116
- function loadDeferredImagesStart(options) {
117
- if (options.loadDeferredImagesBlockCookies) {
116
+ function loadDeferredContentStart(options) {
117
+ if (options.loadDeferredContentBlockCookies) {
118
118
  document.dispatchEvent(new CustomEvent(BLOCK_COOKIES_START_EVENT));
119
119
  }
120
- if (options.loadDeferredImagesBlockStorage) {
120
+ if (options.loadDeferredContentBlockStorage) {
121
121
  document.dispatchEvent(new CustomEvent(BLOCK_STORAGE_START_EVENT));
122
122
  }
123
- if (options.loadDeferredImagesDispatchScrollEvent) {
123
+ if (options.loadDeferredContentDispatchScrollEvent) {
124
124
  document.dispatchEvent(new CustomEvent(DISPATCH_SCROLL_START_EVENT));
125
125
  }
126
- if (options.loadDeferredImagesKeepZoomLevel) {
127
- document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT));
126
+ const detail = JSON.stringify({ minZoomFactor: options.loadDeferredContentMinZoomFactor });
127
+ if (options.loadDeferredContentKeepZoomLevel) {
128
+ document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_CONTENT_KEEP_ZOOM_LEVEL_START_EVENT, { detail }));
128
129
  } else {
129
- document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT));
130
+ document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_CONTENT_START_EVENT, { detail }));
130
131
  }
131
132
  }
132
133
 
133
- function loadDeferredImagesEnd(options) {
134
- if (options.loadDeferredImagesBlockCookies) {
134
+ function loadDeferredContentEnd(options) {
135
+ if (options.loadDeferredContentBlockCookies) {
135
136
  document.dispatchEvent(new CustomEvent(BLOCK_COOKIES_END_EVENT));
136
137
  }
137
- if (options.loadDeferredImagesBlockStorage) {
138
+ if (options.loadDeferredContentBlockStorage) {
138
139
  document.dispatchEvent(new CustomEvent(BLOCK_STORAGE_END_EVENT));
139
140
  }
140
- if (options.loadDeferredImagesDispatchScrollEvent) {
141
+ if (options.loadDeferredContentDispatchScrollEvent) {
141
142
  document.dispatchEvent(new CustomEvent(DISPATCH_SCROLL_END_EVENT));
142
143
  }
143
- if (options.loadDeferredImagesKeepZoomLevel) {
144
- document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT));
144
+ if (options.loadDeferredContentKeepZoomLevel) {
145
+ document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_CONTENT_KEEP_ZOOM_LEVEL_END_EVENT));
145
146
  } else {
146
- document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT));
147
+ document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_CONTENT_END_EVENT));
147
148
  }
148
149
  }
149
150
 
150
- function loadDeferredImagesResetZoomLevel(options) {
151
- if (options.loadDeferredImagesKeepZoomLevel) {
152
- document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT));
151
+ function loadDeferredContentResetZoomLevel(options) {
152
+ if (options.loadDeferredContentKeepZoomLevel) {
153
+ document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_CONTENT_RESET_ZOOM_LEVEL_EVENT));
153
154
  } else {
154
- document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_EVENT));
155
+ document.dispatchEvent(new CustomEvent(LOAD_DEFERRED_CONTENT_RESET_EVENT));
155
156
  }
156
157
  }
@@ -81,7 +81,7 @@ async function process(options) {
81
81
  }
82
82
 
83
83
  function resetZoomLevel(options) {
84
- hooksFrames.loadDeferredImagesResetZoomLevel(options);
84
+ hooksFrames.loadDeferredContentResetZoomLevel(options);
85
85
  }
86
86
 
87
87
  function triggerLazyLoading(options) {
@@ -112,12 +112,12 @@ function triggerLazyLoading(options) {
112
112
  }
113
113
  }
114
114
  });
115
- await setIdleTimeout(options.loadDeferredImagesMaxIdleTime * 2);
115
+ await setIdleTimeout(options.loadDeferredContentMaxIdleTime * 2);
116
116
  await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
117
117
  observer.observe(document, { subtree: true, childList: true, attributes: true });
118
118
  document.addEventListener(hooksFrames.LOAD_IMAGE_EVENT, onImageLoadEvent);
119
119
  document.addEventListener(hooksFrames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
120
- hooksFrames.loadDeferredImagesStart(options);
120
+ hooksFrames.loadDeferredContentStart(options);
121
121
 
122
122
  async function setIdleTimeout(delay) {
123
123
  await setAsyncTimeout("idleTimeout", async () => {
@@ -130,7 +130,7 @@ function triggerLazyLoading(options) {
130
130
  clearAsyncTimeout("idleTimeout");
131
131
  await setIdleTimeout(Math.max(500, delay / 2));
132
132
  }
133
- }, delay, options.loadDeferredImagesNativeTimeout);
133
+ }, delay);
134
134
  }
135
135
 
136
136
  function onResourceLoad(event) {
@@ -167,28 +167,28 @@ function triggerLazyLoading(options) {
167
167
  }
168
168
 
169
169
  async function deferLazyLoadEnd(observer, options, resolve) {
170
- await setAsyncTimeout("loadTimeout", () => lazyLoadEnd(observer, options, resolve), options.loadDeferredImagesMaxIdleTime, options.loadDeferredImagesNativeTimeout);
170
+ await setAsyncTimeout("loadTimeout", () => lazyLoadEnd(observer, options, resolve), options.loadDeferredContentMaxIdleTime);
171
171
  }
172
172
 
173
173
  async function deferForceLazyLoadEnd(observer, options, resolve) {
174
174
  await setAsyncTimeout("maxTimeout", async () => {
175
175
  await clearAsyncTimeout("loadTimeout");
176
176
  await lazyLoadEnd(observer, options, resolve);
177
- }, options.loadDeferredImagesMaxIdleTime * 10, options.loadDeferredImagesNativeTimeout);
177
+ }, options.loadDeferredContentMaxIdleTime * 10);
178
178
  }
179
179
 
180
180
  async function lazyLoadEnd(observer, options, resolve) {
181
181
  await clearAsyncTimeout("idleTimeout");
182
- hooksFrames.loadDeferredImagesEnd(options);
182
+ hooksFrames.loadDeferredContentEnd(options);
183
183
  await setAsyncTimeout("endTimeout", async () => {
184
184
  await clearAsyncTimeout("maxTimeout");
185
185
  resolve();
186
- }, options.loadDeferredImagesMaxIdleTime / 2, options.loadDeferredImagesNativeTimeout);
186
+ }, options.loadDeferredContentMaxIdleTime / 2);
187
187
  observer.disconnect();
188
188
  }
189
189
 
190
- async function setAsyncTimeout(type, callback, delay, forceNativeTimeout) {
191
- if (browser && browser.runtime && browser.runtime.sendMessage && !forceNativeTimeout) {
190
+ async function setAsyncTimeout(type, callback, delay) {
191
+ if (browser && browser.runtime && browser.runtime.sendMessage) {
192
192
  if (!timeouts.get(type) || !timeouts.get(type).pending) {
193
193
  const timeoutData = { callback, pending: true };
194
194
  timeouts.set(type, timeoutData);