diff --git a/background/background.js b/background/background.js index 6194a156..50843039 100644 --- a/background/background.js +++ b/background/background.js @@ -4,15 +4,17 @@ global handleCssTransitionBug detectSloppyRegexps global openEditor global styleViaAPI global loadScript -global usercss +global usercss styleManager */ 'use strict'; window.API_METHODS = Object.assign(window.API_METHODS || {}, { - getStyles, - saveStyle, - deleteStyle, + // getStyles, + getSectionsByUrl: styleManager.getSectionsByUrl, + getSectionsById: styleManager.getSectionsById, + // saveStyle, + // deleteStyle, getStyleFromDB: id => dbExec('get', id).then(event => event.target.result), @@ -495,25 +497,33 @@ function updateIcon({tab, styles}) { function onRuntimeMessage(msg, sender, sendResponse) { - const fn = window.API_METHODS[msg.method]; - if (!fn) return; + 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; - // wrap 'Error' object instance as {__ERROR__: message}, - // which will be unwrapped by sendMessage, - // and prevent exceptions on sending to a closed tab - const respond = data => - tryCatch(sendResponse, - data instanceof Error ? {__ERROR__: data.message} : data); - - const result = fn(msg, sender, respond); - if (result instanceof Promise) { - result - .catch(e => ({__ERROR__: e instanceof Error ? e.message : e})) - .then(respond); - return KEEP_CHANNEL_OPEN; - } else if (result === KEEP_CHANNEL_OPEN) { - return KEEP_CHANNEL_OPEN; - } else if (result !== undefined) { - respond(result); + 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); + } } } diff --git a/background/db.js b/background/db.js index da088e41..081e9642 100644 --- a/background/db.js +++ b/background/db.js @@ -1,3 +1,5 @@ +'use strict'; + const db = (() => { let exec; const preparing = prepare(); @@ -103,9 +105,9 @@ const db = (() => { case 'put': if (!data.id) { - return getStyles().then(() => { + return getAllStyles().then(styles => { data.id = 1; - for (const style of cachedStyles.list) { + for (const style of styles) { data.id = Math.max(data.id, style.id + 1); } return dbExecChromeStorage('put', data); @@ -118,17 +120,22 @@ const db = (() => { return chromeLocal.remove(STYLE_KEY_PREFIX + data); case 'getAll': - return chromeLocal.get(null).then(storage => { - const styles = []; - for (const key in storage) { - if (key.startsWith(STYLE_KEY_PREFIX) && - Number(key.substr(STYLE_KEY_PREFIX.length))) { - styles.push(storage[key]); - } - } - return {target: {result: styles}}; - }); + return getAllStyles() + .then(styles => ({target: {result: styles}})); } return Promise.reject(); + + function getAllStyles() { + return chromeLocal.get(null).then(storage => { + const styles = []; + for (const key in storage) { + if (key.startsWith(STYLE_KEY_PREFIX) && + Number(key.substr(STYLE_KEY_PREFIX.length))) { + styles.push(storage[key]); + } + } + return styles; + }); + } } })(); diff --git a/background/style-manager.js b/background/style-manager.js index a5b97876..d662b217 100644 --- a/background/style-manager.js +++ b/background/style-manager.js @@ -1,6 +1,9 @@ +/* global createCache db calcStyleDigest normalizeStyleSections db */ +'use strict'; + const styleManager = (() => { const preparing = prepare(); - const styles = new Map; + const styles = new Map(); const cachedStyleForUrl = createCache(); const compiledRe = createCache(); const compiledExclusion = createCache(); @@ -50,6 +53,7 @@ const styleManager = (() => { cachedStyleForUrl.clear(); // FIXME: invalid signature notifyAllTabs(); + return style; }); } @@ -68,7 +72,7 @@ const styleManager = (() => { // FIXME: update installDate? style = Object.assign(oldStyle, style); style.sections = normalizeStyleSections(style); - return dbExec('put', style); + return db.exec('put', style); }) .then(event => { if (style.id == null) { @@ -142,7 +146,7 @@ const styleManager = (() => { } function urlMatchStyle(url, style) { - if (style.exclusions && style.exclusions.some(e => compileExclusion(e).test(url)) { + if (style.exclusions && style.exclusions.some(e => compileExclusion(e).test(url))) { return false; } return true; diff --git a/background/style-via-api.js b/background/style-via-api.js index 5a2c2d3c..0213caf1 100644 --- a/background/style-via-api.js +++ b/background/style-via-api.js @@ -22,10 +22,10 @@ API_METHODS.styleViaAPI = !CHROME && (() => { let observingTabs = false; - return (request, sender) => { + return function (request) { const action = ACTIONS[request.action]; return !action ? NOP : - action(request, sender) + action(request, this.sender) .catch(onError) .then(maybeToggleObserver); }; diff --git a/content/api.js b/content/api.js new file mode 100644 index 00000000..6409072f --- /dev/null +++ b/content/api.js @@ -0,0 +1,55 @@ +'use strict'; + +const API = (() => { + const preparing = promisify(chrome.runtime.getBackgroundPage.bind(chrome.runtime))() + .catch(() => null); + const runtimeSendMessage = promisify(chrome.runtime.sendMessage.bind(chrome.runtime)); + return new Proxy(() => {}, { + get: (target, name) => + (...args) => invokeBG(name, args), + }); + + function sendMessage(msg) { + return runtimeSendMessage(msg) + .then(result => { + if (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) { + return sendMessage({ + method: 'invokeAPI', + name, + args + }); + } + // FIXME: why deep-copying input/output? + if (BG !== window) { + args = BG.deepCopy(args); + } + return BG.API_METHODS[name](...args) + .then(BG.deepCopy); + }); + } +})(); diff --git a/content/apply.js b/content/apply.js index d18b0a22..968d0d0a 100644 --- a/content/apply.js +++ b/content/apply.js @@ -25,7 +25,13 @@ const FF_BUG461 = !CHROME && !isOwnPage && !Event.prototype.getPreventDefault; const pageContextQueue = []; - requestStyles(); + requestStyles({}, styles => { + // FIXME: need transition patch? + // if (needTransitionPatch(styles)) { + // applyTransitionPatch(); + // } + applyStyles(styles); + }); chrome.runtime.onMessage.addListener(applyOnMessage); window.applyOnMessage = applyOnMessage; @@ -36,7 +42,7 @@ function requestStyles(options, callback = applyStyles) { if (!chrome.app && document instanceof XMLDocument) { - chrome.runtime.sendMessage({method: 'styleViaAPI', action: 'styleApply'}); + API.styleViaAPI({action: 'styleApply'}); return; } var matchUrl = location.href; @@ -49,19 +55,15 @@ } } catch (e) {} } - const request = Object.assign({ - method: 'getStylesForFrame', - asHash: true, - matchUrl, - }, options); - // On own pages we request the styles directly to minimize delay and flicker - if (typeof API === 'function') { - API.getStyles(request).then(callback); - } else if (!CHROME && getStylesFallback(request)) { - // NOP - } else { - chrome.runtime.sendMessage(request, callback); - } + // const request = Object.assign({ + // method: 'getStylesForFrame', + // asHash: true, + // matchUrl, + // }, options); + // FIXME: options? + // FIXME: getStylesFallback? + API.getSectionsByUrl(matchUrl) + .then(callback); } /** @@ -99,12 +101,12 @@ if (!chrome.app && document instanceof XMLDocument && request.method !== 'ping') { request.action = request.method; - request.method = 'styleViaAPI'; + request.method = null; request.styles = null; if (request.style) { request.style.sections = null; } - chrome.runtime.sendMessage(request); + API.styleViaAPI(request); return; } @@ -120,7 +122,7 @@ } if (request.style.enabled) { removeStyle({id: request.style.id, retire: true}); - requestStyles({id: request.style.id}); + API.getSectionsById(request.style.id).then(applyStyles); } else { removeStyle(request.style); } @@ -128,7 +130,7 @@ case 'styleAdded': if (request.style.enabled) { - requestStyles({id: request.style.id}); + API.getSectionsById(request.style.id).then(applyStyles); } break; @@ -193,7 +195,7 @@ addStyleElement(inCache); disabledElements.delete(id); } else { - requestStyles({id}); + API.getSectionsById(id).then(applyStyles); } } else { if (inDoc) { @@ -224,11 +226,11 @@ } function applyStyles(styles) { - if (!styles) { + // if (!styles) { // Chrome is starting up - requestStyles(); - return; - } + // requestStyles(); + // return; + // } if (!document.documentElement) { new MutationObserver((mutations, observer) => { @@ -240,12 +242,13 @@ return; } - if ('disableAll' in styles) { - doDisableAll(styles.disableAll); - } - if ('exposeIframes' in styles) { - doExposeIframes(styles.exposeIframes); - } + // FIXME: switch to prefs + // if ('disableAll' in styles) { + // doDisableAll(styles.disableAll); + // } + // if ('exposeIframes' in styles) { + // doExposeIframes(styles.exposeIframes); + // } const gotNewStyles = styles.length || styles.needTransitionPatch; if (gotNewStyles) { @@ -256,22 +259,17 @@ } } - if (styles.needTransitionPatch) { - applyTransitionPatch(); - } - if (gotNewStyles) { - for (const id in styles) { - const sections = styles[id]; - if (!Array.isArray(sections)) continue; - applySections(id, sections.map(({code}) => code).join('\n')); + for (const section of styles) { + applySections(section.id, section.code); } docRootObserver.firstStart(); } - if (FF_BUG461 && (gotNewStyles || styles.needTransitionPatch)) { - setContentsInPageContext(); - } + // FIXME + // if (FF_BUG461 && (gotNewStyles || styles.needTransitionPatch)) { + // setContentsInPageContext(); + // } if (!isOwnPage && !docRewriteObserver && styleElements.size) { initDocRewriteObserver(); diff --git a/js/cache.js b/js/cache.js index 10400920..cb6514b0 100644 --- a/js/cache.js +++ b/js/cache.js @@ -1,5 +1,7 @@ +'use strict'; + function createCache(size = 1000) { - const map = new Map; + const map = new Map(); const buffer = Array(size); let index = 0; let lastIndex = 0; @@ -9,7 +11,9 @@ function createCache(size = 1000) { delete: delete_, clear, has: id => map.has(id), - get size: () => map.size + get size() { + return map.size; + } }; function get(id) { diff --git a/js/messaging.js b/js/messaging.js index 3023ad7b..ccca8de6 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -104,61 +104,6 @@ if (FIREFOX_NO_DOM_STORAGE) { Object.defineProperty(window, 'sessionStorage', {value: {}}); } -// eslint-disable-next-line no-var -var API = (() => { - return new Proxy(() => {}, { - get: (target, name) => - name === 'remoteCall' ? - remoteCall : - arg => invokeBG(name, arg), - }); - - function remoteCall(name, arg, remoteWindow) { - let thing = window[name] || window.API_METHODS[name]; - if (typeof thing === 'function') { - thing = thing(arg); - } - if (!thing || typeof thing !== 'object') { - return thing; - } else if (thing instanceof Promise) { - return thing.then(product => remoteWindow.deepCopy(product)); - } else { - return remoteWindow.deepCopy(thing); - } - } - - function invokeBG(name, arg = {}) { - if (BG && (name in BG || name in BG.API_METHODS)) { - const call = BG !== window ? - BG.API.remoteCall(name, BG.deepCopy(arg), window) : - remoteCall(name, arg, BG); - return Promise.resolve(call); - } - if (BG && BG.getStyles) { - throw new Error('Bad API method', name, arg); - } - if (FIREFOX) { - arg.method = name; - return sendMessage(arg); - } - return onBackgroundReady().then(() => invokeBG(name, arg)); - } - - function onBackgroundReady() { - return BG && BG.getStyles ? Promise.resolve() : new Promise(function ping(resolve) { - sendMessage({method: 'healthCheck'}, health => { - if (health !== undefined) { - BG = chrome.extension.getBackgroundPage(); - resolve(); - } else { - setTimeout(ping, 0, resolve); - } - }); - }); - } -})(); - - function notifyAllTabs(msg) { const originalMessage = msg; const styleUpdated = msg.method === 'styleUpdated' || msg.method === 'exclusionsUpdated'; @@ -224,7 +169,6 @@ function notifyAllTabs(msg) { } } - function sendMessage(msg, callback) { /* Promise mode [default]: diff --git a/manifest.json b/manifest.json index 2a6a3ad2..9fdac317 100644 --- a/manifest.json +++ b/manifest.json @@ -57,7 +57,7 @@ "run_at": "document_start", "all_frames": true, "match_about_blank": true, - "js": ["content/apply.js"] + "js": ["content/api.js", "content/apply.js"] }, { "matches": ["http://userstyles.org/*", "https://userstyles.org/*"],