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/background.js b/background.js
index bb658b1b..9ff19214 100644
--- a/background.js
+++ b/background.js
@@ -54,7 +54,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;
@@ -88,7 +90,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;
}
});
@@ -98,11 +100,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 });
});
@@ -110,16 +112,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);
- notifyAllTabs({method: "styleDisableAll", disableAll: newState});
+ prefs.set("disableAll", newState);
}
function getStyles(options, callback) {
@@ -132,7 +133,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;
@@ -243,6 +244,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;
@@ -401,7 +406,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 a3bd5032..777a6b3d 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,26 +125,23 @@ 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,
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"
}
- }
- shallowMerge(stylishOptions, CM.defaults);
- shallowMerge(userOptions, CM.defaults);
+ }, prefs.get("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";
@@ -224,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
@@ -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);
+ setupLivePrefs(
+ document.querySelectorAll("#options *[data-option][id^='editor.']")
+ .map(function(option) { return option.id })
+ );
});
hotkeyRerouter.setState(true);
@@ -276,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;
@@ -400,7 +394,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) {
@@ -434,7 +428,7 @@ function goBackToManage(event) {
window.onbeforeunload = function() {
if (saveSizeOnClose) {
- prefs.setPref("windowPosition", {
+ prefs.set("windowPosition", {
left: screenLeft,
top: screenTop,
width: outerWidth,
@@ -994,9 +988,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);
@@ -1045,7 +1039,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();
});
@@ -1120,7 +1114,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"));
}
}
@@ -1472,12 +1466,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"),
'
' +
' | ' +
' |
' +
@@ -1585,7 +1579,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,
@@ -1594,9 +1588,9 @@ 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) });
popup.codebox.on("blur", function() { hotkeyRerouter.setState(true) });
diff --git a/manage.js b/manage.js
index 8c35cbb8..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,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
- });
+ setupLivePrefs([
+ "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/manifest.json b/manifest.json
index ecbd1eca..25d3f090 100644
--- a/manifest.json
+++ b/manifest.json
@@ -13,11 +13,12 @@
"tabs",
"webNavigation",
"contextMenus",
+ "storage",
"http://userstyles.org/",
"https://userstyles.org/"
],
"background": {
- "page": "background.html"
+ "scripts": ["messaging.js", "storage.js", "background.js"]
},
"commands": {
"openManage": {
diff --git a/messaging.js b/messaging.js
index ea30055b..0678f0db 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);
}
@@ -48,15 +44,20 @@ 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
+ }, 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.getPref("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 d14cefe6..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 {
@@ -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.get("disableAll"));
});
+setupLivePrefs(["disableAll"]);
diff --git a/storage.js b/storage.js
index f05cc1bd..621e5395 100644
--- a/storage.js
+++ b/storage.js
@@ -136,96 +136,170 @@ 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.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.set(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.get(id);
el.dispatchEvent(new Event("change", {bubbles: true, cancelable: true}));
- el.addEventListener("change", changePref);
+ return el;
}
}
-var prefs = {
-// NB: localStorage["not_key"] is undefined, localStorage.getItem("not_key") is null
+var prefs = chrome.extension.getBackgroundPage().prefs || new function Prefs() {
+ var me = this;
- // 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
+ 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
+ "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.lintDelay": 500, // lint gutter marker update delay, ms
- "editor.lintReportDelay": 4500, // lint report update delay, ms
+ "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
+ };
+ 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[key], or undefined
- // as type of defaultValue, this[key], or localStorage[key]
- var value = localStorage[key];
- if (value === undefined) {
- return defaultValue === undefined ? shallowCopy(this[key]) : defaultValue;
+ Object.defineProperty(this, "readOnlyValues", {value: {}});
+
+ Prefs.prototype.get = function(key, defaultValue) {
+ if (key in values) {
+ return values[key];
}
- switch (typeof (defaultValue === undefined ? this[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;
+ }
+ if (key in defaults) {
+ return defaults[key];
+ }
+ console.warn("No default preference for '%s'", key);
+ };
+
+ Prefs.prototype.getAll = function(key) {
+ return deepCopy(values);
+ };
+
+ Prefs.prototype.set = 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);
+ }
+ };
+
+ Prefs.prototype.remove = function(key) { me.set(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(syncTimeout);
+ syncTimeout = setTimeout(function() {
+ chrome.storage.sync.set({"settings": values});
+ }, 0);
+ }
+ };
+
+ Object.keys(defaults).forEach(function(key) {
+ 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.set(key, synced[key], {noSync: true});
+ } else {
+ var value = tryMigrating(key);
+ if (value !== undefined) {
+ me.set(key, value);
+ }
+ }
+ }
+ });
+
+ 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.set(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;
- },
- setPref: function(key, value) {
- var oldValue = localStorage[key];
- if (value === undefined || equal(value, this[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);
- }
- },
- removePref: function(key) { setPref(key, undefined) }
+ }
};
function getCodeMirrorThemes(callback) {
@@ -261,17 +335,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) {
@@ -288,3 +387,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})
+}