Kaynağa Gözat

Merge pull request #7 from gildas-lormeau/master

upd
solokot 5 yıl önce
ebeveyn
işleme
7073f1f458

+ 2 - 1
README.MD

@@ -85,8 +85,9 @@ See https://addons.mozilla.org/firefox/addon/single-file/versions/
 - Firefox:
 - Firefox:
   - The "File name > file name conflict resolution" option does not work if set to "prompt for a name"
   - The "File name > file name conflict resolution" option does not work if set to "prompt for a name"
   - Sometimes, SingleFile is unable to save the contents of  sandboxed iframes because of [this bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1411641).
   - Sometimes, SingleFile is unable to save the contents of  sandboxed iframes because of [this bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1411641).
+  - When processing a page from the filesystem, external resources (e.g. images, stylesheets, fonts etc.) will not be embedded into the saved page. You can find more info about this bug [here](https://bugzilla.mozilla.org/show_bug.cgi?id=1644488). This bug has been closed by Mozilla as "WontFix".
 - Waterfox
 - Waterfox
-  - When opening pages saved with the option "Images > group duplicate images together" enabled, some duplicate images might not displayed. It is recommended to disable this option.
+  - When opening pages saved with the option "Images > group duplicate images together" enabled, some duplicate images might not displayed. It is recommended to disable this option.  
 
 
 ## Troubleshooting unknown issues
 ## Troubleshooting unknown issues
 Please follow these steps if you find an unknown issue:
 Please follow these steps if you find an unknown issue:

+ 4 - 0
_locales/de/messages.json

@@ -235,6 +235,10 @@
 		"message": "maximale Inaktivitätszeit (ms)",
 		"message": "maximale Inaktivitätszeit (ms)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "Seite verkleinern",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "Bilder für alternative Bildschirmauflösungen entfernen",
 		"message": "Bilder für alternative Bildschirmauflösungen entfernen",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 4 - 0
_locales/en/messages.json

@@ -235,6 +235,10 @@
 		"message": "maximum idle time (ms)",
 		"message": "maximum idle time (ms)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "zoom out the page",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "remove images for alternative screen resolutions",
 		"message": "remove images for alternative screen resolutions",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 4 - 0
_locales/es/messages.json

@@ -235,6 +235,10 @@
 		"message": "periodo máximo de espera (ms)",
 		"message": "periodo máximo de espera (ms)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "alejar la página",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "eliminar imágenes para resoluciones alternativas de pantalla",
 		"message": "eliminar imágenes para resoluciones alternativas de pantalla",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 4 - 0
_locales/fr/messages.json

@@ -235,6 +235,10 @@
 		"message": "temps d'inactivité maximal (ms)",
 		"message": "temps d'inactivité maximal (ms)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "dézoomer la page",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "supprimer les images pour des résolutions d'écran alternatives",
 		"message": "supprimer les images pour des résolutions d'écran alternatives",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 12 - 8
_locales/ja/messages.json

@@ -172,7 +172,7 @@
 		"description": "Options page label: 'template'"
 		"description": "Options page label: 'template'"
 	},
 	},
 	"optionFilenameMaxLength": {
 	"optionFilenameMaxLength": {
-		"message": "max length (bytes)",
+		"message": "最大長(バイト)",
 		"description": "Options page label: 'max length (bytes)'"
 		"description": "Options page label: 'max length (bytes)'"
 	},
 	},
 	"optionConfirmFilename": {
 	"optionConfirmFilename": {
@@ -196,10 +196,10 @@
 		"description": "Value for 'name conflict resolution' option: 'prompt for a name'"
 		"description": "Value for 'name conflict resolution' option: 'prompt for a name'"
 	},
 	},
 	"optionFilenameConflictActionSkip": {
 	"optionFilenameConflictActionSkip": {
-        "message": "skip duplicate files",
-        "description": "Value for 'filename conflict resolution' option: 'skip duplicate files'"
-    },
-    "optionsHTMLContentSubTitle": {
+		"message": "重複ファイルをスキップ",
+		"description": "Value for 'filename conflict resolution' option: 'skip duplicate files'"
+	},
+	"optionsHTMLContentSubTitle": {
 		"message": "HTML コンテンツ",
 		"message": "HTML コンテンツ",
 		"description": "Options sub-title: 'HTML content'"
 		"description": "Options sub-title: 'HTML content'"
 	},
 	},
@@ -235,6 +235,10 @@
 		"message": "最大アイドル時間(ミリ秒)",
 		"message": "最大アイドル時間(ミリ秒)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "zoom out the page",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "代替画面解像度用の画像を削除します",
 		"message": "代替画面解像度用の画像を削除します",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
@@ -376,7 +380,7 @@
 		"description": "Options page label: 'save pages in background'"
 		"description": "Options page label: 'save pages in background'"
 	},
 	},
 	"optionApplySystemTheme": {
 	"optionApplySystemTheme": {
-		"message": "apply the system theme when formatting a page in the annotation editor",
+		"message": "(注釈エディタ)でページをフォーマットするときにシステムテーマを適用する",
 		"description": "Title of the button 'apply the system theme when formatting a page in the annotation editor"
 		"description": "Title of the button 'apply the system theme when formatting a page in the annotation editor"
 	},
 	},
 	"optionDisplayStats": {
 	"optionDisplayStats": {
@@ -416,7 +420,7 @@
 		"description": "Options page label: 'save the page of a newly created bookmark'"
 		"description": "Options page label: 'save the page of a newly created bookmark'"
 	},
 	},
 	"optionReplaceBookmarkURL": {
 	"optionReplaceBookmarkURL": {
-		"message": "link the new bookmark to the saved page",
+		"message": "新しいブックマークを保存したページにリンクさせることを可能にする",
 		"description": "Options page label: 'link the new bookmark to the saved page'"
 		"description": "Options page label: 'link the new bookmark to the saved page'"
 	},
 	},
 	"optionsHelpLink": {
 	"optionsHelpLink": {
@@ -631,4 +635,4 @@
 		"message": "Cancel",
 		"message": "Cancel",
 		"description": "Add URLs popup cancel button: 'Cancel'"
 		"description": "Add URLs popup cancel button: 'Cancel'"
 	}
 	}
-}
+}

+ 4 - 0
_locales/pl/messages.json

@@ -235,6 +235,10 @@
 		"message": "maksymalny czas bezczynności (ms)",
 		"message": "maksymalny czas bezczynności (ms)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "pomniejsz stronę",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "usuwaj obrazy dla alternatywnych rozdzielczości ekranu",
 		"message": "usuwaj obrazy dla alternatywnych rozdzielczości ekranu",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 4 - 0
