From 965ea073c93f1254290df9af8195f886fd6bb965 Mon Sep 17 00:00:00 2001 From: tophf Date: Sun, 4 Oct 2015 15:26:50 +0300 Subject: [PATCH 1/7] Sync localStorage prefs --- manifest.json | 1 + storage.js | 119 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 80 insertions(+), 40 deletions(-) diff --git a/manifest.json b/manifest.json index ecbd1eca..fea8ffd5 100644 --- a/manifest.json +++ b/manifest.json @@ -13,6 +13,7 @@ "tabs", "webNavigation", "contextMenus", + "storage", "http://userstyles.org/", "https://userstyles.org/" ], diff --git a/storage.js b/storage.js index f05cc1bd..8494a98f 100644 --- a/storage.js +++ b/storage.js @@ -157,52 +157,52 @@ function loadPrefs(prefs) { } var prefs = { -// NB: localStorage["not_key"] is undefined, localStorage.getItem("not_key") is null + defaults: { + "openEditInWindow": false, // new editor opens in a own browser window + "windowPosition": {}, // detached window position + "show-badge": true, // display text on popup menu icon + "disableAll": false, // boss key - // defaults - "openEditInWindow": false, // new editor opens in a own browser window - "windowPosition": {}, // detached window position - "show-badge": true, // display text on popup menu icon - "disableAll": false, // boss key + "popup.breadcrumbs": true, // display "New style" links as URL breadcrumbs + "popup.breadcrumbs.usePath": false, // use URL path for "this URL" + "popup.enabledFirst": true, // display enabled styles before disabled styles + "popup.stylesFirst": true, // display enabled styles before disabled styles - "popup.breadcrumbs": true, // display "New style" links as URL breadcrumbs - "popup.breadcrumbs.usePath": false, // use URL path for "this URL" - "popup.enabledFirst": true, // display enabled styles before disabled styles - "popup.stylesFirst": true, // display enabled styles before disabled styles + "manage.onlyEnabled": false, // display only enabled styles + "manage.onlyEdited": false, // display only styles created locally - "manage.onlyEnabled": false, // display only enabled styles - "manage.onlyEdited": false, // display only styles created locally - - "editor.options": {}, // CodeMirror.defaults.* - "editor.lineWrapping": true, // word wrap - "editor.smartIndent": true, // "smart" indent - "editor.indentWithTabs": false,// smart indent with tabs - "editor.tabSize": 4, // tab width, in spaces - "editor.keyMap": navigator.appVersion.indexOf("Windows") > 0 ? "sublime" : "default", - "editor.theme": "default", // CSS theme - "editor.beautify": { // CSS beautifier - selector_separator_newline: true, - newline_before_open_brace: false, - newline_after_open_brace: true, - newline_between_properties: true, - newline_before_close_brace: true, - newline_between_rules: false, - end_with_newline: false + "editor.options": {}, // CodeMirror.defaults.* + "editor.lineWrapping": true, // word wrap + "editor.smartIndent": true, // "smart" indent + "editor.indentWithTabs": false, // smart indent with tabs + "editor.tabSize": 4, // tab width, in spaces + "editor.keyMap": navigator.appVersion.indexOf("Windows") > 0 ? "sublime" : "default", + "editor.theme": "default", // CSS theme + "editor.beautify": { // CSS beautifier + selector_separator_newline: true, + newline_before_open_brace: false, + newline_after_open_brace: true, + newline_between_properties: true, + newline_before_close_brace: true, + newline_between_rules: false, + end_with_newline: false + }, + "editor.lintDelay": 500, // lint gutter marker update delay, ms + "editor.lintReportDelay": 4500, // lint report update delay, ms }, - "editor.lintDelay": 500, // lint gutter marker update delay, ms - "editor.lintReportDelay": 4500, // lint report update delay, ms NO_DEFAULT_PREFERENCE: "No default preference for '%s'", UNHANDLED_DATA_TYPE: "Default '%s' is of type '%s' - what should be done with it?", getPref: function(key, defaultValue) { - // Returns localStorage[key], defaultValue, this[key], or undefined - // as type of defaultValue, this[key], or localStorage[key] + // Returns localStorage[key], defaultValue, this.defaults[key], or undefined + // as type of defaultValue, this.defaults[key], or localStorage[key] var value = localStorage[key]; + // NB: localStorage["not_key"] is undefined, localStorage.getItem("not_key") is null if (value === undefined) { - return defaultValue === undefined ? shallowCopy(this[key]) : defaultValue; + return defaultValue === undefined ? shallowCopy(this.defaults[key]) : defaultValue; } - switch (typeof (defaultValue === undefined ? this[key] : defaultValue)) { + switch (typeof (defaultValue === undefined ? this.defaults[key] : defaultValue)) { case "boolean": return value.toLowerCase() === "true"; case "number": return Number(value); case "object": return JSON.parse(value); @@ -212,22 +212,61 @@ var prefs = { } return value; }, - setPref: function(key, value) { + getAllPrefs: function() { + var all = {}, me = this; + Object.keys(this.defaults).forEach(function(key) { all[key] = me.getPref(key) }); + return all; + }, + setPref: function(key, value, options) { var oldValue = localStorage[key]; - if (value === undefined || equal(value, this[key])) { + if (value === undefined || equal(value, this.defaults[key])) { delete localStorage[key]; } else { localStorage[key] = typeof value == "string" ? value : JSON.stringify(value); } - if (!equal(value, oldValue === undefined ? this[key] : oldValue)) { - var message = {method: "prefChanged", prefName: key, value: value}; - notifyAllTabs(message); - chrome.extension.sendMessage(message); + if (!equal(value, oldValue === undefined ? this.defaults[key] : oldValue)) { + this.broadcast(key, value, options); + } + }, + broadcast: function(key, value, options) { + var message = {method: "prefChanged", prefName: key, value: value}; + notifyAllTabs(message); + chrome.extension.sendMessage(message); + if (!options || !options.noSync) { + clearTimeout(this.syncTimeout); + this.syncTimeout = setTimeout((function() { + chrome.storage.sync.set({"settings": this.getAllPrefs()}); + }).bind(this), 0); } }, removePref: function(key) { setPref(key, undefined) } }; +chrome.storage.sync.get({settings: prefs.getAllPrefs()}, function(result) { + Object.keys(prefs.defaults).forEach(function(key) { + if (key in result.settings) { + prefs.setPref(key, result.settings[key], {noSync: true}); + } + }); +}); + +chrome.storage.onChanged.addListener(function(changes, area) { + if (area == "sync" && "settings" in changes) { + var newSettings = changes.settings.newValue; + for (key in prefs.defaults) { + if (key in newSettings) { + prefs.setPref(key, newSettings[key], {noSync: true}); + } + } + } +}); + +window.addEventListener("storage", function(event) { + if (event.storageArea == localStorage && event.key in prefs.defaults) { + prefs.broadcast(event.key, prefs.getPref(event.key)); + } +}); + function getCodeMirrorThemes(callback) { chrome.runtime.getPackageDirectoryEntry(function(rootDir) { rootDir.getDirectory("codemirror/theme", {create: false}, function(themeDir) { From 274584510f3b4908699012444603abaf0838e277 Mon Sep 17 00:00:00 2001 From: tophf Date: Mon, 5 Oct 2015 17:44:44 +0300 Subject: [PATCH 2/7] Fix getStyles returning global styles for extensions pages This didn't work anyway but wrongly showed nonzero style count in an opened popup when the option was changed in another window. --- background.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/background.js b/background.js index be6cc985..2f362e5c 100644 --- a/background.js +++ b/background.js @@ -224,6 +224,10 @@ function sectionAppliesToUrl(section, url) { if (url.indexOf("http") != 0 && url.indexOf("file") != 0 && url.indexOf("chrome-extension") != 0 && url.indexOf("ftp") != 0) { return false; } + // other extensions can't be styled + if (url.indexOf("chrome-extension") == 0 && url.indexOf(chrome.extension.getURL("")) != 0) { + return false; + } if (!section.urls && !section.domains && !section.urlPrefixes && !section.regexps) { //console.log(section.id + " is global"); return true; From 69a085c116765584498a9fa1bccdf2a5f6530f16 Mon Sep 17 00:00:00 2001 From: tophf Date: Mon, 5 Oct 2015 18:06:05 +0300 Subject: [PATCH 3/7] Fix occasional Chrome mismatch requestUrl!=sender.tab.url --- background.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/background.js b/background.js index 2f362e5c..9c3c1779 100644 --- a/background.js +++ b/background.js @@ -35,7 +35,9 @@ chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { case "getStyles": var styles = getStyles(request, sendResponse); // check if this is a main content frame style enumeration - if (request.matchUrl && !request.id && sender && sender.tab && sender.frameId == 0) { + if (request.matchUrl && !request.id + && sender && sender.tab && sender.frameId == 0 + && sender.tab.url == request.matchUrl) { updateIcon(sender.tab, styles); } return true; From 1f961b099394b91f5837d49fe6e7a324b2225a65 Mon Sep 17 00:00:00 2001 From: tophf Date: Mon, 5 Oct 2015 14:12:24 +0300 Subject: [PATCH 4/7] Deprecate localStorage, refactor prefs --- background.js | 1 - edit.js | 30 +++---- manage.js | 14 +-- messaging.js | 6 +- popup.js | 16 +--- storage.js | 231 ++++++++++++++++++++++++++++++++------------------ 6 files changed, 170 insertions(+), 128 deletions(-) diff --git a/background.js b/background.js index 9c3c1779..27218ef6 100644 --- a/background.js +++ b/background.js @@ -102,7 +102,6 @@ function disableAllStylesToggle(newState) { newState = !prefs.getPref("disableAll"); } prefs.setPref("disableAll", newState); - notifyAllTabs({method: "styleDisableAll", disableAll: newState}); } function getStyles(options, callback) { diff --git a/edit.js b/edit.js index d884fd44..74f0a82b 100644 --- a/edit.js +++ b/edit.js @@ -11,7 +11,7 @@ var propertyToCss = {urls: "url", urlPrefixes: "url-prefix", domains: "domain", var CssToProperty = {"url": "urls", "url-prefix": "urlPrefixes", "domain": "domains", "regexp": "regexps"}; // make querySelectorAll enumeration code readable -["forEach", "some", "indexOf"].forEach(function(method) { +["forEach", "some", "indexOf", "map"].forEach(function(method) { NodeList.prototype[method]= Array.prototype[method]; }); @@ -125,8 +125,7 @@ function initCodeMirror() { var isWindowsOS = navigator.appVersion.indexOf("Windows") > 0; // default option values - var userOptions = prefs.getPref("editor.options"); - var stylishOptions = { + shallowMerge(CM.defaults, { mode: 'css', lineNumbers: true, lineWrapping: true, @@ -142,9 +141,7 @@ function initCodeMirror() { "Alt-PageDown": "nextEditor", "Alt-PageUp": "prevEditor" } - } - shallowMerge(stylishOptions, CM.defaults); - shallowMerge(userOptions, CM.defaults); + }, prefs.getPref("editor.options")); // additional commands CM.commands.jumpToLine = jumpToLine; @@ -158,11 +155,9 @@ function initCodeMirror() { // "basic" keymap only has basic keys by design, so we skip it var extraKeysCommands = {}; - if (userOptions && typeof userOptions.extraKeys == "object") { - Object.keys(userOptions.extraKeys).forEach(function(key) { - extraKeysCommands[userOptions.extraKeys[key]] = true; - }); - } + Object.keys(CM.defaults.extraKeys).forEach(function(key) { + extraKeysCommands[CM.defaults.extraKeys[key]] = true; + }); if (!extraKeysCommands.jumpToLine) { CM.keyMap.sublime["Ctrl-G"] = "jumpToLine"; CM.keyMap.emacsy["Ctrl-G"] = "jumpToLine"; @@ -246,12 +241,11 @@ function initCodeMirror() { }); } document.getElementById("editor.keyMap").innerHTML = optionsHtmlFromArray(Object.keys(CM.keyMap).sort()); - var controlPrefs = {}; - document.querySelectorAll("#options *[data-option][id^='editor.']").forEach(function(option) { - controlPrefs[option.id] = CM.defaults[option.dataset.option]; - }); document.getElementById("options").addEventListener("change", acmeEventListener, false); - loadPrefs(controlPrefs); + loadPrefs( + document.querySelectorAll("#options *[data-option][id^='editor.']") + .map(function(option) { return option.id }) + ); }); hotkeyRerouter.setState(true); @@ -1561,7 +1555,7 @@ function showCodeMirrorPopup(title, html, options) { var popup = showHelp(title, html); popup.classList.add("big"); - popup.codebox = CodeMirror(popup.querySelector(".contents"), shallowMerge(options, { + popup.codebox = CodeMirror(popup.querySelector(".contents"), shallowMerge({ mode: "css", lineNumbers: true, lineWrapping: true, @@ -1572,7 +1566,7 @@ function showCodeMirrorPopup(title, html, options) { styleActiveLine: true, theme: prefs.getPref("editor.theme"), keyMap: prefs.getPref("editor.keyMap") - })); + }, options)); popup.codebox.focus(); popup.codebox.on("focus", function() { hotkeyRerouter.setState(false) }); popup.codebox.on("blur", function() { hotkeyRerouter.setState(true) }); diff --git a/manage.js b/manage.js index 8c35cbb8..5ab077d1 100644 --- a/manage.js +++ b/manage.js @@ -110,7 +110,7 @@ function createStyleElement(style) { event.stopPropagation(); if (openWindow || openBackgroundTab || openForegroundTab) { if (openWindow) { - var options = prefs.getPref('windowPosition', {}); + var options = prefs.getPref("windowPosition"); options.url = url; chrome.windows.create(options); } else { @@ -475,12 +475,12 @@ document.addEventListener("DOMContentLoaded", function() { document.getElementById("search").addEventListener("input", searchStyles); searchStyles(true); // re-apply filtering on history Back - loadPrefs({ - "manage.onlyEnabled": false, - "manage.onlyEdited": false, - "show-badge": true, - "popup.stylesFirst": true - }); + loadPrefs([ + "manage.onlyEnabled", + "manage.onlyEdited", + "show-badge", + "popup.stylesFirst" + ]); initFilter("enabled-only", document.getElementById("manage.onlyEnabled")); initFilter("edited-only", document.getElementById("manage.onlyEdited")); }); diff --git a/messaging.js b/messaging.js index ea30055b..5ecadfff 100644 --- a/messaging.js +++ b/messaging.js @@ -8,11 +8,7 @@ function notifyAllTabs(request) { }); }); // notify all open popups - // use a shallow copy since the original `request` is still being processed - var reqPopup = {}; - for (var k in request) reqPopup[k] = request[k]; - reqPopup.reason = request.method; - reqPopup.method = "updatePopup"; + var reqPopup = shallowMerge({}, request, {method: "updatePopup", reason: request.method}); chrome.extension.sendMessage(reqPopup); } diff --git a/popup.js b/popup.js index d14cefe6..83ce5d1d 100644 --- a/popup.js +++ b/popup.js @@ -185,10 +185,6 @@ function handleDelete(id) { } } -function handleDisableAll(disableAll) { - installed.classList.toggle("disabled", disableAll); -} - chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { if (request.method == "updatePopup") { switch (request.reason) { @@ -199,12 +195,6 @@ chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { case "styleDeleted": handleDelete(request.id); break; - case "prefChanged": - if (request.prefName == "disableAll") { - document.getElementById("disableAll").checked = request.value; - handleDisableAll(request.value); - } - break; } } }); @@ -213,9 +203,7 @@ chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { document.getElementById(id).addEventListener("click", openLink, false); }); -loadPrefs({"disableAll": false}); -handleDisableAll(prefs.getPref("disableAll")); document.getElementById("disableAll").addEventListener("change", function(event) { - notifyAllTabs({method: "styleDisableAll", disableAll: event.target.checked}); - handleDisableAll(event.target.checked); + installed.classList.toggle("disabled", prefs.getPref("disableAll")); }); +loadPrefs(["disableAll"]); diff --git a/storage.js b/storage.js index 8494a98f..ba0d6cc5 100644 --- a/storage.js +++ b/storage.js @@ -136,28 +136,32 @@ function isCheckbox(el) { return el.nodeName.toLowerCase() == "input" && "checkbox" == el.type.toLowerCase(); } -function changePref(event) { - var el = event.target; - prefs.setPref(el.id, isCheckbox(el) ? el.checked : el.value); -} - -// Accepts a hash of pref name to default value -function loadPrefs(prefs) { - for (var id in prefs) { - var value = this.prefs.getPref(id, prefs[id]); - var el = document.getElementById(id); - if (isCheckbox(el)) { - el.checked = value; - } else { - el.value = value; +// Accepts an array of pref names (values are fetched via prefs.getPref) +function loadPrefs(IDs) { + var localIDs = {}; + IDs.forEach(function(id) { + localIDs[id] = true; + updateElement(id).addEventListener("change", function() { + prefs.setPref(this.id, isCheckbox(this) ? this.checked : this.value); + }); + }); + chrome.extension.onMessage.addListener(function(request) { + if (request.prefName in localIDs) { + updateElement(request.prefName); } + }); + function updateElement(id) { + var el = document.getElementById(id); + el[isCheckbox(el) ? "checked" : "value"] = prefs.getPref(id); el.dispatchEvent(new Event("change", {bubbles: true, cancelable: true})); - el.addEventListener("change", changePref); + return el; } } -var prefs = { - defaults: { +var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() { + var me = this; + + var defaults = { "openEditInWindow": false, // new editor opens in a own browser window "windowPosition": {}, // detached window position "show-badge": true, // display text on popup menu icon @@ -189,83 +193,113 @@ var prefs = { }, "editor.lintDelay": 500, // lint gutter marker update delay, ms "editor.lintReportDelay": 4500, // lint report update delay, ms - }, + }; + var values = deepCopy(defaults); - NO_DEFAULT_PREFERENCE: "No default preference for '%s'", - UNHANDLED_DATA_TYPE: "Default '%s' is of type '%s' - what should be done with it?", + var syncTimeout; // see broadcast() function below - getPref: function(key, defaultValue) { - // Returns localStorage[key], defaultValue, this.defaults[key], or undefined - // as type of defaultValue, this.defaults[key], or localStorage[key] - var value = localStorage[key]; - // NB: localStorage["not_key"] is undefined, localStorage.getItem("not_key") is null - if (value === undefined) { - return defaultValue === undefined ? shallowCopy(this.defaults[key]) : defaultValue; + Object.defineProperty(this, "readOnlyValues", {value: {}}); + + Prefs.prototype.getPref = function(key, defaultValue) { + if (key in values) { + return values[key]; } - switch (typeof (defaultValue === undefined ? this.defaults[key] : defaultValue)) { - case "boolean": return value.toLowerCase() === "true"; - case "number": return Number(value); - case "object": return JSON.parse(value); - case "string": break; - case "undefined": console.warn(this.NO_DEFAULT_PREFERENCE, key); break; - default: console.error(UNHANDLED_DATA_TYPE, key, typeof defaultValue); + if (defaultValue !== undefined) { + return defaultValue; } - return value; - }, - getAllPrefs: function() { - var all = {}, me = this; - Object.keys(this.defaults).forEach(function(key) { all[key] = me.getPref(key) }); - return all; - }, - setPref: function(key, value, options) { - var oldValue = localStorage[key]; - if (value === undefined || equal(value, this.defaults[key])) { - delete localStorage[key]; - } else { - localStorage[key] = typeof value == "string" ? value : JSON.stringify(value); + if (key in defaults) { + return defaults[key]; } - if (!equal(value, oldValue === undefined ? this.defaults[key] : oldValue)) { - this.broadcast(key, value, options); + console.warn("No default preference for '%s'", key); + }; + + Prefs.prototype.getAllPrefs = function(key) { + return deepCopy(values); + }; + + Prefs.prototype.setPref = function(key, value, options) { + var oldValue = deepCopy(values[key]); + values[key] = value; + defineReadonlyProperty(this.readOnlyValues, key, value); + if ((!options || !options.noBroadcast) && !equal(value, oldValue)) { + me.broadcast(key, value, options); } - }, - broadcast: function(key, value, options) { + }; + + Prefs.prototype.removePref = function(key) { me.setPref(key, undefined) }; + + Prefs.prototype.broadcast = function(key, value, options) { var message = {method: "prefChanged", prefName: key, value: value}; notifyAllTabs(message); chrome.extension.sendMessage(message); + if (key == "disableAll") { + notifyAllTabs({method: "styleDisableAll", disableAll: value}); + } if (!options || !options.noSync) { - clearTimeout(this.syncTimeout); - this.syncTimeout = setTimeout((function() { - chrome.storage.sync.set({"settings": this.getAllPrefs()}); - }).bind(this), 0); + clearTimeout(syncTimeout); + syncTimeout = setTimeout(function() { + chrome.storage.sync.set({"settings": values}); + }, 0); } - }, - removePref: function(key) { setPref(key, undefined) } -}; + }; -chrome.storage.sync.get({settings: prefs.getAllPrefs()}, function(result) { - Object.keys(prefs.defaults).forEach(function(key) { - if (key in result.settings) { - prefs.setPref(key, result.settings[key], {noSync: true}); - } + Object.keys(defaults).forEach(function(key) { + me.setPref(key, defaults[key], {noBroadcast: true}); }); -}); -chrome.storage.onChanged.addListener(function(changes, area) { - if (area == "sync" && "settings" in changes) { - var newSettings = changes.settings.newValue; - for (key in prefs.defaults) { - if (key in newSettings) { - prefs.setPref(key, newSettings[key], {noSync: true}); + chrome.storage.sync.get("settings", function(result) { + var synced = result.settings; + for (var key in defaults) { + if (synced && (key in synced)) { + me.setPref(key, synced[key], {noSync: true}); + } else { + var value = tryMigrating(key); + if (value !== undefined) { + me.setPref(key, value); + } } } - } -}); + }); -window.addEventListener("storage", function(event) { - if (event.storageArea == localStorage && event.key in prefs.defaults) { - prefs.broadcast(event.key, prefs.getPref(event.key)); + chrome.storage.onChanged.addListener(function(changes, area) { + if (area == "sync" && "settings" in changes) { + var synced = changes.settings.newValue; + if (synced) { + for (key in defaults) { + if (key in synced) { + me.setPref(key, synced[key], {noSync: true}); + } + } + } else { + // user manually deleted our settings, we'll recreate them + chrome.storage.sync.set({"settings": values}); + } + } + }); + + function tryMigrating(key) { + if (!(key in localStorage)) { + return undefined; + } + var value = localStorage[key]; + delete localStorage[key]; + localStorage["DEPRECATED: " + key] = value; + switch (typeof defaults[key]) { + case "boolean": + return value.toLowerCase() === "true"; + case "number": + return Number(value); + case "object": + try { + return JSON.parse(value); + } catch(e) { + console.log("Cannot migrate from localStorage %s = '%s': %o", key, value, e); + return undefined; + } + } + return value; } -}); +}; function getCodeMirrorThemes(callback) { chrome.runtime.getPackageDirectoryEntry(function(rootDir) { @@ -300,17 +334,42 @@ function sessionStorageHash(name) { return hash; } -function shallowCopy(obj) { - return typeof obj == "object" ? shallowMerge(obj, {}) : obj; +function deepCopy(obj) { + if (!obj || typeof obj != "object") { + return obj; + } else { + var emptyCopy = Object.create(Object.getPrototypeOf(obj)); + return deepMerge(emptyCopy, obj); + } } -function shallowMerge(from, to) { - if (typeof from == "object" && typeof to == "object") { - for (var k in from) { - to[k] = from[k]; +function deepMerge(target, obj1 /* plus any number of object arguments */) { + for (var i = 1; i < arguments.length; i++) { + var obj = arguments[i]; + for (var k in obj) { + // hasOwnProperty checking is not needed for our non-OOP stuff + var value = obj[k]; + if (!value || typeof value != "object") { + target[k] = value; + } else if (k in target) { + deepMerge(target[k], value); + } else { + target[k] = deepCopy(value); + } } } - return to; + return target; +} + +function shallowMerge(target, obj1 /* plus any number of object arguments */) { + for (var i = 1; i < arguments.length; i++) { + var obj = arguments[i]; + for (var k in obj) { + target[k] = obj[k]; + // hasOwnProperty checking is not needed for our non-OOP stuff + } + } + return target; } function equal(a, b) { @@ -327,3 +386,9 @@ function equal(a, b) { } return true; } + +function defineReadonlyProperty(obj, key, value) { + var copy = deepCopy(value); + Object.freeze(copy); + Object.defineProperty(obj, key, {value: copy, configurable: true}) +} From d971bbda8a39129ecbf4d1d337bd45ef9c1a5891 Mon Sep 17 00:00:00 2001 From: tophf Date: Mon, 5 Oct 2015 14:27:17 +0300 Subject: [PATCH 5/7] Remove unneeded "Pref" word from `prefs` methods --- background.js | 16 ++++++++-------- edit.js | 40 ++++++++++++++++++++-------------------- manage.js | 4 ++-- messaging.js | 4 ++-- popup.js | 18 +++++++++--------- storage.js | 25 +++++++++++++------------ 6 files changed, 54 insertions(+), 53 deletions(-) diff --git a/background.js b/background.js index 27218ef6..e10f464c 100644 --- a/background.js +++ b/background.js @@ -71,7 +71,7 @@ chrome.commands.onCommand.addListener(function(command) { break; case "styleDisableAll": disableAllStylesToggle(); - chrome.contextMenus.update("disableAll", {checked: prefs.getPref("disableAll")}); + chrome.contextMenus.update("disableAll", {checked: prefs.get("disableAll")}); break; } }); @@ -81,11 +81,11 @@ chrome.commands.onCommand.addListener(function(command) { runTryCatch(function() { chrome.contextMenus.create({ id: "show-badge", title: chrome.i18n.getMessage("menuShowBadge"), - type: "checkbox", contexts: ["browser_action"], checked: prefs.getPref("show-badge") + type: "checkbox", contexts: ["browser_action"], checked: prefs.get("show-badge") }, function() { var clearError = chrome.runtime.lastError }); chrome.contextMenus.create({ id: "disableAll", title: chrome.i18n.getMessage("disableAllStyles"), - type: "checkbox", contexts: ["browser_action"], checked: prefs.getPref("disableAll") + type: "checkbox", contexts: ["browser_action"], checked: prefs.get("disableAll") }, function() { var clearError = chrome.runtime.lastError }); }); @@ -93,15 +93,15 @@ chrome.contextMenus.onClicked.addListener(function(info, tab) { if (info.menuItemId == "disableAll") { disableAllStylesToggle(info.checked); } else { - prefs.setPref(info.menuItemId, info.checked); + prefs.set(info.menuItemId, info.checked); } }); function disableAllStylesToggle(newState) { if (newState === undefined || newState === null) { - newState = !prefs.getPref("disableAll"); + newState = !prefs.get("disableAll"); } - prefs.setPref("disableAll", newState); + prefs.set("disableAll", newState); } function getStyles(options, callback) { @@ -114,7 +114,7 @@ function getStyles(options, callback) { var asHash = "asHash" in options ? options.asHash : false; var callCallback = function() { - var styles = asHash ? {disableAll: prefs.getPref("disableAll", false)} : []; + var styles = asHash ? {disableAll: prefs.get("disableAll", false)} : []; cachedStyles.forEach(function(style) { if (enabled != null && fixBoolean(style.enabled) != enabled) { return; @@ -387,7 +387,7 @@ chrome.tabs.onAttached.addListener(function(tabId, data) { if (tabData.url.indexOf(editFullUrl) == 0) { chrome.windows.get(tabData.windowId, {populate: true}, function(win) { // If there's only one tab in this window, it's been dragged to new window - prefs.setPref('openEditInWindow', win.tabs.length == 1); + prefs.set("openEditInWindow", win.tabs.length == 1); }); } }); diff --git a/edit.js b/edit.js index 74f0a82b..552157cc 100644 --- a/edit.js +++ b/edit.js @@ -132,16 +132,16 @@ function initCodeMirror() { foldGutter: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"], matchBrackets: true, - lint: {getAnnotations: CodeMirror.lint.css, delay: prefs.getPref("editor.lintDelay")}, - lintReportDelay: prefs.getPref("editor.lintReportDelay"), + lint: {getAnnotations: CodeMirror.lint.css, delay: prefs.get("editor.lintDelay")}, + lintReportDelay: prefs.get("editor.lintReportDelay"), styleActiveLine: true, theme: "default", - keyMap: prefs.getPref("editor.keyMap"), + keyMap: prefs.get("editor.keyMap"), extraKeys: { // independent of current keyMap "Alt-PageDown": "nextEditor", "Alt-PageUp": "prevEditor" } - }, prefs.getPref("editor.options")); + }, prefs.get("editor.options")); // additional commands CM.commands.jumpToLine = jumpToLine; @@ -219,8 +219,8 @@ function initCodeMirror() { }); } - // preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->loadPrefs() - var theme = prefs.getPref("editor.theme"); + // preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->setupLivePrefs() + var theme = prefs.get("editor.theme"); document.getElementById("cm-theme").href = theme == "default" ? "" : "codemirror/theme/" + theme + ".css"; // initialize global editor controls @@ -242,7 +242,7 @@ function initCodeMirror() { } document.getElementById("editor.keyMap").innerHTML = optionsHtmlFromArray(Object.keys(CM.keyMap).sort()); document.getElementById("options").addEventListener("change", acmeEventListener, false); - loadPrefs( + setupLivePrefs( document.querySelectorAll("#options *[data-option][id^='editor.']") .map(function(option) { return option.id }) ); @@ -270,8 +270,8 @@ function acmeEventListener(event) { // use non-localized "default" internally if (!value || value == "default" || value == t("defaultTheme")) { value = "default"; - if (prefs.getPref(el.id) != value) { - prefs.setPref(el.id, value); + if (prefs.get(el.id) != value) { + prefs.set(el.id, value); } themeLink.href = ""; el.selectedIndex = 0; @@ -386,7 +386,7 @@ document.addEventListener("wheel", function(event) { chrome.tabs.query({currentWindow: true}, function(tabs) { var windowId = tabs[0].windowId; - if (prefs.getPref("openEditInWindow")) { + if (prefs.get("openEditInWindow")) { if (tabs.length == 1 && window.history.length == 1) { chrome.windows.getAll(function(windows) { if (windows.length > 1) { @@ -420,7 +420,7 @@ function goBackToManage(event) { window.onbeforeunload = function() { if (saveSizeOnClose) { - prefs.setPref("windowPosition", { + prefs.set("windowPosition", { left: screenLeft, top: screenTop, width: outerWidth, @@ -964,9 +964,9 @@ function beautify(event) { script.onload = doBeautify; } function doBeautify() { - var tabs = prefs.getPref("editor.indentWithTabs"); - var options = prefs.getPref("editor.beautify"); - options.indent_size = tabs ? 1 : prefs.getPref("editor.tabSize"); + var tabs = prefs.get("editor.indentWithTabs"); + var options = prefs.get("editor.beautify"); + options.indent_size = tabs ? 1 : prefs.get("editor.tabSize"); options.indent_char = tabs ? "\t" : " "; var section = getSectionForChild(event.target); @@ -1015,7 +1015,7 @@ function beautify(event) { document.querySelector(".beautify-options").addEventListener("change", function(event) { var value = event.target.selectedIndex > 0; options[event.target.dataset.option] = value; - prefs.setPref("editor.beautify", options); + prefs.set("editor.beautify", options); event.target.parentNode.setAttribute("newline", value.toString()); doBeautify(); }); @@ -1090,7 +1090,7 @@ function initWithStyle(style) { function add() { var sectionDiv = addSection(null, queue.shift()); maximizeCodeHeight(sectionDiv, !queue.length); - updateLintReport(getCodeMirrorForSection(sectionDiv), prefs.getPref("editor.lintDelay")); + updateLintReport(getCodeMirrorForSection(sectionDiv), prefs.get("editor.lintDelay")); } } @@ -1442,12 +1442,12 @@ function showToMozillaHelp() { } function showKeyMapHelp() { - var keyMap = mergeKeyMaps({}, prefs.getPref("editor.keyMap"), CodeMirror.defaults.extraKeys); + var keyMap = mergeKeyMaps({}, prefs.get("editor.keyMap"), CodeMirror.defaults.extraKeys); var keyMapSorted = Object.keys(keyMap) .map(function(key) { return {key: key, cmd: keyMap[key]} }) .concat([{key: "Shift-Ctrl-Wheel", cmd: "scrollWindow"}]) .sort(function(a, b) { return a.cmd < b.cmd || (a.cmd == b.cmd && a.key < b.key) ? -1 : 1 }); - showHelp(t("cm_keyMap") + ": " + prefs.getPref("editor.keyMap"), + showHelp(t("cm_keyMap") + ": " + prefs.get("editor.keyMap"), '' + '' + '' + @@ -1564,8 +1564,8 @@ function showCodeMirrorPopup(title, html, options) { matchBrackets: true, lint: {getAnnotations: CodeMirror.lint.css, delay: 0}, styleActiveLine: true, - theme: prefs.getPref("editor.theme"), - keyMap: prefs.getPref("editor.keyMap") + theme: prefs.get("editor.theme"), + keyMap: prefs.get("editor.keyMap") }, options)); popup.codebox.focus(); popup.codebox.on("focus", function() { hotkeyRerouter.setState(false) }); diff --git a/manage.js b/manage.js index 5ab077d1..fa9d9dcf 100644 --- a/manage.js +++ b/manage.js @@ -110,7 +110,7 @@ function createStyleElement(style) { event.stopPropagation(); if (openWindow || openBackgroundTab || openForegroundTab) { if (openWindow) { - var options = prefs.getPref("windowPosition"); + var options = prefs.get("windowPosition"); options.url = url; chrome.windows.create(options); } else { @@ -475,7 +475,7 @@ document.addEventListener("DOMContentLoaded", function() { document.getElementById("search").addEventListener("input", searchStyles); searchStyles(true); // re-apply filtering on history Back - loadPrefs([ + setupLivePrefs([ "manage.onlyEnabled", "manage.onlyEdited", "show-badge", diff --git a/messaging.js b/messaging.js index 5ecadfff..1d2c9eb6 100644 --- a/messaging.js +++ b/messaging.js @@ -44,13 +44,13 @@ function updateIcon(tab, styles) { }); function stylesReceived(styles) { - var disableAll = "disableAll" in styles ? styles.disableAll : prefs.getPref("disableAll"); + var disableAll = "disableAll" in styles ? styles.disableAll : prefs.get("disableAll"); var postfix = styles.length == 0 || disableAll ? "w" : ""; chrome.browserAction.setIcon({ path: {19: "19" + postfix + ".png", 38: "38" + postfix + ".png"}, tabId: tab.id }); - var t = prefs.getPref("show-badge") && styles.length ? ("" + styles.length) : ""; + var t = prefs.get("show-badge") && styles.length ? ("" + styles.length) : ""; chrome.browserAction.setBadgeText({text: t, tabId: tab.id}); chrome.browserAction.setBadgeBackgroundColor({color: disableAll ? "#aaa" : [0, 0, 0, 0]}); //console.log("Tab " + tab.id + " (" + tab.url + ") badge text set to '" + t + "'."); diff --git a/popup.js b/popup.js index 83ce5d1d..6cf7648f 100644 --- a/popup.js +++ b/popup.js @@ -3,7 +3,7 @@ writeStyleTemplate.className = "write-style-link"; var installed = document.getElementById("installed"); -if (!prefs.getPref("popup.stylesFirst")) { +if (!prefs.get("popup.stylesFirst")) { document.body.insertBefore(document.querySelector("body > .actions"), installed); } @@ -29,14 +29,14 @@ function updatePopUp(url) { var urlLink = writeStyleTemplate.cloneNode(true); urlLink.href = "edit.html?url-prefix=" + encodeURIComponent(url); urlLink.appendChild(document.createTextNode( // switchable; default="this URL" - !prefs.getPref("popup.breadcrumbs.usePath") + !prefs.get("popup.breadcrumbs.usePath") ? t("writeStyleForURL").replace(/ /g, "\u00a0") : /\/\/[^/]+\/(.*)/.exec(url)[1] )); urlLink.title = "url-prefix(\"$\")".replace("$", url); writeStyleLinks.push(urlLink); document.querySelector("#write-style").appendChild(urlLink) - if (prefs.getPref("popup.breadcrumbs")) { // switchable; default=enabled + if (prefs.get("popup.breadcrumbs")) { // switchable; default=enabled urlLink.addEventListener("mouseenter", function(event) { this.parentNode.classList.add("url()") }, false); urlLink.addEventListener("focus", function(event) { this.parentNode.classList.add("url()") }, false); urlLink.addEventListener("mouseleave", function(event) { this.parentNode.classList.remove("url()") }, false); @@ -63,7 +63,7 @@ function updatePopUp(url) { link.addEventListener("click", openLinkInTabOrWindow, false); container.appendChild(link); }); - if (prefs.getPref("popup.breadcrumbs")) { + if (prefs.get("popup.breadcrumbs")) { container.classList.add("breadcrumbs"); container.appendChild(container.removeChild(container.firstChild)); } @@ -71,7 +71,7 @@ function updatePopUp(url) { } function showStyles(styles) { - var enabledFirst = prefs.getPref("popup.enabledFirst"); + var enabledFirst = prefs.get("popup.enabledFirst"); styles.sort(function(a, b) { if (enabledFirst && a.enabled !== b.enabled) return !(a.enabled < b.enabled) ? -1 : 1; return a.name.localeCompare(b.name); @@ -146,9 +146,9 @@ function getId(event) { function openLinkInTabOrWindow(event) { event.preventDefault(); - if (prefs.getPref('openEditInWindow', false)) { + if (prefs.get("openEditInWindow", false)) { var options = {url: event.target.href} - var wp = prefs.getPref('windowPosition', {}); + var wp = prefs.get("windowPosition", {}); for (var k in wp) options[k] = wp[k]; chrome.windows.create(options); } else { @@ -204,6 +204,6 @@ chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { }); document.getElementById("disableAll").addEventListener("change", function(event) { - installed.classList.toggle("disabled", prefs.getPref("disableAll")); + installed.classList.toggle("disabled", prefs.get("disableAll")); }); -loadPrefs(["disableAll"]); +setupLivePrefs(["disableAll"]); diff --git a/storage.js b/storage.js index ba0d6cc5..621e5395 100644 --- a/storage.js +++ b/storage.js @@ -136,13 +136,14 @@ function isCheckbox(el) { return el.nodeName.toLowerCase() == "input" && "checkbox" == el.type.toLowerCase(); } -// Accepts an array of pref names (values are fetched via prefs.getPref) -function loadPrefs(IDs) { +// Accepts an array of pref names (values are fetched via prefs.get) +// and establishes a two-way connection between the document elements and the actual prefs +function setupLivePrefs(IDs) { var localIDs = {}; IDs.forEach(function(id) { localIDs[id] = true; updateElement(id).addEventListener("change", function() { - prefs.setPref(this.id, isCheckbox(this) ? this.checked : this.value); + prefs.set(this.id, isCheckbox(this) ? this.checked : this.value); }); }); chrome.extension.onMessage.addListener(function(request) { @@ -152,7 +153,7 @@ function loadPrefs(IDs) { }); function updateElement(id) { var el = document.getElementById(id); - el[isCheckbox(el) ? "checked" : "value"] = prefs.getPref(id); + el[isCheckbox(el) ? "checked" : "value"] = prefs.get(id); el.dispatchEvent(new Event("change", {bubbles: true, cancelable: true})); return el; } @@ -200,7 +201,7 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() { Object.defineProperty(this, "readOnlyValues", {value: {}}); - Prefs.prototype.getPref = function(key, defaultValue) { + Prefs.prototype.get = function(key, defaultValue) { if (key in values) { return values[key]; } @@ -213,11 +214,11 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() { console.warn("No default preference for '%s'", key); }; - Prefs.prototype.getAllPrefs = function(key) { + Prefs.prototype.getAll = function(key) { return deepCopy(values); }; - Prefs.prototype.setPref = function(key, value, options) { + Prefs.prototype.set = function(key, value, options) { var oldValue = deepCopy(values[key]); values[key] = value; defineReadonlyProperty(this.readOnlyValues, key, value); @@ -226,7 +227,7 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() { } }; - Prefs.prototype.removePref = function(key) { me.setPref(key, undefined) }; + Prefs.prototype.remove = function(key) { me.set(key, undefined) }; Prefs.prototype.broadcast = function(key, value, options) { var message = {method: "prefChanged", prefName: key, value: value}; @@ -244,18 +245,18 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() { }; Object.keys(defaults).forEach(function(key) { - me.setPref(key, defaults[key], {noBroadcast: true}); + me.set(key, defaults[key], {noBroadcast: true}); }); chrome.storage.sync.get("settings", function(result) { var synced = result.settings; for (var key in defaults) { if (synced && (key in synced)) { - me.setPref(key, synced[key], {noSync: true}); + me.set(key, synced[key], {noSync: true}); } else { var value = tryMigrating(key); if (value !== undefined) { - me.setPref(key, value); + me.set(key, value); } } } @@ -267,7 +268,7 @@ var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() { if (synced) { for (key in defaults) { if (key in synced) { - me.setPref(key, synced[key], {noSync: true}); + me.set(key, synced[key], {noSync: true}); } } } else { From 23190af27049fdf556fd8503820d357ff1a9568e Mon Sep 17 00:00:00 2001 From: tophf Date: Sun, 11 Oct 2015 16:55:47 +0300 Subject: [PATCH 6/7] Switch from the dummy background.html to "scripts" key Also fix the order: messaging.js should precede storage.js for notifyAllTabs() to work --- background.html | 5 ----- manifest.json | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 background.html diff --git a/background.html b/background.html deleted file mode 100644 index d05d55da..00000000 --- a/background.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/manifest.json b/manifest.json index fea8ffd5..25d3f090 100644 --- a/manifest.json +++ b/manifest.json @@ -18,7 +18,7 @@ "https://userstyles.org/" ], "background": { - "page": "background.html" + "scripts": ["messaging.js", "storage.js", "background.js"] }, "commands": { "openManage": { From 6fa6982e8ef8c07dda9362c6e15ac1b5d2695929 Mon Sep 17 00:00:00 2001 From: tophf Date: Sun, 11 Oct 2015 17:09:49 +0300 Subject: [PATCH 7/7] Check if the tab is closed when trying to set its icon --- messaging.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/messaging.js b/messaging.js index 1d2c9eb6..0678f0db 100644 --- a/messaging.js +++ b/messaging.js @@ -49,10 +49,15 @@ function updateIcon(tab, styles) { chrome.browserAction.setIcon({ path: {19: "19" + postfix + ".png", 38: "38" + postfix + ".png"}, tabId: tab.id + }, function() { + // if the tab was just closed an error may occur, + // e.g. 'windowPosition' pref updated in edit.js::window.onbeforeunload + if (!chrome.runtime.lastError) { + var t = prefs.get("show-badge") && styles.length ? ("" + styles.length) : ""; + chrome.browserAction.setBadgeText({text: t, tabId: tab.id}); + chrome.browserAction.setBadgeBackgroundColor({color: disableAll ? "#aaa" : [0, 0, 0, 0]}); + } }); - var t = prefs.get("show-badge") && styles.length ? ("" + styles.length) : ""; - chrome.browserAction.setBadgeText({text: t, tabId: tab.id}); - chrome.browserAction.setBadgeBackgroundColor({color: disableAll ? "#aaa" : [0, 0, 0, 0]}); //console.log("Tab " + tab.id + " (" + tab.url + ") badge text set to '" + t + "'."); } }