瀏覽代碼

introduced concept of profiles

Gildas 7 年之前
父節點
當前提交
3b787676af

+ 99 - 0
extension/core/bg/autosave.js

@@ -0,0 +1,99 @@
+/*
+ * Copyright 2018 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.autosave = (() => {
+
+	browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
+		const [options, tabsData] = await Promise.all([singlefile.config.getDefaultConfig(), singlefile.tabsData.get()]);
+		if ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && (tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || (tabsData[tab.id] && tabsData[tab.id].autoSave))) {
+			if (changeInfo.status == "complete") {
+				singlefile.ui.saveTab(tab, { autoSave: true });
+			}
+		}
+	});
+	browser.runtime.onMessage.addListener((message, sender) => {
+		if (message.isAutoSaveEnabled) {
+			return isAutoSaveEnabled(sender.tab.id);
+		}
+	});
+
+	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 {
+		enabled,
+		refresh
+	};
+
+	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.button.refresh(tabId, { autoSave: enabled });
+		}
+	}
+
+	async function isAutoSaveEnabled(tabId) {
+		const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getDefaultConfig(), enabled(tabId)]);
+		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 [autoSaveEnabled, options] = await Promise.all([enabled(tab.id), singlefile.config.getDefaultConfig()]);
+				await browser.tabs.sendMessage(tab.id, { autoSaveUnloadEnabled: true, autoSaveEnabled, options });
+			} catch (error) {
+				/* ignored */
+			}
+		}));
+	}
+
+})();

+ 35 - 28
extension/core/bg/config.js