_locales/ru/messages.json

@@ -235,6 +235,10 @@
 		"message": "максимальное время простоя (мс)",
 		"message": "максимальное время простоя (мс)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "zoom out the page",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "удалить изображения для альтернативных разрешений экрана",
 		"message": "удалить изображения для альтернативных разрешений экрана",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 4 - 0
_locales/uk/messages.json

@@ -235,6 +235,10 @@
 		"message": "максимальний час простою (мс)",
 		"message": "максимальний час простою (мс)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "zoom out the page",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "видалити зображення для альтернативних розширень екрану ",
 		"message": "видалити зображення для альтернативних розширень екрану ",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 5 - 1
_locales/zh_CN/messages.json

@@ -196,7 +196,7 @@
 		"description": "Value for 'name conflict resolution' option: 'prompt for a name'"
 		"description": "Value for 'name conflict resolution' option: 'prompt for a name'"
 	},
 	},
 	"optionFilenameConflictActionSkip": {
 	"optionFilenameConflictActionSkip": {
-        "message": "skip duplicate files",
+        "message": "跳过重复文件",
         "description": "Value for 'filename conflict resolution' option: 'skip duplicate files'"
         "description": "Value for 'filename conflict resolution' option: 'skip duplicate files'"
     },
     },
     "optionsHTMLContentSubTitle": {
     "optionsHTMLContentSubTitle": {
@@ -235,6 +235,10 @@
 		"message": "最长空闲时间(毫秒)",
 		"message": "最长空闲时间(毫秒)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "缩小页面",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "移除用于备选分辨率的图片",
 		"message": "移除用于备选分辨率的图片",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 5 - 1
_locales/zh_TW/messages.json

@@ -196,7 +196,7 @@
 		"description": "Value for 'name conflict resolution' option: 'prompt for a name'"
 		"description": "Value for 'name conflict resolution' option: 'prompt for a name'"
 	},
 	},
 	"optionFilenameConflictActionSkip": {
 	"optionFilenameConflictActionSkip": {
-        "message": "skip duplicate files",
+        "message": "跳過重複文件",
         "description": "Value for 'filename conflict resolution' option: 'skip duplicate files'"
         "description": "Value for 'filename conflict resolution' option: 'skip duplicate files'"
     },
     },
     "optionsHTMLContentSubTitle": {
     "optionsHTMLContentSubTitle": {
@@ -235,6 +235,10 @@
 		"message": "最長空閒時間(毫秒)",
 		"message": "最長空閒時間(毫秒)",
 		"description": "Options page label: 'maximum idle time (ms)'"
 		"description": "Options page label: 'maximum idle time (ms)'"
 	},
 	},
+	"optionLoadDeferredImagesKeepZoomLevel": {
+		"message": "縮小頁面",
+		"description": "Options page label: 'zoom out the page'"
+	},
 	"optionRemoveAlternativeImages": {
 	"optionRemoveAlternativeImages": {
 		"message": "移除用於備選分辨率的圖片",
 		"message": "移除用於備選分辨率的圖片",
 		"description": "Options page label: 'remove images for alternative screen resolutions'"
 		"description": "Options page label: 'remove images for alternative screen resolutions'"

+ 1 - 1
extension/core/bg/business.js

@@ -207,7 +207,7 @@ singlefile.extension.core.bg.business = (() => {
 		const tabId = taskInfo.tab.id;
 		const tabId = taskInfo.tab.id;
 		const taskId = taskInfo.id;
 		const taskId = taskInfo.id;
 		taskInfo.cancelled = true;
 		taskInfo.cancelled = true;
-		singlefile.extension.core.bg.tabs.sendMessage(tabId, { method: "content.cancelSave" });
+		singlefile.extension.core.bg.tabs.sendMessage(tabId, { method: "content.cancelSave", resetZoomLevel: taskInfo.options.loadDeferredImagesKeepZoomLevel });
 		if (taskInfo.cancel) {
 		if (taskInfo.cancel) {
 			taskInfo.cancel();
 			taskInfo.cancel();
 		}
 		}

+ 1 - 0
extension/core/bg/config.js

@@ -43,6 +43,7 @@ singlefile.extension.core.bg.config = (() => {
 		loadDeferredImagesMaxIdleTime: 1500,
 		loadDeferredImagesMaxIdleTime: 1500,
 		loadDeferredImagesBlockCookies: false,
 		loadDeferredImagesBlockCookies: false,
 		loadDeferredImagesBlockStorage: false,
 		loadDeferredImagesBlockStorage: false,
+		loadDeferredImagesKeepZoomLevel: false,
 		filenameTemplate: "{page-title} ({date-iso} {time-locale}).html",
 		filenameTemplate: "{page-title} ({date-iso} {time-locale}).html",
 		infobarTemplate: "",
 		infobarTemplate: "",
 		includeInfobar: false,
 		includeInfobar: false,

+ 36 - 36
extension/core/bg/downloads.js

@@ -141,27 +141,12 @@ singlefile.extension.core.bg.downloads = (() => {
 				});
 				});
 			} else {
 			} else {
 				message.url = URL.createObjectURL(blob);
 				message.url = URL.createObjectURL(blob);
-				const filenameConflictAction = message.filenameConflictAction;
-				let skipped;
-				if (filenameConflictAction == CONFLICT_ACTION_SKIP) {
-					const downloadItems = await browser.downloads.search({
-						filenameRegex: "(\\\\|/)" + getRegExp(message.filename) + "$",
-						exists: true
-					});
-					if (downloadItems.length) {
-						skipped = true;
-					} else {
-						message.filenameConflictAction = CONFLICT_ACTION_UNIQUIFY;
-					}
-				}
-				if (!skipped) {
-					await downloadPage(message, {
-						confirmFilename: message.confirmFilename,
-						incognito,
-						filenameConflictAction: message.filenameConflictAction,
-						filenameReplacementCharacter: message.filenameReplacementCharacter
-					});
-				}
+				await downloadPage(message, {
+					confirmFilename: message.confirmFilename,
+					incognito,
+					filenameConflictAction: message.filenameConflictAction,
+					filenameReplacementCharacter: message.filenameReplacementCharacter
+				});
 			}
 			}
 			singlefile.extension.ui.bg.main.onEnd(tabId);
 			singlefile.extension.ui.bg.main.onEnd(tabId);
 		} catch (error) {
 		} catch (error) {
@@ -238,24 +223,39 @@ singlefile.extension.core.bg.downloads = (() => {
 	}
 	}
 
 
 	async function downloadPage(pageData, options) {
 	async function downloadPage(pageData, options) {
-		const downloadInfo = {
-			url: pageData.url,
-			saveAs: options.confirmFilename,
-			filename: pageData.filename,
-			conflictAction: options.filenameConflictAction
-		};
-		if (options.incognito) {
-			downloadInfo.incognito = true;
+		const filenameConflictAction = options.filenameConflictAction;
+		let skipped;
+		if (filenameConflictAction == CONFLICT_ACTION_SKIP) {
+			const downloadItems = await browser.downloads.search({
+				filenameRegex: "(\\\\|/)" + getRegExp(pageData.filename) + "$",
+				exists: true
+			});
+			if (downloadItems.length) {
+				skipped = true;
+			} else {
+				options.filenameConflictAction = CONFLICT_ACTION_UNIQUIFY;
+			}
 		}
 		}
-		const downloadData = await download(downloadInfo, options.filenameReplacementCharacter);
-		if (downloadData.filename && pageData.bookmarkId && pageData.replaceBookmarkURL) {
-			if (!downloadData.filename.startsWith("file:")) {
-				if (downloadData.filename.startsWith("/")) {
-					downloadData.filename = downloadData.filename.substring(1);
+		if (!skipped) {
+			const downloadInfo = {
+				url: pageData.url,
+				saveAs: options.confirmFilename,
+				filename: pageData.filename,
+				conflictAction: options.filenameConflictAction
+			};
+			if (options.incognito) {
+				downloadInfo.incognito = true;
+			}
+			const downloadData = await download(downloadInfo, options.filenameReplacementCharacter);
+			if (downloadData.filename && pageData.bookmarkId && pageData.replaceBookmarkURL) {
+				if (!downloadData.filename.startsWith("file:")) {
+					if (downloadData.filename.startsWith("/")) {
+						downloadData.filename = downloadData.filename.substring(1);
+					}
+					downloadData.filename = "file:///" + downloadData.filename;
 				}
 				}
-				downloadData.filename = "file:///" + downloadData.filename;
+				await singlefile.extension.core.bg.bookmarks.update(pageData.bookmarkId, { url: downloadData.filename });
 			}
 			}
-			await singlefile.extension.core.bg.bookmarks.update(pageData.bookmarkId, { url: downloadData.filename });
 		}
 		}
 	}
 	}
 
 

+ 6 - 0
extension/core/content/content-main.js

@@ -56,6 +56,9 @@ this.singlefile.extension.core.content.main = this.singlefile.extension.core.con
 					ui.onEndPage();
 					ui.onEndPage();
 					browser.runtime.sendMessage({ method: "ui.processCancelled" });
 					browser.runtime.sendMessage({ method: "ui.processCancelled" });
 				}
 				}
