瀏覽代碼

fixed issue #152

Gildas 7 年之前
父節點
當前提交
c9632b9873

+ 238 - 180
extension/core/bg/data/config.js

@@ -18,7 +18,7 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global browser, singlefile, URL, Blob, FileReader */
+/* global browser, singlefile, URL, Blob */
 
 singlefile.config = (() => {
 
@@ -63,6 +63,12 @@ singlefile.config = (() => {
 	};
 
 	let pendingUpgradePromise = upgrade();
+	return {
+		getRule,
+		getOptions,
+		getProfiles,
+		onMessage
+	};
 
 	async function upgrade() {
 		const config = await browser.storage.local.get();
@@ -147,192 +153,244 @@ singlefile.config = (() => {
 		return rule.url.toLowerCase().startsWith(REGEXP_RULE_PREFIX);
 	}
 
-	return {
-		DISABLED_PROFILE_NAME,
-		DEFAULT_PROFILE_NAME,
-		async createProfile(profileName) {
-			const config = await getConfig();
-			if (Object.keys(config.profiles).includes(profileName)) {
-				throw new Error("Duplicate profile name");
-			}
-			config.profiles[profileName] = DEFAULT_CONFIG;
-			await browser.storage.local.set({ profiles: config.profiles });
-		},
-		async getProfiles() {
-			const config = await getConfig();
-			return config.profiles;
-		},
-		async getRule(url) {
-			return 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) {
-			const config = await getConfig();
-			if (!Object.keys(config.profiles).includes(profileName)) {
-				throw new Error("Profile not found");
-			}
-			config.profiles[profileName] = profile;
-			await browser.storage.local.set({ profiles: config.profiles });
-		},
-		async renameProfile(oldProfileName, profileName) {
-			const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
-			if (!Object.keys(config.profiles).includes(oldProfileName)) {
-				throw new Error("Profile not found");
-			}
-			if (Object.keys(config.profiles).includes(profileName)) {
-				throw new Error("Duplicate profile name");
-			}
-			if (oldProfileName == DEFAULT_PROFILE_NAME) {
-				throw new Error("Default settings cannot be renamed");
-			}
-			if (tabsData.profileName == oldProfileName) {
-				tabsData.profileName = profileName;
-				await singlefile.tabsData.set(tabsData);
-			}
-			config.profiles[profileName] = config.profiles[oldProfileName];
-			config.rules.forEach(rule => {
-				if (rule.profile == oldProfileName) {
-					rule.profile = profileName;
-				}
-				if (rule.autoSaveProfile == oldProfileName) {
-					rule.autoSaveProfile = profileName;
-				}
-			});
-			delete config.profiles[oldProfileName];
-			await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
-		},
-		async deleteProfile(profileName) {
-			const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
-			if (!Object.keys(config.profiles).includes(profileName)) {
-				throw new Error("Profile not found");
-			}
-			if (profileName == DEFAULT_PROFILE_NAME) {
-				throw new Error("Default settings cannot be deleted");
-			}
-			if (tabsData.profileName == profileName) {
-				delete tabsData.profileName;
-				await singlefile.tabsData.set(tabsData);
-			}
-			config.rules.forEach(rule => {
-				if (rule.profile == profileName) {
-					rule.profile = DEFAULT_PROFILE_NAME;
-				}
-				if (rule.autoSaveProfile == profileName) {
-					rule.autoSaveProfile = DEFAULT_PROFILE_NAME;
-				}
-			});
-			delete config.profiles[profileName];
-			await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
-		},
-		async getRules() {
-			const config = await getConfig();
-			return config.rules;
-		},
-		async addRule(url, profile, autoSaveProfile) {
-			if (!url) {
-				throw new Error("URL is empty");
-			}
-			const config = await getConfig();
-			if (config.rules.find(rule => rule.url == url)) {
-				throw new Error("URL already exists");
-			}
-			config.rules.push({
-				url,
-				profile,
-				autoSaveProfile
-			});
-			await browser.storage.local.set({ rules: config.rules });
-		},
-		async deleteRule(url) {
-			if (!url) {
-				throw new Error("URL is empty");
-			}
-			const config = await getConfig();
-			config.rules = config.rules.filter(rule => rule.url != url);
-			await browser.storage.local.set({ rules: config.rules });
-		},
-		async deleteRules(profileName) {
-			const config = await getConfig();
-			config.rules = config.rules = profileName ? config.rules.filter(rule => rule.autoSaveProfile != profileName && rule.profile != profileName) : [];
-			await browser.storage.local.set({ rules: config.rules });
-		},
-		async updateRule(url, newURL, profile, autoSaveProfile) {
-			if (!url || !newURL) {
-				throw new Error("URL is empty");
-			}
-			const config = await getConfig();
-			const urlConfig = config.rules.find(rule => rule.url == url);
-			if (!urlConfig) {
-				throw new Error("URL not found");
+	async function onMessage(message) {
+		if (message.deleteRules) {
+			await deleteRules(message.profileName);
+		}
+		if (message.deleteRule) {
+			await deleteRule(message.url);
+		}
+		if (message.addRule) {
+			await addRule(message.url, message.profileName, message.autoSaveProfileName);
+		}
+		if (message.createProfile) {
+			await createProfile(message.profileName);
+		}
+		if (message.renameProfile) {
+			await renameProfile(message.profileName, message.newProfileName);
+		}
+		if (message.deleteProfile) {
+			await deleteProfile(message.profileName);
+		}
+		if (message.resetProfiles) {
+			await resetProfiles();
+		}
+		if (message.resetProfile) {
+			await resetProfile(message.profileName);
+		}
+		if (message.importConfig) {
+			await importConfig(message.config);
+		}
+		if (message.updateProfile) {
+			await updateProfile(message.profileName, message.profile);
+		}
+		if (message.updateRule) {
+			await updateRule(message.url, message.newUrl, message.profileName, message.autoSaveProfileName);
+		}
+		if (message.getConfigConstants) {
+			return {
+				DISABLED_PROFILE_NAME,
+				DEFAULT_PROFILE_NAME
+			};
+		}
+		if (message.getRules) {
+			return getRules();
+		}
+		if (message.getProfiles) {
+			return getProfiles();
+		}
+		if (message.exportConfig) {
+			return exportConfig();
+		}
+		return {};
+	}
+
+	async function createProfile(profileName) {
+		const config = await getConfig();
+		if (Object.keys(config.profiles).includes(profileName)) {
+			throw new Error("Duplicate profile name");
+		}
+		config.profiles[profileName] = DEFAULT_CONFIG;
+		await browser.storage.local.set({ profiles: config.profiles });
+	}
+
+	async function getProfiles() {
+		const config = await getConfig();
+		return config.profiles;
+	}
+
+	async function 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 function updateProfile(profileName, profile) {
+		const config = await getConfig();
+		if (!Object.keys(config.profiles).includes(profileName)) {
+			throw new Error("Profile not found");
+		}
+		config.profiles[profileName] = profile;
+		await browser.storage.local.set({ profiles: config.profiles });
+	}
+
+	async function renameProfile(oldProfileName, profileName) {
+		const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
+		if (!Object.keys(config.profiles).includes(oldProfileName)) {
+			throw new Error("Profile not found");
+		}
+		if (Object.keys(config.profiles).includes(profileName)) {
+			throw new Error("Duplicate profile name");
+		}
+		if (oldProfileName == DEFAULT_PROFILE_NAME) {
+			throw new Error("Default settings cannot be renamed");
+		}
+		if (tabsData.profileName == oldProfileName) {
+			tabsData.profileName = profileName;
+			await singlefile.tabsData.set(tabsData);
+		}
+		config.profiles[profileName] = config.profiles[oldProfileName];
+		config.rules.forEach(rule => {
+			if (rule.profile == oldProfileName) {
+				rule.profile = profileName;
 			}
-			if (config.rules.find(rule => rule.url == newURL && rule.url != url)) {
-				throw new Error("New URL already exists");
+			if (rule.autoSaveProfile == oldProfileName) {
+				rule.autoSaveProfile = profileName;
 			}
-			urlConfig.url = newURL;
-			urlConfig.profile = profile;
-			urlConfig.autoSaveProfile = autoSaveProfile;
-			await browser.storage.local.set({ rules: config.rules });
-		},
-		async resetProfiles() {
-			await pendingUpgradePromise;
-			const tabsData = await singlefile.tabsData.get();
+		});
+		delete config.profiles[oldProfileName];
+		await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
+	}
+
+	async function deleteProfile(profileName) {
+		const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
+		if (!Object.keys(config.profiles).includes(profileName)) {
+			throw new Error("Profile not found");
+		}
+		if (profileName == DEFAULT_PROFILE_NAME) {
+			throw new Error("Default settings cannot be deleted");
+		}
+		if (tabsData.profileName == profileName) {
 			delete tabsData.profileName;
 			await singlefile.tabsData.set(tabsData);
-			await browser.storage.local.remove(["profiles", "rules"]);
-			await browser.storage.local.set({ profiles: { [DEFAULT_PROFILE_NAME]: DEFAULT_CONFIG }, rules: [] });
-		},
-		async resetProfile(profileName) {
-			const config = await getConfig();
-			if (!Object.keys(config.profiles).includes(profileName)) {
-				throw new Error("Profile not found");
+		}
+		config.rules.forEach(rule => {
+			if (rule.profile == profileName) {
+				rule.profile = DEFAULT_PROFILE_NAME;
 			}
-			config.profiles[profileName] = DEFAULT_CONFIG;
-			await browser.storage.local.set({ profiles: config.profiles });
-		},
-		async export() {
-			const config = await getConfig();
-			const url = URL.createObjectURL(new Blob([JSON.stringify({ profiles: config.profiles, rules: config.rules }, null, 2)], { type: "text/json" }));
-			const downloadInfo = {
-				url,
-				filename: "singlefile-settings.json",
-				saveAs: true
-			};
-			const downloadId = await browser.downloads.download(downloadInfo);
-			return new Promise((resolve, reject) => {
-				browser.downloads.onChanged.addListener(onChanged);
+			if (rule.autoSaveProfile == profileName) {
+				rule.autoSaveProfile = DEFAULT_PROFILE_NAME;
+			}
+		});
+		delete config.profiles[profileName];
+		await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
+	}
+
+	async function getRules() {
+		const config = await getConfig();
+		return config.rules;
+	}
+
+	async function addRule(url, profile, autoSaveProfile) {
+		if (!url) {
+			throw new Error("URL is empty");
+		}
+		const config = await getConfig();
+		if (config.rules.find(rule => rule.url == url)) {
+			throw new Error("URL already exists");
+		}
+		config.rules.push({
+			url,
+			profile,
+			autoSaveProfile
+		});
+		await browser.storage.local.set({ rules: config.rules });
+	}
+
+	async function deleteRule(url) {
+		if (!url) {
+			throw new Error("URL is empty");
+		}
+		const config = await getConfig();
+		config.rules = config.rules.filter(rule => rule.url != url);
+		await browser.storage.local.set({ rules: config.rules });
+	}
 
-				function onChanged(event) {
-					if (event.id == downloadId && event.state) {
-						if (event.state.current == "complete") {
-							URL.revokeObjectURL(url);
-							resolve({});
-							browser.downloads.onChanged.removeListener(onChanged);
-						}
-						if (event.state.current == "interrupted" && (!event.error || event.error.current != "USER_CANCELED")) {
-							URL.revokeObjectURL(url);
-							reject(new Error(event.state.current));
-							browser.downloads.onChanged.removeListener(onChanged);
-						}
+	async function deleteRules(profileName) {
+		const config = await getConfig();
+		config.rules = config.rules = profileName ? config.rules.filter(rule => rule.autoSaveProfile != profileName && rule.profile != profileName) : [];
+		await browser.storage.local.set({ rules: config.rules });
+	}
+
+	async function updateRule(url, newURL, profile, autoSaveProfile) {
+		if (!url || !newURL) {
+			throw new Error("URL is empty");
+		}
+		const config = await getConfig();
+		const urlConfig = config.rules.find(rule => rule.url == url);
+		if (!urlConfig) {
+			throw new Error("URL not found");
+		}
+		if (config.rules.find(rule => rule.url == newURL && rule.url != url)) {
+			throw new Error("New URL already exists");
+		}
+		urlConfig.url = newURL;
+		urlConfig.profile = profile;
+		urlConfig.autoSaveProfile = autoSaveProfile;
+		await browser.storage.local.set({ rules: config.rules });
+	}
+
+	async function resetProfiles() {
+		await pendingUpgradePromise;
+		const tabsData = await singlefile.tabsData.get();
+		delete tabsData.profileName;
+		await singlefile.tabsData.set(tabsData);
+		await browser.storage.local.remove(["profiles", "rules"]);
+		await browser.storage.local.set({ profiles: { [DEFAULT_PROFILE_NAME]: DEFAULT_CONFIG }, rules: [] });
+	}
+
+	async function resetProfile(profileName) {
+		const config = await getConfig();
+		if (!Object.keys(config.profiles).includes(profileName)) {
+			throw new Error("Profile not found");
+		}
+		config.profiles[profileName] = DEFAULT_CONFIG;
+		await browser.storage.local.set({ profiles: config.profiles });
+	}
+
+	async function exportConfig() {
+		const config = await getConfig();
+		const url = URL.createObjectURL(new Blob([JSON.stringify({ profiles: config.profiles, rules: config.rules }, null, 2)], { type: "text/json" }));
+		const downloadInfo = {
+			url,
+			filename: "singlefile-settings.json",
+			saveAs: true
+		};
+		const downloadId = await browser.downloads.download(downloadInfo);
+		return new Promise((resolve, reject) => {
+			browser.downloads.onChanged.addListener(onChanged);
+
+			function onChanged(event) {
+				if (event.id == downloadId && event.state) {
+					if (event.state.current == "complete") {
+						URL.revokeObjectURL(url);
+						resolve({});
+						browser.downloads.onChanged.removeListener(onChanged);
+					}
+					if (event.state.current == "interrupted" && (!event.error || event.error.current != "USER_CANCELED")) {
+						URL.revokeObjectURL(url);
+						reject(new Error(event.state.current));
+						browser.downloads.onChanged.removeListener(onChanged);
 					}
 				}
-			});
-		},
-		async import(file) {
-			const reader = new FileReader();
-			reader.readAsText(file);
-			const serializedConfig = await new Promise((resolve, reject) => {
-				reader.addEventListener("load", () => resolve(reader.result), false);
-				reader.addEventListener("error", reject, false);
-			});
-			const config = JSON.parse(serializedConfig);
-			await browser.storage.local.remove(["profiles", "rules"]);
-			await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
-			await upgrade();
-		}
-	};
+			}
+		});
+	}
+
+	async function importConfig(config) {
+		await browser.storage.local.remove(["profiles", "rules"]);
+		await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
+		await upgrade();
+	}
 
 })();

+ 6 - 1
extension/core/bg/messages.js

@@ -32,9 +32,14 @@ singlefile.messages = (() => {
 		if (message.initAutoSave || message.autoSaveContent) {
 			return singlefile.autosave.onMessage(message, sender);
 		}
-		if (message.loadURL || message.processProgress || message.processEnd || message.processError || message.processCancelled) {
+		if (message.loadURL || message.processProgress || message.processEnd || message.processError || message.processCancelled || message.refreshMenu) {
 			return singlefile.ui.onMessage(message, sender);
 		}
+		if (message.getConfigConstants || message.deleteRules || message.deleteRule || message.addRule || message.getRules ||
+			message.createProfile || message.renameProfile || message.deleteProfile || message.resetProfiles || message.getProfiles ||
+			message.resetProfile || message.exportConfig || message.importConfig || message.updateProfile || message.updateRule) {
+			return singlefile.config.onMessage(message, sender);
+		}
 	});
 	if (browser.runtime.onMessageExternal) {
 		browser.runtime.onMessageExternal.addListener(async (message, sender) => {

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

@@ -29,6 +29,9 @@ singlefile.ui = (() => {
 		refresh(tab) {
 			return Promise.all([singlefile.ui.menu.refresh(tab), singlefile.ui.button.refresh(tab)]);
 		},
+		refreshMenu() {
+			return singlefile.ui.menu.refresh();
+		},
 		onProgress(tabId, index, maxIndex, options) {
 			singlefile.ui.button.onProgress(tabId, index, maxIndex, options);
 		},

+ 74 - 64
extension/ui/bg/ui-options.js

@@ -18,12 +18,11 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global browser, window, document, localStorage */
+/* global browser, window, document, localStorage, FileReader */
 
 (async () => {
 
-	const bgPage = await browser.runtime.getBackgroundPage();
-	const singlefile = bgPage.singlefile;
+	const { DEFAULT_PROFILE_NAME, DISABLED_PROFILE_NAME } = await browser.runtime.sendMessage({ getConfigConstants: true });
 	const removeHiddenElementsLabel = document.getElementById("removeHiddenElementsLabel");
 	const removeUnusedStylesLabel = document.getElementById("removeUnusedStylesLabel");
 	const removeUnusedFontsLabel = document.getElementById("removeUnusedFontsLabel");
@@ -154,16 +153,16 @@
 	};
 	rulesDeleteAllButton.addEventListener("click", async () => {
 		if (await confirm(browser.i18n.getMessage("optionsDeleteDisplayedRulesConfirm"))) {
-			await singlefile.config.deleteRules(!showAllProfilesInput.checked && profileNamesInput.value);
+			await browser.runtime.sendMessage({ deleteRules: true, profileName: !showAllProfilesInput.checked && profileNamesInput.value });
 			await refresh();
 		}
 	}, false);
 	createURLElement.onsubmit = async event => {
 		event.preventDefault();
 		try {
-			await singlefile.config.addRule(ruleUrlInput.value, ruleProfileInput.value, ruleAutoSaveProfileInput.value);
+			await browser.runtime.sendMessage({ addRule: true, url: ruleUrlInput.value, profileName: ruleProfileInput.value, autoSaveProfileName: ruleAutoSaveProfileInput.value });
 			ruleUrlInput.value = "";
-			ruleProfileInput.value = ruleAutoSaveProfileInput.value = singlefile.config.DEFAULT_PROFILE_NAME;
+			ruleProfileInput.value = ruleAutoSaveProfileInput.value = DEFAULT_PROFILE_NAME;
 			autoSaveProfileChanged = false;
 			await refresh();
 			ruleUrlInput.focus();
@@ -173,14 +172,14 @@
 	};
 	ruleUrlInput.onclick = ruleUrlInput.onkeyup = ruleUrlInput.onchange = async () => {
 		ruleAddButton.disabled = !ruleUrlInput.value;
-		const rules = await singlefile.config.getRules();
+		const rules = await browser.runtime.sendMessage({ getRules: true });
 		if (rules.find(rule => rule.url == ruleUrlInput.value)) {
 			ruleAddButton.disabled = true;
 		}
 	};
 	ruleEditUrlInput.onclick = ruleEditUrlInput.onkeyup = ruleEditUrlInput.onchange = async () => {
 		ruleEditButton.disabled = !ruleEditUrlInput.value;
-		const rules = await singlefile.config.getRules();
+		const rules = await browser.runtime.sendMessage({ getRules: true });
 		if (rules.find(rule => rule.url == ruleEditUrlInput.value)) {
 			ruleEditButton.disabled = true;
 		}
@@ -212,8 +211,8 @@
 		const profileName = await prompt(browser.i18n.getMessage("profileAddPrompt"));
 		if (profileName) {
 			try {
-				await singlefile.config.createProfile(profileName);
-				await Promise.all([refresh(profileName), singlefile.ui.menu.refresh()]);
+				await browser.runtime.sendMessage({ createProfile: true, profileName });
+				await Promise.all([refresh(profileName), browser.runtime.sendMessage({ refreshMenu: true })]);
 			} catch (error) {
 				// ignored
 			}
@@ -222,9 +221,9 @@
 	deleteProfileButton.addEventListener("click", async () => {
 		if (await confirm(browser.i18n.getMessage("profileDeleteConfirm"))) {
 			try {
-				await singlefile.config.deleteProfile(profileNamesInput.value);
+				await browser.runtime.sendMessage({ deleteProfile: true, profileName: profileNamesInput.value });
 				profileNamesInput.value = null;
-				await Promise.all([refresh(), singlefile.ui.menu.refresh()]);
+				await Promise.all([refresh(), browser.runtime.sendMessage({ refreshMenu: true })]);
 			} catch (error) {
 				// ignored
 			}
@@ -234,8 +233,8 @@
 		const profileName = await prompt(browser.i18n.getMessage("profileRenamePrompt"), profileNamesInput.value);
 		if (profileName) {
 			try {
-				await singlefile.config.renameProfile(profileNamesInput.value, profileName);
-				await Promise.all([refresh(profileName), singlefile.ui.menu.refresh()]);
+				await browser.runtime.sendMessage({ renameProfile: true, profileName: profileNamesInput.value, newProfileName: profileName });
+				await Promise.all([refresh(profileName), browser.runtime.sendMessage({ refreshMenu: true })]);
 			} catch (error) {
 				// ignored
 			}
@@ -245,24 +244,31 @@
 		const choice = await reset();
 		if (choice) {
 			if (choice == "all") {
-				await singlefile.config.resetProfiles();
-				await Promise.all([refresh(singlefile.config.DEFAULT_PROFILE_NAME), singlefile.ui.menu.refresh()]);
+				await browser.runtime.sendMessage({ resetProfiles: true });
+				await Promise.all([refresh(DEFAULT_PROFILE_NAME), browser.runtime.sendMessage({ refreshMenu: true })]);
 			}
 			if (choice == "current") {
-				await singlefile.config.resetProfile(profileNamesInput.value);
+				await browser.runtime.sendMessage({ resetProfile: true, profileName: profileNamesInput.value });
 				await refresh();
 			}
 			await update();
 		}
 	}, false);
 	exportButton.addEventListener("click", async () => {
-		await singlefile.config.export();
+		await browser.runtime.sendMessage({ exportConfig: true });
 	}, false);
 	importButton.addEventListener("click", () => {
 		fileInput.onchange = async () => {
 			if (fileInput.files.length) {
-				await singlefile.config.import(fileInput.files[0]);
-				await refresh(singlefile.config.DEFAULT_PROFILE_NAME);
+				const reader = new FileReader();
+				reader.readAsText(fileInput.files[0]);
+				const serializedConfig = await new Promise((resolve, reject) => {
+					reader.addEventListener("load", () => resolve(reader.result), false);
+					reader.addEventListener("error", reject, false);
+				});
+				const config = JSON.parse(serializedConfig);
+				await browser.runtime.sendMessage({ importConfig: true, config });
+				await refresh(DEFAULT_PROFILE_NAME);
 				fileInput.value = "";
 			}
 		};
@@ -385,8 +391,8 @@
 	refresh();
 
 	async function refresh(profileName) {
-		const [profiles, rules] = await Promise.all([singlefile.config.getProfiles(), singlefile.config.getRules()]);
-		const selectedProfileName = profileName || profileNamesInput.value || singlefile.config.DEFAULT_PROFILE_NAME;
+		const [profiles, rules] = await Promise.all([browser.runtime.sendMessage({ getProfiles: true }), browser.runtime.sendMessage({ getRules: true })]);
+		const selectedProfileName = profileName || profileNamesInput.value || DEFAULT_PROFILE_NAME;
 		Array.from(profileNamesInput.childNodes).forEach(node => node.remove());
 		const profileNames = Object.keys(profiles);
 		profileNamesInput.options.length = 0;
@@ -395,7 +401,7 @@
 		ruleEditProfileInput.options.length = 0;
 		ruleEditAutoSaveProfileInput.options.length = 0;
 		let optionElement = document.createElement("option");
-		optionElement.value = singlefile.config.DEFAULT_PROFILE_NAME;
+		optionElement.value = DEFAULT_PROFILE_NAME;
 		optionElement.textContent = browser.i18n.getMessage("profileDefaultSettings");
 		profileNamesInput.appendChild(optionElement);
 		ruleProfileInput.appendChild(optionElement.cloneNode(true));
@@ -403,7 +409,7 @@
 		ruleEditProfileInput.appendChild(optionElement.cloneNode(true));
 		ruleEditAutoSaveProfileInput.appendChild(optionElement.cloneNode(true));
 		profileNames.forEach(profileName => {
-			if (profileName != singlefile.config.DEFAULT_PROFILE_NAME) {
+			if (profileName != DEFAULT_PROFILE_NAME) {
 				const optionElement = document.createElement("option");
 				optionElement.value = optionElement.textContent = profileName;
 				profileNamesInput.appendChild(optionElement);
@@ -414,7 +420,7 @@
 			}
 		});
 		optionElement = document.createElement("option");
-		optionElement.value = singlefile.config.DISABLED_PROFILE_NAME;
+		optionElement.value = DISABLED_PROFILE_NAME;
 		optionElement.textContent = browser.i18n.getMessage("profileDisabled");
 		ruleAutoSaveProfileInput.appendChild(optionElement);
 		ruleEditAutoSaveProfileInput.appendChild(optionElement.cloneNode(true));
@@ -443,7 +449,7 @@
 				ruleDeleteButton.title = browser.i18n.getMessage("optionsDeleteRuleTooltip");
 				ruleDeleteButton.addEventListener("click", async () => {
 					if (await confirm(browser.i18n.getMessage("optionsDeleteRuleConfirm"))) {
-						await singlefile.config.deleteRule(rule.url);
+						await browser.runtime.sendMessage({ deleteRule: true, url: rule.url });
 						await refresh();
 					}
 				}, false);
@@ -460,7 +466,7 @@
 						editURLElement.onsubmit = async event => {
 							event.preventDefault();
 							rulesElement.appendChild(editURLElement);
-							await singlefile.config.updateRule(rule.url, ruleEditUrlInput.value, ruleEditProfileInput.value, ruleEditAutoSaveProfileInput.value);
+							await browser.runtime.sendMessage({ updateRule: true, url: rule.url, newUrl: ruleEditUrlInput.value, profileName: ruleEditProfileInput.value, autoSaveProfileName: ruleEditAutoSaveProfileInput.value });
 							await refresh();
 							ruleUrlInput.focus();
 						};
@@ -471,7 +477,7 @@
 		rulesDeleteAllButton.disabled = !rulesDisplayed;
 		rulesElement.appendChild(createURLElement);
 		profileNamesInput.value = selectedProfileName;
-		renameProfileButton.disabled = deleteProfileButton.disabled = profileNamesInput.value == singlefile.config.DEFAULT_PROFILE_NAME;
+		renameProfileButton.disabled = deleteProfileButton.disabled = profileNamesInput.value == DEFAULT_PROFILE_NAME;
 		const profileOptions = profiles[selectedProfileName];
 		removeHiddenElementsInput.checked = profileOptions.removeHiddenElements;
 		removeUnusedStylesInput.checked = profileOptions.removeUnusedStyles;
@@ -516,48 +522,52 @@
 	}
 
 	function getProfileText(profileName) {
-		return profileName == singlefile.config.DEFAULT_PROFILE_NAME ? browser.i18n.getMessage("profileDefaultSettings") : profileName == singlefile.config.DISABLED_PROFILE_NAME ? browser.i18n.getMessage("profileDisabled") : profileName;
+		return profileName == DEFAULT_PROFILE_NAME ? browser.i18n.getMessage("profileDefaultSettings") : profileName == DISABLED_PROFILE_NAME ? browser.i18n.getMessage("profileDisabled") : profileName;
 	}
 
 	async function update() {
 		await pendingSave;
-		pendingSave = singlefile.config.updateProfile(profileNamesInput.value, {
-			removeHiddenElements: removeHiddenElementsInput.checked,
-			removeUnusedStyles: removeUnusedStylesInput.checked,
-			removeUnusedFonts: removeUnusedFontsInput.checked,
-			removeFrames: removeFramesInput.checked,
-			removeImports: removeImportsInput.checked,
-			removeScripts: removeScriptsInput.checked,
-			saveRawPage: saveRawPageInput.checked,
-			compressHTML: compressHTMLInput.checked,
-			compressCSS: compressCSSInput.checked,
-			loadDeferredImages: loadDeferredImagesInput.checked,
-			loadDeferredImagesMaxIdleTime: Math.max(loadDeferredImagesMaxIdleTimeInput.value, 0),
-			contextMenuEnabled: contextMenuEnabledInput.checked,
-			filenameTemplate: filenameTemplateInput.value,
-			shadowEnabled: shadowEnabledInput.checked,
-			maxResourceSizeEnabled: maxResourceSizeEnabledInput.checked,
-			maxResourceSize: Math.max(maxResourceSizeInput.value, 0),
-			confirmFilename: confirmFilenameInput.checked,
-			filenameConflictAction: filenameConflictActionInput.value,
-			removeAudioSrc: removeAudioSrcInput.checked,
-			removeVideoSrc: removeVideoSrcInput.checked,
-			displayInfobar: displayInfobarInput.checked,
-			displayStats: displayStatsInput.checked,
-			backgroundSave: backgroundSaveInput.checked,
-			autoSaveDelay: Math.max(autoSaveDelayInput.value, 0),
-			autoSaveLoad: autoSaveLoadInput.checked,
-			autoSaveUnload: autoSaveUnloadInput.checked,
-			autoSaveLoadOrUnload: autoSaveLoadOrUnloadInput.checked,
-			removeAlternativeFonts: removeAlternativeFontsInput.checked,
-			removeAlternativeImages: removeAlternativeImagesInput.checked,
-			removeAlternativeMedias: removeAlternativeMediasInput.checked,
-			groupDuplicateImages: groupDuplicateImagesInput.checked,
-			infobarTemplate: infobarTemplateInput.value,
-			confirmInfobarContent: confirmInfobarInput.checked
+		pendingSave = browser.runtime.sendMessage({
+			updateProfile: true,
+			profileName: profileNamesInput.value,
+			profile: {
+				removeHiddenElements: removeHiddenElementsInput.checked,
+				removeUnusedStyles: removeUnusedStylesInput.checked,
+				removeUnusedFonts: removeUnusedFontsInput.checked,
+				removeFrames: removeFramesInput.checked,
+				removeImports: removeImportsInput.checked,
+				removeScripts: removeScriptsInput.checked,
+				saveRawPage: saveRawPageInput.checked,
+				compressHTML: compressHTMLInput.checked,
+				compressCSS: compressCSSInput.checked,
+				loadDeferredImages: loadDeferredImagesInput.checked,
+				loadDeferredImagesMaxIdleTime: Math.max(loadDeferredImagesMaxIdleTimeInput.value, 0),
+				contextMenuEnabled: contextMenuEnabledInput.checked,
+				filenameTemplate: filenameTemplateInput.value,
+				shadowEnabled: shadowEnabledInput.checked,
+				maxResourceSizeEnabled: maxResourceSizeEnabledInput.checked,
+				maxResourceSize: Math.max(maxResourceSizeInput.value, 0),
+				confirmFilename: confirmFilenameInput.checked,
+				filenameConflictAction: filenameConflictActionInput.value,
+				removeAudioSrc: removeAudioSrcInput.checked,
+				removeVideoSrc: removeVideoSrcInput.checked,
+				displayInfobar: displayInfobarInput.checked,
+				displayStats: displayStatsInput.checked,
+				backgroundSave: backgroundSaveInput.checked,
+				autoSaveDelay: Math.max(autoSaveDelayInput.value, 0),
+				autoSaveLoad: autoSaveLoadInput.checked,
+				autoSaveUnload: autoSaveUnloadInput.checked,
+				autoSaveLoadOrUnload: autoSaveLoadOrUnloadInput.checked,
+				removeAlternativeFonts: removeAlternativeFontsInput.checked,
+				removeAlternativeImages: removeAlternativeImagesInput.checked,
+				removeAlternativeMedias: removeAlternativeMediasInput.checked,
+				groupDuplicateImages: groupDuplicateImagesInput.checked,
+				infobarTemplate: infobarTemplateInput.value,
+				confirmInfobarContent: confirmInfobarInput.checked
+			}
 		});
 		await pendingSave;
-		await singlefile.ui.menu.refresh();
+		await browser.runtime.sendMessage({ refreshMenu: true });
 	}
 
 	async function confirm(message) {

+ 0 - 9
lib/browser-polyfill/chrome-browser-polyfill.js

@@ -202,15 +202,6 @@
 						}
 					})
 				),
-				getBackgroundPage: () => new Promise((resolve, reject) =>
-					nativeAPI.runtime.getBackgroundPage(bgPage => {
-						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
-						} else {
-							resolve(bgPage);
-						}
-					})
-				),
 				getURL: (path) => nativeAPI.runtime.getURL(path),
 				get lastError() {
 					return nativeAPI.runtime.lastError;