Răsfoiți Sursa

refactored ui-button.js (intoduced button states)

Gildas 6 ani în urmă
părinte
comite
fc74c6c67b

+ 7 - 18
extension/core/bg/autosave.js

@@ -34,22 +34,24 @@ singlefile.extension.core.bg.autosave = (() => {
 	};
 
 	async function onMessage(message, sender) {
+		const ui = singlefile.extension.ui.bg.main;
 		if (message.method.endsWith(".init")) {
 			const [options, autoSaveEnabled] = await Promise.all([singlefile.extension.core.bg.config.getOptions(sender.tab.url, true), isEnabled(sender.tab)]);
 			return { options, autoSaveEnabled };
 		}
 		if (message.method.endsWith(".save")) {
+			ui.onInitialize(sender.tab.id, 1, true);
 			await saveContent(message, sender.tab);
+			ui.onEnd(sender.tab.id, true);
 			return {};
 		}
 	}
 
 	async function onMessageExternal(message, currentTab) {
-		const tabsData = singlefile.extension.core.bg.tabsData;
 		if (message.method == "enableAutoSave") {
-			const allTabsData = await tabsData.get(currentTab.id);
-			allTabsData[currentTab.id].autoSave = message.enabled;
-			await tabsData.set(allTabsData);
+			const tabsData = await singlefile.extension.core.bg.tabsData.get(currentTab.id);
+			tabsData[currentTab.id].autoSave = message.enabled;
+			await singlefile.extension.core.bg.tabsData.set(tabsData);
 			singlefile.extension.ui.bg.main.refreshTab(currentTab);
 		}
 		if (message.method == "isAutoSaveEnabled") {
@@ -79,7 +81,7 @@ singlefile.extension.core.bg.autosave = (() => {
 
 	async function refreshTabs() {
 		const tabs = singlefile.extension.core.bg.tabs;
-		const allTabs = (await tabs.get({})).filter(tab => singlefile.extension.core.bg.util.isAllowedURL(tab.url));
+		const allTabs = (await singlefile.extension.core.bg.tabs.get({}));
 		return Promise.all(allTabs.map(async tab => {
 			const [options, autoSaveEnabled] = await Promise.all([singlefile.extension.core.bg.config.getOptions(tab.url, true), isEnabled(tab)]);
 			tabs.sendMessage(tab.id, { method: "content.init", autoSaveEnabled, options }).catch(() => { /* ignored */ });
@@ -108,19 +110,6 @@ singlefile.extension.core.bg.autosave = (() => {
 		options.incognito = tab.incognito;
 		options.tabId = tabId;
 		options.tabIndex = tab.index;
-		let index = 0, maxIndex = 0;
-		options.onprogress = async event => {
-			if (event.type == event.RESOURCES_INITIALIZED) {
-				maxIndex = event.detail.max;
-				singlefile.extension.ui.bg.main.onProgress(tabId, index, maxIndex, { autoSave: true });
-			}
-			if (event.type == event.RESOURCE_LOADED) {
-				index++;
-				singlefile.extension.ui.bg.main.onProgress(tabId, index, maxIndex, { autoSave: true });
-			} else if (event.type == event.PAGE_ENDED) {
-				singlefile.extension.ui.bg.main.onEnd(tabId, { autoSave: true });
-			}
-		};
 		const processor = new (singlefile.lib.SingleFile.getClass())(options);
 		await processor.run();
 		const page = await processor.getPageData();

+ 37 - 39
extension/core/bg/business.js

@@ -84,52 +84,50 @@ singlefile.extension.core.bg.business = (() => {
 		const tabs = singlefile.extension.core.bg.tabs;
 		const ui = singlefile.extension.ui.bg.main;
 		maxParallelWorkers = (await config.get()).maxParallelWorkers;
-		if (singlefile.extension.core.bg.util.isAllowedURL(tab.url)) {
-			await initScripts();
-			const tabId = tab.id;
-			options.tabId = tabId;
-			options.tabIndex = tab.index;
-			try {
-				if (options.autoSave) {
-					const tabOptions = await config.getOptions(tab.url, true);
-					if (autosave.isEnabled(tab)) {
-						await requestSaveTab(tabId, "content.autosave", tabOptions);
-					}
-				} else {
-					ui.onInitialize(tabId, options, 1);
-					const tabOptions = await config.getOptions(tab.url);
-					Object.keys(options).forEach(key => tabOptions[key] = options[key]);
-					let scriptsInjected;
-					if (!tabOptions.removeFrames) {
-						try {
-							await tabs.executeScript(tabId, { code: frameScript, allFrames: true, matchAboutBlank: true, runAt: "document_start" });
-						} catch (error) {
-							// ignored
-						}
-					}
+		await initScripts();
+		const tabId = tab.id;
+		options.tabId = tabId;
+		options.tabIndex = tab.index;
+		try {
+			if (options.autoSave) {
+				const tabOptions = await config.getOptions(tab.url, true);
+				if (autosave.isEnabled(tab)) {
+					await requestSaveTab(tabId, "content.autosave", tabOptions);
+				}
+			} else {
+				ui.onInitialize(tabId, 1);
+				const tabOptions = await config.getOptions(tab.url);
+				Object.keys(options).forEach(key => tabOptions[key] = options[key]);
+				let scriptsInjected;
+				if (!tabOptions.removeFrames) {
 					try {
-						await initScripts();
-						await tabs.executeScript(tabId, { code: modulesScript + "\n" + contentScript, allFrames: false, runAt: "document_idle" });
-						scriptsInjected = true;
+						await tabs.executeScript(tabId, { code: frameScript, allFrames: true, matchAboutBlank: true, runAt: "document_start" });
 					} catch (error) {
 						// ignored
 					}
-					if (scriptsInjected) {
-						ui.onInitialize(tabId, tabOptions, 2);
-						if (tabOptions.frameId) {
-							await tabs.executeScript(tabId, { code: "document.documentElement.dataset.requestedFrameId = true", frameId: tabOptions.frameId, matchAboutBlank: true, runAt: "document_start" });
-						}
-						await requestSaveTab(tabId, "content.save", tabOptions);
-					} else {
-						ui.onForbiddenDomain(tab, tabOptions);
-					}
 				}
-			} catch (error) {
-				if (error && (!error.message || (error.message != ERROR_CONNECTION_LOST_CHROMIUM && error.message != ERROR_CONNECTION_ERROR_CHROMIUM && error.message != ERROR_CONNECTION_LOST_GECKO))) {
-					console.log(error); // eslint-disable-line no-console
-					ui.onError(tabId, options);
+				try {
+					await initScripts();
+					await tabs.executeScript(tabId, { code: modulesScript + "\n" + contentScript, allFrames: false, runAt: "document_idle" });
+					scriptsInjected = true;
+				} catch (error) {
+					// ignored
+				}
+				if (scriptsInjected) {
+					ui.onInitialize(tabId, 2);
+					if (tabOptions.frameId) {
+						await tabs.executeScript(tabId, { code: "document.documentElement.dataset.requestedFrameId = true", frameId: tabOptions.frameId, matchAboutBlank: true, runAt: "document_start" });
+					}
+					await requestSaveTab(tabId, "content.save", tabOptions);
+				} else {
+					ui.onForbiddenDomain(tab);
 				}
 			}
+		} catch (error) {
+			if (error && (!error.message || (error.message != ERROR_CONNECTION_LOST_CHROMIUM && error.message != ERROR_CONNECTION_ERROR_CHROMIUM && error.message != ERROR_CONNECTION_LOST_GECKO))) {
+				console.log(error); // eslint-disable-line no-console
+				ui.onError(tabId);
+			}
 		}
 	}
 

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

@@ -69,15 +69,15 @@ singlefile.extension.core.bg.downloads = (() => {
 				saveToClipboard(message);
 			} else {
 				try {
-					await downloadPage(message, { 
-						confirmFilename: message.confirmFilename, 
-						incognito: sender.tab.incognito, 
+					await downloadPage(message, {
+						confirmFilename: message.confirmFilename,
+						incognito: sender.tab.incognito,
 						filenameConflictAction: message.filenameConflictAction,
 						filenameReplacementCharacter: message.filenameReplacementCharacter
 					});
 				} catch (error) {
 					console.error(error); // eslint-disable-line no-console
-					singlefile.extension.ui.bg.main.onError(sender.tab.id, {});
+					singlefile.extension.ui.bg.main.onError(sender.tab.id);
 				} finally {
 					URL.revokeObjectURL(message.url);
 				}

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

@@ -49,7 +49,7 @@ singlefile.extension.core.bg.messages = (() => {
 		browser.runtime.onMessageExternal.addListener(async (message, sender) => {
 			const allTabs = await singlefile.extension.core.bg.tabs.get({ currentWindow: true, active: true });
 			const currentTab = allTabs[0];
-			if (currentTab && singlefile.extension.core.bg.util.isAllowedURL(currentTab.url)) {
+			if (currentTab) {
 				return singlefile.extension.core.bg.autosave.onMessageExternal(message, currentTab, sender);
 			} else {
 				return false;

+ 4 - 2
extension/core/bg/tabs.js

@@ -51,8 +51,10 @@ singlefile.extension.core.bg.tabs = (() => {
 	}
 
 	function onTabUpdated(tabId, changeInfo, tab) {
-		singlefile.extension.core.bg.autosave.onTabUpdated(tabId, changeInfo, tab);
-		singlefile.extension.ui.bg.main.onTabUpdated(tabId, changeInfo, tab);
+		if (changeInfo.status == "complete") {
+			singlefile.extension.core.bg.autosave.onTabUpdated(tabId, changeInfo, tab);
+			singlefile.extension.ui.bg.main.onTabUpdated(tabId, changeInfo, tab);
+		}
 	}
 
 	function onTabRemoved(tabId) {

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

@@ -1,46 +0,0 @@
-/*
- * Copyright 2010-2019 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file 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 Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global singlefile */
-
-singlefile.extension.core.bg.util = (() => {
-
-	const FORBIDDEN_DOMAINS = [
-		"chrome.google.com/webstore/",
-		"addons.mozilla.org"
-	];
-
-	return {
-		isAllowedProtocol,
-		isAllowedURL
-	};
-
-	function isAllowedProtocol(url) {
-		return url && (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://"));
-	}
-
-	function isAllowedURL(url) {
-		return isAllowedProtocol(url) && !FORBIDDEN_DOMAINS.find(domain => url.startsWith("https://" + domain) || url.startsWith("http://" + domain));
-	}
-
-})();

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

@@ -35,6 +35,7 @@ this.singlefile.extension.core.content.bootstrap = this.singlefile.extension.cor
 	});
 	browser.runtime.onMessage.addListener(message => { onMessage(message); });
 	browser.runtime.sendMessage({ method: "ui.loadURL" });
+	addEventListener("single-file-push-state", () => browser.runtime.sendMessage({ method: "ui.loadURL" }));
 	return {};
 
 	async function onMessage(message) {

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

@@ -59,10 +59,10 @@ this.singlefile.extension.core.content.main = this.singlefile.extension.core.con
 					await downloadPage(page, options);
 				} catch (error) {
 					console.error(error); // eslint-disable-line no-console
-					browser.runtime.sendMessage({ method: "ui.processError", error, options: {} });
+					browser.runtime.sendMessage({ method: "ui.processError", error });
 				}
 			} else {
-				browser.runtime.sendMessage({ method: "ui.processCancelled", options: {} });
+				browser.runtime.sendMessage({ method: "ui.processCancelled" });
 			}
 			processing = false;
 		}
@@ -103,10 +103,10 @@ this.singlefile.extension.core.content.main = this.singlefile.extension.core.con
 				if (event.type == event.RESOURCE_LOADED) {
 					index++;
 				}
-				browser.runtime.sendMessage({ method: "ui.processProgress", index, maxIndex, options: {} });
+				browser.runtime.sendMessage({ method: "ui.processProgress", index, maxIndex });
 				ui.onLoadResource(index, maxIndex, options);
 			} if (event.type == event.PAGE_ENDED) {
-				browser.runtime.sendMessage({ method: "ui.processEnd", options: {} });
+				browser.runtime.sendMessage({ method: "ui.processEnd" });
 			} else if (!event.detail.frame) {
 				if (event.type == event.PAGE_LOADING) {
 					ui.onPageLoading();

+ 130 - 95
extension/ui/bg/ui-button.js

@@ -29,6 +29,7 @@ singlefile.extension.ui.bg.button = (() => {
 	const WAIT_ICON_PATH_PREFIX = "/extension/ui/resources/icon_128_wait";
 	const BUTTON_DEFAULT_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonDefaultTooltip");
 	const BUTTON_BLOCKED_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonBlockedTooltip");
+	const BUTTON_DEFAULT_BADGE_MESSAGE = "";
 	const BUTTON_INITIALIZING_BADGE_MESSAGE = browser.i18n.getMessage("buttonInitializingBadge");
 	const BUTTON_INITIALIZING_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonInitializingTooltip");
 	const BUTTON_ERROR_BADGE_MESSAGE = browser.i18n.getMessage("buttonErrorBadge");
@@ -36,9 +37,67 @@ singlefile.extension.ui.bg.button = (() => {
 	const BUTTON_OK_BADGE_MESSAGE = browser.i18n.getMessage("buttonOKBadge");
 	const BUTTON_SAVE_PROGRESS_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonSaveProgressTooltip");
 	const BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE = browser.i18n.getMessage("buttonAutoSaveActiveBadge");
-	const AUTOSAVE_COLOR = [208, 208, 208, 192];
 	const BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonAutoSaveActiveTooltip");
 	const DEFAULT_COLOR = [2, 147, 20, 192];
+	const ACTIVE_COLOR = [4, 229, 36, 192];
+	const FORBIDDEN_COLOR = [255, 255, 255, 1];
+	const ERROR_COLOR = [229, 4, 12, 192];
+	const AUTOSAVE_DEFAULT_COLOR = [208, 208, 208, 192];
+	const AUTOSAVE_INITIALIZING_COLOR = [64, 64, 64, 192];
+
+	const BUTTON_STATES = {
+		default: {
+			setBadgeBackgroundColor: { color: DEFAULT_COLOR },
+			setBadgeText: { text: BUTTON_DEFAULT_BADGE_MESSAGE },
+			setTitle: { title: BUTTON_DEFAULT_TOOLTIP_MESSAGE },
+			setIcon: { path: DEFAULT_ICON_PATH }
+		},
+		initialize: {
+			setBadgeBackgroundColor: { color: DEFAULT_COLOR },
+			setBadgeText: { text: BUTTON_INITIALIZING_BADGE_MESSAGE },
+			setTitle: { title: BUTTON_INITIALIZING_TOOLTIP_MESSAGE },
+		},
+		execute: {
+			setBadgeBackgroundColor: { color: ACTIVE_COLOR },
+			setBadgeText: { text: BUTTON_INITIALIZING_BADGE_MESSAGE },
+		},
+		progress: {
+			setBadgeBackgroundColor: { color: ACTIVE_COLOR },
+			setBadgeText: { text: BUTTON_DEFAULT_BADGE_MESSAGE }
+		},
+		end: {
+			setBadgeBackgroundColor: { color: ACTIVE_COLOR },
+			setBadgeText: { text: BUTTON_OK_BADGE_MESSAGE },
+			setTitle: { title: BUTTON_DEFAULT_TOOLTIP_MESSAGE },
+			setIcon: { path: DEFAULT_ICON_PATH }
+		},
+		error: {
+			setBadgeBackgroundColor: { color: ERROR_COLOR },
+			setBadgeText: { text: BUTTON_ERROR_BADGE_MESSAGE },
+			setTitle: { title: BUTTON_DEFAULT_BADGE_MESSAGE },
+			setIcon: { path: DEFAULT_ICON_PATH }
+		},
+		forbidden: {
+			setBadgeBackgroundColor: { color: FORBIDDEN_COLOR },
+			setBadgeText: { text: BUTTON_BLOCKED_BADGE_MESSAGE },
+			setTitle: { title: BUTTON_BLOCKED_TOOLTIP_MESSAGE },
+			setIcon: { path: DEFAULT_ICON_PATH }
+		},
+		autosave: {
+			initialize: {
+				setBadgeBackgroundColor: { color: AUTOSAVE_INITIALIZING_COLOR },
+				setBadgeText: { text: BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE },
+				setTitle: { title: BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE },
+				setIcon: { path: DEFAULT_ICON_PATH }
+			},
+			default: {
+				setBadgeBackgroundColor: { color: AUTOSAVE_DEFAULT_COLOR },
+				setBadgeText: { text: BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE },
+				setTitle: { title: BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE },
+				setIcon: { path: DEFAULT_ICON_PATH }
+			}
+		}
+	};
 
 	browser.browserAction.onClicked.addListener(async tab => {
 		const business = singlefile.extension.core.bg.business;
@@ -52,149 +111,120 @@ singlefile.extension.ui.bg.button = (() => {
 
 	return {
 		onMessage,
-		onTabCreated,
 		onTabUpdated,
 		onInitialize,
 		onProgress,
-		onEnd,
 		onForbiddenDomain,
 		onError,
-		refreshTab,
-		refresh: async (tab, options) => {
-			if (tab.id) {
-				await refresh(tab.id, getProperties(options));
-			}
-		}
+		onEnd,
+		refreshTab
 	};
 
 	function onMessage(message, sender) {
 		if (message.method.endsWith(".loadURL")) {
-			onLoad(sender.tab.id);
+			refreshTab(sender.tab);
 		}
 		if (message.method.endsWith(".processProgress")) {
 			if (message.maxIndex) {
-				onProgress(sender.tab.id, message.index, message.maxIndex, message.options);
+				onProgress(sender.tab.id, message.index, message.maxIndex);
 			}
 		}
 		if (message.method.endsWith(".processEnd")) {
-			onEnd(sender.tab.id, message.options);
+			onEnd(sender.tab.id);
 		}
 		if (message.method.endsWith(".processError")) {
 			if (message.error) {
 				console.error("Initialization error", message.error); // eslint-disable-line no-console
 			}
-			onError(sender.tab.id, message.options);
+			onError(sender.tab.id);
 		}
 		if (message.method.endsWith(".processCancelled")) {
-			onCancelled(sender.tab.id, message.options);
+			onCancelled(sender.tab);
 		}
 		return Promise.resolve({});
 	}
 
-	function onTabUpdated(tabId, changeInfo, tab) {
-		refreshTab(tab);
-	}
-
-	async function onTabCreated(tab) {
-		refreshTab(tab);
-	}
-
-	function onLoad(tabId) {
-		refresh(tabId, getProperties({}, "", DEFAULT_COLOR, BUTTON_DEFAULT_TOOLTIP_MESSAGE));
+	function onTabUpdated(/*tabId, changeInfo, tab*/) {
+		/*
+		const tabsData = singlefile.extension.core.bg.tabsData.getTemporary(tabId);
+		if (tabsData[tabId].button) {
+			delete tabsData[tabId].button.state;
+			refreshTab(tab);
+		}
+		*/
 	}
 
-	function onInitialize(tabId, options, step) {
-		refresh(tabId, getProperties(options, BUTTON_INITIALIZING_BADGE_MESSAGE, step == 1 ? DEFAULT_COLOR : [4, 229, 36, 192], BUTTON_INITIALIZING_TOOLTIP_MESSAGE + " (" + step + "/2)", WAIT_ICON_PATH_PREFIX + "0.png"));
+	function onInitialize(tabId, step, autoSave) {
+		let state;
+		if (autoSave) {
+			state = getButtonState("initialize", true);
+		} else {
+			state = step == 1 ? getButtonState("initialize") : getButtonState("execute");
+			state.setTitle = { title: BUTTON_INITIALIZING_TOOLTIP_MESSAGE + " (" + step + "/2)" };
+			state.setIcon = { path: WAIT_ICON_PATH_PREFIX + "0.png" };
+		}
+		refresh(tabId, state);
 	}
 
-	function onError(tabId, options) {
-		refresh(tabId, getProperties(options, BUTTON_ERROR_BADGE_MESSAGE, [229, 4, 12, 192]));
+	function onError(tabId) {
+		refresh(tabId, getButtonState("error"));
 	}
 
-	function onForbiddenDomain(tab, options) {
-		if (singlefile.extension.core.bg.util.isAllowedProtocol(tab.url)) {
-			refresh(tab.id, getProperties(options, BUTTON_BLOCKED_BADGE_MESSAGE, [255, 255, 255, 1], BUTTON_BLOCKED_TOOLTIP_MESSAGE));
-		}
+	function onEnd(tabId, autoSave) {
+		refresh(tabId, autoSave ? getButtonState("default", true) : getButtonState("end"));
 	}
 
-	function onCancelled(tabId, options) {
-		refresh(tabId, getProperties(options, "", DEFAULT_COLOR, BUTTON_DEFAULT_TOOLTIP_MESSAGE));
+	function onForbiddenDomain(tab) {
+		refresh(tab.id, getButtonState("forbidden"));
 	}
 
-	function onEnd(tabId, options) {
-		refresh(tabId, getProperties(options, BUTTON_OK_BADGE_MESSAGE, [4, 229, 36, 192]));
+	function onCancelled(tab) {
+		refreshTab(tab);
 	}
 
-	function onProgress(tabId, index, maxIndex, options) {
+	function onProgress(tabId, index, maxIndex) {
 		const progress = Math.max(Math.min(20, Math.floor((index / maxIndex) * 20)), 0);
 		const barProgress = Math.min(Math.floor((index / maxIndex) * 8), 8);
 		const path = WAIT_ICON_PATH_PREFIX + barProgress + ".png";
-		refresh(tabId, getProperties(options, "", [4, 229, 36, 192], BUTTON_SAVE_PROGRESS_TOOLTIP_MESSAGE + (progress * 5) + "%", path, [128, 128, 128, 192]));
+		const state = getButtonState("progress");
+		state.setTitle = { title: BUTTON_SAVE_PROGRESS_TOOLTIP_MESSAGE + (progress * 5) + "%" };
+		state.setIcon = { path };
+		refresh(tabId, state);
 	}
 
 	async function refreshTab(tab) {
-		const options = { autoSave: await singlefile.extension.core.bg.autosave.isEnabled(tab) };
-		const properties = getCurrentProperties(tab.id, options);
-		if (singlefile.extension.core.bg.util.isAllowedURL(tab.url)) {
-			await refresh(tab.id, properties);
-		} else {
-			try {
-				await onForbiddenDomain(tab, options);
-			} catch (error) {
-				/* ignored */
-			}
-		}
-	}
-
-	function getCurrentProperties(tabId, options) {
-		if (options.autoSave) {
-			return getProperties(options);
-		} else {
-			const allTabsData = singlefile.extension.core.bg.tabsData.getTemporary(tabId);
-			delete allTabsData[tabId].button;
-			return getProperties(options);
-		}
+		const autoSave = await singlefile.extension.core.bg.autosave.isEnabled(tab);
+		const state = getButtonState("default", autoSave);
+		await refresh(tab.id, state);
 	}
 
-	function getProperties(options, text = "", color = DEFAULT_COLOR, title = BUTTON_DEFAULT_TOOLTIP_MESSAGE, path = DEFAULT_ICON_PATH) {
-		return options.autoSave ?
-			{
-				setBadgeBackgroundColor: { color: AUTOSAVE_COLOR },
-				setBadgeText: { BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE },
-				setTitle: { title: BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE },
-				setIcon: { path: DEFAULT_ICON_PATH }
-			} :
-			{
-				setBadgeBackgroundColor: { color },
-				setBadgeText: { text },
-				setTitle: { title },
-				setIcon: { path }
-		};
-	}
-
-	async function refresh(tabId, tabData) {
-		const allTabsData = singlefile.extension.core.bg.tabsData.getTemporary(tabId);
-		const oldTabData = allTabsData[tabId].button || {};
-		allTabsData[tabId].button = tabData;
-		if (!tabData.pendingRefresh) {
-			tabData.pendingRefresh = Promise.resolve();
-		}
-		try {
-			await tabData.pendingRefresh;
-		} catch (error) {
-			/* ignored */
+	async function refresh(tabId, state) {
+		const tabsData = singlefile.extension.core.bg.tabsData.getTemporary(tabId);
+		if (state) {
+			if (!tabsData[tabId].button) {
+				tabsData[tabId].button = { lastState: null };
+			}
+			const lastState = tabsData[tabId].button.lastState || {};
+			const newState = {};
+			Object.keys(state).forEach(property => {
+				if (state[property] !== undefined && (JSON.stringify(lastState[property]) != JSON.stringify(state[property]))) {
+					newState[property] = state[property];
+				}
+			});
+			if (Object.keys(newState).length) {
+				tabsData[tabId].button.lastState = state;
+				await refreshAsync(tabId, newState);
+			}
 		}
-		tabData.pendingRefresh = refreshAsync(tabId, tabData, oldTabData);
 	}
 
-	async function refreshAsync(tabId, tabData, oldTabData) {
-		for (const browserActionMethod of Object.keys(tabData)) {
-			if (browserActionMethod != "pendingRefresh" && (!oldTabData[browserActionMethod] || JSON.stringify(oldTabData[browserActionMethod]) != JSON.stringify(tabData[browserActionMethod]))) {
-				try {
-					await refreshProperty(tabId, browserActionMethod, tabData[browserActionMethod]);
-				} catch (error) {
-					/* ignored */
-				}
+	async function refreshAsync(tabId, state) {
+		for (const browserActionMethod of Object.keys(state)) {
+			try {
+				// console.log(tabId, "refreshAsync", browserActionMethod, state[browserActionMethod]);
+				await refreshProperty(tabId, browserActionMethod, state[browserActionMethod]);
+			} catch (error) {
+				/* ignored */
 			}
 		}
 	}
@@ -203,8 +233,13 @@ singlefile.extension.ui.bg.button = (() => {
 		if (browser.browserAction[browserActionMethod]) {
 			const parameter = JSON.parse(JSON.stringify(browserActionParameter));
 			parameter.tabId = tabId;
+			// console.log(browserActionMethod, parameter);
 			await browser.browserAction[browserActionMethod](parameter);
 		}
 	}
 
+	function getButtonState(name, autoSave) {
+		return JSON.parse(JSON.stringify(autoSave ? BUTTON_STATES.autosave[name] : BUTTON_STATES[name]));
+	}
+
 })();

+ 11 - 15
extension/ui/bg/ui-main.js

@@ -36,33 +36,29 @@ singlefile.extension.ui.bg.main = (() => {
 		async refreshTab(tab) {
 			return Promise.all([singlefile.extension.ui.bg.menu.refreshTab(tab), singlefile.extension.ui.bg.button.refreshTab(tab)]);
 		},
-		onForbiddenDomain(tab, options) {
-			singlefile.extension.ui.bg.button.onForbiddenDomain(tab, options);
+		onForbiddenDomain(tab) {
+			singlefile.extension.ui.bg.button.onForbiddenDomain(tab);
 		},
-		onInitialize(tabId, options, step) {
-			singlefile.extension.ui.bg.button.onInitialize(tabId, options, step);
+		onInitialize(tabId, step, autoSave) {
+			singlefile.extension.ui.bg.button.onInitialize(tabId, step, autoSave);
 		},
-		onProgress(tabId, index, maxIndex, options) {
-			singlefile.extension.ui.bg.button.onProgress(tabId, index, maxIndex, options);
+		onProgress(tabId, index, maxIndex) {
+			singlefile.extension.ui.bg.button.onProgress(tabId, index, maxIndex);
 		},
-		onError(tabId, options) {
-			singlefile.extension.ui.bg.button.onError(tabId, options);
+		onError(tabId) {
+			singlefile.extension.ui.bg.button.onError(tabId);
 		},
-		onEnd(tabId, options) {
-			singlefile.extension.ui.bg.button.onEnd(tabId, options);
+		onEnd(tabId, autoSave) {
+			singlefile.extension.ui.bg.button.onEnd(tabId, autoSave);
 		},
 		onTabCreated(tab) {
-			singlefile.extension.ui.bg.button.onTabCreated(tab);
 			singlefile.extension.ui.bg.menu.onTabCreated(tab);
 		},
 		onTabActivated(tab, activeInfo) {
 			singlefile.extension.ui.bg.menu.onTabActivated(tab, activeInfo);
 		},
 		onTabUpdated(tabId, changeInfo, tab) {
-			if (changeInfo.status) {
-				singlefile.extension.ui.bg.menu.onTabUpdated(tabId, changeInfo, tab);
-				singlefile.extension.ui.bg.button.onTabUpdated(tabId, changeInfo, tab);
-			}
+			singlefile.extension.ui.bg.menu.onTabUpdated(tabId, changeInfo, tab);
 		}
 	};
 

+ 18 - 15
extension/ui/bg/ui-menu.js

@@ -313,7 +313,6 @@ singlefile.extension.ui.bg.menu = (() => {
 		const tabs = singlefile.extension.core.bg.tabs;
 		const tabsData = singlefile.extension.core.bg.tabsData;
 		const config = singlefile.extension.core.bg.config;
-		const autosave = singlefile.extension.core.bg.autosave;
 		if (BROWSER_MENUS_API_SUPPORTED) {
 			createMenus();
 			menus.onClicked.addListener(async (event, tab) => {
@@ -342,26 +341,26 @@ singlefile.extension.ui.bg.menu = (() => {
 					const allTabsData = await tabsData.get(tab.id);
 					allTabsData[tab.id].autoSave = true;
 					await tabsData.set(allTabsData);
-					refreshExternalComponents(tab, { autoSave: true });
+					refreshExternalComponents(tab);
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_DISABLED) {
 					const allTabsData = await tabsData.get();
 					Object.keys(allTabsData).forEach(tabId => allTabsData[tabId].autoSave = false);
 					allTabsData.autoSaveUnpinned = allTabsData.autoSaveAll = false;
 					await tabsData.set(allTabsData);
-					refreshExternalComponents(tab, {});
+					refreshExternalComponents(tab);
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_ALL) {
 					const allTabsData = await tabsData.get();
 					allTabsData.autoSaveAll = event.checked;
 					await tabsData.set(allTabsData);
-					refreshExternalComponents(tab, { autoSave: true });
+					refreshExternalComponents(tab);
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_UNPINNED) {
 					const allTabsData = await tabsData.get();
 					allTabsData.autoSaveUnpinned = event.checked;
 					await tabsData.set(allTabsData);
-					refreshExternalComponents(tab, { autoSave: true });
+					refreshExternalComponents(tab);
 				}
 				if (event.menuItemId.startsWith(MENU_ID_SELECT_PROFILE_PREFIX)) {
 					const [profiles, allTabsData] = await Promise.all([config.getProfiles(), tabsData.get()]);
@@ -373,7 +372,7 @@ singlefile.extension.ui.bg.menu = (() => {
 						allTabsData.profileName = Object.keys(profiles)[profileIndex];
 					}
 					await tabsData.set(allTabsData);
-					refreshExternalComponents(tab, { autoSave: await autosave.isEnabled() });
+					refreshExternalComponents(tab);
 				}
 				if (event.menuItemId.startsWith(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX)) {
 					const [profiles, rule] = await Promise.all([config.getProfiles(), config.getRule(tab.url)]);
@@ -399,23 +398,27 @@ singlefile.extension.ui.bg.menu = (() => {
 		}
 	}
 
-	async function refreshExternalComponents(tab, options) {
-		const allTabsData = await singlefile.extension.core.bg.tabsData.get(tab.id);
+	async function refreshExternalComponents(tab) {
+		const tabsData = await singlefile.extension.core.bg.tabsData.get(tab.id);
 		await singlefile.extension.core.bg.autosave.refreshTabs();
-		singlefile.extension.ui.bg.button.refresh(tab, options);
-		browser.runtime.sendMessage({ method: "options.refresh", profileName: allTabsData.profileName }).catch(() => { /* ignored */ });
+		await singlefile.extension.ui.bg.button.refreshTab(tab);
+		try {
+			await browser.runtime.sendMessage({ method: "options.refresh", profileName: tabsData.profileName });
+		} catch (error) {
+			// ignored
+		}
 	}
 
 	async function refreshTab(tab) {
 		const config = singlefile.extension.core.bg.config;
 		if (BROWSER_MENUS_API_SUPPORTED) {
-			const allTabsData = await singlefile.extension.core.bg.tabsData.get(tab.id);
+			const tabsData = await singlefile.extension.core.bg.tabsData.get(tab.id);
 			const promises = [];
 			try {
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_DISABLED, !allTabsData[tab.id].autoSave));
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_TAB, allTabsData[tab.id].autoSave));
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_UNPINNED, Boolean(allTabsData.autoSaveUnpinned)));
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_ALL, Boolean(allTabsData.autoSaveAll)));
+				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) {
 					let selectedEntryId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default";
 					let title = MENU_CREATE_DOMAIN_RULE_MESSAGE;

+ 0 - 1
manifest.json

@@ -56,7 +56,6 @@
 			"lib/frame-tree/bg/frame-tree.js",
 			"extension/core/bg/config.js",
 			"extension/core/bg/tabs-data.js",
-			"extension/core/bg/util.js",
 			"extension/core/bg/business.js",
 			"extension/core/bg/messages.js",
 			"extension/core/bg/tabs.js",