+				if (message.resetZoomLevel) {
+					singlefile.lib.processors.lazy.content.loader.resetZoomLevel();
+				}
 				return {};
 				return {};
 			}
 			}
 			if (message.method == "content.getSelectedLinks") {
 			if (message.method == "content.getSelectedLinks") {
@@ -141,6 +144,9 @@ this.singlefile.extension.core.content.main = this.singlefile.extension.core.con
 			if (!processor.cancelled) {
 			if (!processor.cancelled) {
 				if (event.type == event.RESOURCES_INITIALIZED) {
 				if (event.type == event.RESOURCES_INITIALIZED) {
 					maxIndex = event.detail.max;
 					maxIndex = event.detail.max;
+					if (options.loadDeferredImagesKeepZoomLevel) {
+						singlefile.lib.processors.lazy.content.loader.resetZoomLevel();
+					}
 				}
 				}
 				if (event.type == event.RESOURCES_INITIALIZED || event.type == event.RESOURCE_LOADED) {
 				if (event.type == event.RESOURCES_INITIALIZED || event.type == event.RESOURCE_LOADED) {
 					if (event.type == event.RESOURCE_LOADED) {
 					if (event.type == event.RESOURCE_LOADED) {

+ 6 - 0
extension/ui/bg/ui-options.js

@@ -42,6 +42,7 @@
 	const compressCSSLabel = document.getElementById("compressCSSLabel");
 	const compressCSSLabel = document.getElementById("compressCSSLabel");
 	const loadDeferredImagesLabel = document.getElementById("loadDeferredImagesLabel");
 	const loadDeferredImagesLabel = document.getElementById("loadDeferredImagesLabel");
 	const loadDeferredImagesMaxIdleTimeLabel = document.getElementById("loadDeferredImagesMaxIdleTimeLabel");
 	const loadDeferredImagesMaxIdleTimeLabel = document.getElementById("loadDeferredImagesMaxIdleTimeLabel");
+	const loadDeferredImagesKeepZoomLevelLabel = document.getElementById("loadDeferredImagesKeepZoomLevelLabel");
 	const addMenuEntryLabel = document.getElementById("addMenuEntryLabel");
 	const addMenuEntryLabel = document.getElementById("addMenuEntryLabel");
 	const filenameTemplateLabel = document.getElementById("filenameTemplateLabel");
 	const filenameTemplateLabel = document.getElementById("filenameTemplateLabel");
 	const filenameMaxLengthLabel = document.getElementById("filenameMaxLengthLabel");
 	const filenameMaxLengthLabel = document.getElementById("filenameMaxLengthLabel");
@@ -118,6 +119,7 @@
 	const compressCSSInput = document.getElementById("compressCSSInput");
 	const compressCSSInput = document.getElementById("compressCSSInput");
 	const loadDeferredImagesInput = document.getElementById("loadDeferredImagesInput");
 	const loadDeferredImagesInput = document.getElementById("loadDeferredImagesInput");
 	const loadDeferredImagesMaxIdleTimeInput = document.getElementById("loadDeferredImagesMaxIdleTimeInput");
 	const loadDeferredImagesMaxIdleTimeInput = document.getElementById("loadDeferredImagesMaxIdleTimeInput");
+	const loadDeferredImagesKeepZoomLevelInput = document.getElementById("loadDeferredImagesKeepZoomLevelInput");
 	const contextMenuEnabledInput = document.getElementById("contextMenuEnabledInput");
 	const contextMenuEnabledInput = document.getElementById("contextMenuEnabledInput");
 	const filenameTemplateInput = document.getElementById("filenameTemplateInput");
 	const filenameTemplateInput = document.getElementById("filenameTemplateInput");
 	const filenameMaxLengthInput = document.getElementById("filenameMaxLengthInput");
 	const filenameMaxLengthInput = document.getElementById("filenameMaxLengthInput");
@@ -437,6 +439,7 @@
 	compressCSSLabel.textContent = browser.i18n.getMessage("optionCompressCSS");
 	compressCSSLabel.textContent = browser.i18n.getMessage("optionCompressCSS");
 	loadDeferredImagesLabel.textContent = browser.i18n.getMessage("optionLoadDeferredImages");
 	loadDeferredImagesLabel.textContent = browser.i18n.getMessage("optionLoadDeferredImages");
 	loadDeferredImagesMaxIdleTimeLabel.textContent = browser.i18n.getMessage("optionLoadDeferredImagesMaxIdleTime");
 	loadDeferredImagesMaxIdleTimeLabel.textContent = browser.i18n.getMessage("optionLoadDeferredImagesMaxIdleTime");
+	loadDeferredImagesKeepZoomLevelLabel.textContent = browser.i18n.getMessage("optionLoadDeferredImagesKeepZoomLevel");
 	addMenuEntryLabel.textContent = browser.i18n.getMessage("optionAddMenuEntry");
 	addMenuEntryLabel.textContent = browser.i18n.getMessage("optionAddMenuEntry");
 	filenameTemplateLabel.textContent = browser.i18n.getMessage("optionFilenameTemplate");
 	filenameTemplateLabel.textContent = browser.i18n.getMessage("optionFilenameTemplate");
 	filenameMaxLengthLabel.textContent = browser.i18n.getMessage("optionFilenameMaxLength");
 	filenameMaxLengthLabel.textContent = browser.i18n.getMessage("optionFilenameMaxLength");
@@ -620,6 +623,8 @@
 		loadDeferredImagesInput.checked = profileOptions.loadDeferredImages && !profileOptions.saveRawPage;
 		loadDeferredImagesInput.checked = profileOptions.loadDeferredImages && !profileOptions.saveRawPage;
 		loadDeferredImagesInput.disabled = profileOptions.saveRawPage;
 		loadDeferredImagesInput.disabled = profileOptions.saveRawPage;
 		loadDeferredImagesMaxIdleTimeInput.value = profileOptions.loadDeferredImagesMaxIdleTime;
 		loadDeferredImagesMaxIdleTimeInput.value = profileOptions.loadDeferredImagesMaxIdleTime;
+		loadDeferredImagesKeepZoomLevelInput.checked = profileOptions.loadDeferredImagesKeepZoomLevel && !profileOptions.saveRawPage;
+		loadDeferredImagesKeepZoomLevelInput.disabled = !profileOptions.loadDeferredImages || profileOptions.saveRawPape;
 		loadDeferredImagesMaxIdleTimeInput.disabled = !profileOptions.loadDeferredImages || profileOptions.saveRawPage;
 		loadDeferredImagesMaxIdleTimeInput.disabled = !profileOptions.loadDeferredImages || profileOptions.saveRawPage;
 		contextMenuEnabledInput.checked = profileOptions.contextMenuEnabled;
 		contextMenuEnabledInput.checked = profileOptions.contextMenuEnabled;
 		filenameTemplateInput.value = profileOptions.filenameTemplate;
 		filenameTemplateInput.value = profileOptions.filenameTemplate;
@@ -696,6 +701,7 @@
 				compressCSS: compressCSSInput.checked,
 				compressCSS: compressCSSInput.checked,
 				loadDeferredImages: loadDeferredImagesInput.checked,
 				loadDeferredImages: loadDeferredImagesInput.checked,
 				loadDeferredImagesMaxIdleTime: Math.max(loadDeferredImagesMaxIdleTimeInput.value, 0),
 				loadDeferredImagesMaxIdleTime: Math.max(loadDeferredImagesMaxIdleTimeInput.value, 0),
+				loadDeferredImagesKeepZoomLevel: loadDeferredImagesKeepZoomLevelInput.checked,
 				contextMenuEnabled: contextMenuEnabledInput.checked,
 				contextMenuEnabled: contextMenuEnabledInput.checked,
 				filenameTemplate: filenameTemplateInput.value,
 				filenameTemplate: filenameTemplateInput.value,
 				filenameMaxLength: filenameMaxLengthInput.value,
 				filenameMaxLength: filenameMaxLengthInput.value,

+ 7 - 4
extension/ui/content/content-ui-main.js

@@ -58,8 +58,8 @@ this.singlefile.extension.ui.content.main = this.singlefile.extension.ui.content
 			let maskElement = document.querySelector(MASK_TAGNAME);
 			let maskElement = document.querySelector(MASK_TAGNAME);
 			if (!maskElement) {
 			if (!maskElement) {
 				if (options.logsEnabled) {
 				if (options.logsEnabled) {
-					document.body.appendChild(logsWindowElement);
 					setLogsWindowStyle();
 					setLogsWindowStyle();
+					document.body.appendChild(logsWindowElement);
 				}
 				}
 				if (options.shadowEnabled) {
 				if (options.shadowEnabled) {
 					const maskElement = createMaskElement();
 					const maskElement = createMaskElement();
@@ -378,10 +378,9 @@ this.singlefile.extension.ui.content.main = this.singlefile.extension.ui.content
 	function createMaskElement() {
 	function createMaskElement() {
 		let maskElement = document.querySelector(MASK_TAGNAME);
 		let maskElement = document.querySelector(MASK_TAGNAME);
 		if (!maskElement) {
 		if (!maskElement) {
-			maskElement = createElement(MASK_TAGNAME, document.body);
+			maskElement = createElement(MASK_TAGNAME);
 			maskElement.style.setProperty("opacity", 0, "important");
 			maskElement.style.setProperty("opacity", 0, "important");
 			maskElement.style.setProperty("background-color", "transparent", "important");
 			maskElement.style.setProperty("background-color", "transparent", "important");
-			maskElement.offsetWidth;
 			maskElement.style.setProperty("position", "fixed", "important");
 			maskElement.style.setProperty("position", "fixed", "important");
 			maskElement.style.setProperty("top", "0", "important");
 			maskElement.style.setProperty("top", "0", "important");
 			maskElement.style.setProperty("left", "0", "important");
 			maskElement.style.setProperty("left", "0", "important");
@@ -389,6 +388,8 @@ this.singlefile.extension.ui.content.main = this.singlefile.extension.ui.content
 			maskElement.style.setProperty("height", "100%", "important");
 			maskElement.style.setProperty("height", "100%", "important");
 			maskElement.style.setProperty("z-index", 2147483646, "important");
 			maskElement.style.setProperty("z-index", 2147483646, "important");
 			maskElement.style.setProperty("transition", "opacity 250ms", "important");
 			maskElement.style.setProperty("transition", "opacity 250ms", "important");
+			document.body.appendChild(maskElement);
+			maskElement.offsetWidth;
 		}
 		}
 		return maskElement;
 		return maskElement;
 	}
 	}
@@ -529,7 +530,9 @@ this.singlefile.extension.ui.content.main = this.singlefile.extension.ui.content
 	function createElement(tagName, parentElement) {
 	function createElement(tagName, parentElement) {
 		const element = document.createElement(tagName);
 		const element = document.createElement(tagName);
 		element.className = SINGLE_FILE_UI_ELEMENT_CLASS;
 		element.className = SINGLE_FILE_UI_ELEMENT_CLASS;
-		parentElement.appendChild(element);
+		if (parentElement) {
+			parentElement.appendChild(element);
+		}
 		initStyle(element);
 		initStyle(element);
 		return element;
 		return element;
 	}
 	}

+ 7 - 0
extension/ui/pages/help.html

@@ -267,6 +267,13 @@
 							for example the network or system conditions are degraded. You can also decrease this value
 							for example the network or system conditions are degraded. You can also decrease this value
 							otherwise.</p>
 							otherwise.</p>
 					</li>
 					</li>
+					<li data-options-label="loadDeferredImagesKeepZoomLevelLabel"> <span class="option">Option: zoom out
+							the page</span>
+						<p>Check this option to save the entire content of a page by zooming out the page. This can help
+							saving pages using infinite virtual lists for example.
+						</p>
+						<p class="notice">It is recommended to <u>uncheck</u> this option</p>
+					</li>
 					<li data-options-label="removeAlternativeImagesLabel"> <span class="option">Option: remove images
 					<li data-options-label="removeAlternativeImagesLabel"> <span class="option">Option: remove images
 							for alternative screen resolutions</span>
 							for alternative screen resolutions</span>
 						<p>Check this option to remove images that are alternatives in lower and/or higher resolutions
 						<p>Check this option to remove images that are alternatives in lower and/or higher resolutions

+ 4 - 0
extension/ui/pages/options.html

@@ -135,6 +135,10 @@
 				<label for="loadDeferredImagesMaxIdleTimeInput" id="loadDeferredImagesMaxIdleTimeLabel"></label>
 				<label for="loadDeferredImagesMaxIdleTimeInput" id="loadDeferredImagesMaxIdleTimeLabel"></label>
 				<input type="number" class="large-input" id="loadDeferredImagesMaxIdleTimeInput" step="100">
 				<input type="number" class="large-input" id="loadDeferredImagesMaxIdleTimeInput" step="100">
 			</div>
 			</div>
+			<div class="option second-level">
+				<label for="loadDeferredImagesKeepZoomLevelInput" id="loadDeferredImagesKeepZoomLevelLabel"></label>
+				<input type="checkbox" id="loadDeferredImagesKeepZoomLevelInput">
+			</div>
 			<div class="option">
 			<div class="option">
 				<label for="removeAlternativeImagesInput" id="removeAlternativeImagesLabel"></label>
 				<label for="removeAlternativeImagesInput" id="removeAlternativeImagesLabel"></label>
 				<input type="checkbox" id="removeAlternativeImagesInput">
 				<input type="checkbox" id="removeAlternativeImagesInput">

+ 8 - 2
lib/single-file/modules/css-fonts-alt-minifier.js

@@ -273,11 +273,17 @@ this.singlefile.lib.modules.fontsAltMinifier = this.singlefile.lib.modules.fonts
 		const cssTree = singlefile.lib.vendor.cssTree;
 		const cssTree = singlefile.lib.vendor.cssTree;
 		let property;
 		let property;
 		if (ruleData.block.children) {
 		if (ruleData.block.children) {
-			property = ruleData.block.children.filter(node => node.property == propertyName).tail;
+			property = ruleData.block.children.filter(node => {
+				try {
+					return node.property == propertyName && !cssTree.generate(node.value).match(/\\9$/);
+				} catch (error) {
+					return node.property == propertyName;
+				}
+			}).tail;
 		}
 		}
 		if (property) {
 		if (property) {
 			try {
 			try {
-				return cssTree.generate(property.data.value).replace(/\\9$/, "");
+				return cssTree.generate(property.data.value);
 			} catch (error) {
 			} catch (error) {
 				// ignored
 				// ignored
 			}
 			}

+ 5 - 5
lib/single-file/modules/html-serializer.js

@@ -77,13 +77,13 @@ this.singlefile.lib.modules.serializer = this.singlefile.lib.modules.serializer
 		}
 		}
 	};
 	};
 
 
-	function serialize(node, compressHTML) {
+	function serialize(node, compressHTML, isSVG) {
 		if (node.nodeType == Node_TEXT_NODE) {
 		if (node.nodeType == Node_TEXT_NODE) {
 			return serializeTextNode(node);
 			return serializeTextNode(node);
 		} else if (node.nodeType == Node_COMMENT_NODE) {
 		} else if (node.nodeType == Node_COMMENT_NODE) {
 			return serializeCommentNode(node);
 			return serializeCommentNode(node);
 		} else if (node.nodeType == Node_ELEMENT_NODE) {
 		} else if (node.nodeType == Node_ELEMENT_NODE) {
-			return serializeElement(node, compressHTML);
+			return serializeElement(node, compressHTML, isSVG);
 		}
 		}
 	}
 	}
 
 
@@ -107,7 +107,7 @@ this.singlefile.lib.modules.serializer = this.singlefile.lib.modules.serializer
 		return "<!--" + commentNode.textContent + "-->";
 		return "<!--" + commentNode.textContent + "-->";
 	}
 	}
 
 
-	function serializeElement(element, compressHTML) {
+	function serializeElement(element, compressHTML, isSVG) {
 		const tagName = element.tagName.toLowerCase();
 		const tagName = element.tagName.toLowerCase();
 		const omittedStartTag = compressHTML && OMITTED_START_TAGS.find(omittedStartTag => tagName == omittedStartTag.tagName && omittedStartTag.accept(element));
 		const omittedStartTag = compressHTML && OMITTED_START_TAGS.find(omittedStartTag => tagName == omittedStartTag.tagName && omittedStartTag.accept(element));
 		let content = "";
 		let content = "";
@@ -119,10 +119,10 @@ this.singlefile.lib.modules.serializer = this.singlefile.lib.modules.serializer
 		if (element.tagName == "TEMPLATE" && !element.childNodes.length) {
 		if (element.tagName == "TEMPLATE" && !element.childNodes.length) {
 			content += element.innerHTML;
 			content += element.innerHTML;
 		} else {
 		} else {
-			Array.from(element.childNodes).forEach(childNode => content += serialize(childNode, compressHTML));
+			Array.from(element.childNodes).forEach(childNode => content += serialize(childNode, compressHTML, isSVG || tagName == "svg"));
 		}
 		}
 		const omittedEndTag = compressHTML && OMITTED_END_TAGS.find(omittedEndTag => tagName == omittedEndTag.tagName && omittedEndTag.accept(element.nextSibling, element));
 		const omittedEndTag = compressHTML && OMITTED_END_TAGS.find(omittedEndTag => tagName == omittedEndTag.tagName && omittedEndTag.accept(element.nextSibling, element));
-		if (!omittedEndTag && !SELF_CLOSED_TAG_NAMES.includes(tagName)) {
+		if (isSVG || (!omittedEndTag && !SELF_CLOSED_TAG_NAMES.includes(tagName))) {
 			content += "</" + tagName + ">";
 			content += "</" + tagName + ">";
 		}
 		}
 		return content;
 		return content;

+ 95 - 57
lib/single-file/processors/hooks/content/content-hooks-frames-web.js

@@ -27,6 +27,9 @@
 
 
 	const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
 	const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
 	const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
 	const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
+	const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-images-keep-zoom-level-start";
+	const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-end";
+	const LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-reset";
 	const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
 	const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
 	const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
 	const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
 	const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
 	const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
@@ -60,36 +63,41 @@
 	const observers = new Map();
 	const observers = new Map();
 	const observedElements = new Map();
 	const observedElements = new Map();
 
 
-	addEventListener.call(window, LOAD_DEFERRED_IMAGES_START_EVENT, () => {
+	addEventListener.call(window, LOAD_DEFERRED_IMAGES_START_EVENT, () => loadDeferredImagesStart());
+	addEventListener.call(window, LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT, () => loadDeferredImagesStart(true));
+
+	function loadDeferredImagesStart(keepZoomLevel) {
 		const scrollingElement = document.scrollingElement || document.documentElement;
 		const scrollingElement = document.scrollingElement || document.documentElement;
 		const clientHeight = scrollingElement.clientHeight;
 		const clientHeight = scrollingElement.clientHeight;
 		const clientWidth = scrollingElement.clientWidth;
 		const clientWidth = scrollingElement.clientWidth;
 		const scrollHeight = Math.max(scrollingElement.scrollHeight - clientHeight, clientHeight);
 		const scrollHeight = Math.max(scrollingElement.scrollHeight - clientHeight, clientHeight);
 		const scrollWidth = Math.max(scrollingElement.scrollWidth - clientWidth, clientWidth);
 		const scrollWidth = Math.max(scrollingElement.scrollWidth - clientWidth, clientWidth);
-		scrollingElement.__defineGetter__("clientHeight", () => scrollHeight);
-		scrollingElement.__defineGetter__("clientWidth", () => scrollWidth);
-		screen.__defineGetter__("height", () => scrollHeight);
-		screen.__defineGetter__("width", () => scrollWidth);
 		document.querySelectorAll("[loading=lazy]").forEach(element => {
 		document.querySelectorAll("[loading=lazy]").forEach(element => {
 			element.loading = "eager";
 			element.loading = "eager";
 			element.setAttribute(LAZY_LOAD_ATTRIBUTE, "");
 			element.setAttribute(LAZY_LOAD_ATTRIBUTE, "");
 		});
 		});
-		if (!window._singleFile_getBoundingClientRect) {
-			window._singleFile_getBoundingClientRect = Element.prototype.getBoundingClientRect;
-			Element.prototype.getBoundingClientRect = function () {
-				const boundingRect = window._singleFile_getBoundingClientRect.call(this);
-				if (this == scrollingElement) {
-					boundingRect.__defineGetter__("height", () => scrollHeight);
-					boundingRect.__defineGetter__("bottom", () => scrollHeight + boundingRect.top);
-					boundingRect.__defineGetter__("width", () => scrollWidth);
-					boundingRect.__defineGetter__("right", () => scrollWidth + boundingRect.left);
-				}
-				return boundingRect;
-			};
-			window._singleFile_innerHeight = window.innerHeight;
-			window._singleFile_innerWidth = window.innerWidth;
-			window.__defineGetter__("innerHeight", () => scrollHeight);
-			window.__defineGetter__("innerWidth", () => scrollWidth);
+		if (!keepZoomLevel) {
+			scrollingElement.__defineGetter__("clientHeight", () => scrollHeight);
+			scrollingElement.__defineGetter__("clientWidth", () => scrollWidth);
+			screen.__defineGetter__("height", () => scrollHeight);
+			screen.__defineGetter__("width", () => scrollWidth);
+			if (!window._singleFile_getBoundingClientRect) {
+				window._singleFile_getBoundingClientRect = Element.prototype.getBoundingClientRect;
+				Element.prototype.getBoundingClientRect = function () {
+					const boundingRect = window._singleFile_getBoundingClientRect.call(this);
+					if (this == scrollingElement) {
+						boundingRect.__defineGetter__("height", () => scrollHeight);
+						boundingRect.__defineGetter__("bottom", () => scrollHeight + boundingRect.top);
+						boundingRect.__defineGetter__("width", () => scrollWidth);
+						boundingRect.__defineGetter__("right", () => scrollWidth + boundingRect.left);
+					}
+					return boundingRect;
+				};
+				window._singleFile_innerHeight = window.innerHeight;
+				window._singleFile_innerWidth = window.innerWidth;
+				window.__defineGetter__("innerHeight", () => scrollHeight);
+				window.__defineGetter__("innerWidth", () => scrollWidth);
+			}
 		}
 		}
 		if (!window._singleFileImage) {
 		if (!window._singleFileImage) {
 			const Image = window.Image;
 			const Image = window.Image;
@@ -123,8 +131,14 @@
 				};
 				};
 			});
 			});
 		}
 		}
-		const zoomFactorX = (clientHeight + window.scrollY) / scrollHeight;
-		const zoomFactorY = (clientWidth + window.scrollX) / scrollWidth;
+		let zoomFactorX, zoomFactorY;
+		if (keepZoomLevel) {
+			zoomFactorX = clientHeight / scrollHeight;
+			zoomFactorY = clientWidth / scrollWidth;
+		} else {
+			zoomFactorX = (clientHeight + window.scrollY) / scrollHeight;
+			zoomFactorY = (clientWidth + window.scrollX) / scrollWidth;
+		}
 		const zoomFactor = Math.min(zoomFactorX, zoomFactorY);
 		const zoomFactor = Math.min(zoomFactorX, zoomFactorY);
 		if (zoomFactor < 1) {
 		if (zoomFactor < 1) {
 			const transform = document.documentElement.style.getPropertyValue("transform");
 			const transform = document.documentElement.style.getPropertyValue("transform");
@@ -135,54 +149,78 @@
 			document.documentElement.style.setProperty("transform", "scale3d(" + zoomFactor + ", " + zoomFactor + ", 1)", "important");
 			document.documentElement.style.setProperty("transform", "scale3d(" + zoomFactor + ", " + zoomFactor + ", 1)", "important");
 			dispatchEvent.call(window, new UIEvent("resize"));
 			dispatchEvent.call(window, new UIEvent("resize"));
 			dispatchEvent.call(window, new UIEvent("scroll"));
 			dispatchEvent.call(window, new UIEvent("scroll"));
-			document.documentElement.style.setProperty("transform", transform, transformPriority);
-			document.documentElement.style.setProperty("transform-origin", transformOrigin, transformOriginPriority);
-		}
-		dispatchEvent.call(window, new UIEvent("resize"));
-		dispatchEvent.call(window, new UIEvent("scroll"));
-		const docBoundingRect = scrollingElement.getBoundingClientRect();
-		[...observers].forEach(([intersectionObserver, observer]) => {
-			const rootBoundingRect = observer.options && observer.options.root && observer.options.root.getBoundingClientRect();
-			const targetElements = observedElements.get(intersectionObserver);
-			if (targetElements) {
-				observer.callback(targetElements.map(target => {
-					const boundingClientRect = target.getBoundingClientRect();
-					const isIntersecting = true;
-					const intersectionRatio = 1;
-					const rootBounds = observer.options && observer.options.root ? rootBoundingRect : docBoundingRect;
-					const time = 0;
-					return { target, intersectionRatio, boundingClientRect, intersectionRect: boundingClientRect, isIntersecting, rootBounds, time };
-				}), intersectionObserver);
+			if (keepZoomLevel) {
+				document.documentElement.style.setProperty("-sf-transform", transform, transformPriority);
+				document.documentElement.style.setProperty("-sf-transform-origin", transformOrigin, transformOriginPriority);
+			} else {
+				document.documentElement.style.setProperty("transform", transform, transformPriority);
+				document.documentElement.style.setProperty("transform-origin", transformOrigin, transformOriginPriority);
 			}
 			}
-		});
+		}
+		if (!keepZoomLevel) {
+			dispatchEvent.call(window, new UIEvent("resize"));
+			dispatchEvent.call(window, new UIEvent("scroll"));
+			const docBoundingRect = scrollingElement.getBoundingClientRect();
+			[...observers].forEach(([intersectionObserver, observer]) => {
+				const rootBoundingRect = observer.options && observer.options.root && observer.options.root.getBoundingClientRect();
+				const targetElements = observedElements.get(intersectionObserver);
+				if (targetElements) {
+					observer.callback(targetElements.map(target => {
+						const boundingClientRect = target.getBoundingClientRect();
+						const isIntersecting = true;
+						const intersectionRatio = 1;
+						const rootBounds = observer.options && observer.options.root ? rootBoundingRect : docBoundingRect;
+						const time = 0;
+						return { target, intersectionRatio, boundingClientRect, intersectionRect: boundingClientRect, isIntersecting, rootBounds, time };
+					}), intersectionObserver);
+				}
+			});
+		}
+	}
+
+	addEventListener.call(window, LOAD_DEFERRED_IMAGES_END_EVENT, () => loadDeferredImagesEnd());
+	addEventListener.call(window, LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT, () => loadDeferredImagesEnd(true));
+	addEventListener.call(window, LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_END_EVENT, () => {
+		const transform = document.documentElement.style.getPropertyValue("-sf-transform");
+		const transformPriority = document.documentElement.style.getPropertyPriority("-sf-transform");
+		const transformOrigin = document.documentElement.style.getPropertyValue("-sf-transform-origin");
+		const transformOriginPriority = document.documentElement.style.getPropertyPriority("-sf-transform-origin");
+		document.documentElement.style.setProperty("transform", transform, transformPriority);
+		document.documentElement.style.setProperty("transform-origin", transformOrigin, transformOriginPriority);
+		document.documentElement.style.removeProperty("-sf-transform");
+		document.documentElement.style.removeProperty("-sf-transform-origin");
 	});
 	});
 
 
-	addEventListener.call(window, LOAD_DEFERRED_IMAGES_END_EVENT, () => {
+	function loadDeferredImagesEnd(keepZoomLevel) {
 		const scrollingElement = document.scrollingElement || document.documentElement;
 		const scrollingElement = document.scrollingElement || document.documentElement;
 		document.querySelectorAll("[" + LAZY_LOAD_ATTRIBUTE + "]").forEach(element => {
 		document.querySelectorAll("[" + LAZY_LOAD_ATTRIBUTE + "]").forEach(element => {
 			element.loading = "lazy";
 			element.loading = "lazy";
 			element.removeAttribute(LAZY_LOAD_ATTRIBUTE);
 			element.removeAttribute(LAZY_LOAD_ATTRIBUTE);
 		});
 		});
-		delete scrollingElement.clientHeight;
-		delete scrollingElement.clientWidth;
-		delete screen.height;
-		delete screen.width;
-		if (window._singleFile_getBoundingClientRect) {
-			Element.prototype.getBoundingClientRect = window._singleFile_getBoundingClientRect;
-			window.innerHeight = window._singleFile_innerHeight;
-			window.innerWidth = window._singleFile_innerWidth;
-			delete window._singleFile_getBoundingClientRect;
-			delete window._singleFile_innerHeight;
-			delete window._singleFile_innerWidth;
+		if (!keepZoomLevel) {
+			delete scrollingElement.clientHeight;
+			delete scrollingElement.clientWidth;
+			delete screen.height;
+			delete screen.width;
+			if (window._singleFile_getBoundingClientRect) {
+				Element.prototype.getBoundingClientRect = window._singleFile_getBoundingClientRect;
+				window.innerHeight = window._singleFile_innerHeight;
+				window.innerWidth = window._singleFile_innerWidth;
+				delete window._singleFile_getBoundingClientRect;
+				delete window._singleFile_innerHeight;
+				delete window._singleFile_innerWidth;
+			}
 		}
 		}
 		if (window._singleFileImage) {
 		if (window._singleFileImage) {
 			delete window.Image;
 			delete window.Image;
 			window.Image = window._singleFileImage;
 			window.Image = window._singleFileImage;
 			delete window._singleFileImage;
 			delete window._singleFileImage;
 		}
 		}
-		dispatchEvent.call(window, new UIEvent("resize"));
-		dispatchEvent.call(window, new UIEvent("scroll"));
-	});
+		if (!keepZoomLevel) {
+			dispatchEvent.call(window, new UIEvent("resize"));
+			dispatchEvent.call(window, new UIEvent("scroll"));
+		}
+	}
 
 
 	addEventListener.call(window, BLOCK_COOKIES_START_EVENT, () => {
 	addEventListener.call(window, BLOCK_COOKIES_START_EVENT, () => {
 		try {
 		try {

+ 16 - 2
lib/single-file/processors/hooks/content/content-hooks-frames.js

@@ -27,6 +27,9 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 
 
 	const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
 	const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
 	const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
 	const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
+	const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-images-keep-zoom-level-start";
+	const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-end";
+	const LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-reset";
 	const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
 	const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
 	const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
 	const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
 	const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
 	const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
@@ -80,7 +83,11 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 			if (options.loadDeferredImagesBlockStorage) {
 			if (options.loadDeferredImagesBlockStorage) {
 				dispatchEvent.call(window, new CustomEvent(BLOCK_STORAGE_START_EVENT));
 				dispatchEvent.call(window, new CustomEvent(BLOCK_STORAGE_START_EVENT));
 			}
 			}
-			dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT));
+			if (options.loadDeferredImagesKeepZoomLevel) {
+				dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT));
+			} else {
+				dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT));
+			}
 		},
 		},
 		loadDeferredImagesEnd: options => {
 		loadDeferredImagesEnd: options => {
 			if (options.loadDeferredImagesBlockCookies) {
 			if (options.loadDeferredImagesBlockCookies) {
@@ -89,7 +96,14 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 			if (options.loadDeferredImagesBlockStorage) {
 			if (options.loadDeferredImagesBlockStorage) {
 				dispatchEvent.call(window, new CustomEvent(BLOCK_STORAGE_END_EVENT));
 				dispatchEvent.call(window, new CustomEvent(BLOCK_STORAGE_END_EVENT));
 			}
 			}
-			dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT));
+			if (options.loadDeferredImagesKeepZoomLevel) {
+				dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT));
+			} else {
+				dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT));
+			}
+		},
+		loadDeferredImagesResetZoomLevel: () => {
+			dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_END_EVENT));
 		},
 		},
 		LOAD_IMAGE_EVENT,
 		LOAD_IMAGE_EVENT,
 		IMAGE_LOADED_EVENT
 		IMAGE_LOADED_EVENT