@@ -18,7 +18,7 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global browser, singlefile, localStorage */
+/* global browser, singlefile */
 
 singlefile.config = (() => {
 
@@ -54,31 +54,34 @@ singlefile.config = (() => {
 		removeAlternativeFonts: true,
 		removeAlternativeMedias: true,
 		removeAlternativeImages: true,
-		groupDuplicateImages: true
+		groupDuplicateImages: true,
+		saveRawPage: false
 	};
 
-	let pendingUpgradePromise;
+	let pendingUpgradePromise = upgrade();
 	browser.runtime.onMessage.addListener(request => {
 		if (request.getConfig) {
 			return getConfig();
 		}
 	});
-	upgrade();
 
 	async function upgrade() {
-		if (localStorage.config) {
-			const config = JSON.parse(localStorage.config);
-			await upgradeConfig(config);
-			delete localStorage.config;
-			pendingUpgradePromise = browser.storage.local.set(config);
+		const config = await browser.storage.local.get();
+		const defaultConfig = config;
+		if (!config.profiles) {
+			delete defaultConfig.tabsData;
+			applyUpgrade(defaultConfig);
+			const config = { profiles: {}, defaultProfile: "default" };
+			config.profiles[config.defaultProfile] = defaultConfig;
+			browser.storage.local.remove(Object.keys(DEFAULT_CONFIG));
+			return browser.storage.local.set(config);
 		} else {
-			const config = await browser.storage.local.get();
-			await upgradeConfig(config);
-			pendingUpgradePromise = browser.storage.local.set(config);
+			Object.keys(config.profiles).forEach(profileName => applyUpgrade(config.profiles[profileName]));
+			return browser.storage.local.set({ profiles: config.profiles });
 		}
 	}
 
-	async function upgradeConfig(config) {
+	function applyUpgrade(config) {
 		if (config.removeScripts === undefined) {
 			config.removeScripts = true;
 		}
@@ -97,7 +100,7 @@ singlefile.config = (() => {
 		}
 		if (config.filenameTemplate === undefined) {
 			if (config.appendSaveDate || config.appendSaveDate === undefined) {
-				config.filenameTemplate = "{page-title} ({date-iso} {time-locale}).html";
+				config.filenameTemplate = DEFAULT_CONFIG.filenameTemplate;
 			} else {
 				config.filenameTemplate = "{page-title}.html";
 			}
@@ -113,7 +116,7 @@ singlefile.config = (() => {
 			config.shadowEnabled = true;
 		}
 		if (config.maxResourceSize === undefined) {
-			config.maxResourceSize = 10;
+			config.maxResourceSize = DEFAULT_CONFIG.maxResourceSize;
 		}
 		if (config.maxResourceSize === 0) {
 			config.maxResourceSize = 1;
@@ -135,7 +138,7 @@ singlefile.config = (() => {
 			config.backgroundSave = true;
 		}
 		if (config.autoSaveDelay === undefined) {
-			config.autoSaveDelay = 1;
+			config.autoSaveDelay = DEFAULT_CONFIG.autoSaveDelay;
 		}
 		if (config.removeAlternativeFonts === undefined) {
 			config.removeAlternativeFonts = true;
@@ -162,38 +165,42 @@ singlefile.config = (() => {
 			config.autoSaveUnload = false;
 		}
 		if (config.maxLazyLoadImagesIdleTime === undefined) {
-			config.maxLazyLoadImagesIdleTime = 1000;
+			config.maxLazyLoadImagesIdleTime = DEFAULT_CONFIG.maxLazyLoadImagesIdleTime;
 		}
 		if (config.confirmFilename === undefined) {
 			config.confirmFilename = false;
 		}
 		if (config.conflictAction === undefined) {
-			config.conflictAction = "uniquify";
+			config.conflictAction = DEFAULT_CONFIG.conflictAction;
 		}
 	}
 
 	async function getConfig() {
 		await pendingUpgradePromise;
-		const config = await browser.storage.local.get();
-		config.tabsData = undefined;
-		return Object.keys(config).length ? config : DEFAULT_CONFIG;
+		return await browser.storage.local.get(["profiles", "defaultProfile"]);
 	}
 
 	return {
-		async set(config) {
+		async set(profiles) {
 			await pendingUpgradePromise;
-			await browser.storage.local.set(config);
+			await browser.storage.local.set({ profiles });
 		},
 		async get() {
 			return getConfig();
 		},
+		async getDefaultConfig() {
+			const config = await getConfig();
+			return config.profiles[config.defaultProfile];
+		},
+		async setDefaultConfig(defaultConfig) {
+			const config = await getConfig();
+			config[config.defaultProfile] = defaultConfig;
+			await this.set(config);
+		},
 		async reset() {
 			await pendingUpgradePromise;
-			const { tabsData } = await browser.storage.local.get();
-			await browser.storage.local.clear();
-			const config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
-			config.tabsData = tabsData;
-			await browser.storage.local.set(config);
+			await browser.storage.local.remove(["profiles", "defaultProfile"]);
+			await browser.storage.local.set({ profiles: { default: DEFAULT_CONFIG }, defaultProfile: "default" });
 		}
 	};
 

+ 5 - 3
extension/core/bg/core.js

@@ -27,14 +27,16 @@ singlefile.core = (() => {
 	return { saveTab, autoSaveTab, isAllowedURL };
 
 	async function saveTab(tab, processOptions) {
-		const options = await singlefile.config.get();
+		const options = await singlefile.config.getDefaultConfig();
 		Object.keys(processOptions).forEach(key => options[key] = processOptions[key]);
 		return singlefile.scriptLoader.executeScripts(tab, options);
 	}
 
 	async function autoSaveTab(tab) {
-		const options = await singlefile.config.get();
-		await browser.tabs.sendMessage(tab.id, { autoSavePage: true, options });
+		let options = await singlefile.config.getDefaultConfig();
+		if (singlefile.autosave.enabled(tab.id)) {
+			await browser.tabs.sendMessage(tab.id, { autoSavePage: true, options });
+		}
 	}
 
 	function isAllowedURL(url) {

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

@@ -30,7 +30,7 @@ singlefile.processor = (() => {
 	return true;
 
 	async function saveContent(message, tabId, incognito) {
-		const options = await singlefile.config.get();
+		const options = await singlefile.config.getDefaultConfig();
 		options.content = message.content;
 		options.url = message.url;
 		options.framesData = message.framesData;

+ 3 - 3
extension/core/bg/storage.js → extension/core/bg/tabs-data.js

@@ -20,13 +20,13 @@
 
 /* global browser, singlefile */
 
-singlefile.storage = (() => {
+singlefile.tabsData = (() => {
 
 	let persistentData, temporaryData;
 	browser.tabs.onRemoved.addListener(async tabId => {
-		const tabsData = await singlefile.storage.get();
+		const tabsData = await getPersistent();
 		delete tabsData[tabId];
-		await singlefile.storage.set(tabsData);
+		await setPersistent(tabsData);
 	});
 	getPersistent().then(tabsData => persistentData = tabsData);
 	return {

+ 36 - 40
extension/core/content/content-bootstrap.js

@@ -22,15 +22,22 @@
 
 this.singlefile.autosave = this.singlefile.autosave || (async () => {
 
-	let listenerAdded, options, autoSaveTimeout, autoSavingPage;
-	refresh();
+	let listenerAdded, options, autoSaveEnabled, autoSaveTimeout, autoSavingPage;
+	browser.runtime.sendMessage({ isAutoSaveEnabled: true }).then(message => {
+		options = message.options;
+		autoSaveEnabled = message.autoSaveEnabled;
+		refresh();
+	});
 	browser.runtime.onMessage.addListener(message => {
 		if (message.autoSavePage) {
 			autoSavingPage = false;
 			singlefile.pageAutoSaved = false;
+			options = message.options;
 			autoSavePage();
 		}
 		if (message.autoSaveUnloadEnabled) {
+			options = message.options;
+			autoSaveEnabled = message.autoSaveEnabled;
 			refresh();
 		}
 	});
@@ -40,51 +47,40 @@ this.singlefile.autosave = this.singlefile.autosave || (async () => {
 	async function autoSavePage() {
 		if ((!autoSavingPage || autoSaveTimeout) && !singlefile.pageAutoSaved) {
 			autoSavingPage = true;
-			const [autoSaveEnabled, options] = await Promise.all([browser.runtime.sendMessage({ isAutoSaveEnabled: true }), browser.runtime.sendMessage({ getConfig: true })]);
-			if (autoSaveEnabled) {
-				options.sessionId = 0;
-				if (options.autoSaveDelay && !autoSaveTimeout) {
-					autoSaveTimeout = setTimeout(() => {
-						autoSavePage();
-					}, options.autoSaveDelay * 1000);
-				} else {
-					const docData = docHelper.preProcessDoc(document, window, options);
-					let framesData = [];
-					autoSaveTimeout = null;
-					if (!options.removeFrames && this.frameTree) {
-						framesData = await frameTree.getAsync(options);
-					}
-					browser.runtime.sendMessage({
-						saveContent: true,
-						content: docHelper.serialize(document, false),
-						canvasData: docData.canvasData,
-						fontsData: docData.fontsData,
-						stylesheetContents: docData.stylesheetContents,
-						imageData: docData.imageData,
-						postersData: docData.postersData,
-						usedFonts: docData.usedFonts,
-						shadowRootContents: docData.shadowRootContents,
-						framesData,
-						url: location.href
-					});
-					docHelper.postProcessDoc(document, options);
-					singlefile.pageAutoSaved = true;
-					autoSavingPage = false;
-				}
+			options.sessionId = 0;
+			if (options.autoSaveDelay && !autoSaveTimeout) {
+				autoSaveTimeout = setTimeout(() => {
+					autoSavePage();
+				}, options.autoSaveDelay * 1000);
 			} else {
+				const docData = docHelper.preProcessDoc(document, window, options);
+				let framesData = [];
+				autoSaveTimeout = null;
+				if (!options.removeFrames && this.frameTree) {
+					framesData = await frameTree.getAsync(options);
+				}
+				browser.runtime.sendMessage({
+					saveContent: true,
+					content: docHelper.serialize(document, false),
+					canvasData: docData.canvasData,
+					fontsData: docData.fontsData,
+					stylesheetContents: docData.stylesheetContents,
+					imageData: docData.imageData,
+					postersData: docData.postersData,
+					usedFonts: docData.usedFonts,
+					shadowRootContents: docData.shadowRootContents,
+					framesData,
+					url: location.href
+				});
+				docHelper.postProcessDoc(document, options);
+				singlefile.pageAutoSaved = true;
 				autoSavingPage = false;
 			}
 		}
 	}
 
 	async function refresh() {
-		const [autoSaveEnabled, config] = await Promise.all([browser.runtime.sendMessage({ isAutoSaveEnabled: true }), browser.runtime.sendMessage({ getConfig: true })]);
-		options = config;
-		enableAutoSaveUnload(autoSaveEnabled && (config.autoSaveUnload || config.autoSaveLoadOrUnload));
-	}
-
-	function enableAutoSaveUnload(enabled) {
-		if (enabled) {
+		if (autoSaveEnabled && (options.autoSaveUnload || options.autoSaveLoadOrUnload)) {
 			if (!listenerAdded) {
 				addEventListener("unload", onUnload);
 				addEventListener("single-file-push-state", onUnload);

+ 3 - 0
extension/ui/bg/bg-ui.js

@@ -38,6 +38,9 @@ singlefile.ui = (() => {
 				console.log(error); // eslint-disable-line no-console
 				singlefile.ui.button.onError(tabId, options);
 			}
+		},
+		isAllowedURL(url) {
+			return singlefile.core.isAllowedURL(url);
 		}
 	};
 

+ 0 - 59
extension/ui/bg/ui-autosave.js

@@ -1,59 +0,0 @@
-/*
- * Copyright 2018 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.ui.autosave = (() => {
-	
-	browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
-		const [config, tabsData] = await Promise.all([singlefile.config.get(), singlefile.storage.get()]);
-		if ((config.autoSaveLoad || config.autoSaveLoadOrUnload) && (tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || (tabsData[tab.id] && tabsData[tab.id].autoSave))) {
-			if (changeInfo.status == "complete") {
-				singlefile.ui.saveTab(tab, { autoSave: true });
-			}
-		}
-	});	
-	browser.runtime.onMessage.addListener((request, sender) => {
-		if (request.isAutoSaveEnabled) {
-			return isEnabled(sender.tab.id);
-		}
-	});
-	return {
-		isEnabled,
-		refresh
-	};
-
-	async function refresh() {
-		const tabs = await browser.tabs.query({});
-		return Promise.all(tabs.map(async tab => {
-			try {
-				await browser.tabs.sendMessage(tab.id, { autoSaveUnloadEnabled: true });
-			} catch (error) {
-				/* ignored */
-			}
-		}));
-	}
-
-	async function isEnabled(tabId) {
-		const tabsData = await singlefile.storage.get();
-		return tabsData.autoSaveAll || tabsData.autoSaveUnpinned || (tabsData[tabId] && tabsData[tabId].autoSave);
-	}
-
-})();

+ 6 - 42
extension/ui/bg/ui-button.js

@@ -28,12 +28,12 @@ singlefile.ui.button = (() => {
 	const DEFAULT_COLOR = [2, 147, 20, 255];
 
 	browser.browserAction.onClicked.addListener(async tab => {
-		if (singlefile.core.isAllowedURL(tab.url)) {
+		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.core.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
+				tabs.forEach(tab => (tab.active || tab.highlighted) && singlefile.ui.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
 			}
 		}
 	});
@@ -65,16 +65,6 @@ singlefile.ui.button = (() => {
 			onCancelled(sender.tab.id, request.options);
 		}
 	});
-	if (browser.runtime.onMessageExternal) {
-		browser.runtime.onMessageExternal.addListener(async message => {
-			if (message.method == "enableAutoSave") {
-				setAutoSaveActiveTabEnabled(message.enabled);
-			}
-			if (message.method == "isAutoSaveEnabled") {
-				return isAutoSaveEnabled();
-			}
-		});
-	}
 
 	return {
 		onInitialize,
@@ -84,32 +74,6 @@ singlefile.ui.button = (() => {
 		refresh: (tabId, options) => refresh(tabId, getProperties(options))
 	};
 
-	async function setAutoSaveActiveTabEnabled(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.storage.get();
-			if (!tabsData[tabId]) {
-				tabsData[tabId] = {};
-			}
-			tabsData[tabId].autoSave = enabled;
-			await singlefile.storage.set(tabsData);
-			refresh(tabId, getProperties({ autoSave: enabled }));
-		}
-	}
-
-	async function 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.storage.get();
-			return tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || (tabsData[tabId] && tabsData[tabId].autoSave);
-		}
-		return false;
-	}
-
 	function onReset(tabId) {
 		refresh(tabId, getProperties({}, "", DEFAULT_COLOR, DEFAULT_TITLE));
 	}
@@ -141,11 +105,11 @@ singlefile.ui.button = (() => {
 	}
 
 	async function onTabActivated(tab) {
-		const autoSave = await singlefile.ui.autosave.isEnabled(tab.id);
+		const autoSave = await singlefile.autosave.enabled(tab.id);
 		const properties = getCurrentProperties(tab.id, { autoSave });
 		await refresh(tab.id, properties, true);
 		if (browser.browserAction && browser.browserAction.enable && browser.browserAction.disable) {
-			if (singlefile.core.isAllowedURL(tab.url)) {
+			if (singlefile.ui.isAllowedURL(tab.url)) {
 				try {
 					await browser.browserAction.enable(tab.id);
 				} catch (error) {
@@ -165,7 +129,7 @@ singlefile.ui.button = (() => {
 		if (options.autoSave) {
 			return getProperties(options);
 		} else {
-			const tabsData = singlefile.storage.getTemporary();
+			const tabsData = singlefile.tabsData.getTemporary();
 			const tabData = tabsData[tabId] && tabsData[tabId].button;
 			if (tabData) {
 				return tabData;
@@ -185,7 +149,7 @@ singlefile.ui.button = (() => {
 	}
 
 	async function refresh(tabId, tabData) {
-		const tabsData = singlefile.storage.getTemporary();
+		const tabsData = singlefile.tabsData.getTemporary();
 		if (!tabsData[tabId]) {
 			tabsData[tabId] = {};
 		}

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

@@ -38,29 +38,29 @@ singlefile.ui.menu = (() => {
 	initialize();
 	browser.tabs.onActivated.addListener(async activeInfo => {
 		const tab = await browser.tabs.get(activeInfo.tabId);
-		await refreshState(tab);
+		await refreshTab(tab);
 	});
-	browser.tabs.onCreated.addListener(refreshState);
+	browser.tabs.onCreated.addListener(refreshTab);
 	return {
 		refresh
 	};
 
 	async function refresh() {
-		const config = await singlefile.config.get();
+		const options = await singlefile.config.getDefaultConfig();
 		if (BROWSER_MENUS_API_SUPPORTED) {
 			const pageContextsEnabled = ["page", "frame", "image", "link", "video", "audio"];
 			const defaultContextsDisabled = ["browser_action"];
 			const defaultContextsEnabled = defaultContextsDisabled.concat(...pageContextsEnabled);
-			const defaultContexts = config.contextMenuEnabled ? defaultContextsEnabled : defaultContextsDisabled;
+			const defaultContexts = options.contextMenuEnabled ? defaultContextsEnabled : defaultContextsDisabled;
 			await browser.menus.removeAll();
-			if (config.contextMenuEnabled) {
+			if (options.contextMenuEnabled) {
 				browser.menus.create({
 					id: MENU_ID_SAVE_PAGE,
 					contexts: pageContextsEnabled,
 					title: browser.i18n.getMessage("menuSavePage")
 				});
 			}
-			if (config.contextMenuEnabled) {
+			if (options.contextMenuEnabled) {
 				browser.menus.create({
 					id: "separator-1",
 					contexts: pageContextsEnabled,
@@ -72,7 +72,7 @@ singlefile.ui.menu = (() => {
 				contexts: defaultContexts,
 				title: browser.i18n.getMessage("menuSaveSelection")
 			});
-			if (config.contextMenuEnabled) {
+			if (options.contextMenuEnabled) {
 				browser.menus.create({
 					id: MENU_ID_SAVE_FRAME,
 					contexts: ["frame"],
@@ -94,7 +94,7 @@ singlefile.ui.menu = (() => {
 				contexts: defaultContexts,
 				title: browser.i18n.getMessage("menuAllTabs")
 			});
-			if (config.contextMenuEnabled) {
+			if (options.contextMenuEnabled) {
 				browser.menus.create({
 					id: "separator-2",
 					contexts: pageContextsEnabled,
@@ -156,57 +156,57 @@ singlefile.ui.menu = (() => {
 				}
 				if (event.menuItemId == MENU_ID_SAVE_SELECTED_TABS) {
 					const tabs = await browser.tabs.query({ currentWindow: true, highlighted: true });
-					tabs.forEach(tab => singlefile.core.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
+					tabs.forEach(tab => singlefile.ui.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_SAVE_UNPINNED_TABS) {
 					const tabs = await browser.tabs.query({ currentWindow: true, pinned: false });
-					tabs.forEach(tab => singlefile.core.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
+					tabs.forEach(tab => singlefile.ui.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_SAVE_ALL_TABS) {
 					const tabs = await browser.tabs.query({ currentWindow: true });
-					tabs.forEach(tab => singlefile.core.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
+					tabs.forEach(tab => singlefile.ui.isAllowedURL(tab.url) && singlefile.ui.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_TAB) {
-					const tabsData = await singlefile.storage.get();
+					const tabsData = await singlefile.tabsData.get();
 					if (!tabsData[tab.id]) {
 						tabsData[tab.id] = {};
 					}
 					tabsData[tab.id].autoSave = event.checked;
-					await singlefile.storage.set(tabsData);
+					await singlefile.tabsData.set(tabsData);
 					refreshExternalComponents(tab.id, { autoSave: true });
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_DISABLED) {
-					const tabsData = await singlefile.storage.get();
+					const tabsData = await singlefile.tabsData.get();
 					Object.keys(tabsData).forEach(tabId => tabsData[tabId].autoSave = false);
 					tabsData.autoSaveUnpinned = tabsData.autoSaveAll = false;
-					await singlefile.storage.set(tabsData);
+					await singlefile.tabsData.set(tabsData);
 					refreshExternalComponents(tab.id, { autoSave: false });
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_ALL) {
-					const tabsData = await singlefile.storage.get();
+					const tabsData = await singlefile.tabsData.get();
 					tabsData.autoSaveAll = event.checked;
-					await singlefile.storage.set(tabsData);
+					await singlefile.tabsData.set(tabsData);
 					refreshExternalComponents(tab.id, { autoSave: true });
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_UNPINNED) {
-					const tabsData = await singlefile.storage.get();
+					const tabsData = await singlefile.tabsData.get();
 					tabsData.autoSaveUnpinned = event.checked;
-					await singlefile.storage.set(tabsData);
+					await singlefile.tabsData.set(tabsData);
 					refreshExternalComponents(tab.id, { autoSave: true });
 				}
 			});
 			const tabs = await browser.tabs.query({});
-			tabs.forEach(tab => refreshState(tab));
+			tabs.forEach(tab => refreshTab(tab));
 		}
 	}
 
 	async function refreshExternalComponents(tabId, tabData) {
-		await singlefile.ui.autosave.refresh();
+		await singlefile.autosave.refresh();
 		singlefile.ui.button.refresh(tabId, tabData);
 	}
 
-	async function refreshState(tab) {
-		const tabsData = await singlefile.storage.get();
+	async function refreshTab(tab) {
+		const tabsData = await singlefile.tabsData.get();
 		if (BROWSER_MENUS_API_SUPPORTED) {
 			try {
 				const disabled = Boolean(!tabsData[tab.id] || !tabsData[tab.id].autoSave);

+ 39 - 39
extension/ui/bg/options.js → extension/ui/bg/ui-options.js

@@ -189,49 +189,49 @@
 	refresh();
 
 	async function refresh() {
-		const config = await bgPage.singlefile.config.get();
-		removeHiddenElementsInput.checked = config.removeHiddenElements;
-		removeUnusedStylesInput.checked = config.removeUnusedStyles;
-		removeFramesInput.checked = config.removeFrames;
-		removeImportsInput.checked = config.removeImports;
-		removeScriptsInput.checked = config.removeScripts;
-		saveRawPageInput.checked = config.saveRawPage;
-		compressHTMLInput.checked = config.compressHTML;
-		compressCSSInput.checked = config.compressCSS;
-		lazyLoadImagesInput.checked = config.lazyLoadImages;
-		maxLazyLoadImagesIdleTimeInput.value = config.maxLazyLoadImagesIdleTime;
-		maxLazyLoadImagesIdleTimeInput.disabled = !config.lazyLoadImages;
-		contextMenuEnabledInput.checked = config.contextMenuEnabled;
-		filenameTemplateInput.value = config.filenameTemplate;
-		shadowEnabledInput.checked = config.shadowEnabled;
-		maxResourceSizeEnabledInput.checked = config.maxResourceSizeEnabled;
-		maxResourceSizeInput.value = config.maxResourceSize;
-		maxResourceSizeInput.disabled = !config.maxResourceSizeEnabled;
-		confirmFilenameInput.checked = config.confirmFilename;
-		conflictActionInput.value = config.conflictAction;
-		removeAudioSrcInput.checked = config.removeAudioSrc;
-		removeVideoSrcInput.checked = config.removeVideoSrc;
-		displayInfobarInput.checked = config.displayInfobar;
-		displayStatsInput.checked = config.displayStats;
-		backgroundSaveInput.checked = config.backgroundSave;
-		autoSaveDelayInput.value = config.autoSaveDelay;
-		autoSaveDelayInput.disabled = !config.autoSaveLoadOrUnload && !config.autoSaveLoad;
-		autoSaveLoadInput.checked = !config.autoSaveLoadOrUnload && config.autoSaveLoad;
-		autoSaveLoadOrUnloadInput.checked = config.autoSaveLoadOrUnload;
-		autoSaveUnloadInput.checked = !config.autoSaveLoadOrUnload && config.autoSaveUnload;
-		autoSaveLoadInput.disabled = config.autoSaveLoadOrUnload;
-		autoSaveUnloadInput.disabled = config.autoSaveLoadOrUnload;
-		removeAlternativeFontsInput.checked = config.removeAlternativeFonts;
-		removeAlternativeImagesInput.checked = config.removeAlternativeImages;
-		groupDuplicateImagesInput.checked = config.groupDuplicateImages;
-		removeAlternativeMediasInput.checked = config.removeAlternativeMedias;
-		infobarTemplateInput.value = config.infobarTemplate;
-		confirmInfobarInput.checked = config.confirmInfobar;
+		const options = await bgPage.singlefile.config.getDefaultConfig();
+		removeHiddenElementsInput.checked = options.removeHiddenElements;
+		removeUnusedStylesInput.checked = options.removeUnusedStyles;
+		removeFramesInput.checked = options.removeFrames;
+		removeImportsInput.checked = options.removeImports;
+		removeScriptsInput.checked = options.removeScripts;
+		saveRawPageInput.checked = options.saveRawPage;
+		compressHTMLInput.checked = options.compressHTML;
+		compressCSSInput.checked = options.compressCSS;
+		lazyLoadImagesInput.checked = options.lazyLoadImages;
+		maxLazyLoadImagesIdleTimeInput.value = options.maxLazyLoadImagesIdleTime;
+		maxLazyLoadImagesIdleTimeInput.disabled = !options.lazyLoadImages;
+		contextMenuEnabledInput.checked = options.contextMenuEnabled;
+		filenameTemplateInput.value = options.filenameTemplate;
+		shadowEnabledInput.checked = options.shadowEnabled;
+		maxResourceSizeEnabledInput.checked = options.maxResourceSizeEnabled;
+		maxResourceSizeInput.value = options.maxResourceSize;
+		maxResourceSizeInput.disabled = !options.maxResourceSizeEnabled;
+		confirmFilenameInput.checked = options.confirmFilename;
+		conflictActionInput.value = options.conflictAction;
+		removeAudioSrcInput.checked = options.removeAudioSrc;
+		removeVideoSrcInput.checked = options.removeVideoSrc;
+		displayInfobarInput.checked = options.displayInfobar;
+		displayStatsInput.checked = options.displayStats;
+		backgroundSaveInput.checked = options.backgroundSave;
+		autoSaveDelayInput.value = options.autoSaveDelay;
+		autoSaveDelayInput.disabled = !options.autoSaveLoadOrUnload && !options.autoSaveLoad;
+		autoSaveLoadInput.checked = !options.autoSaveLoadOrUnload && options.autoSaveLoad;
+		autoSaveLoadOrUnloadInput.checked = options.autoSaveLoadOrUnload;
+		autoSaveUnloadInput.checked = !options.autoSaveLoadOrUnload && options.autoSaveUnload;
+		autoSaveLoadInput.disabled = options.autoSaveLoadOrUnload;
+		autoSaveUnloadInput.disabled = options.autoSaveLoadOrUnload;
+		removeAlternativeFontsInput.checked = options.removeAlternativeFonts;
+		removeAlternativeImagesInput.checked = options.removeAlternativeImages;
+		groupDuplicateImagesInput.checked = options.groupDuplicateImages;
+		removeAlternativeMediasInput.checked = options.removeAlternativeMedias;
+		infobarTemplateInput.value = options.infobarTemplate;
+		confirmInfobarInput.checked = options.confirmInfobar;
 	}
 
 	async function update() {
 		await pendingSave;
-		pendingSave = bgPage.singlefile.config.set({
+		pendingSave = bgPage.singlefile.config.setDefaultConfig({
 			removeHiddenElements: removeHiddenElementsInput.checked,
 			removeUnusedStyles: removeUnusedStylesInput.checked,
 			removeFrames: removeFramesInput.checked,

+ 1 - 1
extension/ui/pages/options.html

@@ -170,7 +170,7 @@
 		<button id="resetButton"></button>
 	</div>
 	<script type="text/javascript" src="/lib/browser-polyfill/chrome-browser-polyfill.js"></script>
-	<script type="text/javascript" src="../bg/options.js"></script>
+	<script type="text/javascript" src="../bg/ui-options.js"></script>
 </body>
 
 </html>

+ 2 - 2
manifest.json

@@ -54,16 +54,16 @@
 			"lib/fetch/bg/fetch.js",
 			"lib/fetch/content/fetch.js",
 			"extension/index.js",
-			"extension/core/bg/storage.js",
+			"extension/core/bg/tabs-data.js",
 			"extension/core/bg/download.js",
 			"extension/core/bg/script-loader.js",
 			"extension/core/bg/processor.js",
+			"extension/core/bg/autosave.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",
-			"extension/ui/bg/ui-autosave.js",
 			"lib/lazy/bg/bg-lazy-timeout.js",
 			"lib/single-file/vendor/css-minifier.js",
 			"lib/single-file/vendor/css-tree.js",