Explorar el Código

refactored message passing implementation

Gildas hace 7 años
padre
commit
abd31cd72d

+ 46 - 72
extension/core/bg/autosave.js

@@ -18,55 +18,68 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global browser, singlefile, SingleFileBrowser, URL, Blob */
+/* global singlefile, SingleFileBrowser, URL, Blob */
 
 singlefile.autosave = (() => {
 
-	browser.runtime.onMessage.addListener((message, sender) => {
-		if (message.isAutoSaveEnabled) {
-			return isAutoSaveEnabled(sender.tab);
+	return {
+		onMessage,
+		onMessageExternal,
+		onTabUpdated,
+		isEnabled,
+		refreshTabs
+	};
+
+	async function onMessage(message, sender) {
+		if (message.initAutoSave) {
+			const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(sender.tab.url, true), isEnabled(sender.tab)]);
+			return { options, autoSaveEnabled };
 		}
 		if (message.autoSaveContent) {
-			saveContent(message, sender.tab);
+			return saveContent(message, sender.tab);
 		}
-	});
-	if (browser.runtime.onMessageExternal) {
-		browser.runtime.onMessageExternal.addListener(async message => {
-			if (message.method == "enableAutoSave") {
-				enableActiveTab(message.enabled);
-			}
-			if (message.method == "isAutoSaveEnabled") {
-				const tabs = await browser.tabs.query({ currentWindow: true, active: true });
-				const tab = tabs[0];
-				if (tab && singlefile.core.isAllowedURL(tab.url)) {
-					const tabId = tab.id;
-					const tabsData = await singlefile.tabsData.get();
-					return tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || (tabsData[tabId] && tabsData[tabId].autoSave);
-				}
-				return false;
-			}
-		});
 	}
 
-	return {
-		onTabUpdated,
-		enabled,
-		refresh
-	};
+	async function onMessageExternal(message, currentTab) {
+		if (message.method == "enableAutoSave") {
+			const tabsData = await singlefile.tabsData.get(currentTab.id);
+			tabsData[currentTab.id].autoSave = message.enabled;
+			await singlefile.tabsData.set(tabsData);
+			singlefile.ui.refresh(currentTab);
+		}
+		if (message.method == "isAutoSaveEnabled") {
+			return await isEnabled(currentTab);
+		}
+	}
 
 	async function onTabUpdated(tabId, changeInfo, tab) {
-		const tabsData = await singlefile.tabsData.get();
-		const options = await singlefile.config.getOptions(tabsData.profileName, tab.url, true);
-		if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && (tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || (tabsData[tab.id] && tabsData[tab.id].autoSave)))) {
+		const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(tab.url, true), isEnabled(tab)]);
+		if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && autoSaveEnabled)) {
 			if (changeInfo.status == "complete") {
-				singlefile.ui.saveTab(tab, { autoSave: true });
+				singlefile.core.saveTab(tab, { autoSave: true });
 			}
 		}
 	}
 
+	async function isEnabled(tab) {
+		const [tabsData, rule] = await Promise.all([singlefile.tabsData.get(), singlefile.config.getRule(tab.url)]);
+		return singlefile.util.isAllowedURL(tab.url) && Boolean(tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || tabsData[tab.id].autoSave) && (!rule || rule.autoSaveProfile != singlefile.config.DISABLED_PROFILE_NAME);
+	}
+
+	async function refreshTabs() {
+		const tabs = await singlefile.tabs.get({});
+		return Promise.all(tabs.map(async tab => {
+			try {
+				const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(tab.url, true), isEnabled(tab)]);
+				await singlefile.tabs.sendMessage(tab.id, { initAutoSave: true, autoSaveEnabled, options });
+			} catch (error) {
+				/* ignored */
+			}
+		}));
+	}
+
 	async function saveContent(message, tab) {
-		const tabsData = await singlefile.tabsData.get();
-		const options = await singlefile.config.getOptions(tabsData.profileName, tab.url, true);
+		const options = await singlefile.config.getOptions(tab.url, true);
 		const tabId = tab.id;
 		options.content = message.content;
 		options.url = message.url;
@@ -106,43 +119,4 @@ singlefile.autosave = (() => {
 		return singlefile.download.downloadPage(page, options);
 	}
 
-	async function enableActiveTab(enabled) {
-		const tabs = await browser.tabs.query({ currentWindow: true, active: true });
-		const tab = tabs[0];
-		if (tab) {
-			const tabId = tab.id;
-			const tabsData = await singlefile.tabsData.get();
-			if (!tabsData[tabId]) {
-				tabsData[tabId] = {};
-			}
-			tabsData[tabId].autoSave = enabled;
-			await singlefile.tabsData.set(tabsData);
-			singlefile.ui.refresh(tab);
-		}
-	}
-
-	async function isAutoSaveEnabled(tab) {
-		const tabsData = await singlefile.tabsData.get();
-		const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(tabsData.profileName, tab.url, true), enabled(tab.id)]);
-		return { autoSaveEnabled, options };
-	}
-
-	async function enabled(tabId) {
-		const tabsData = await singlefile.tabsData.get();
-		return Boolean(tabsData.autoSaveAll || tabsData.autoSaveUnpinned || (tabsData[tabId] && tabsData[tabId].autoSave));
-	}
-
-	async function refresh() {
-		const tabs = await browser.tabs.query({});
-		return Promise.all(tabs.map(async tab => {
-			try {
-				const tabsData = await singlefile.tabsData.get();
-				const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(tabsData.profileName, tab.url, true), enabled(tab.id)]);
-				await browser.tabs.sendMessage(tab.id, { autoSaveUnloadEnabled: true, autoSaveEnabled, options });
-			} catch (error) {
-				/* ignored */
-			}
-		}));
-	}
-
 })();