+ 6 - 0
lib/single-file/processors/lazy/content/content-lazy-loader.js

@@ -44,6 +44,12 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
 			if (scrollY <= maxScrollY && scrollX <= maxScrollX) {
 			if (scrollY <= maxScrollY && scrollX <= maxScrollX) {
 				return process(options);
 				return process(options);
 			}
 			}
+		},
+		resetZoomLevel: () => {
+			const frames = singlefile.lib.processors.hooks.content.frames;
+			if (frames) {
+				frames.loadDeferredImagesResetZoomLevel();
+			}
 		}
 		}
 	};
 	};
 
 

+ 14 - 2
lib/single-file/single-file-core.js

@@ -93,6 +93,7 @@ this.singlefile.lib.core = this.singlefile.lib.core || (() => {
 	const STAGES = [{
 	const STAGES = [{
 		sequential: [
 		sequential: [
 			{ action: "preProcessPage" },
 			{ action: "preProcessPage" },
+			{ option: "loadDeferredImagesKeepZoomLevel", action: "resetZoomLevel" },
 			{ action: "replaceStyleContents" },
 			{ action: "replaceStyleContents" },
 			{ action: "resetCharsetMeta" },
 			{ action: "resetCharsetMeta" },
 			{ option: "saveFavicon", action: "saveFavicon" },
 			{ option: "saveFavicon", action: "saveFavicon" },
@@ -1351,6 +1352,17 @@ this.singlefile.lib.core = this.singlefile.lib.core || (() => {
 			}
 			}
 		}
 		}
 
 
+		resetZoomLevel() {
+			const transform = this.doc.documentElement.style.getPropertyValue("-sf-transform");
+			const transformPriority = this.doc.documentElement.style.getPropertyPriority("-sf-transform");
+			const transformOrigin = this.doc.documentElement.style.getPropertyValue("-sf-transform-origin");
+			const transformOriginPriority = this.doc.documentElement.style.getPropertyPriority("-sf-transform-origin");
+			this.doc.documentElement.style.setProperty("transform", transform, transformPriority);
+			this.doc.documentElement.style.setProperty("transform-origin", transformOrigin, transformOriginPriority);
+			this.doc.documentElement.style.removeProperty("-sf-transform");
+			this.doc.documentElement.style.removeProperty("-sf-transform-origin");
+		}
+
 		async insertMAFFMetaData() {
 		async insertMAFFMetaData() {
 			const maffMetaData = await this.maffMetaDataPromise;
 			const maffMetaData = await this.maffMetaDataPromise;
 			if (maffMetaData && maffMetaData.content) {
 			if (maffMetaData && maffMetaData.content) {
@@ -1526,7 +1538,7 @@ this.singlefile.lib.core = this.singlefile.lib.core || (() => {
 								maxResourceSize: options.maxResourceSize,
 								maxResourceSize: options.maxResourceSize,
 								maxResourceSizeEnabled: options.maxResourceSizeEnabled,
 								maxResourceSizeEnabled: options.maxResourceSizeEnabled,
 								validateTextContentType: true,
 								validateTextContentType: true,
-								frameId: options.windowId
+								frameId: options.frameId
 							});
 							});
 							resourceURL = content.resourceURL;
 							resourceURL = content.resourceURL;
 							content.data = getUpdatedResourceContent(resourceURL, content, options);
 							content.data = getUpdatedResourceContent(resourceURL, content, options);
@@ -1596,7 +1608,7 @@ this.singlefile.lib.core = this.singlefile.lib.core || (() => {
 					maxResourceSize: options.maxResourceSize,
 					maxResourceSize: options.maxResourceSize,
 					maxResourceSizeEnabled: options.maxResourceSizeEnabled,
 					maxResourceSizeEnabled: options.maxResourceSizeEnabled,
 					charset: options.charset,
 					charset: options.charset,
-					frameId: options.windowId,
+					frameId: options.frameId,
 					validateTextContentType: true
 					validateTextContentType: true
 				});
 				});
 				resourceURL = content.resourceURL;
 				resourceURL = content.resourceURL;

+ 1 - 1
manifest.json

@@ -8,7 +8,7 @@
 		"64": "extension/ui/resources/icon_64.png",
 		"64": "extension/ui/resources/icon_64.png",
 		"128": "extension/ui/resources/icon_128.png"
 		"128": "extension/ui/resources/icon_128.png"
 	},
 	},
-	"version": "1.17.44",
+	"version": "1.17.52",
 	"description": "__MSG_extensionDescription__",
 	"description": "__MSG_extensionDescription__",
 	"content_scripts": [
 	"content_scripts": [
 		{
 		{