diff --git a/background/background.js b/background/background.js index 58c741a9..1909b136 100644 --- a/background/background.js +++ b/background/background.js @@ -53,7 +53,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, { optionsCustomizeHotkeys() { return browser.runtime.openOptionsPage() .then(() => new Promise(resolve => setTimeout(resolve, 100))) - .then(() => sendMessage({method: 'optionsCustomizeHotkeys'})); + .then(() => msg.broadcastExtension({method: 'optionsCustomizeHotkeys'})); }, }); @@ -62,7 +62,7 @@ var browserCommands, contextMenus; // ************************************************************************* // register all listeners -chrome.runtime.onMessage.addListener(onRuntimeMessage); +msg.on(onRuntimeMessage); if (FIREFOX) { // see notes in apply.js for getStylesFallback @@ -196,7 +196,7 @@ contextMenus = { contexts: ['editable'], documentUrlPatterns: [URLS.ownOrigin + 'edit*'], click: (info, tab) => { - sendMessage({tabId: tab.id, method: 'editDeleteText'}); + msg.sendTab(tab.id, {method: 'editDeleteText'}); }, } }; @@ -280,11 +280,12 @@ window.addEventListener('storageReady', function _() { }; const pingCS = (cs, {id, url}) => { - const maybeInject = pong => !pong && injectCS(cs, id); + const maybeInject = ; cs.matches.some(match => { if ((match === ALL_URLS || url.match(match)) && (!url.startsWith('chrome') || url === NTP)) { - sendMessage({method: 'ping', tabId: id}, maybeInject); + msg.sendTab(id, {method: 'ping'}) + .then(pong => !pong && injectCS(cs, id)); return true; } }); @@ -332,13 +333,15 @@ function webNavigationListener(method, {url, tabId, frameId}) { handleCssTransitionBug({tabId, frameId, url, styles}); } if (tab) styles.exposeIframes = tab.url.replace(/(\/\/[^/]*).*/, '$1'); - sendMessage({ + msg.sendTab( tabId, - frameId, - method, - // ping own page so it retrieves the styles directly - styles: url.startsWith(URLS.ownOrigin) ? 'DIY' : styles, - }); + { + method, + // ping own page so it retrieves the styles directly + styles: url.startsWith(URLS.ownOrigin) ? 'DIY' : styles, + }, + {frameId} + ); } // main page frame id is 0 if (frameId === 0) { @@ -370,7 +373,7 @@ function webNavigationListenerChrome(method, data) { function webNavUsercssInstallerFF(data) { const {tabId} = data; Promise.all([ - sendMessage({tabId, method: 'ping'}), + msg.sendTab(tabId, {method: 'ping'}), // we need tab index to open the installer next to the original one // and also to skip the double-invocation in FF which assigns tab url later getTab(tabId), @@ -384,15 +387,15 @@ function webNavUsercssInstallerFF(data) { function webNavIframeHelperFF({tabId, frameId}) { if (!frameId) return; - sendMessage({method: 'ping', tabId, frameId}, pong => { - ignoreChromeError(); - if (pong) return; - chrome.tabs.executeScript(tabId, { - frameId, - file: '/content/apply.js', - matchAboutBlank: true, - }, ignoreChromeError); - }); + msg.sendTab(tabId, {method: 'ping'}, {frameId}) + .then(pong => { + if (pong) return; + chrome.tabs.executeScript(tabId, { + frameId, + file: '/content/apply.js', + matchAboutBlank: true, + }, ignoreChromeError); + }); } @@ -498,35 +501,14 @@ function updateIcon({tab, styles}) { } } - -function onRuntimeMessage(msg, sender, sendResponse) { +function onRuntimeMessage(msg, sender) { if (msg.method !== 'invokeAPI') { - // FIXME: switch everything to api.js then throw an error when msg.method is unknown. return; } - invoke() - .catch(err => - // wrap 'Error' object instance as {__ERROR__: message}, - // which will be unwrapped by api.js, - ({ - __ERROR__: err.message || String(err) - }) - ) - // prevent exceptions on sending to a closed tab - .then(output => tryCatch(sendResponse, output)); - // keep channel open - return true; - - function invoke() { - try { - const fn = window.API_METHODS[msg.name]; - if (!fn) { - throw new Error(`unknown API: ${msg.name}`); - } - const context = {msg, sender}; - return Promise.resolve(fn.apply(context, msg.args)); - } catch (err) { - return Promise.reject(err); - } + const fn = window.API_METHODS[msg.name]; + if (!fn) { + throw new Error(`unknown API: ${msg.name}`); } + const context = {msg, sender}; + return fn.apply(context, msg.args); } diff --git a/background/style-manager.js b/background/style-manager.js index d8c2f6e1..c90b9286 100644 --- a/background/style-manager.js +++ b/background/style-manager.js @@ -1,6 +1,8 @@ /* eslint no-eq-null: 0, eqeqeq: [2, "smart"] */ -/* global createCache db calcStyleDigest normalizeStyleSections db promisify - getStyleWithNoCode */ +/* + global createCache db calcStyleDigest normalizeStyleSections db promisify + getStyleWithNoCode msg +*/ 'use strict'; const styleManager = (() => { @@ -10,9 +12,6 @@ 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({ @@ -42,11 +41,15 @@ const styleManager = (() => { return saveStyle(newData) .then(newData => { style.data = newData; - return emitChanges({ + const message = { method: 'styleUpdated', codeIsUpdated: false, style: {id, enabled} - }, style.appliesTo); + }; + if ([...style.appliesTo].every(isExtensionUrl)) { + return msg.broadcastExtension(message); + } + return msg.broadcast(message); }) .then(() => id); } @@ -120,6 +123,7 @@ const styleManager = (() => { } function editSave(style) { + // const style = return saveStyle(style); } @@ -142,9 +146,9 @@ const styleManager = (() => { delete cache[id]; } styles.delete(id); - return emitChanges({ + return msg.broadcast({ method: 'styleDeleted', - data: {id} + style: {id} }); }) .then(() => id); @@ -182,28 +186,9 @@ const styleManager = (() => { appliesTo, data: newData }); - return Promise.all([ - // manager and editor might be notified twice... - runtimeSendMessage({method: 'styleAdded', style: getStyleWithNoCode(newData)}), - emitChangesToTabs(tab => { - const code = getAppliedCode(tab.url, newData); - if (!code) { - return; - } - const cache = cachedStyleForUrl.get(tab.url); - if (cache) { - cache[newData.id] = code; - } - appliesTo.add(tab.url); - return { - method: 'styleAdded', - style: { - id: newData.id, - enabled: newData.enabled, - sections: code - } - }; - }) + return Promis.all([ + msg.broadcastExtension({method: 'styleAdded', style: getStyleWithNoCode(newData)}), + msg.broadcastTab(tab => emitStyleAdded(tab, newData, appliesTo)) ]); } else { const excluded = new Set(); @@ -223,14 +208,51 @@ const styleManager = (() => { } style.appliesTo = new Set(updated.keys()); return Promise.all([ - // FIXME: don't sendMessage - runtimeSendMessage({method: 'styleUpdated', }) + msg.broadcastExtension({method: 'styleUpdated', style: getStyleWithNoCode(newData)}) + msg.broadcastTab(tab => { + if (excluded.has(tab.url)) { + return { + method: 'styleDeleted', + style: {id: newData.id} + }; + } + if (updated.has(tab.url)) { + return { + method: 'styleUpdated', + style: { + id: newData.id, + sections: updated.get(tab.url) + }; + }; + } + return emitStyleAdded(tab, newData, style.appliesTo); + }) ]) } return style; }); } + function emitStyleAdded(tab, data, appliesTo) { + const code = getAppliedCode(tab.url, data); + if (!code) { + return; + } + const cache = cachedStyleForUrl.get(tab.url); + if (cache) { + cache[data.id] = code; + } + appliesTo.add(tab.url); + return { + method: 'styleAdded', + style: { + id: data.id, + enabled: data.enabled, + sections: code + } + }; + } + function importStyle(style) { // FIXME: move this to importer // style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future @@ -241,35 +263,16 @@ const styleManager = (() => { } function saveStyle(style) { - return (style.id == null ? getNewStyle() : getOldStyle()) - .then(oldStyle => { - style = Object.assign(oldStyle, style); - // FIXME: why we always run `normalizeStyleSections` at each `saveStyle`? - style.sections = normalizeStyleSections(style); - return db.exec('put', style); - }) + if (!style.name) { + throw new Error('style name is empty'); + } + return db.exec('put', style); .then(event => { if (style.id == null) { style.id = event.target.result; } return style; }); - - function getOldStyle() { - return db.exec('get', style.id) - .then((event, store) => { - if (!event.target.result) { - throw new Error(`Unknown style id: ${style.id}`); - } - return event.target.result; - }); - } - - // FIXME: don't overwrite style name when the name is empty - - function getNewStyle() { - return Promise.resolve(); - } } function getStylesInfoForUrl(url) { diff --git a/content/apply.js b/content/apply.js index 45d44e06..bf7a0834 100644 --- a/content/apply.js +++ b/content/apply.js @@ -32,7 +32,7 @@ // } applyStyles(styles); }); - chrome.runtime.onMessage.addListener(applyOnMessage); + msg.onTab(applyOnMessage); window.applyOnMessage = applyOnMessage; if (!isOwnPage) { @@ -92,7 +92,7 @@ } } - function applyOnMessage(request, sender, sendResponse) { + function applyOnMessage(request) { if (request.styles === 'DIY') { // Do-It-Yourself tells our built-in pages to fetch the styles directly // which is faster because IPC messaging JSON-ifies everything internally @@ -160,8 +160,7 @@ break; case 'ping': - sendResponse(true); - break; + return true; } } @@ -447,7 +446,7 @@ [docRewriteObserver, docRootObserver].forEach(ob => ob && ob.disconnect()); window.removeEventListener(chrome.runtime.id, orphanCheck, true); try { - chrome.runtime.onMessage.removeListener(applyOnMessage); + msg.off(applyOnMessage); } catch (e) {} } diff --git a/content/install-hook-userstyles.js b/content/install-hook-userstyles.js index 40097b3d..0ae2a4f6 100644 --- a/content/install-hook-userstyles.js +++ b/content/install-hook-userstyles.js @@ -8,7 +8,7 @@ document.addEventListener('stylishInstallChrome', onClick); document.addEventListener('stylishUpdateChrome', onClick); - chrome.runtime.onMessage.addListener(onMessage); + msg.on(onMessage); onDOMready().then(() => { window.postMessage({ @@ -43,16 +43,14 @@ } } - function onMessage(msg, sender, sendResponse) { + function onMessage(msg) { switch (msg.method) { case 'ping': // orphaned content script check - sendResponse(true); - break; + return true; case 'openSettings': openSettings(); - sendResponse(true); - break; + return true; } } @@ -328,7 +326,7 @@ document.removeEventListener('stylishInstallChrome', onClick); document.removeEventListener('stylishUpdateChrome', onClick); try { - chrome.runtime.onMessage.removeListener(onMessage); + msg.off(onMessage); } catch (e) {} } })(); diff --git a/edit/applies-to-line-widget.js b/edit/applies-to-line-widget.js index fe995be7..62c7b506 100644 --- a/edit/applies-to-line-widget.js +++ b/edit/applies-to-line-widget.js @@ -131,7 +131,7 @@ function createAppliesToLineWidget(cm) { cm.on('change', onChange); cm.on('optionChange', onOptionChange); - chrome.runtime.onMessage.addListener(onRuntimeMessage); + msg.onExtension(onRuntimeMessage); requestAnimationFrame(updateWidgetStyle); update(); @@ -144,7 +144,7 @@ function createAppliesToLineWidget(cm) { widgets.length = 0; cm.off('change', onChange); cm.off('optionChange', onOptionChange); - chrome.runtime.onMessage.removeListener(onRuntimeMessage); + msg.off(onRuntimeMessage); actualStyle.remove(); } diff --git a/edit/edit.js b/edit/edit.js index 5b307aa8..31aa9e4b 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -27,7 +27,7 @@ let editor; document.addEventListener('visibilitychange', beforeUnload); -chrome.runtime.onMessage.addListener(onRuntimeMessage); +msg.on(onRuntimeMessage); preinit(); diff --git a/js/msg.js b/js/msg.js index 0c85db35..20a8dc9a 100644 --- a/js/msg.js +++ b/js/msg.js @@ -34,7 +34,8 @@ const msg = (() => { broadcastExtension: send, // alias of send on, onTab, - onExtension + onExtension, + off }; function send(data, target = 'extension') { @@ -67,6 +68,7 @@ const msg = (() => { throw new Error('there is no bg handler'); } const handlers = bg._msg.handler.extension.concat(bg._msg.handler.both); + // FIXME: do we want to deepCopy `data`? return Promise.resolve(executeCallbacks(handlers, data, {url: location.href})) .then(deepCopy); } @@ -88,6 +90,7 @@ const msg = (() => { for (const tab of tabs) { const isExtension = tab.url.startsWith(EXTENSION_URL); if ( + tab.discarded || !/^(http|ftp|file)/.test(tab.url) && !tab.url.startsWith('chrome://newtab/') && !isExtension || @@ -129,6 +132,15 @@ const msg = (() => { handler.extension.push(fn); } + function off(fn) { + for (const type of ['both', 'tab', 'extension']) { + const index = handler[type].indexOf(fn); + if (index >= 0) { + handler[type].splice(index, 1); + } + } + } + function initHandler() { if (handler) { return; @@ -266,7 +278,7 @@ const msg = (() => { const API = new Proxy({}, { get: (target, name) => - (...args) => msg.send({ + (...args) => msg.sendBg({ method: 'invokeAPI', name, args diff --git a/manage/manage.js b/manage/manage.js index 004398fa..aa55af4b 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -39,7 +39,7 @@ Promise.all([ showStyles(...args); }); -chrome.runtime.onMessage.addListener(onRuntimeMessage); +msg.onExtension(onRuntimeMessage); function onRuntimeMessage(msg) { switch (msg.method) { diff --git a/manifest.json b/manifest.json index 31ba8391..31840ba8 100644 --- a/manifest.json +++ b/manifest.json @@ -25,6 +25,7 @@ "scripts": [ "js/promisify.js", "js/messaging.js", + "js/msg.js", "js/storage-util.js", "js/sections-equal.js", "background/storage-dummy.js", diff --git a/options.html b/options.html index edf5850e..996ca894 100644 --- a/options.html +++ b/options.html @@ -21,6 +21,8 @@ + + diff --git a/options/options.js b/options/options.js index fba0903c..ee150239 100644 --- a/options/options.js +++ b/options/options.js @@ -21,7 +21,7 @@ if (!FIREFOX && !OPERA && CHROME < 3343) { if (FIREFOX && 'update' in (chrome.commands || {})) { $('[data-cmd="open-keyboard"]').classList.remove('chromium-only'); - chrome.runtime.onMessage.addListener(msg => { + msg.onExtension(msg => { if (msg.method === 'optionsCustomizeHotkeys') { customizeHotkeys(); } diff --git a/popup/popup.js b/popup/popup.js index c999ba28..81516e02 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -26,7 +26,7 @@ getActiveTab().then(tab => showStyles(styles); }); -chrome.runtime.onMessage.addListener(onRuntimeMessage); +msg.onExtension(onRuntimeMessage); function onRuntimeMessage(msg) { switch (msg.method) { @@ -112,8 +112,7 @@ function initPopup() { } getActiveTab().then(function ping(tab, retryCountdown = 10) { - const sendMessage = promisify(chrome.tabs.sendMessage.bind(chrome.tabs)); - sendMessage(tab.id, {method: 'ping'}, {frameId: 0}).then(pong => { + msg.sendTab(tab.id, {method: 'ping'}, {frameId: 0}).then(pong => { if (pong) { return; } @@ -459,9 +458,8 @@ Object.assign(handleEvent, { })) .then(tab => { if (message) { - const sendMessage = promisify(chrome.tabs.sendMessage.bind(chrome.tabs.sendMessage)); return onTabReady(tab) - .then(() => sendMessage(tab.id, message)); + .then(() => msg.sendTab(tab.id, message)); } }) .then(window.close);