+ 98 - 14
extension/core/bg/core.js

@@ -22,27 +22,111 @@
 
 singlefile.core = (() => {
 
-	const FORBIDDEN_URLS = ["https://chrome.google.com", "https://addons.mozilla.org"];
+	const contentScriptFiles = [
+		"/lib/hooks/hooks.js",
+		"/lib/browser-polyfill/chrome-browser-polyfill.js",
+		"/lib/single-file/vendor/css-tree.js",
+		"/lib/single-file/vendor/html-srcset-parser.js",
+		"/lib/single-file/util/timeout.js",
+		"/lib/single-file/util/doc-helper.js",
+		"/lib/fetch/content/fetch.js",
+		"/lib/single-file/single-file-core.js",
+		"/lib/single-file/single-file-browser.js",
+		"/extension/index.js",
+		"/extension/ui/content/content-ui.js",
+		"/extension/core/content/content.js"
+	];
 
-	return { saveTab, autoSaveTab, isAllowedURL };
+	const frameScriptFiles = [
+		"/lib/hooks/hooks-frame.js",
+		"/lib/browser-polyfill/chrome-browser-polyfill.js",
+		"/extension/index.js",
+		"/lib/single-file/util/doc-helper.js",
+		"/lib/single-file/util/timeout.js",
+		"/lib/fetch/content/fetch.js",
+		"/lib/frame-tree/frame-tree.js",
+		"/extension/core/content/content-frame.js"
+	];
 
-	async function saveTab(tab, options) {
-		const tabsData = await singlefile.tabsData.get();
-		const mergedOptions = await singlefile.config.getOptions(tabsData.profileName, tab.url);
-		Object.keys(options).forEach(key => mergedOptions[key] = options[key]);
-		return singlefile.runner.saveTab(tab, mergedOptions);
+	const optionalContentScriptFiles = {
+		compressHTML: [
+			"/lib/single-file/modules/html-minifier.js",
+			"/lib/single-file/modules/html-serializer.js"
+		],
+		compressCSS: [
+			"/lib/single-file/vendor/css-minifier.js"
+		],
+		loadDeferredImages: [
+			"/lib/lazy/content/content-lazy-loader.js"
+		],
+		removeAlternativeImages: [
+			"/lib/single-file/modules/html-images-alt-minifier.js"
+		],
+		removeUnusedFonts: [
+			"/lib/single-file/vendor/css-font-property-parser.js",
+			"/lib/single-file/modules/css-fonts-minifier.js"
+		],
+		removeAlternativeFonts: [
+			"/lib/single-file/modules/css-fonts-alt-minifier.js"
+		],
+		removeUnusedStyles: [
+			"/lib/single-file/modules/css-matched-rules.js",
+			"/lib/single-file/modules/css-rules-minifier.js"
+		],
+		removeAlternativeMedias: [
+			"/lib/single-file/vendor/css-media-query-parser.js",
+			"/lib/single-file/modules/css-medias-alt-minifier.js"
+		]
+	};
+
+	return { saveTab };
+
+	async function saveTab(tab, options = {}) {
+		if (singlefile.util.isAllowedURL(tab.url)) {
+			const tabId = tab.id;
+			options.tabId = tabId;
+			try {
+				singlefile.ui.button.onInitialize(tabId, options, 1);
+				if (options.autoSave) {
+					const options = await singlefile.config.getOptions(tab.url, true);
+					if (singlefile.autosave.isEnabled(tab)) {
+						await singlefile.tabs.sendMessage(tab.id, { autoSavePage: true, options });
+					}
+				} else {
+					const mergedOptions = await singlefile.config.getOptions(tab.url);
+					Object.keys(options).forEach(key => mergedOptions[key] = options[key]);
+					if (!mergedOptions.removeFrames) {
+						await executeContentScripts(tab.id, frameScriptFiles, true, "document_start");
+					}
+					await executeContentScripts(tab.id, getContentScriptFiles(mergedOptions), false, "document_idle");
+					if (mergedOptions.frameId) {
+						await singlefile.tabs.sendMessage(tab.id, { saveFrame: true, options: mergedOptions }, { frameId: mergedOptions.frameId });
+					} else {
+						await singlefile.tabs.sendMessage(tab.id, { savePage: true, options: mergedOptions });
+					}
+				}
+				singlefile.ui.button.onInitialize(tabId, options, 2);
+			} catch (error) {
+				console.log(error); // eslint-disable-line no-console
+				singlefile.ui.button.onError(tabId, options);
+			}
+		}
 	}
 
-	async function autoSaveTab(tab) {
-		const tabsData = await singlefile.tabsData.get();
-		const options = await singlefile.config.getOptions(tabsData.profileName, tab.url, true);
-		if (singlefile.autosave.enabled(tab.id)) {
-			await browser.tabs.sendMessage(tab.id, { autoSavePage: true, options });
+	async function executeContentScripts(tabId, scriptFiles, allFrames, runAt) {
+		for (const file of scriptFiles) {
+			await browser.tabs.executeScript(tabId, { file, allFrames, runAt });
 		}
 	}
 
-	function isAllowedURL(url) {
-		return url && (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://")) && !FORBIDDEN_URLS.find(storeUrl => url.startsWith(storeUrl));
+	function getContentScriptFiles(options) {
+		let files = contentScriptFiles;
+		Object.keys(optionalContentScriptFiles).forEach(option => {
+			if (options[option]) {
+				files = optionalContentScriptFiles[option].concat(files);
+			}
+		});
+		return files;
 	}
 
 })();

+ 3 - 13
extension/core/bg/config.js → extension/core/bg/data/config.js

@@ -63,11 +63,6 @@ singlefile.config = (() => {
 	};
 
 	let pendingUpgradePromise = upgrade();
-	browser.runtime.onMessage.addListener(request => {
-		if (request.getOptions) {
-			return getUrlOptions(request.url);
-		}
-	});
 
 	async function upgrade() {
 		const config = await browser.storage.local.get();
@@ -128,12 +123,6 @@ singlefile.config = (() => {
 		}
 	}
 
-	async function getUrlOptions(url) {
-		const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
-		const rule = await getRule(url);
-		return rule ? config.profiles[rule["profile"]] : config.profiles[tabsData.profileName || singlefile.config.DEFAULT_PROFILE_NAME];
-	}
-
 	async function getRule(url) {
 		const config = await getConfig();
 		const regExpRules = config.rules.filter(rule => testRegExpRule(rule));
@@ -176,8 +165,9 @@ singlefile.config = (() => {
 		async getRule(url) {
 			return getRule(url);
 		},
-		async getOptions(profileName, url, autoSave) {
-			const [config, rule] = await Promise.all([getConfig(), getRule(url)]);
+		async getOptions(url, autoSave) {
+			const [config, rule, tabsData] = await Promise.all([getConfig(), getRule(url), singlefile.tabsData.get()]);
+			const profileName = tabsData.profileName;
 			return rule ? config.profiles[rule[autoSave ? "autoSaveProfile" : "profile"]] : config.profiles[profileName || singlefile.config.DEFAULT_PROFILE_NAME];
 		},
 		async updateProfile(profileName, profile) {

+ 14 - 11
extension/core/bg/tabs-data.js → extension/core/bg/data/tabs-data.js

@@ -37,27 +37,30 @@ singlefile.tabsData = (() => {
 		setPersistent(tabsData);
 	}
 
-	function getTemporary() {
-		if (temporaryData) {
-			return temporaryData;
-		} else {
+	function getTemporary(tabId) {
+		if (!temporaryData) {
 			temporaryData = {};
-			return {};
 		}
+		if (tabId !== undefined && !temporaryData[tabId]) {
+			temporaryData[tabId] = {};
+		}
+		return temporaryData;
 	}
 
-	async function getPersistent() {
-		if (persistentData) {
-			return persistentData;
-		} else {
+	async function getPersistent(tabId) {
+		if (!persistentData) {
 			const config = await browser.storage.local.get();
 			persistentData = config.tabsData || {};
-			await cleanup();
-			return persistentData;
+			cleanup();
+		}
+		if (tabId !== undefined && !persistentData[tabId]) {
+			persistentData[tabId] = {};
 		}
+		return persistentData;
 	}
 
 	async function setPersistent(tabsData) {
+		persistentData = tabsData;
 		await browser.storage.local.set({ tabsData });
 	}
 

+ 32 - 31
extension/core/bg/download.js

@@ -24,40 +24,41 @@ singlefile.download = (() => {
 
 	const partialContents = new Map();
 
-	browser.runtime.onMessage.addListener((request, sender) => {
-		if (request.download) {
-			try {
-				if (request.truncated) {
-					let partialContent = partialContents.get(sender.tab.id);
-					if (!partialContent) {
-						partialContent = [];
-						partialContents.set(sender.tab.id, partialContent);
-					}
-					partialContent.push(request.content);
-					if (request.finished) {
-						partialContents.delete(sender.tab.id);
-						request.url = URL.createObjectURL(new Blob(partialContent, { type: "text/html" }));
-					} else {
-						return Promise.resolve({});
-					}
-				} else if (request.content) {
-					request.url = URL.createObjectURL(new Blob([request.content], { type: "text/html" }));
+	return {
+		onMessage,
+		downloadPage
+	};
+
+	function onMessage(message, sender) {
+		try {
+			if (message.truncated) {
+				let partialContent = partialContents.get(sender.tab.id);
+				if (!partialContent) {
+					partialContent = [];
+					partialContents.set(sender.tab.id, partialContent);
+				}
+				partialContent.push(message.content);
+				if (message.finished) {
+					partialContents.delete(sender.tab.id);
+					message.url = URL.createObjectURL(new Blob(partialContent, { type: "text/html" }));
+				} else {
+					return Promise.resolve({});
 				}
-				return downloadPage(request, { confirmFilename: request.confirmFilename, incognito: sender.tab.incognito, conflictAction: request.filenameConflictAction })
-					.catch(error => {
-						if (error.message && error.message.includes("'incognito'")) {
-							return downloadPage(request, { confirmFilename: request.confirmFilename, conflictAction: request.filenameConflictAction });
-						} else {
-							return { notSupported: true };
-						}
-					});
-			} catch (error) {
-				return Promise.resolve({ notSupported: true });
+			} else if (message.content) {
+				message.url = URL.createObjectURL(new Blob([message.content], { type: "text/html" }));
 			}
+			return downloadPage(message, { confirmFilename: message.confirmFilename, incognito: sender.tab.incognito, conflictAction: message.filenameConflictAction })
+				.catch(error => {
+					if (error.message && error.message.includes("'incognito'")) {
+						return downloadPage(message, { confirmFilename: message.confirmFilename, conflictAction: message.filenameConflictAction });
+					} else {
+						return { notSupported: true };
+					}
+				});
+		} catch (error) {
+			return Promise.resolve({ notSupported: true });
 		}
-	});
-
-	return { downloadPage };
+	}
 
 	async function downloadPage(page, options) {
 		const downloadInfo = {

+ 52 - 0
extension/core/bg/messages.js

@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2019 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* global browser, singlefile, */
+
+singlefile.messages = (() => {
+
+	browser.runtime.onMessage.addListener((message, sender) => {
+		if (message.loadFileURI) {
+			return singlefile.tabs.onMessage(message, sender);
+		}
+		if (message.download) {
+			return singlefile.download.onMessage(message, sender);
+		}
+		if (message.initAutoSave || message.autoSaveContent) {
+			return singlefile.autosave.onMessage(message, sender);
+		}
+		if (message.loadURL || message.processProgress || message.processEnd || message.processError || message.processCancelled) {
+			return singlefile.ui.onMessage(message, sender);
+		}
+	});
+	if (browser.runtime.onMessageExternal) {
+		browser.runtime.onMessageExternal.addListener(async (message, sender) => {
+			const tabs = await singlefile.tabs.get({ currentWindow: true, active: true });
+			const currentTab = tabs[0];
+			if (currentTab && singlefile.util.isAllowedURL(currentTab.url)) {
+				return singlefile.autosave.onMessageExternal(message, currentTab, sender);
+			} else {
+				return false;
+			}
+		});
+	}
+	return {};
+
+})();

+ 0 - 112
extension/core/bg/runner.js

@@ -1,112 +0,0 @@
-/*
- * Copyright 2010-2019 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   SingleFile is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 3 of the License, or
- *   (at your option) any later version.
- *
- *   SingleFile is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-/* global browser, singlefile */
-
-singlefile.runner = (() => {
-
-	const contentScriptFiles = [
-		"/lib/hooks/hooks.js",
-		"/lib/browser-polyfill/chrome-browser-polyfill.js",
-		"/lib/single-file/vendor/css-tree.js",
-		"/lib/single-file/vendor/html-srcset-parser.js",
-		"/lib/single-file/util/timeout.js",
-		"/lib/single-file/util/doc-helper.js",
-		"/lib/fetch/content/fetch.js",
-		"/lib/single-file/single-file-core.js",
-		"/lib/single-file/single-file-browser.js",
-		"/extension/index.js",
-		"/extension/ui/content/content-ui.js",
-		"/extension/core/content/content.js"
-	];
-
-	const frameScriptFiles = [
-		"/lib/hooks/hooks-frame.js",
-		"/lib/browser-polyfill/chrome-browser-polyfill.js",
-		"/extension/index.js",
-		"/lib/single-file/util/doc-helper.js",
-		"/lib/single-file/util/timeout.js",
-		"/lib/fetch/content/fetch.js",
-		"/lib/frame-tree/frame-tree.js",
-		"/extension/core/content/content-frame.js"
-	];
-
-	const optionalContentScriptFiles = {
-		compressHTML: [
-			"/lib/single-file/modules/html-minifier.js",
-			"/lib/single-file/modules/html-serializer.js"
-		],
-		compressCSS: [
-			"/lib/single-file/vendor/css-minifier.js"
-		],
-		loadDeferredImages: [
-			"/lib/lazy/content/content-lazy-loader.js"
-		],
-		removeAlternativeImages: [
-			"/lib/single-file/modules/html-images-alt-minifier.js"
-		],
-		removeUnusedFonts: [
-			"/lib/single-file/vendor/css-font-property-parser.js",
-			"/lib/single-file/modules/css-fonts-minifier.js"
-		],
-		removeAlternativeFonts: [
-			"/lib/single-file/modules/css-fonts-alt-minifier.js"
-		],
-		removeUnusedStyles: [
-			"/lib/single-file/modules/css-matched-rules.js",
-			"/lib/single-file/modules/css-rules-minifier.js"
-		],
-		removeAlternativeMedias: [
-			"/lib/single-file/vendor/css-media-query-parser.js",
-			"/lib/single-file/modules/css-medias-alt-minifier.js"
-		]
-	};
-
-	return { saveTab };
-
-	async function saveTab(tab, options) {
-		if (!options.removeFrames) {
-			await executeContentScripts(tab.id, frameScriptFiles, true, "document_start");
-		}
-		await executeContentScripts(tab.id, getContentScriptFiles(options), false, "document_idle");
-		if (options.frameId) {
-			await browser.tabs.sendMessage(tab.id, { saveFrame: true, options }, { frameId: options.frameId });
-		} else {
-			await browser.tabs.sendMessage(tab.id, { savePage: true, options });
-		}
-	}
-
-	async function executeContentScripts(tabId, scriptFiles, allFrames, runAt) {
-		for (const file of scriptFiles) {
-			await browser.tabs.executeScript(tabId, { file, allFrames, runAt });
-		}
-	}
-
-	function getContentScriptFiles(options) {
-		let files = contentScriptFiles;
-		Object.keys(optionalContentScriptFiles).forEach(option => {
-			if (options[option]) {
-				files = optionalContentScriptFiles[option].concat(files);
-			}
-		});
-		return files;
-	}
-
-})();

+ 12 - 6
extension/core/bg/tabs.js

@@ -26,22 +26,28 @@ singlefile.tabs = (() => {
 	browser.tabs.onActivated.addListener(activeInfo => onTabActivated(activeInfo));
 	browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => onTabUpdated(tabId, changeInfo, tab));
 	browser.tabs.onRemoved.addListener(tabId => onTabRemoved(tabId));
+	return {
+		onMessage,
+		get: options => browser.tabs.query(options),
+		sendMessage: (tabId, message) => browser.tabs.sendMessage(tabId, message)
+	};
+
+	async function onMessage(message) {
+		return singlefile.config.getOptions(message.url);
+	}
 
 	function onTabCreated(tab) {
-		singlefile.ui.button.onTabCreated(tab);
-		singlefile.ui.menu.onTabCreated(tab);
+		singlefile.ui.onTabCreated(tab);
 	}
 
 	async function onTabActivated(activeInfo) {
 		const tab = await browser.tabs.get(activeInfo.tabId);
-		singlefile.ui.menu.onTabActivated(tab, activeInfo);
-		singlefile.ui.button.onTabActivated(tab);
+		singlefile.ui.onTabActivated(tab, activeInfo);
 	}
 
 	function onTabUpdated(tabId, changeInfo, tab) {
 		singlefile.autosave.onTabUpdated(tabId, changeInfo, tab);
-		singlefile.ui.menu.onTabUpdated(tabId, changeInfo, tab);
-		singlefile.ui.button.onTabUpdated(tabId, changeInfo, tab);
+		singlefile.ui.onTabUpdated(tabId, changeInfo, tab);
 	}
 
 	function onTabRemoved(tabId) {

+ 33 - 0
extension/core/bg/util.js

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2019 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* global singlefile */
+
+singlefile.util = (() => {
+
+	const FORBIDDEN_URLS = ["https://chrome.google.com", "https://addons.mozilla.org"];
+
+	return { isAllowedURL };
+
+	function isAllowedURL(url) {
+		return url && (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://")) && !FORBIDDEN_URLS.find(storeUrl => url.startsWith(storeUrl));
+	}
+
+})();

+ 3 - 3
extension/core/content/content-bootstrap.js

@@ -23,7 +23,7 @@
 this.singlefile.bootstrap = this.singlefile.bootstrap || (async () => {
 
 	let unloadListenerAdded, options, autoSaveEnabled, autoSaveTimeout, autoSavingPage;
-	browser.runtime.sendMessage({ isAutoSaveEnabled: true }).then(message => {
+	browser.runtime.sendMessage({ initAutoSave: true }).then(message => {
 		options = message.options;
 		autoSaveEnabled = message.autoSaveEnabled;
 		refresh();
@@ -35,13 +35,13 @@ this.singlefile.bootstrap = this.singlefile.bootstrap || (async () => {
 			options = message.options;
 			autoSavePage();
 		}
-		if (message.autoSaveUnloadEnabled) {
+		if (message.initAutoSave) {
 			options = message.options;
 			autoSaveEnabled = message.autoSaveEnabled;
 			refresh();
 		}
 	});
-	browser.runtime.sendMessage({ processReset: true });
+	browser.runtime.sendMessage({ loadURL: true });
 	return {};
 
 	async function autoSavePage() {

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

@@ -58,10 +58,10 @@ this.singlefile.top = this.singlefile.top || (() => {
 					await downloadPage(page, options);
 				} catch (error) {
 					console.error(error); // eslint-disable-line no-console
-					browser.runtime.sendMessage({ processError: true, error, options: { autoSave: false } });
+					browser.runtime.sendMessage({ processError: true, error, options: {} });
 				}
 			} else {
-				browser.runtime.sendMessage({ processCancelled: true, options: { autoSave: false } });
+				browser.runtime.sendMessage({ processCancelled: true, options: {} });
 			}
 			processing = false;
 		}
@@ -101,12 +101,12 @@ this.singlefile.top = this.singlefile.top || (() => {
 				if (event.type == event.RESOURCE_LOADED) {
 					index++;
 				}
-				browser.runtime.sendMessage({ processProgress: true, index, maxIndex, options: { autoSave: false } });
+				browser.runtime.sendMessage({ processProgress: true, index, maxIndex, options: {} });
 				if (options.shadowEnabled) {
 					singlefile.ui.onLoadResource(index, maxIndex);
 				}
 			} if (event.type == event.PAGE_ENDED) {
-				browser.runtime.sendMessage({ processEnd: true, options: { autoSave: false } });
+				browser.runtime.sendMessage({ processEnd: true, options: {} });
 			} else if (options.shadowEnabled && !event.detail.frame) {
 				if (event.type == event.PAGE_LOADING) {
 					singlefile.ui.onPageLoading();

+ 15 - 19
extension/ui/bg/bg-ui.js

@@ -23,33 +23,29 @@
 singlefile.ui = (() => {
 
 	return {
-		async saveTab(tab, options = {}) {
-			const tabId = tab.id;
-			options.tabId = tabId;
-			try {
-				singlefile.ui.button.onInitialize(tabId, options, 1);
-				if (options.autoSave) {
-					await singlefile.core.autoSaveTab(tab, options);
-				} else {
-					await singlefile.core.saveTab(tab, options);
-				}
-				singlefile.ui.button.onInitialize(tabId, options, 2);
-			} catch (error) {
-				console.log(error); // eslint-disable-line no-console
-				singlefile.ui.button.onError(tabId, options);
-			}
-		},
-		isAllowedURL(url) {
-			return singlefile.core.isAllowedURL(url);
+		onMessage(message, sender) {
+			return singlefile.ui.button.onMessage(message, sender);
 		},
 		refresh(tab) {
-			return Promise.all([singlefile.ui.menu.refresh(tab), singlefile.ui.button.refresh(tab.id)]);
+			return Promise.all([singlefile.ui.menu.refresh(tab), singlefile.ui.button.refresh(tab)]);
 		},
 		onProgress(tabId, index, maxIndex, options) {
 			singlefile.ui.button.onProgress(tabId, index, maxIndex, options);
 		},
 		onEnd(tabId, options) {
 			singlefile.ui.button.onEnd(tabId, options);
+		},
+		onTabCreated(tab) {
+			singlefile.ui.button.onTabCreated(tab);
+			singlefile.ui.menu.onTabCreated(tab);
+		},
+		onTabActivated(tab, activeInfo) {
+			singlefile.ui.menu.onTabActivated(tab, activeInfo);
+			singlefile.ui.button.onTabActivated(tab);
+		},
+		onTabUpdated(tabId, changeInfo, tab) {
+			singlefile.ui.menu.onTabUpdated(tabId, changeInfo, tab);
+			singlefile.ui.button.onTabUpdated(tabId, changeInfo, tab);
 		}
 	};
 

+ 39 - 44
extension/ui/bg/ui-button.js

@@ -28,39 +28,16 @@ singlefile.ui.button = (() => {
 	const DEFAULT_COLOR = [2, 147, 20, 255];
 
 	browser.browserAction.onClicked.addListener(async tab => {
-		if (singlefile.ui.isAllowedURL(tab.url)) {
-			const tabs = await browser.tabs.query({ currentWindow: true, highlighted: true });
-			if (!tabs.length) {
-				singlefile.ui.saveTab(tab);
-			} else {
-				tabs.forEach(tab => (tab.active || tab.highlighted) && singlefile.ui.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
-			}
-		}
-	});
-	browser.runtime.onMessage.addListener((request, sender) => {
-		if (request.processReset) {
-			onReset(sender.tab.id);
-		}
-		if (request.processProgress) {
-			if (request.maxIndex) {
-				onProgress(sender.tab.id, request.index, request.maxIndex, request.options);
-			}
-		}
-		if (request.processEnd) {
-			onEnd(sender.tab.id, request.options);
-		}
-		if (request.processError) {
-			if (request.error) {
-				console.error("Initialization error", request.error); // eslint-disable-line no-console
-			}
-			onError(sender.tab.id, request.options);
-		}
-		if (request.processCancelled) {
-			onCancelled(sender.tab.id, request.options);
+		const tabs = await singlefile.tabs.get({ currentWindow: true, highlighted: true });
+		if (!tabs.length) {
+			singlefile.core.saveTab(tab);
+		} else {
+			tabs.forEach(tab => (tab.active || tab.highlighted) && singlefile.core.saveTab(tab));
 		}
 	});
 
 	return {
+		onMessage,
 		onTabCreated,
 		onTabActivated,
 		onTabUpdated,
@@ -68,14 +45,36 @@ singlefile.ui.button = (() => {
 		onProgress,
 		onEnd,
 		onError,
-		refresh: async tabId => {
-			if (tabId) {
-				const tabsData = await singlefile.tabsData.get();
-				await refresh(tabId, getProperties({ autoSave: tabsData.autoSaveAll || tabsData.autoSaveUnpinned || (tabsData[tabId] && tabsData[tabId].autoSave) }));
+		refresh: async tab => {
+			if (tab.id) {
+				await refresh(tab.id, getProperties({ autoSave: await singlefile.autosave.isEnabled(tab) }));
 			}
 		}
 	};
 
+	function onMessage(message, sender) {
+		if (message.loadURL) {
+			onLoad(sender.tab.id);
+		}
+		if (message.processProgress) {
+			if (message.maxIndex) {
+				onProgress(sender.tab.id, message.index, message.maxIndex, message.options);
+			}
+		}
+		if (message.processEnd) {
+			onEnd(sender.tab.id, message.options);
+		}
+		if (message.processError) {
+			if (message.error) {
+				console.error("Initialization error", message.error); // eslint-disable-line no-console
+			}
+			onError(sender.tab.id, message.options);
+		}
+		if (message.processCancelled) {
+			onCancelled(sender.tab.id, message.options);
+		}
+	}
+
 	function onTabUpdated(tabId, changeInfo, tab) {
 		refreshTab(tab);
 	}
@@ -89,13 +88,13 @@ singlefile.ui.button = (() => {
 		refreshTab(tab);
 	}
 
-	function onReset(tabId) {
+	function onLoad(tabId) {
 		refresh(tabId, getProperties({}, "", DEFAULT_COLOR, DEFAULT_TITLE));
 	}
 
 	function onInitialize(tabId, options, step) {
 		if (step == 1) {
-			onReset(tabId);
+			onLoad(tabId);
 		}
 		refresh(tabId, getProperties(options, browser.i18n.getMessage("buttonInitializingBadge"), step == 1 ? DEFAULT_COLOR : [4, 229, 36, 255], browser.i18n.getMessage("buttonInitializingTooltip") + " (" + step + "/2)", WAIT_ICON_PATH_PREFIX + "0.png"));
 	}
@@ -120,11 +119,10 @@ singlefile.ui.button = (() => {
 	}
 
 	async function refreshTab(tab) {
-		const autoSave = await singlefile.autosave.enabled(tab.id);
-		const properties = getCurrentProperties(tab.id, { autoSave });
+		const properties = getCurrentProperties(tab.id, { autoSave: await singlefile.autosave.isEnabled(tab) });
 		await refresh(tab.id, properties, true);
 		if (browser.browserAction && browser.browserAction.enable && browser.browserAction.disable) {
-			if (singlefile.ui.isAllowedURL(tab.url)) {
+			if (singlefile.util.isAllowedURL(tab.url)) {
 				try {
 					await browser.browserAction.enable(tab.id);
 				} catch (error) {
@@ -144,8 +142,8 @@ singlefile.ui.button = (() => {
 		if (options.autoSave) {
 			return getProperties(options);
 		} else {
-			const tabsData = singlefile.tabsData.getTemporary();
-			const tabData = tabsData[tabId] && tabsData[tabId].button;
+			const tabsData = singlefile.tabsData.getTemporary(tabId);
+			const tabData = tabsData[tabId].button;
 			if (tabData) {
 				return tabData;
 			} else {
@@ -164,10 +162,7 @@ singlefile.ui.button = (() => {
 	}
 
 	async function refresh(tabId, tabData) {
-		const tabsData = singlefile.tabsData.getTemporary();
-		if (!tabsData[tabId]) {
-			tabsData[tabId] = {};
-		}
+		const tabsData = singlefile.tabsData.getTemporary(tabId);
 		const oldTabData = tabsData[tabId].button || {};
 		tabsData[tabId].button = tabData;
 		if (!tabData.pendingRefresh) {

+ 23 - 28
extension/ui/bg/ui-menu.js

@@ -63,7 +63,7 @@ singlefile.ui.menu = (() => {
 
 	async function createMenus(tab) {
 		const [profiles, tabsData] = await Promise.all([singlefile.config.getProfiles(), singlefile.tabsData.get()]);
-		const options = await singlefile.config.getOptions(tabsData.profileName, tab && tab.url, true);
+		const options = await singlefile.config.getOptions(tab && tab.url, true);
 		if (BROWSER_MENUS_API_SUPPORTED) {
 			const pageContextsEnabled = ["page", "frame", "image", "link", "video", "audio"];
 			const defaultContextsDisabled = ["browser_action"];
@@ -266,53 +266,50 @@ singlefile.ui.menu = (() => {
 			createMenus();
 			menus.onClicked.addListener(async (event, tab) => {
 				if (event.menuItemId == MENU_ID_SAVE_PAGE) {
-					singlefile.ui.saveTab(tab);
+					singlefile.core.saveTab(tab);
 				}
 				if (event.menuItemId == MENU_ID_SAVE_SELECTED) {
-					singlefile.ui.saveTab(tab, { selected: true });
+					singlefile.core.saveTab(tab, { selected: true });
 				}
 				if (event.menuItemId == MENU_ID_SAVE_FRAME) {
-					singlefile.ui.saveTab(tab, { frameId: event.frameId });
+					singlefile.core.saveTab(tab, { frameId: event.frameId });
 				}
 				if (event.menuItemId == MENU_ID_SAVE_SELECTED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_SELECTED_TABS) {
-					const tabs = await browser.tabs.query({ currentWindow: true, highlighted: true });
-					tabs.forEach(tab => singlefile.ui.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
+					const tabs = await singlefile.tabs.get({ currentWindow: true, highlighted: true });
+					tabs.forEach(tab => singlefile.core.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_SAVE_UNPINNED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_UNPINNED_TABS) {
-					const tabs = await browser.tabs.query({ currentWindow: true, pinned: false });
-					tabs.forEach(tab => singlefile.ui.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
+					const tabs = await singlefile.tabs.get({ currentWindow: true, pinned: false });
+					tabs.forEach(tab => singlefile.core.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_SAVE_ALL_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_ALL_TABS) {
-					const tabs = await browser.tabs.query({ currentWindow: true });
-					tabs.forEach(tab => singlefile.ui.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
+					const tabs = await singlefile.tabs.get({ currentWindow: true });
+					tabs.forEach(tab => singlefile.core.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_TAB) {
-					const tabsData = await singlefile.tabsData.get();
-					if (!tabsData[tab.id]) {
-						tabsData[tab.id] = {};
-					}
+					const tabsData = await singlefile.tabsData.get(tab.id);
 					tabsData[tab.id].autoSave = event.checked;
 					await singlefile.tabsData.set(tabsData);
-					refreshExternalComponents(tab.id, { autoSave: true });
+					refreshExternalComponents(tab, { autoSave: true });
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_DISABLED) {
 					const tabsData = await singlefile.tabsData.get();
 					Object.keys(tabsData).forEach(tabId => tabsData[tabId].autoSave = false);
 					tabsData.autoSaveUnpinned = tabsData.autoSaveAll = false;
 					await singlefile.tabsData.set(tabsData);
-					refreshExternalComponents(tab.id, { autoSave: false });
+					refreshExternalComponents(tab, {});
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_ALL) {
 					const tabsData = await singlefile.tabsData.get();
 					tabsData.autoSaveAll = event.checked;
 					await singlefile.tabsData.set(tabsData);
-					refreshExternalComponents(tab.id, { autoSave: true });
+					refreshExternalComponents(tab, { autoSave: true });
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_UNPINNED) {
 					const tabsData = await singlefile.tabsData.get();
 					tabsData.autoSaveUnpinned = event.checked;
 					await singlefile.tabsData.set(tabsData);
-					refreshExternalComponents(tab.id, { autoSave: true });
+					refreshExternalComponents(tab, { autoSave: true });
 				}
 				if (event.menuItemId.startsWith(MENU_ID_SELECT_PROFILE_PREFIX)) {
 					const [profiles, tabsData] = await Promise.all([singlefile.config.getProfiles(), singlefile.tabsData.get()]);
@@ -324,7 +321,7 @@ singlefile.ui.menu = (() => {
 						tabsData.profileName = Object.keys(profiles)[profileIndex];
 					}
 					await singlefile.tabsData.set(tabsData);
-					refreshExternalComponents(tab.id, { autoSave: tabsData.autoSaveAll || tabsData.autoSaveUnpinned || (tabsData[tab.id] && tabsData[tab.id].autoSave) });
+					refreshExternalComponents(tab, { autoSave: await singlefile.autosave.isEnabled() });
 				}
 				if (event.menuItemId.startsWith(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX)) {
 					const [profiles, rule] = await Promise.all([singlefile.config.getProfiles(), singlefile.config.getRule(tab.url)]);
@@ -344,24 +341,22 @@ singlefile.ui.menu = (() => {
 					}
 				}
 			});
-			const tabs = await browser.tabs.query({});
-			tabs.forEach(tab => refreshTab(tab));
+			(await singlefile.tabs.get({})).forEach(tab => refreshTab(tab));
 		}
 	}
 
-	async function refreshExternalComponents(tabId) {
-		await singlefile.autosave.refresh();
-		singlefile.ui.button.refresh(tabId);
+	async function refreshExternalComponents(tab) {
+		await singlefile.autosave.refreshTabs();
+		singlefile.ui.button.refresh(tab);
 	}
 
 	async function refreshTab(tab) {
 		if (BROWSER_MENUS_API_SUPPORTED) {
-			const tabsData = await singlefile.tabsData.get();
+			const tabsData = await singlefile.tabsData.get(tab.id);
 			const promises = [];
 			try {
-				const disabled = Boolean(!tabsData[tab.id] || !tabsData[tab.id].autoSave);
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_DISABLED, disabled));
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_TAB, !disabled));
+				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_DISABLED, !tabsData[tab.id].autoSave));
+				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_TAB, tabsData[tab.id].autoSave));
 				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_UNPINNED, Boolean(tabsData.autoSaveUnpinned)));
 				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_ALL, Boolean(tabsData.autoSaveAll)));
 				if (tab && tab.url) {

+ 1 - 1
extension/ui/content/infobar.js

@@ -44,7 +44,7 @@ this.singlefile.infobar = this.singlefile.infobar || (() => {
 		if (singleFileComment) {
 			const info = singleFileComment.textContent.split("\n");
 			const [, , url, saveDate, ...infoData] = info;
-			const options = await browser.runtime.sendMessage({ getOptions: true, url });
+			const options = await browser.runtime.sendMessage({ loadFileURI: true, url });
 			if (options.displayInfobar) {
 				initInfobar(url, saveDate, infoData);
 			}

+ 6 - 5
manifest.json

@@ -54,13 +54,14 @@
 			"lib/fetch/bg/fetch.js",
 			"lib/fetch/content/fetch.js",
 			"extension/index.js",
-			"extension/core/bg/tabs-data.js",
+			"extension/core/bg/data/config.js",
+			"extension/core/bg/data/tabs-data.js",
+			"extension/core/bg/util.js",
+			"extension/core/bg/core.js",
+			"extension/core/bg/messages.js",
+			"extension/core/bg/tabs.js",
 			"extension/core/bg/download.js",
-			"extension/core/bg/runner.js",
 			"extension/core/bg/autosave.js",
-			"extension/core/bg/tabs.js",
-			"extension/core/bg/config.js",
-			"extension/core/bg/core.js",
 			"extension/ui/bg/bg-ui.js",
 			"extension/ui/bg/ui-menu.js",
 			"extension/ui/bg/ui-button.js",