diff --git a/background/background.js b/background/background.js index 50843039..cdbfd33e 100644 --- a/background/background.js +++ b/background/background.js @@ -13,6 +13,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, { // getStyles, getSectionsByUrl: styleManager.getSectionsByUrl, getSectionsById: styleManager.getSectionsById, + getStylesInfo: styleManager.getStylesInfo, // saveStyle, // deleteStyle, diff --git a/background/style-manager.js b/background/style-manager.js index d662b217..f89696fe 100644 --- a/background/style-manager.js +++ b/background/style-manager.js @@ -1,4 +1,4 @@ -/* global createCache db calcStyleDigest normalizeStyleSections db */ +/* global createCache db calcStyleDigest normalizeStyleSections db promisify */ 'use strict'; const styleManager = (() => { @@ -8,18 +8,37 @@ const styleManager = (() => { const compiledRe = createCache(); const compiledExclusion = createCache(); const BAD_MATCHER = {test: () => false}; + const tabQuery = promisify(chrome.tabs.query.bind(chrome.tabs)); + const tabSendMessage = promisify(chrome.tabs.sendMessage.bind(chrome.tabs)); + const runtimeSendMessage = promisify(chrome.runtime.sendMessage.bind(chrome.runtime)); // FIXME: do we have to prepare `styles` map for all methods? return ensurePrepared({ - getSectionsForURL, + styles, + cachedStyleForUrl, + getStylesInfo, + getSectionsByUrl, installStyle, deleteStyle, setStyleExclusions, - editSave + editSave, + toggleStyle // TODO: get all styles API? // TODO: get style by ID? }); + function toggleStyle(id, enabled) { + const style = styles.get(id); + style.enabled = enabled; + return saveStyle(style) + .then(() => broadcastMessage('styleUpdated', {id, enabled})); + } + + function getStylesInfo() { + // FIXME: remove code? + return [...styles.values()]; + } + function editSave() {} function setStyleExclusions() {} @@ -105,14 +124,14 @@ const styleManager = (() => { } } - function getSectionsForURL(url) { + function getSectionsByUrl(url) { // if (!URLS.supported(url) || prefs.get('disableAll')) { // return []; // } let result = cachedStyleForUrl.get(url); if (!result) { result = []; - for (const style of styles) { + for (const style of styles.values()) { if (!urlMatchStyle(url, style)) { continue; } @@ -213,4 +232,81 @@ const styleManager = (() => { function getUrlNoHash(url) { return url.split('#')[0]; } + + function cleanData(method, data) { + if ( + (method === 'styleUpdated' || method === 'styleAdded') && + (data.sections || data.sourceCode) + ) { + // apply/popup/manage use only meta for these two methods, + // editor may need the full code but can fetch it directly, + // so we send just the meta to avoid spamming lots of tabs with huge styles + return getStyleWithNoCode(data); + } + return output; + } + + function isExtensionStyle(id) { + // TODO + // const style = styles.get(id); + // if (!style) + return false; + } + + function broadcastMessage(method, data) { + const pendingPrivilage = runtimeSendMessage({method, cleanData(method, data)}); + // const affectsAll = !msg.affects || msg.affects.all; + // const affectsOwnOriginOnly = + // !affectsAll && (msg.affects.editor || msg.affects.manager); + // const affectsTabs = affectsAll || affectsOwnOriginOnly; + // const affectsIcon = affectsAll || msg.affects.icon; + // const affectsPopup = affectsAll || msg.affects.popup; + // const affectsSelf = affectsPopup || msg.prefs; + // notify all open extension pages and popups + // if (affectsSelf) { + // msg.tabId = undefined; + // sendMessage(msg, ignoreChromeError); + // } + // notify tabs + if (affectsTabs || affectsIcon) { + const notifyTab = tab => { + if (!styleUpdated + && (affectsTabs || URLS.optionsUI.includes(tab.url)) + // own pages are already notified via sendMessage + && !(affectsSelf && tab.url.startsWith(URLS.ownOrigin)) + // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF + && (!FIREFOX || tab.width)) { + msg.tabId = tab.id; + sendMessage(msg, ignoreChromeError); + } + if (affectsIcon) { + // eslint-disable-next-line no-use-before-define + // debounce(API.updateIcon, 0, {tab}); + } + }; + // list all tabs including chrome-extension:// which can be ours + Promise.all([ + queryTabs(isExtensionStyle(data.id) ? {url: URLS.ownOrigin + '*'} : {}), + getActiveTab(), + ]).then(([tabs, activeTab]) => { + const activeTabId = activeTab && activeTab.id; + for (const tab of tabs) { + invokeOrPostpone(tab.id === activeTabId, notifyTab, tab); + } + }); + } + // notify self: the message no longer is sent to the origin in new Chrome + if (typeof onRuntimeMessage !== 'undefined') { + onRuntimeMessage(originalMessage); + } + // notify apply.js on own pages + if (typeof applyOnMessage !== 'undefined') { + applyOnMessage(originalMessage); + } + // propagate saved style state/code efficiently + if (styleUpdated) { + msg.refreshOwnTabs = false; + API.refreshAllTabs(msg); + } + } })(); diff --git a/content/api.js b/content/api.js index 6409072f..3fe85d68 100644 --- a/content/api.js +++ b/content/api.js @@ -1,3 +1,4 @@ +/* global promisify */ 'use strict'; const API = (() => { @@ -12,29 +13,13 @@ const API = (() => { function sendMessage(msg) { return runtimeSendMessage(msg) .then(result => { - if (result.__ERROR__) { + if (result && result.__ERROR__) { throw new Error(result.__ERROR__); } return result; }); } - function promisify(fn) { - return (...args) => - new Promise((resolve, reject) => { - fn(...args, (...result) => { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError); - return; - } - resolve( - result.length === 0 ? undefined : - result.length === 1 ? result[1] : result - ); - }); - }); - } - function invokeBG(name, args) { return preparing.then(BG => { if (!BG) { @@ -48,7 +33,11 @@ const API = (() => { if (BG !== window) { args = BG.deepCopy(args); } - return BG.API_METHODS[name](...args) + const fn = BG.API_METHODS[name]; + if (!fn) { + throw new Error(`unknown API method: ${name}`); + } + return Promise.resolve(fn(...args)) .then(BG.deepCopy); }); } diff --git a/content/apply.js b/content/apply.js index 968d0d0a..8c07f685 100644 --- a/content/apply.js +++ b/content/apply.js @@ -116,7 +116,7 @@ break; case 'styleUpdated': - if (request.codeIsUpdated === false) { + if (!request.codeIsUpdated) { applyStyleState(request.style); break; } diff --git a/js/messaging.js b/js/messaging.js index ccca8de6..1f594fab 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -104,71 +104,6 @@ if (FIREFOX_NO_DOM_STORAGE) { Object.defineProperty(window, 'sessionStorage', {value: {}}); } -function notifyAllTabs(msg) { - const originalMessage = msg; - const styleUpdated = msg.method === 'styleUpdated' || msg.method === 'exclusionsUpdated'; - if (styleUpdated || msg.method === 'styleAdded') { - // apply/popup/manage use only meta for these two methods, - // editor may need the full code but can fetch it directly, - // so we send just the meta to avoid spamming lots of tabs with huge styles - msg = Object.assign({}, msg, { - style: getStyleWithNoCode(msg.style) - }); - } - const affectsAll = !msg.affects || msg.affects.all; - const affectsOwnOriginOnly = !affectsAll && (msg.affects.editor || msg.affects.manager); - const affectsTabs = affectsAll || affectsOwnOriginOnly; - const affectsIcon = affectsAll || msg.affects.icon; - const affectsPopup = affectsAll || msg.affects.popup; - const affectsSelf = affectsPopup || msg.prefs; - // notify all open extension pages and popups - if (affectsSelf) { - msg.tabId = undefined; - sendMessage(msg, ignoreChromeError); - } - // notify tabs - if (affectsTabs || affectsIcon) { - const notifyTab = tab => { - if (!styleUpdated - && (affectsTabs || URLS.optionsUI.includes(tab.url)) - // own pages are already notified via sendMessage - && !(affectsSelf && tab.url.startsWith(URLS.ownOrigin)) - // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF - && (!FIREFOX || tab.width)) { - msg.tabId = tab.id; - sendMessage(msg, ignoreChromeError); - } - if (affectsIcon) { - // eslint-disable-next-line no-use-before-define - debounce(API.updateIcon, 0, {tab}); - } - }; - // list all tabs including chrome-extension:// which can be ours - Promise.all([ - queryTabs(affectsOwnOriginOnly ? {url: URLS.ownOrigin + '*'} : {}), - getActiveTab(), - ]).then(([tabs, activeTab]) => { - const activeTabId = activeTab && activeTab.id; - for (const tab of tabs) { - invokeOrPostpone(tab.id === activeTabId, notifyTab, tab); - } - }); - } - // notify self: the message no longer is sent to the origin in new Chrome - if (typeof onRuntimeMessage !== 'undefined') { - onRuntimeMessage(originalMessage); - } - // notify apply.js on own pages - if (typeof applyOnMessage !== 'undefined') { - applyOnMessage(originalMessage); - } - // propagate saved style state/code efficiently - if (styleUpdated) { - msg.refreshOwnTabs = false; - API.refreshAllTabs(msg); - } -} - function sendMessage(msg, callback) { /* Promise mode [default]: diff --git a/js/promisify.js b/js/promisify.js new file mode 100644 index 00000000..4c1ae9e3 --- /dev/null +++ b/js/promisify.js @@ -0,0 +1,17 @@ +'use strict'; + +function promisify(fn) { + return (...args) => + new Promise((resolve, reject) => { + fn(...args, (...result) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + return; + } + resolve( + result.length === 0 ? undefined : + result.length === 1 ? result[0] : result + ); + }); + }); +} diff --git a/manage.html b/manage.html index 20cd9618..85d5e3b7 100644 --- a/manage.html +++ b/manage.html @@ -153,9 +153,11 @@

+ + diff --git a/manage/manage.js b/manage/manage.js index 275dcba8..0430c371 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -32,7 +32,7 @@ const OWN_ICON = chrome.runtime.getManifest().icons['16']; const handleEvent = {}; Promise.all([ - API.getStyles({omitCode: !BG}), + API.getStylesInfo(), urlFilterParam && API.searchDB({query: 'url:' + urlFilterParam}), onDOMready().then(initGlobalEvents), ]).then(args => { @@ -195,7 +195,7 @@ function createStyleElement({style, name}) { }; } const parts = createStyleElement.parts; - const configurable = style.usercssData && Object.keys(style.usercssData.vars).length > 0; + const configurable = style.usercssData && style.usercssData.vars && Object.keys(style.usercssData.vars).length > 0; parts.checker.checked = style.enabled; parts.nameLink.textContent = tWordBreak(style.name); parts.nameLink.href = parts.editLink.href = parts.editHrefBase + style.id; @@ -403,10 +403,7 @@ Object.assign(handleEvent, { }, toggle(event, entry) { - API.saveStyle({ - id: entry.styleId, - enabled: this.matches('.enable') || this.checked, - }); + API.toggleStyle(entry.styleId, this.matches('.enable') || this.checked); }, check(event, entry) { diff --git a/manifest.json b/manifest.json index 86421bc8..ced9f3a0 100644 --- a/manifest.json +++ b/manifest.json @@ -23,6 +23,7 @@ ], "background": { "scripts": [ + "js/promisify.js", "js/messaging.js", "js/storage-util.js", "js/sections-equal.js", @@ -60,7 +61,7 @@ "run_at": "document_start", "all_frames": true, "match_about_blank": true, - "js": ["content/api.js", "content/apply.js"] + "js": ["js/promisify.js", "content/api.js", "content/apply.js"] }, { "matches": ["http://userstyles.org/*", "https://userstyles.org/*"],