Quellcode durchsuchen

Merge pull request #17 from gildas-lormeau/master

upd
solokot vor 5 Jahren
Ursprung
Commit
ae6862e430

+ 3 - 3
_locales/de/messages.json

@@ -3,9 +3,9 @@
 		"message": "Speichern einer kompletten Webseite in eine einzige HTML-Datei",
 		"description": "Description of the extension."
 	},
-	"commandSaveTab": {
-		"message": "Speichern der aktuellen Tab oder des ausgewählten Inhalts",
-		"description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+	"commandSaveSelectedTabs": {
+		"message": "Speichern der ausgewählten Tabs oder ihrer ausgewählten Inhalte",
+		"description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
 	},
 	"commandSaveAllTabs": {
 		"message": "Speichern aller Tabs",

+ 3 - 3
_locales/en/messages.json

@@ -3,9 +3,9 @@
 		"message": "Save a complete page into a single HTML file",
 		"description": "Description of the extension."
 	},
-    "commandSaveTab": {
-        "message": "Save the current tab or the selected content",
-        "description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+    "commandSaveSelectedTabs": {
+        "message": "Save the selected tabs or their selected contents",
+        "description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
     },
     "commandSaveAllTabs": {
         "message": "Save all tabs",

+ 3 - 3
_locales/es/messages.json

@@ -3,9 +3,9 @@
 		"message": "Guarda una página completa en un único archivo HTML",
 		"description": "Description of the extension."
 	},
-    "commandSaveTab": {
-        "message": "Guardar la pestaña actual o el contenido seleccionado",
-        "description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+    "commandSaveSelectedTabs": {
+        "message": "Guardar las pestañas seleccionadas o su contenidos seleccionados",
+        "description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
     },
     "commandSaveAllTabs": {
         "message": "Guardar todas las pestañas",

+ 3 - 3
_locales/fr/messages.json

@@ -3,9 +3,9 @@
 		"message": "Sauvegardez une page complète dans un simple fichier HTML",
 		"description": "Description of the extension."
 	},
-	"commandSaveTab": {
-		"message": "Sauver l'onglet courant ou le contenu sélectionné",
-		"description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+	"commandSaveSelectedTabs": {
+		"message": "Sauvegarder les onglets sélectionnés ou leurs contenus sélectionnés",
+		"description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
 	},
 	"commandSaveAllTabs": {
 		"message": "Sauver tous les onglets",

+ 3 - 3
_locales/ja/messages.json

@@ -3,9 +3,9 @@
 		"message": "完全なページを単一の HTML ファイルに保存する",
 		"description": "Description of the extension."
 	},
-    "commandSaveTab": {
-        "message": "Save the current tab or the selected content",
-        "description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+    "commandSaveSelectedTabs": {
+        "message": "Save the selected tabs or their selected contents",
+        "description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
     },
     "commandSaveAllTabs": {
         "message": "すべてのタブを保存",

+ 3 - 3
_locales/pl/messages.json

@@ -3,9 +3,9 @@
 		"message": "Zapisuj kompletną stronę w pojedynczym pliku HTML.",
 		"description": "Description of the extension."
 	},
-    "commandSaveTab": {
-        "message": "Zapisz bieżącą kartę lub wybraną zawartość",
-        "description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+    "commandSaveSelectedTabs": {
+        "message": "Save the selected tabs or their selected contents",
+        "description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
     },
     "commandSaveAllTabs": {
         "message": "Zapisz wszystkie karty",

+ 3 - 3
_locales/ru/messages.json

@@ -3,9 +3,9 @@
 		"message": "Сохранение полной страницы в едином HTML-файле",
 		"description": "Description of the extension."
 	},
-	"commandSaveTab": {
-		"message": "Сохранить текущую вкладку или выделенное",
-		"description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+	"commandSaveSelectedTabs": {
+		"message": "Save the selected tabs or their selected contents",
+		"description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
 	},
 	"commandSaveAllTabs": {
 		"message": "Сохранить все вкладки",

+ 3 - 3
_locales/uk/messages.json

@@ -3,9 +3,9 @@
 		"message": "Зберегти всю сторінку в один HTML-файл",
 		"description": "Description of the extension."
 	},
-    "commandSaveTab": {
-        "message": "Save the current tab or the selected content",
-        "description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+    "commandSaveSelectedTabs": {
+        "message": "Save the selected tabs or their selected contents",
+        "description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
     },
     "commandSaveAllTabs": {
         "message": "Зберегти всі вкладки",

+ 3 - 3
_locales/zh_CN/messages.json

@@ -3,9 +3,9 @@
 		"message": "将一个完整的页面保存到单个 HTML 文件中",
 		"description": "Description of the extension."
 	},
