diff --git a/background/background.js b/background/background.js index 667206c0..8b141d36 100644 --- a/background/background.js +++ b/background/background.js @@ -1,7 +1,8 @@ -/* global download prefs openURL FIREFOX CHROME VIVALDI - debounce URLS ignoreChromeError usercssHelper - styleManager msg navigatorUtil iconUtil workerUtil contentScripts sync - findExistingTab createTab activateTab isTabReplaceable getActiveTab */ +/* global download prefs openURL FIREFOX CHROME + URLS ignoreChromeError usercssHelper + styleManager msg navigatorUtil workerUtil contentScripts sync + findExistingTab createTab activateTab isTabReplaceable getActiveTab + iconManager */ 'use strict'; @@ -49,14 +50,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, { openEditor, updateIconBadge(count) { - // TODO: remove once our manifest's minimum_chrome_version is 50+ - // Chrome 49 doesn't report own extension pages in webNavigation apparently - // so we do a force update which doesn't use the cache. - if (CHROME && CHROME < 2661 && this.sender.tab.url.startsWith(URLS.ownOrigin)) { - updateIconBadgeForce(this.sender.tab.id, count); - } else { - updateIconBadge(this.sender.tab.id, count); - } + iconManager.updateIconBadge(this.sender.tab.id, count); return true; }, @@ -87,6 +81,14 @@ var browserCommands, contextMenus; // register all listeners msg.on(onRuntimeMessage); +// tell apply.js to refresh styles for non-committed navigation +navigatorUtil.onUrlChange(({tabId, frameId}, type) => { + if (type !== 'committed') { + msg.sendTab(tabId, {method: 'urlChanged'}, {frameId}) + .catch(msg.ignoreError); + } +}); + if (FIREFOX) { // FF misses some about:blank iframes so we inject our content script explicitly navigatorUtil.onDOMContentLoaded(webNavIframeHelperFF, { @@ -106,55 +108,10 @@ if (chrome.commands) { chrome.commands.onCommand.addListener(command => browserCommands[command]()); } -const tabData = new Map(); -const tabDataFor = tabId => { - let data = tabData.get(tabId); - if (!data) tabData.set(tabId, (data = {})); - return data; -}; -chrome.tabs.onRemoved.addListener(tabId => tabData.delete(tabId)); -chrome.tabs.onReplaced.addListener((added, removed) => tabData.delete(removed)); - -prefs.subscribe([ - 'disableAll', - 'badgeDisabled', - 'badgeNormal', -], () => debounce(refreshIconBadgeColor)); - -prefs.subscribe([ - 'show-badge' -], () => debounce(refreshAllIconsBadgeText)); - -prefs.subscribe([ - 'disableAll', - 'iconset', -], () => debounce(refreshAllIcons)); - -prefs.initializing.then(() => { - refreshIconBadgeColor(); - refreshAllIconsBadgeText(); - refreshAllIcons(); -}); - -navigatorUtil.onUrlChange(({tabId, frameId, transitionQualifiers, url}, type) => { - if (type !== 'committed') { - msg.sendTab(tabId, {method: 'urlChanged'}, {frameId}) - .catch(msg.ignoreError); - } else if (!frameId) { - if (usercssHelper.testUrl(url) && !`${tabDataFor(tabId).url}`.startsWith(URLS.installUsercss)) { - usercssHelper.openInstallerPage(tabId, url).then(newUrl => { - tabDataFor(tabId).url = newUrl || url; - }); - } - // it seems that the tab icon would be reset by navigation. We - // invalidate the cache here so it would be refreshed by `apply.js`. - tabData.set(tabId, {url}); - // however, if the tab was swapped in by forward/backward buttons, - // `apply.js` doesn't notify the background to update the icon, - // so we have to refresh it manually. - if (transitionQualifiers.includes('forward_back')) { - msg.sendTab(tabId, {method: 'updateCount'}).catch(msg.ignoreError); - } +// detect usercss and open the installer page +navigatorUtil.onCommitted(({tabId, frameId, url}) => { + if (!frameId && usercssHelper.testUrl(url)) { + usercssHelper.openInstallerPage(tabId, url); } }); @@ -307,74 +264,6 @@ function webNavIframeHelperFF({tabId, frameId}) { }); } -function updateIconBadge(tabId, count) { - const tabIcon = tabDataFor(tabId); - if (tabIcon.count === count) { - return; - } - const oldCount = tabIcon.count; - tabIcon.count = count; - refreshIconBadgeText(tabId, tabIcon); - if (Boolean(oldCount) !== Boolean(count)) { - refreshIcon(tabId, tabIcon); - } -} - -function updateIconBadgeForce(tabId, count) { - refreshIconBadgeText(tabId, {count}); - refreshIcon(tabId, {count}); -} - -function refreshIconBadgeText(tabId, icon) { - iconUtil.setBadgeText({ - text: prefs.get('show-badge') && icon.count ? String(icon.count) : '', - tabId - }); -} - -function refreshIcon(tabId, icon) { - const disableAll = prefs.get('disableAll'); - const iconset = prefs.get('iconset') === 1 ? 'light/' : ''; - const postfix = disableAll ? 'x' : !icon.count ? 'w' : ''; - const iconType = iconset + postfix; - - if (icon.iconType === iconType) { - return; - } - icon.iconType = iconset + postfix; - const sizes = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38]; - iconUtil.setIcon({ - path: sizes.reduce( - (obj, size) => { - obj[size] = `/images/icon/${iconset}${size}${postfix}.png`; - return obj; - }, - {} - ), - tabId - }); -} - -function refreshIconBadgeColor() { - const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal'); - iconUtil.setBadgeBackgroundColor({ - color - }); -} - -function refreshAllIcons() { - for (const [tabId, data] of tabData) { - refreshIcon(tabId, data); - } - refreshIcon(null, {}); // default icon -} - -function refreshAllIconsBadgeText() { - for (const [tabId, data] of tabData) { - refreshIconBadgeText(tabId, data); - } -} - function onRuntimeMessage(msg, sender) { if (msg.method !== 'invokeAPI') { return; diff --git a/background/icon-manager.js b/background/icon-manager.js new file mode 100644 index 00000000..f5d84f3b --- /dev/null +++ b/background/icon-manager.js @@ -0,0 +1,101 @@ +/* global prefs debounce iconUtil FIREFOX CHROME VIVALDI tabManager */ +/* exported iconManager */ +'use strict'; + +const iconManager = (() => { + const ICON_SIZES = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38]; + + prefs.subscribe([ + 'disableAll', + 'badgeDisabled', + 'badgeNormal', + ], () => debounce(refreshIconBadgeColor)); + + prefs.subscribe([ + 'show-badge' + ], () => debounce(refreshAllIconsBadgeText)); + + prefs.subscribe([ + 'disableAll', + 'iconset', + ], () => debounce(refreshAllIcons)); + + prefs.initializing.then(() => { + refreshIconBadgeColor(); + refreshAllIconsBadgeText(); + refreshAllIcons(); + }); + + return {updateIconBadge}; + + // FIXME: in some cases, we only have to redraw the badge. is it worth a optimization? + function updateIconBadge(tabId, count, force = true) { + tabManager.setMeta(tabId, 'count', count); + refreshIconBadgeText(tabId); + refreshIcon(tabId, force); + } + + function refreshIconBadgeText(tabId) { + const count = tabManager.getMeta(tabId, 'count'); + iconUtil.setBadgeText({ + text: prefs.get('show-badge') && count ? String(count) : '', + tabId + }); + } + + function getIconName(count = 0) { + const iconset = prefs.get('iconset') === 1 ? 'light/' : ''; + const postfix = prefs.get('disableAll') ? 'x' : !count ? 'w' : ''; + return `${iconset}$SIZE$${postfix}`; + } + + function refreshIcon(tabId, force = false) { + const oldIcon = tabManager.getMeta(tabId, 'icon'); + const newIcon = getIconName(tabManager.getMeta(tabId, 'count')); + + if (!force && oldIcon === newIcon) { + return; + } + tabManager.setMeta(tabId, 'icon', newIcon); + iconUtil.setIcon({ + path: getIconPath(newIcon), + tabId + }); + } + + function getIconPath(icon) { + return ICON_SIZES.reduce( + (obj, size) => { + obj[size] = `/images/icon/${icon.replace('$SIZE$', size)}.png`; + return obj; + }, + {} + ); + } + + function refreshGlobalIcon() { + iconUtil.setIcon({ + path: getIconPath(getIconName()) + }); + } + + function refreshIconBadgeColor() { + const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal'); + iconUtil.setBadgeBackgroundColor({ + color + }); + } + + function refreshAllIcons() { + for (const tabId of tabManager.list()) { + refreshIcon(tabId); + } + refreshGlobalIcon(); + } + + function refreshAllIconsBadgeText() { + for (const tabId of tabManager.list()) { + refreshIconBadgeText(tabId); + } + } +})(); diff --git a/background/tab-manager.js b/background/tab-manager.js new file mode 100644 index 00000000..30179ac0 --- /dev/null +++ b/background/tab-manager.js @@ -0,0 +1,48 @@ +/* global navigatorUtil */ +/* exported tabManager */ +'use strict'; + +const tabManager = (() => { + const listeners = []; + const cache = new Map(); + chrome.tabs.onRemoved.addListener(tabId => cache.delete(tabId)); + chrome.tabs.onReplaced.addListener((added, removed) => cache.delete(removed)); + navigatorUtil.onUrlChange(({tabId, frameId, url}) => { + if (frameId) return; + setMeta(tabId, 'url', url); + emitUpdate({tabId, url}); + }); + + return {onUpdate, setMeta, getMeta, list}; + + function list() { + return cache.keys(); + } + + function onUpdate(callback) { + listeners.push(callback); + } + + function emitUpdate(e) { + for (const callback of listeners) { + try { + callback(e); + } catch (err) { + console.error(err); + } + } + } + + function setMeta(tabId, key, value) { + let meta = cache.get(tabId); + if (!meta) { + meta = new Map(); + cache.set(tabId, meta); + } + meta.set(key, value); + } + + function getMeta(tabId, key) { + return cache.get(tabId).get(key); + } +})(); diff --git a/background/usercss-helper.js b/background/usercss-helper.js index fdf86cc8..f353bb47 100644 --- a/background/usercss-helper.js +++ b/background/usercss-helper.js @@ -30,7 +30,8 @@ const usercssHelper = (() => { testUrl(url) { return url.includes('.user.') && /^(https?|file|ftps?):/.test(url) && - /\.user\.(css|styl)$/.test(url.split(/[#?]/, 1)[0]); + /\.user\.(css|styl)$/.test(url.split(/[#?]/, 1)[0]) && + !url.startsWith(URLS.installUsercss); }, openInstallerPage(tabId, url) { diff --git a/manifest.json b/manifest.json index fcd28945..b411d1b8 100644 --- a/manifest.json +++ b/manifest.json @@ -46,6 +46,8 @@ "background/style-manager.js", "background/navigator-util.js", "background/icon-util.js", + "background/tab-manager.js", + "background/icon-manager.js", "background/background.js", "background/usercss-helper.js", "background/style-via-api.js", diff --git a/package.json b/package.json index 52df771e..c17d875f 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "update-transifex": "tx push -s", "build-vendor": "shx rm -rf vendor/* && node tools/build-vendor", "zip": "node tools/zip.js", - "start": "web-ext run --bc", - "start-chrome": "web-ext run -t chromium --bc", + "start": "web-ext run", + "start-chrome": "web-ext run -t chromium", "preversion": "npm test", "version": "sync-version manifest.json && git add .", "postversion": "npm run zip && git push --follow-tags"