diff --git a/background.js b/background.js index 567dbfd3..14e57d53 100644 --- a/background.js +++ b/background.js @@ -62,10 +62,10 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { && sender.tab.url == request.matchUrl) { updateIcon(sender.tab, styles); } - return true; + return KEEP_CHANNEL_OPEN; case "saveStyle": - saveStyle(request, sendResponse); - return true; + saveStyle(request).then(sendResponse); + return KEEP_CHANNEL_OPEN; case "invalidateCache": if (typeof invalidateCache != "undefined") { invalidateCache(false); @@ -73,7 +73,7 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { break; case "healthCheck": getDatabase(function() { sendResponse(true); }, function() { sendResponse(false); }); - return true; + return KEEP_CHANNEL_OPEN; case "openURL": openURL(request); break; @@ -88,6 +88,9 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { chrome.contextMenus.update("disableAll", {checked: request.value}); } break; + case "refreshAllTabs": + refreshAllTabs().then(sendResponse); + return KEEP_CHANNEL_OPEN; } }); diff --git a/backup/fileSaveLoad.js b/backup/fileSaveLoad.js index 0f481ee4..8d5415a3 100644 --- a/backup/fileSaveLoad.js +++ b/backup/fileSaveLoad.js @@ -1,4 +1,4 @@ -/* globals getStyles, saveStyle */ +/* globals getStyles, saveStyle, invalidateCache, refreshAllTabs, handleUpdate */ 'use strict'; var STYLISH_DUMP_FILE_EXT = '.txt'; @@ -71,27 +71,26 @@ document.getElementById('file-all-styles').addEventListener('click', function () }); }); -document.getElementById('unfile-all-styles').addEventListener('click', function () { - loadFromFile(STYLISH_DUMPFILE_EXTENSION).then(function (rawText) { - var json = JSON.parse(rawText); - var i = 0, nextStyle; +document.getElementById('unfile-all-styles').addEventListener('click', () => { + loadFromFile(STYLISH_DUMPFILE_EXTENSION).then(rawText => { + const json = JSON.parse(rawText); + const numStyles = json.length; - function done() { - window.alert(i + ' styles installed/updated'); - location.reload(); - } + invalidateCache(true); + proceed(); function proceed() { - nextStyle = json[i++]; + const nextStyle = json.shift(); if (nextStyle) { - saveStyle(nextStyle, proceed); - } - else { - i--; - done(); + saveStyle(nextStyle, {notify: false}).then(style => { + handleUpdate(style); + setTimeout(proceed, 0); + }); + } else { + refreshAllTabs().then(() => { + setTimeout(alert, 100, numStyles + ' styles installed/updated'); + }); } } - - proceed(); }); }); diff --git a/manage.js b/manage.js index 465ca650..97f826fe 100644 --- a/manage.js +++ b/manage.js @@ -19,9 +19,7 @@ function showStyles(styles) { return; } styles.sort(function(a, b) { return a.name.localeCompare(b.name)}); - styles.map(createStyleElement).forEach(function(e) { - installed.appendChild(e); - }); + styles.forEach(handleUpdate); if (history.state) { window.scrollTo(0, history.state.scrollY); } @@ -163,10 +161,8 @@ function getStyleElement(event) { chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { switch (request.method) { case "styleUpdated": - handleUpdate(request.style); - break; case "styleAdded": - installed.appendChild(createStyleElement(request.style)); + handleUpdate(request.style); break; case "styleDeleted": handleDelete(request.id); @@ -176,12 +172,17 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { function handleUpdate(style) { var element = createStyleElement(style); - installed.replaceChild(element, installed.querySelector("[style-id='" + style.id + "']")); + var oldElement = installed.querySelector(`[style-id="${style.id}"]`); + if (!oldElement) { + installed.appendChild(element); + return; + } + installed.replaceChild(element, oldElement); if (style.id == lastUpdatedStyleId) { lastUpdatedStyleId = null; - element.className = element.className += " update-done"; - element.querySelector(".update-note").innerHTML = t('updateCompleted'); - }; + element.className = element.className += ' update-done'; + element.querySelector('.update-note').innerHTML = t('updateCompleted'); + } } function handleDelete(id) { @@ -465,41 +466,6 @@ function initFilter(className, node) { onFilterChange(className, {target: node}); } -function importStyles (e) { - var file = e.target.files[0]; - var reader = new FileReader(); - var styles = []; - - function save () { - var style = styles.shift(); - if (style) { - delete style.id; - saveStyle(style, save); - } - else { - window.location.reload() - } - } - - reader.onloadend = function (evt) { - try { - styles = JSON.parse(evt.target.result); - save(); - } - catch (e) { - window.alert(e.message); - } - }; - reader.onerror = function (e) { - window.alert(e.message); - } - reader.readAsText(file) -} - -function selectAll () { - document.execCommand('selectAll'); -} - document.addEventListener("DOMContentLoaded", function() { installed = document.getElementById("installed"); if (document.stylishStyles) { @@ -520,5 +486,4 @@ document.addEventListener("DOMContentLoaded", function() { ]); initFilter("enabled-only", document.getElementById("manage.onlyEnabled")); initFilter("edited-only", document.getElementById("manage.onlyEdited")); - }); diff --git a/messaging.js b/messaging.js index 1f818ab3..1e7cc94e 100644 --- a/messaging.js +++ b/messaging.js @@ -1,3 +1,6 @@ +// keep message channel open for sendResponse in chrome.runtime.onMessage listener +const KEEP_CHANNEL_OPEN = true; + function notifyAllTabs(request) { chrome.windows.getAll({populate: true}, function(windows) { windows.forEach(function(win) { @@ -16,6 +19,29 @@ function notifyAllTabs(request) { } } +function refreshAllTabs() { + return new Promise(resolve => { + chrome.windows.getAll({populate: true}, windows => { + windows.forEach((win, winIndex) => { + win.tabs.forEach((tab, tabIndex) => { + getStyles({matchUrl: tab.url, enabled: true, asHash: true}, styles => { + const message = {method: 'styleReplaceAll', styles}; + if (tab.url == location.href && typeof applyOnMessage !== 'undefined') { + applyOnMessage(message); + } else { + chrome.tabs.sendMessage(tab.id, message); + } + updateIcon(tab, styles); + if (winIndex == windows.length - 1 && tabIndex == win.tabs.length - 1) { + resolve(); + } + }); + }); + }); + }); + }); +} + function updateIcon(tab, styles) { // while NTP is still loading only process the request for its main frame with a real url // (but when it's loaded we should process style toggle requests from popups, for example) diff --git a/storage.js b/storage.js index d375525c..8ff97738 100644 --- a/storage.js +++ b/storage.js @@ -95,72 +95,65 @@ function filterStyles(styles, options) { return styles; } -function saveStyle(o, callback) { - getDatabase(function(db) { - var tx = db.transaction(["styles"], "readwrite"); - var os = tx.objectStore("styles"); +function saveStyle(style, {notify = true} = {}) { + return new Promise(resolve => { + getDatabase(db => { + const tx = db.transaction(['styles'], 'readwrite'); + const os = tx.objectStore('styles'); - // Update - if (o.id) { - var request = os.get(Number(o.id)); - request.onsuccess = function(event) { - var style = request.result || {}; - for (var prop in o) { - if (prop == "id") { - continue; - } - style[prop] = o[prop]; - } - request = os.put(style); - request.onsuccess = function(event) { - notifyAllTabs({method: "styleUpdated", style: style}); - invalidateCache(true); - if (callback) { - callback(style); - } + // Update + if (style.id) { + style.id = Number(style.id); + os.get(style.id).onsuccess = eventGet => { + style = Object.assign({}, eventGet.target.result, style); + os.put(style).onsuccess = eventPut => { + style.id = style.id || eventPut.target.result; + if (notify) { + notifyAllTabs({method: 'styleUpdated', style}); + } + invalidateCache(notify); + resolve(style); + }; }; - }; - return; - } - - // Create - // Set optional things to null if they're undefined - ["updateUrl", "md5Url", "url", "originalMd5"].filter(function(att) { - return !(att in o); - }).forEach(function(att) { - o[att] = null; - }); - // Set other optional things to empty array if they're undefined - o.sections.forEach(function(section) { - ["urls", "urlPrefixes", "domains", "regexps"].forEach(function(property) { - if (!section[property]) { - section[property] = []; - } - }); - }); - // Set to enabled if not set - if (!("enabled" in o)) { - o.enabled = true; - } - // Make sure it's not null - that makes indexeddb sad - delete o["id"]; - var request = os.add(o); - request.onsuccess = function(event) { - invalidateCache(true); - // Give it the ID that was generated - o.id = event.target.result; - notifyAllTabs({method: "styleAdded", style: o}); - if (callback) { - callback(o); + return; } - }; + + // Create + style = Object.assign({ + // Set optional things if they're undefined + enabled: true, + updateUrl: null, + md5Url: null, + url: null, + originalMd5: null, + }, style, { + // Set other optional things to empty array if they're undefined + sections: style.sections.map(section => + Object.assign({ + urls: [], + urlPrefixes: [], + domains: [], + regexps: [], + }, section) + ), + }) + // Make sure it's not null - that makes indexeddb sad + delete style.id; + os.add(style).onsuccess = event => { + invalidateCache(true); + // Give it the ID that was generated + style.id = event.target.result; + notifyAllTabs({method: 'styleAdded', style}); + resolve(style); + }; + }); }); } function enableStyle(id, enabled) { - saveStyle({id: id, enabled: enabled}, function(style) { + saveStyle({id: id, enabled: enabled}).then(style => { handleUpdate(style); - notifyAllTabs({method: "styleUpdated", style: style}); + notifyAllTabs({method: "styleUpdated", style}); }); } diff --git a/update.js b/update.js index cf7e4645..e974471f 100644 --- a/update.js +++ b/update.js @@ -71,7 +71,7 @@ var update = { json.method = 'saveStyle'; json.id = style.id; - saveStyle(json, function () { + saveStyle(json).then(style => { observe('single-updated', style.name); }); }