-	"commandSaveTab": {
-		"message": "保存当前标签页或选中部分",
-		"description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+	"commandSaveSelectedTabs": {
+		"message": "Save the selected tabs or their selected contents",
+		"description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
 	},
 	"commandSaveAllTabs": {
 		"message": "保存所有标签页",

+ 3 - 3
_locales/zh_TW/messages.json

@@ -3,9 +3,9 @@
 		"message": "將一個完整的頁面保存到單個 HTML 文件中",
 		"description": "Description of the extension."
 	},
-	"commandSaveTab": {
-		"message": "保存當前標籤頁或選中部分",
-		"description": "Command (Ctrl+Shift+Y): 'Save the current tab or the selected content'"
+	"commandSaveSelectedTabs": {
+		"message": "Save the selected tabs or their selected contents",
+		"description": "Command (Ctrl+Shift+Y): 'Save the selected tabs or their selected contents'"
 	},
 	"commandSaveAllTabs": {
 		"message": "保存所有標籤頁",

+ 5 - 11
cli/README.MD

@@ -12,6 +12,10 @@ SingleFile can be launched from the command line by running it into a (headless)
 
 - There are 3 ways to download the code of SingleFile, choose the one you prefer (`npm` is installed with Node.js):
 
+  - Download and install globally with `npm`
+    
+    `npm install -g "gildas-lormeau/SingleFile#master"`
+  
   - Download and unzip manually the [master archive](https://github.com/gildas-lormeau/SingleFile/archive/master.zip) provided by Github      
 
     `unzip master.zip .`
@@ -22,16 +26,6 @@ SingleFile can be launched from the command line by running it into a (headless)
   
     `cd cli`    
     
-  - Download with `npm`
-    
-    `npm install 'gildas-lormeau/SingleFile#master'`
-       
-    `cd node-modules`
-       
-    `cd single-file`
-       
-    `cd cli`  
-  
   - Download with `git`
 
     `git clone --depth 1 --recursive https://github.com/gildas-lormeau/SingleFile.git`
@@ -42,7 +36,7 @@ SingleFile can be launched from the command line by running it into a (headless)
   
     `cd cli`           
   
-- Make `single-file` executable (Linux/Unix/BSD etc.).
+- Make `single-file` executable (Linux/Unix/BSD etc.) if SingleFile is not installed globally.
 
   `chmod +x single-file`
 

+ 5 - 0
extension/core/content/content-bootstrap.js

@@ -125,15 +125,20 @@ this.singlefile.extension.core.content.bootstrap = this.singlefile.extension.cor
 				await autoSavePage();
 			} else {
 				let frames = [];
+				let framesSessionId;
 				autoSaveTimeout = null;
 				if (!options.removeFrames && singlefile.lib.processors.frameTree.content.frames && window.frames && window.frames.length) {
 					frames = await singlefile.lib.processors.frameTree.content.frames.getAsync(options);
 				}
+				framesSessionId = frames && frames.sessionId;
 				if (options.userScriptEnabled && helper.waitForUserScript) {
 					await helper.waitForUserScript(helper.ON_BEFORE_CAPTURE_EVENT_NAME);
 				}
 				const docData = helper.preProcessDoc(document, window, options);
 				savePage(docData, frames);
+				if (framesSessionId) {
+					singlefile.lib.processors.frameTree.content.frames.cleanup(framesSessionId);
+				}
 				helper.postProcessDoc(document, docData.markedElements);
 				if (options.userScriptEnabled && helper.waitForUserScript) {
 					await helper.waitForUserScript(helper.ON_AFTER_CAPTURE_EVENT_NAME);

+ 4 - 2
extension/core/content/content-main.js

@@ -106,6 +106,7 @@ this.singlefile.extension.core.content.main = this.singlefile.extension.core.con
 
 	async function processPage(options) {
 		const frames = singlefile.lib.processors.frameTree.content.frames;
+		let framesSessionId;
 		singlefile.lib.helper.initDoc(document);
 		ui.onStartPage(options);
 		processor = new singlefile.lib.SingleFile(options);
@@ -184,6 +185,7 @@ this.singlefile.extension.core.content.main = this.singlefile.extension.core.con
 			};
 			preInitializationAllPromises.then(() => resolve(preInitializationAllPromises));
 		});
+		framesSessionId = options.frames && options.frames.sessionId;
 		const selectedFrame = options.frames && options.frames.find(frameData => frameData.requestedFrame);
 		options.win = window;
 		if (selectedFrame) {
@@ -203,8 +205,8 @@ this.singlefile.extension.core.content.main = this.singlefile.extension.core.con
 		if (!processor.cancelled) {
 			await processor.run();
 		}
-		if (!options.saveRawPage && !options.removeFrames && frames) {
-			frames.cleanup(options);
+		if (framesSessionId) {
+			frames.cleanup(framesSessionId);
 		}
 		let page;
 		if (!processor.cancelled) {

+ 9 - 16
extension/lib/single-file/fetch/content/content-fetch.js

@@ -21,12 +21,13 @@
  *   Source.
  */
 
-/* global browser, window, CustomEvent */
+/* global browser, window, CustomEvent, setTimeout */
 
 this.singlefile.extension.lib.fetch.content.resources = this.singlefile.extension.lib.fetch.content.resources || (() => {
 
 	const FETCH_REQUEST_EVENT = "single-file-request-fetch";
 	const FETCH_RESPONSE_EVENT = "single-file-response-fetch";
+	const HOST_FETCH_MAX_DELAY = 5000;
 	const addEventListener = (type, listener, options) => window.addEventListener(type, listener, options);
 	const dispatchEvent = event => window.dispatchEvent(event);
 	const removeEventListener = (type, listener, options) => window.removeEventListener(type, listener, options);
@@ -42,7 +43,11 @@ this.singlefile.extension.lib.fetch.content.resources = this.singlefile.extensio
 		try {
 			let response = await fetch(message.url, { cache: "force-cache" });
 			if (response.status == 401 || response.status == 403 || response.status == 404) {
-				response = hostFetch(message.url);
+				response = await Promise.race(
+					[
+						hostFetch(message.url),
+						new Promise((resolve, reject) => setTimeout(() => reject(), HOST_FETCH_MAX_DELAY))
+					]);
 			}
 			return {
 				status: response.status,
@@ -78,14 +83,7 @@ this.singlefile.extension.lib.fetch.content.resources = this.singlefile.extensio
 			const response = await sendMessage({ method: "singlefile.fetchFrame", url, frameId });
 			return {
 				status: response.status,
-				headers: {
-					get: headerName => {
-						const headerArray = response.headers.find(headerArray => headerArray[0] == headerName);
-						if (headerArray) {
-							return headerArray[1];
-						}
-					}
-				},
+				headers: new Map(response.headers),
 				arrayBuffer: async () => new Uint8Array(response.array).buffer
 			};
 		}
@@ -112,12 +110,7 @@ this.singlefile.extension.lib.fetch.content.resources = this.singlefile.extensio
 						if (event.detail.response) {
 							resolve({
 								status: event.detail.status,
-								headers: {
-									get: name => {
-										const header = event.detail.headers.find(header => header[0] == name);
-										return header && header[1];
-									}
-								},
+								headers: new Map(event.detail.headers),
 								arrayBuffer: async () => event.detail.response
 							});
 						} else {

+ 1 - 1
extension/lib/woleet/woleet.js

@@ -26,7 +26,7 @@
 this.woleet = this.woleet || (() => {
 
 	const urlService = "https://api.woleet.io/v1/anchor";
-	const apiKey = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhYzZmZTMzMi0wODNjLTRjZmMtYmYxNC0xNWU5MTJmMWY4OWIiLCJpYXQiOjE1NzYxNzQzNDV9.n31j9ctJj7R1Vjwyc5yd1d6Cmg0NDnpwSaLWsqtZJQA";
+	const apiKey = "__WOLEET_API_KEY__";
 
 	return {
 		anchor

+ 2 - 3
extension/ui/bg/ui-commands.js

@@ -30,9 +30,8 @@ singlefile.extension.ui.bg.commands = (() => {
 
 	if (BROWSER_COMMANDS_API_SUPPORTED) {
 		commands.onCommand.addListener(async command => {
-			if (command == "save-tab") {
-				const allTabs = await singlefile.extension.core.bg.tabs.get({ currentWindow: true, active: true });
-				allTabs.length = 1;
+			if (command == "save-selected-tabs") {
+				const allTabs = await singlefile.extension.core.bg.tabs.get({ currentWindow: true, highlighted: true });
 				singlefile.extension.core.bg.business.saveTabs(allTabs, { optionallySelected: true });
 			}
 			if (command == "save-all-tabs") {

+ 6 - 4
lib/single-file/index.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-/* global window, setTimeout */
+/* global window */
 
 this.singlefile = this.singlefile || {
 	lib: {
@@ -40,6 +40,7 @@ this.singlefile = this.singlefile || {
 		modules: {},
 		async getPageData(options = {}, initOptions, doc = window.document, win = window) {
 			const frames = this.processors.frameTree.content.frames;
+			let framesSessionId;
 			this.main.init(initOptions);
 			if (doc && window) {
 				this.helper.initDoc(doc);
@@ -48,7 +49,7 @@ this.singlefile = this.singlefile || {
 					if (!options.removeFrames && frames && window.frames && window.frames.length) {
 						let frameTreePromise;
 						if (options.loadDeferredImages) {
-							frameTreePromise = new Promise(resolve => setTimeout(() => resolve(frames.getAsync(options)), options.loadDeferredImagesMaxIdleTime - frames.TIMEOUT_INIT_REQUEST_MESSAGE));
+							frameTreePromise = new Promise(resolve => window.setTimeout(() => resolve(frames.getAsync(options)), options.loadDeferredImagesMaxIdleTime - frames.TIMEOUT_INIT_REQUEST_MESSAGE));
 						} else {
 							frameTreePromise = frames.getAsync(options);
 						}
@@ -59,6 +60,7 @@ this.singlefile = this.singlefile || {
 					}
 				}
 				[options.frames] = await Promise.all(preInitializationPromises);
+				framesSessionId = options.frames && options.frames.sessionId;
 			}
 			options.doc = doc;
 			options.win = win;
@@ -73,8 +75,8 @@ this.singlefile = this.singlefile || {
 			};
 			const processor = new this.SingleFile(options);
 			await processor.run();
-			if (!options.saveRawPage && !options.removeFrames && frames) {
-				frames.cleanup(options);
+			if (framesSessionId) {
+				frames.cleanup(framesSessionId);
 			}
 			return await processor.getPageData();
 		}

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

@@ -28,7 +28,6 @@ this.singlefile.lib.modules.fontsAltMinifier = this.singlefile.lib.modules.fonts
 	const singlefile = this.singlefile;
 
 	const FontFace = window.FontFace;
-	const setTimeout = window.setTimeout;
 
 	const REGEXP_URL_SIMPLE_QUOTES_FN = /url\s*\(\s*'(.*?)'\s*\)/i;
 	const REGEXP_URL_DOUBLE_QUOTES_FN = /url\s*\(\s*"(.*?)"\s*\)/i;
@@ -231,7 +230,7 @@ this.singlefile.lib.modules.fontsAltMinifier = this.singlefile.lib.modules.fonts
 										await Promise.race(
 											[
 												fontFace.load().then(() => fontFace.loaded).then(() => source.valid = true),
-												new Promise(resolve => setTimeout.call(window, () => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
+												new Promise(resolve => window.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
 											]);
 									} catch (error) {
 										// ignored

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

@@ -27,8 +27,6 @@ this.singlefile.lib.modules.fontsMinifier = this.singlefile.lib.modules.fontsMin
 
 	const singlefile = this.singlefile;
 
-	const getComputedStyle = window.getComputedStyle;
-
 	const REGEXP_COMMA = /\s*,\s*/;
 	const REGEXP_DASH = /-/;
 	const REGEXP_QUESTION_MARK = /\?/g;
@@ -59,11 +57,11 @@ this.singlefile.lib.modules.fontsMinifier = this.singlefile.lib.modules.fontsMin
 			});
 			workStyleElement.remove();
 			docContent += doc.body.innerText;
-			if (getComputedStyle && options.doc) {
+			if (window.getComputedStyle && options.doc) {
 				fontsInfo.used = fontsInfo.used.map(fontNames => fontNames.map(familyName => {
 					const matchedVar = familyName.match(/^var\((--.*)\)$/);
 					if (matchedVar && matchedVar[1]) {
-						const computedFamilyName = getComputedStyle.call(window, options.doc.body).getPropertyValue(matchedVar[1]);
+						const computedFamilyName = window.getComputedStyle(options.doc.body).getPropertyValue(matchedVar[1]);
 						return (computedFamilyName && computedFamilyName.split(",").map(name => singlefile.lib.helper.normalizeFontFamily(name))) || familyName;
 					}
 					return familyName;

+ 31 - 15
lib/single-file/processors/frame-tree/content/content-frame-tree.js

@@ -46,8 +46,6 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 	const top = window.top;
 	const MessageChannel = window.MessageChannel;
 	const document = window.document;
-	const setTimeout = window.setTimeout;
-	const clearTimeout = window.clearTimeout;
 
 	const sessions = new Map();
 	let windowId;
@@ -67,7 +65,7 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 			});
 		}
 	}
-	addEventListener.call(window, "message", async event => {
+	addEventListener("message", async event => {
 		if (typeof event.data == "string" && event.data.startsWith(MESSAGE_PREFIX)) {
 			event.preventDefault();
 			event.stopPropagation();
@@ -88,7 +86,7 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 				createFrameResponseTimeout(message.sessionId, message.windowId);
 			} else if (message.method == CLEANUP_REQUEST_MESSAGE) {
 				cleanupRequest(message);
-			} else if ((!browser || !browser.runtime) && message.method == INIT_RESPONSE_MESSAGE) {
+			} else if (message.method == INIT_RESPONSE_MESSAGE && sessions.get(message.sessionId)) {
 				const port = event.ports[0];
 				port.onmessage = event => initResponse(event.data);
 			}
@@ -96,28 +94,46 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 	}, true);
 	return {
 		getAsync: options => {
-			const sessionId = options.sessionId || 0;
+			const sessionId = getNewSessionId();
 			options = JSON.parse(JSON.stringify(options));
 			return new Promise(resolve => {
-				sessions.set(sessionId, { frames: [], requestTimeouts: {}, responseTimeouts: {}, resolve });
+				sessions.set(sessionId, {
+					frames: [],
+					requestTimeouts: {},
+					responseTimeouts: {},
+					resolve: frames => {
+						frames.sessionId = sessionId;
+						resolve(frames);
+					}
+				});
 				initRequestAsync({ windowId, sessionId, options });
 			});
 		},
 		getSync: options => {
-			const sessionId = options.sessionId || 0;
+			const sessionId = getNewSessionId();
 			options = JSON.parse(JSON.stringify(options));
-			sessions.set(sessionId, { frames: [], requestTimeouts: {}, responseTimeouts: {} });
+			sessions.set(sessionId, {
+				frames: [],
+				requestTimeouts: {},
+				responseTimeouts: {}
+			});
 			initRequestSync({ windowId, sessionId, options });
-			return sessions.get(sessionId).frames;
+			const frames = sessions.get(sessionId).frames;
+			frames.sessionId = sessionId;
+			return frames;
 		},
-		cleanup: options => {
-			const sessionId = options.sessionId || 0;
+		cleanup: sessionId => {
+			sessions.delete(sessionId);
 			cleanupRequest({ windowId, sessionId, options: { sessionId } });
 		},
 		initResponse,
 		TIMEOUT_INIT_REQUEST_MESSAGE
 	};
 
+	function getNewSessionId() {
+		return window.crypto.getRandomValues(new Uint32Array(32)).join("");
+	}
+
 	function initRequestSync(message) {
 		const waitForUserScript = singlefile.lib.helper.waitForUserScript;
 		const sessionId = message.sessionId;
@@ -235,7 +251,7 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 			} catch (error) {
 				// ignored
 			}
-			requestTimeouts[windowId] = setTimeout.call(window, () => sendInitResponse({ frames: [{ windowId, processed: true }], sessionId }), TIMEOUT_INIT_REQUEST_MESSAGE);
+			requestTimeouts[windowId] = window.setTimeout(() => sendInitResponse({ frames: [{ windowId, processed: true }], sessionId }), TIMEOUT_INIT_REQUEST_MESSAGE);
 		});
 		delete doc.documentElement.dataset.requestedFrameId;
 	}
@@ -271,7 +287,7 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 		if (session && session[type]) {
 			const timeout = session[type][windowId];
 			if (timeout) {
-				clearTimeout.call(window, timeout);
+				window.clearTimeout(timeout);
 				delete session[type][windowId];
 			}
 		}
@@ -280,7 +296,7 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 	function createFrameResponseTimeout(sessionId, windowId) {
 		const session = sessions.get(sessionId);
 		if (session && session.responseTimeouts) {
-			session.responseTimeouts[windowId] = setTimeout.call(window, () => sendInitResponse({ frames: [{ windowId: windowId, processed: true }], sessionId: sessionId }), TIMEOUT_INIT_RESPONSE_MESSAGE);
+			session.responseTimeouts[windowId] = window.setTimeout(() => sendInitResponse({ frames: [{ windowId: windowId, processed: true }], sessionId: sessionId }), TIMEOUT_INIT_RESPONSE_MESSAGE);
 		}
 	}
 
@@ -327,7 +343,7 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 		} else {
 			if (useChannel) {
 				const channel = new MessageChannel();
-				targetWindow.postMessage(MESSAGE_PREFIX + JSON.stringify({ method: message.method }), TARGET_ORIGIN, [channel.port2]);
+				targetWindow.postMessage(MESSAGE_PREFIX + JSON.stringify({ method: message.method, sessionId: message.sessionId }), TARGET_ORIGIN, [channel.port2]);
 				channel.port1.postMessage(message);
 			} else {
 				targetWindow.postMessage(MESSAGE_PREFIX + JSON.stringify(message), TARGET_ORIGIN);

+ 29 - 23
lib/single-file/processors/hooks/content/content-hooks-frames-web.js

@@ -58,13 +58,13 @@
 	const FileReader = window.FileReader;
 	const Blob = window.Blob;
 	const console = window.console;
-	const warn = (console && console.warn) || (() => { });
+	const warn = (console && console.warn && ((...args) => console.warn(...args))) || (() => { });
 
 	const observers = new Map();
 	const observedElements = new Map();
 
-	addEventListener.call(window, LOAD_DEFERRED_IMAGES_START_EVENT, () => loadDeferredImagesStart());
-	addEventListener.call(window, LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT, () => loadDeferredImagesStart(true));
+	addEventListener(LOAD_DEFERRED_IMAGES_START_EVENT, () => loadDeferredImagesStart());
+	addEventListener(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT, () => loadDeferredImagesStart(true));
 
 	function loadDeferredImagesStart(keepZoomLevel) {
 		const scrollingElement = document.scrollingElement || document.documentElement;
@@ -108,20 +108,20 @@
 					const result = new Image(...arguments);
 					result.__defineSetter__("src", function (value) {
 						image.src = value;
-						dispatchEvent.call(window, new CustomEvent(LOAD_IMAGE_EVENT, { detail: image.src }));
+						dispatchEvent(new CustomEvent(LOAD_IMAGE_EVENT, { detail: image.src }));
 					});
 					result.__defineGetter__("src", function () {
 						return image.src;
 					});
 					result.__defineSetter__("srcset", function (value) {
-						dispatchEvent.call(window, new CustomEvent(LOAD_IMAGE_EVENT));
+						dispatchEvent(new CustomEvent(LOAD_IMAGE_EVENT));
 						image.srcset = value;
 					});
 					result.__defineGetter__("srcset", function () {
 						return image.srcset;
 					});
 					image.onload = image.onloadend = image.onerror = event => {
-						dispatchEvent.call(window, new CustomEvent(IMAGE_LOADED_EVENT, { detail: image.src }));
+						dispatchEvent(new CustomEvent(IMAGE_LOADED_EVENT, { detail: image.src }));
 						result.dispatchEvent(new UIEvent(event.type, event));
 					};
 					if (image.decode) {
@@ -150,8 +150,7 @@
 			document.documentElement.style.setProperty("transform-origin", (zoomFactorX < 1 ? "50%" : "0") + " " + (zoomFactorY < 1 ? "50%" : "0") + " 0", "important");
 			document.documentElement.style.setProperty("transform", "scale3d(" + zoomFactor + ", " + zoomFactor + ", 1)", "important");
 			document.documentElement.style.setProperty("min-height", (100 / zoomFactor) + "vh", "important");
-			dispatchEvent.call(window, new UIEvent("resize"));
-			dispatchEvent.call(window, new Event("scroll"));
+			dispatchResizeEvent();
 			if (keepZoomLevel) {
 				document.documentElement.style.setProperty("-sf-transform", transform, transformPriority);
 				document.documentElement.style.setProperty("-sf-transform-origin", transformOrigin, transformOriginPriority);
@@ -163,8 +162,7 @@
 			}
 		}
 		if (!keepZoomLevel) {
-			dispatchEvent.call(window, new UIEvent("resize"));
-			dispatchEvent.call(window, new Event("scroll"));
+			dispatchResizeEvent();
 			const docBoundingRect = scrollingElement.getBoundingClientRect();
 			[...observers].forEach(([intersectionObserver, observer]) => {
 				const rootBoundingRect = observer.options && observer.options.root && observer.options.root.getBoundingClientRect();
@@ -183,9 +181,9 @@
 		}
 	}
 
-	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, () => {
+	addEventListener(LOAD_DEFERRED_IMAGES_END_EVENT, () => loadDeferredImagesEnd());
+	addEventListener(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT, () => loadDeferredImagesEnd(true));
+	addEventListener(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");
@@ -226,12 +224,11 @@
 			delete window._singleFileImage;
 		}
 		if (!keepZoomLevel) {
-			dispatchEvent.call(window, new UIEvent("resize"));
-			dispatchEvent.call(window, new Event("scroll"));
+			dispatchResizeEvent();
 		}
 	}
 
-	addEventListener.call(window, BLOCK_COOKIES_START_EVENT, () => {
+	addEventListener(BLOCK_COOKIES_START_EVENT, () => {
 		try {
 			document.__defineGetter__("cookie", () => { throw new Error("document.cookie temporary blocked by SingleFile"); });
 		} catch (error) {
@@ -239,11 +236,11 @@
 		}
 	});
 
-	addEventListener.call(window, BLOCK_COOKIES_END_EVENT, () => {
+	addEventListener(BLOCK_COOKIES_END_EVENT, () => {
 		delete document.cookie;
 	});
 
-	addEventListener.call(window, BLOCK_STORAGE_START_EVENT, () => {
+	addEventListener(BLOCK_STORAGE_START_EVENT, () => {
 		if (!window._singleFile_localStorage) {
 			window._singleFile_localStorage = window.localStorage;
 			window.__defineGetter__("localStorage", () => { throw new Error("localStorage temporary blocked by SingleFile"); });
@@ -254,7 +251,7 @@
 		}
 	});
 
-	addEventListener.call(window, BLOCK_STORAGE_END_EVENT, () => {
+	addEventListener(BLOCK_STORAGE_END_EVENT, () => {
 		if (window._singleFile_localStorage) {
 			delete window.localStorage;
 			window.localStorage = window._singleFile_localStorage;
@@ -272,7 +269,7 @@
 		let warningFontFaceDisplayed;
 		window.FontFace = function () {
 			if (!warningFontFaceDisplayed) {
-				warn.call(console, "SingleFile is hooking the FontFace constructor to get font URLs."); // eslint-disable-line no-console
+				warn("SingleFile is hooking the FontFace constructor to get font URLs."); // eslint-disable-line no-console
 				warningFontFaceDisplayed = true;
 			}
 			const detail = {};
@@ -291,10 +288,10 @@
 				reader.readAsDataURL(new Blob([detail.src]));
 				reader.addEventListener("load", () => {
 					detail.src = "url(" + reader.result + ")";
-					dispatchEvent.call(window, new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
+					dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
 				});
 			} else {
-				dispatchEvent.call(window, new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
+				dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
 			}
 			return new FontFace(...arguments);
 		};
@@ -306,7 +303,7 @@
 		let warningIntersectionObserverDisplayed;
 		window.IntersectionObserver = function () {
 			if (!warningIntersectionObserverDisplayed) {
-				warn.call(console, "SingleFile is hooking the IntersectionObserver API to detect and load deferred images."); // eslint-disable-line no-console
+				warn("SingleFile is hooking the IntersectionObserver API to detect and load deferred images."); // eslint-disable-line no-console
 				warningIntersectionObserverDisplayed = true;
 			}
 			const intersectionObserver = new IntersectionObserver(...arguments);
@@ -347,4 +344,13 @@
 		window.IntersectionObserver.toString = function () { return "function IntersectionObserver() { [native code] }"; };
 	}
 
+	function dispatchResizeEvent() {
+		try {
+			dispatchEvent(new UIEvent("resize"));
+			document.dispatchEvent(new Event("scroll", { bubbles: true }));
+		} catch (error) {
+			// ignored
+		}
+	}
+
 })();

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

@@ -51,7 +51,7 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 
 	if (document instanceof HTMLDocument) {
 		if (browser && browser.runtime && browser.runtime.getURL) {
-			addEventListener.call(window, NEW_FONT_FACE_EVENT, event => {
+			addEventListener(NEW_FONT_FACE_EVENT, event => {
 				const detail = event.detail;
 				if (!fontFaces.find(fontFace => JSON.stringify(fontFace) == JSON.stringify(detail))) {
 					fontFaces.push(event.detail);
@@ -73,32 +73,32 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 		getFontsData: () => fontFaces,
 		loadDeferredImagesStart: options => {
 			if (options.loadDeferredImagesBlockCookies) {
-				dispatchEvent.call(window, new CustomEvent(BLOCK_COOKIES_START_EVENT));
+				dispatchEvent(new CustomEvent(BLOCK_COOKIES_START_EVENT));
 			}
 			if (options.loadDeferredImagesBlockStorage) {
-				dispatchEvent.call(window, new CustomEvent(BLOCK_STORAGE_START_EVENT));
+				dispatchEvent(new CustomEvent(BLOCK_STORAGE_START_EVENT));
 			}
 			if (options.loadDeferredImagesKeepZoomLevel) {
-				dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT));
+				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT));
 			} else {
-				dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT));
+				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT));
 			}
 		},
 		loadDeferredImagesEnd: options => {
 			if (options.loadDeferredImagesBlockCookies) {
-				dispatchEvent.call(window, new CustomEvent(BLOCK_COOKIES_END_EVENT));
+				dispatchEvent(new CustomEvent(BLOCK_COOKIES_END_EVENT));
 			}
 			if (options.loadDeferredImagesBlockStorage) {
-				dispatchEvent.call(window, new CustomEvent(BLOCK_STORAGE_END_EVENT));
+				dispatchEvent(new CustomEvent(BLOCK_STORAGE_END_EVENT));
 			}
 			if (options.loadDeferredImagesKeepZoomLevel) {
-				dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT));
+				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT));
 			} else {
-				dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT));
+				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT));
 			}
 		},
 		loadDeferredImagesResetZoomLevel: () => {
-			dispatchEvent.call(window, new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_END_EVENT));
+			dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_END_EVENT));
 		},
 		LOAD_IMAGE_EVENT,
 		IMAGE_LOADED_EVENT
@@ -106,7 +106,7 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 
 	function injectedScript() {
 		const console = window.console;
-		const warn = (console && console.warn) || (() => { });
+		const warn = (console && console.warn && ((...args) => console.warn(...args))) || (() => { });
 		const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
 		const FONT_STYLE_PROPERTIES = {
 			family: "font-family",
@@ -123,7 +123,7 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 			let warningFontFaceDisplayed;
 			window.FontFace = function () {
 				if (!warningFontFaceDisplayed) {
-					warn.call(console, "SingleFile is hooking the FontFace constructor to get font URLs."); // eslint-disable-line no-console
+					warn("SingleFile is hooking the FontFace constructor to get font URLs."); // eslint-disable-line no-console
 					warningFontFaceDisplayed = true;
 				}
 				const detail = {};
@@ -142,10 +142,10 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 					reader.readAsDataURL(new Blob([detail.src]));
 					reader.addEventListener("load", () => {
 						detail.src = "url(" + reader.result + ")";
-						dispatchEvent.call(window, new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
+						dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
 					});
 				} else {
-					dispatchEvent.call(window, new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
+					dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
 				}
 				return new FontFace(...arguments);
 			};

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

@@ -33,7 +33,7 @@
 	const addEventListener = (type, listener, options) => window.addEventListener(type, listener, options);
 	const dispatchEvent = event => window.dispatchEvent(event);	
 
-	addEventListener.call(window, FETCH_REQUEST_EVENT, async event => {
+	addEventListener(FETCH_REQUEST_EVENT, async event => {
 		const url = event.detail;
 		let detail;
 		try {
@@ -42,7 +42,7 @@
 		} catch (error) {
 			detail = { url, error: error.toString() };
 		}
-		dispatchEvent.call(window, new CustomEvent(FETCH_RESPONSE_EVENT, { detail }));
+		dispatchEvent(new CustomEvent(FETCH_RESPONSE_EVENT, { detail }));
 	});
 
 })();

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

@@ -32,8 +32,6 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
 	const browser = this.browser;
 	const document = window.document;
 	const MutationObserver = window.MutationObserver;
-	const setTimeout = window.setTimeout;
-	const clearTimeout = window.clearTimeout;
 	const addEventListener = (type, listener, options) => window.addEventListener(type, listener, options);
 	const removeEventListener = (type, listener, options) => window.removeEventListener(type, listener, options);
 
@@ -90,8 +88,8 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
 			maxTimeoutId = await deferForceLazyLoadEnd(timeoutId, idleTimeoutId, maxTimeoutId, observer, options, cleanupAndResolve);
 			observer.observe(document, { subtree: true, childList: true, attributes: true });
 			if (frames) {
-				addEventListener.call(window, frames.LOAD_IMAGE_EVENT, onImageLoadEvent);
-				addEventListener.call(window, frames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
+				addEventListener(frames.LOAD_IMAGE_EVENT, onImageLoadEvent);
+				addEventListener(frames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
 				frames.loadDeferredImagesStart(options);
 			}
 
@@ -120,8 +118,8 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
 			function cleanupAndResolve(value) {
 				observer.disconnect();
 				if (frames) {
-					removeEventListener.call(window, frames.LOAD_IMAGE_EVENT, onImageLoadEvent);
-					removeEventListener.call(window, frames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
+					removeEventListener(frames.LOAD_IMAGE_EVENT, onImageLoadEvent);
+					removeEventListener(frames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
 				}
 				resolve(value);
 			}
@@ -163,7 +161,7 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
 			browser.runtime.onMessage.addListener(timeoutCallback);
 			return timeoutId;
 		} else {
-			return setTimeout.call(window, callback, delay);
+			return window.setTimeout(callback, delay);
 		}
 	}
 
@@ -171,7 +169,7 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
 		if (browser && browser.runtime && browser.runtime.sendMessage) {
 			await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.clearTimeout", id: timeoutId });
 		} else {
-			return clearTimeout.call(window, timeoutId);
+			return window.clearTimeout(timeoutId);
 		}
 	}
 

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

@@ -25,7 +25,7 @@ this.singlefile.lib.core = this.singlefile.lib.core || (() => {
 
 	const DEBUG = false;
 
-	let util, cssTree, sessionId = 0;
+	let util, cssTree;
 
 	function getClass(...args) {
 		[util, cssTree] = args;
@@ -35,10 +35,6 @@ this.singlefile.lib.core = this.singlefile.lib.core || (() => {
 	class SingleFileClass {
 		constructor(options) {
 			this.options = options;
-			if (options.sessionId === undefined) {
-				options.sessionId = sessionId;
-				sessionId++;
-			}
 		}
 		async run() {
 			if (this.options.userScriptEnabled) {
@@ -1579,7 +1575,7 @@ this.singlefile.lib.core = this.singlefile.lib.core || (() => {
 								ancestorStyleSheets.add(resourceURL);
 								importedStylesheetContent = await ProcessorHelper.resolveImportURLs(importedStylesheetContent, resourceURL, options, workStylesheet, ancestorStyleSheets);
 								workStylesheet.textContent = importedStylesheetContent;
-								if ((workStylesheet.sheet && (workStylesheet.sheet.cssRules.length) || (!workStylesheet.sheet && importedStylesheetContent))) {
+								if ((workStylesheet.sheet && workStylesheet.sheet.cssRules.length) || (!workStylesheet.sheet && importedStylesheetContent)) {
 									stylesheetContent = stylesheetContent.replace(regExpCssImport, importedStylesheetContent);
 								} else {
 									stylesheetContent = stylesheetContent.replace(regExpCssImport, "");

+ 3 - 3
manifest.json

@@ -8,7 +8,7 @@
 		"64": "extension/ui/resources/icon_64.png",
 		"128": "extension/ui/resources/icon_128.png"
 	},
-	"version": "1.18.20",
+	"version": "1.18.23",
 	"description": "__MSG_extensionDescription__",
 	"content_scripts": [
 		{
@@ -126,11 +126,11 @@
 		"default_title": "__MSG_buttonDefaultTooltip__"
 	},
 	"commands": {
-		"save-tab": {
+		"save-selected-tabs": {
 			"suggested_key": {
 				"default": "Ctrl+Shift+Y"
 			},
-			"description": "__MSG_commandSaveTab__"
+			"description": "__MSG_commandSaveSelectedTabs__"
 		},
 		"save-all-tabs": {
 			"suggested_key": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 	"name": "single-file",
-	"version": "0.1.20",
+	"version": "0.1.23",
 	"description": "SingleFile",
 	"author": "Gildas Lormeau",
 	"license": "AGPL-3.0-or-later",