diff --git a/background.js b/background.js index f709b398..e0367f3e 100644 --- a/background.js +++ b/background.js @@ -1,4 +1,4 @@ -/* global getDatabase, getStyles, saveStyle */ +/* global dbExec, getStyles, saveStyle */ 'use strict'; // eslint-disable-next-line no-var @@ -6,7 +6,7 @@ var browserCommands, contextMenus; // ************************************************************************* // preload the DB and report errors -getDatabase(() => {}, (...args) => { +dbExec().catch((...args) => { args.forEach(arg => 'message' in arg && console.error(arg.message)); }); @@ -183,7 +183,7 @@ prefs.subscribe((id, checked) => { // ************************************************************************* function webNavigationListener(method, {url, tabId, frameId}) { - getStyles({matchUrl: url, enabled: true, asHash: true}, styles => { + getStyles({matchUrl: url, enabled: true, asHash: true}).then(styles => { if (method && !url.startsWith('chrome:') && tabId >= 0) { chrome.tabs.sendMessage(tabId, { method, @@ -209,9 +209,9 @@ function updateIcon(tab, styles) { stylesReceived(styles); return; } - getTabRealURL(tab).then(url => - getStyles({matchUrl: url, enabled: true, asHash: true}, - stylesReceived)); + getTabRealURL(tab) + .then(url => getStyles({matchUrl: url, enabled: true, asHash: true})) + .then(stylesReceived); function stylesReceived(styles) { let numStyles = styles.length; @@ -255,7 +255,7 @@ function onRuntimeMessage(request, sender, sendResponse) { switch (request.method) { case 'getStyles': - getStyles(request, sendResponse); + getStyles(request).then(sendResponse); return KEEP_CHANNEL_OPEN; case 'saveStyle': @@ -263,9 +263,9 @@ function onRuntimeMessage(request, sender, sendResponse) { return KEEP_CHANNEL_OPEN; case 'healthCheck': - getDatabase( - () => sendResponse(true), - () => sendResponse(false)); + dbExec() + .then(() => sendResponse(true)) + .catch(() => sendResponse(false)); return KEEP_CHANNEL_OPEN; case 'download': diff --git a/messaging.js b/messaging.js index 81fe30ca..5c95ad33 100644 --- a/messaging.js +++ b/messaging.js @@ -268,13 +268,13 @@ function sessionStorageHash(name) { } -function onBackgroundReady() { - return BG ? Promise.resolve() : new Promise(ping); +function onBackgroundReady(...dataPassthru) { + return BG ? Promise.resolve(...dataPassthru) : new Promise(ping); function ping(resolve) { chrome.runtime.sendMessage({method: 'healthCheck'}, health => { if (health !== undefined) { BG = chrome.extension.getBackgroundPage(); - resolve(); + resolve(...dataPassthru); } else { ping(resolve); } @@ -285,20 +285,13 @@ function onBackgroundReady() { // in case Chrome haven't yet loaded the bg page and displays our page like edit/manage function getStylesSafe(options) { - return new Promise(resolve => { - if (BG) { - BG.getStyles(options, resolve); - } else { - onBackgroundReady().then(() => - BG.getStyles(options, resolve)); - } - }); + return onBackgroundReady(options).then(BG.getStyles); } function saveStyleSafe(style) { - return onBackgroundReady() - .then(() => BG.saveStyle(BG.deepCopy(style))) + return onBackgroundReady(BG.deepCopy(style)) + .then(BG.saveStyle) .then(savedStyle => { if (style.notify === false) { handleUpdate(savedStyle, style); @@ -309,8 +302,8 @@ function saveStyleSafe(style) { function deleteStyleSafe({id, notify = true} = {}) { - return onBackgroundReady() - .then(() => BG.deleteStyle({id, notify})) + return onBackgroundReady({id, notify}) + .then(BG.deleteStyle) .then(() => { if (!notify) { handleDelete(id); diff --git a/storage.js b/storage.js index a64c2abc..0479c9cf 100644 --- a/storage.js +++ b/storage.js @@ -43,58 +43,69 @@ var chromeLocal = { }; -function getDatabase(ready, error) { - const dbOpenRequest = window.indexedDB.open('stylish', 2); - dbOpenRequest.onsuccess = event => { - ready(event.target.result); - }; - dbOpenRequest.onerror = event => { - console.warn(event.target.errorCode); - if (error) { - error(event); - } - }; - dbOpenRequest.onupgradeneeded = event => { - if (event.oldVersion == 0) { - event.target.result.createObjectStore('styles', { - keyPath: 'id', - autoIncrement: true, - }); - } - }; +function dbExec(method, data) { + return new Promise((resolve, reject) => { + Object.assign(indexedDB.open('stylish', 2), { + onsuccess(event) { + const database = event.target.result; + if (!method) { + resolve(database); + } else { + const transaction = database.transaction(['styles'], 'readwrite'); + const store = transaction.objectStore('styles'); + Object.assign(store[method](data), { + onsuccess: event => resolve(event, store, transaction, database), + onerror: reject, + }); + } + }, + onerror(event) { + console.warn(event.target.errorCode); + reject(event); + }, + onupgradeneeded(event) { + if (event.oldVersion == 0) { + event.target.result.createObjectStore('styles', { + keyPath: 'id', + autoIncrement: true, + }); + } + }, + }); + }); } -function getStyles(options, callback) { +function getStyles(options) { if (cachedStyles.list) { - callback(filterStyles(options)); - return; + return Promise.resolve(filterStyles(options)); } if (cachedStyles.mutex.inProgress) { - cachedStyles.mutex.onDone.push({options, callback}); - return; + return new Promise(resolve => { + cachedStyles.mutex.onDone.push({options, resolve}); + }); } cachedStyles.mutex.inProgress = true; - getDatabase(db => { - const tx = db.transaction(['styles'], 'readonly'); - const os = tx.objectStore('styles'); - os.getAll().onsuccess = event => { - cachedStyles.list = event.target.result || []; - cachedStyles.byId.clear(); - for (const style of cachedStyles.list) { - cachedStyles.byId.set(style.id, style); - compileStyleRegExps({style}); + return dbExec('getAll').then(event => { + cachedStyles.list = event.target.result || []; + cachedStyles.byId.clear(); + const t0 = performance.now(); + let hasTimeToCompile = true; + for (const style of cachedStyles.list) { + cachedStyles.byId.set(style.id, style); + if (hasTimeToCompile) { + hasTimeToCompile = !compileStyleRegExps({style}) || performance.now() - t0 > 100; } - callback(filterStyles(options)); + } - cachedStyles.mutex.inProgress = false; - for (const {options, callback} of cachedStyles.mutex.onDone) { - callback(filterStyles(options)); - } - cachedStyles.mutex.onDone = []; - }; - }, null); + cachedStyles.mutex.inProgress = false; + for (const {options, resolve} of cachedStyles.mutex.onDone) { + resolve(filterStyles(options)); + } + cachedStyles.mutex.onDone = []; + return filterStyles(options); + }); } @@ -213,83 +224,81 @@ function filterStylesInternal({ function saveStyle(style) { - return new Promise(resolve => { - getDatabase(db => { - const tx = db.transaction(['styles'], 'readwrite'); - const os = tx.objectStore('styles'); - - const id = style.id == '0' ? 0 : Number(style.id) || null; - const reason = style.reason; - const notify = style.notify !== false; - delete style.method; - delete style.reason; - delete style.notify; - if (!style.name) { - delete style.name; - } - let existed, codeIsUpdated; - - if (id !== null) { - // Update or create - style.id = id; - os.get(id).onsuccess = eventGet => { - const oldStyle = eventGet.target.result; - existed = Boolean(oldStyle); - codeIsUpdated = !existed || style.sections && !styleSectionsEqual(style, oldStyle); - write(Object.assign({}, oldStyle, style)); - }; - } else { - // Create - delete style.id; - write(Object.assign({ - // Set optional things if they're undefined - enabled: true, - updateUrl: null, - md5Url: null, - url: null, - originalMd5: null, - }, style)); - } - - function write(style) { - style.sections = normalizeStyleSections(style); - os.put(style).onsuccess = event => { - style.id = style.id || event.target.result; - invalidateCache(existed ? {updated: style} : {added: style}); - compileStyleRegExps({style}); - if (notify) { - notifyAllTabs({ - method: existed ? 'styleUpdated' : 'styleAdded', - style, codeIsUpdated, reason, - }); - } - if (reason == 'update') { - updateStyleDigest(style); - } else if (reason == 'import') { - chrome.storage.local.remove(DIGEST_KEY_PREFIX + style.id, ignoreChromeError); - } - resolve(style); - }; - } + const id = Number(style.id) >= 0 ? Number(style.id) : null; + const reason = style.reason; + const notify = style.notify !== false; + delete style.method; + delete style.reason; + delete style.notify; + if (!style.name) { + delete style.name; + } + let existed, codeIsUpdated; + if (id !== null) { + // Update or create + style.id = id; + return dbExec('get', id).then((event, store) => { + const oldStyle = event.target.result; + existed = Boolean(oldStyle); + codeIsUpdated = !existed || style.sections && !styleSectionsEqual(style, oldStyle); + style = Object.assign({}, oldStyle, style); + return write(style, store); }); - }); + } else { + // Create + delete style.id; + style = Object.assign({ + // Set optional things if they're undefined + enabled: true, + updateUrl: null, + md5Url: null, + url: null, + originalMd5: null, + }, style); + return write(style); + } + + function write(style, store) { + style.sections = normalizeStyleSections(style); + if (store) { + return new Promise(resolve => { + store.put(style).onsuccess = event => resolve(done(event)); + }); + } else { + return dbExec('put', style).then(done); + } + } + + function done(event) { + style.id = style.id || event.target.result; + invalidateCache(existed ? {updated: style} : {added: style}); + compileStyleRegExps({style}); + if (notify) { + notifyAllTabs({ + method: existed ? 'styleUpdated' : 'styleAdded', + style, codeIsUpdated, reason, + }); + } + if (reason == 'update') { + updateStyleDigest(style); + } else if (reason == 'import') { + chrome.storage.local.remove(DIGEST_KEY_PREFIX + style.id, ignoreChromeError); + } + return style; + } } function deleteStyle({id, notify = true}) { + id = Number(id); chrome.storage.local.remove(DIGEST_KEY_PREFIX + id, ignoreChromeError); - return new Promise(resolve => - getDatabase(db => { - const tx = db.transaction(['styles'], 'readwrite'); - const os = tx.objectStore('styles'); - os.delete(Number(id)).onsuccess = () => { - invalidateCache({deletedId: id}); - if (notify) { - notifyAllTabs({method: 'styleDeleted', id}); - } - resolve(id); - }; - })); + return dbExec('delete', id).then(() => { + invalidateCache({deletedId: id}); + if (notify) { + notifyAllTabs({method: 'styleDeleted', id}); + } + return id; + }); } @@ -448,11 +457,12 @@ function compileStyleRegExps({style, compileAll}) { const rx = tryRegExp(anchored); cachedStyles.regexps.set(cacheKey, rx || false); if (!compileAll && performance.now() - t0 > 100) { - return; + return false; } } } } + return true; } diff --git a/update.js b/update.js index 8c40a210..5996aa02 100644 --- a/update.js +++ b/update.js @@ -22,17 +22,14 @@ var updater = { checkAllStyles({observer = () => {}, save = true, ignoreDigest} = {}) { updater.resetInterval(); - return new Promise(resolve => { - getStyles({}, styles => { - styles = styles.filter(style => style.updateUrl); - observer(updater.COUNT, styles.length); - Promise.all(styles.map(style => - updater.checkStyle({style, observer, save, ignoreDigest}) - )).then(() => { - observer(updater.DONE); - resolve(); - }); - }); + return getStyles({}).then(styles => { + styles = styles.filter(style => style.updateUrl); + observer(updater.COUNT, styles.length); + return Promise.all( + styles.map(style => + updater.checkStyle({style, observer, save, ignoreDigest}))); + }).then(() => { + observer(updater.DONE); }); },