//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
-"use strict";
+/* use strict */
try {
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("prefs", {
exports: ["Prefs", "localPrefs", "prefs"],
- require: ["services", "util"],
- use: ["config", "messages", "template"]
+ require: ["services", "util"]
}, this);
+this.lazyRequire("messages", ["_"]);
+
var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), {
- SAVED: "extensions.dactyl.saved.",
+ ORIGINAL: "extensions.dactyl.original.",
RESTORE: "extensions.dactyl.restore.",
+ SAVED: "extensions.dactyl.saved.",
INIT: {},
- init: function (branch, defaults) {
+ init: function init(branch, defaults) {
this._prefContexts = [];
this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || "");
- if (this.branch instanceof Ci.nsIPrefBranch2)
- this.branch.QueryInterface(Ci.nsIPrefBranch2);
+ if ("nsIPrefBranch2" in Ci)
+ this.branch instanceof Ci.nsIPrefBranch2;
this.defaults = defaults ? this : this.constructor(branch, true);
+ this.branches = memoize({
+ __proto__: this,
+ get original() this.constructor(this.ORIGINAL + this.root),
+ get restore() this.constructor(this.RESTORE + this.root),
+ get saved() this.constructor(this.SAVED + this.root),
+ });
+
if (!defaults)
this.restore();
this._observers = {};
},
- cleanup: function cleanup() {
+ cleanup: function cleanup(reason) {
if (this.defaults != this)
- this.defaults.cleanup();
+ this.defaults.cleanup(reason);
+
this._observers = {};
if (this.observe) {
this.branch.removeObserver("", this);
this.observe.unregister();
delete this.observe;
}
- },
- observe: null,
- observers: {
- "nsPref:changed": function (subject, data) {
- let observers = this._observers[data];
- if (observers) {
- let value = this.get(data, false);
- this._observers[data] = observers.filter(function (callback) {
- if (!callback.get())
- return false;
- util.trapErrors(callback.get(), null, value);
- return true;
- });
+ if (this == prefs) {
+ if (~["uninstall", "disable"].indexOf(reason)) {
+ for (let name in values(this.branches.saved.getNames()))
+ this.safeReset(name, null, true);
+
+ this.branches.original.resetBranch();
+ this.branches.saved.resetBranch();
}
+
+ if (reason == "uninstall")
+ localPrefs.resetBranch();
}
},
/**
- * Adds a new preference observer for the given preference.
+ * Returns a new Prefs instance for the sub-branch *branch* of this
+ * branch.
*
- * @param {string} pref The preference to observe.
- * @param {function(object)} callback The callback, called with the
- * new value of the preference whenever it changes.
+ * @param {string} branch The sub-branch to branch to.
+ * @returns {Prefs}
*/
- watch: function (pref, callback, strong) {
- if (!this.observe) {
- util.addObserver(this);
- this.branch.addObserver("", this, false);
- }
-
- if (!this._observers[pref])
- this._observers[pref] = [];
- this._observers[pref].push(!strong ? Cu.getWeakReference(callback) : { get: function () callback });
- },
+ Branch: function Branch(branch) Prefs(this.root + branch),
/**
- * Lists all preferences matching *filter* or only those with changed
- * values if *onlyNonDefault* is specified.
+ * Clears the entire branch.
*
- * @param {boolean} onlyNonDefault Limit the list to prefs with a
- * non-default value.
- * @param {string} filter The list filter. A null filter lists all
- * prefs.
- * @optional
+ * @param {string} name The name of the preference branch to delete.
*/
- list: function list(onlyNonDefault, filter) {
- if (!filter)
- filter = "";
-
- let prefArray = this.getNames();
- prefArray.sort();
- function prefs() {
- for (let [, pref] in Iterator(prefArray)) {
- let userValue = services.pref.prefHasUserValue(pref);
- if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
- continue;
-
- let value = this.get(pref);
-
- let option = {
- isDefault: !userValue,
- default: this.defaults.get(pref, null),
- value: <>={template.highlight(value, true, 100)}</>,
- name: pref,
- pre: "\u00a0\u00a0" // Unicode nonbreaking space.
- };
-
- yield option;
- }
- };
-
- return template.options(config.host + " Preferences", prefs.call(this));
+ clear: function clear(branch) {
+ this.branch.deleteBranch(branch || "");
},
/**
- * Returns the value of a preference.
+ * Returns the full name of this object's preference branch.
+ */
+ get root() this.branch.root,
+
+ /**
+ * Returns the value of the preference *name*, or *defaultValue* if
+ * the preference does not exist.
*
- * @param {string} name The preference name.
- * @param {value} defaultValue The value to return if the preference
- * is unset.
+ * @param {string} name The name of the preference to return.
+ * @param {*} defaultValue The value to return if the preference has no value.
+ * @optional
*/
get: function get(name, defaultValue) {
if (defaultValue == null)
defaultValue = null;
+ if (isArray(name))
+ name = name.join(".");
let type = this.branch.getPrefType(name);
try {
getDefault: deprecated("Prefs#defaults.get", function getDefault(name, defaultValue) this.defaults.get(name, defaultValue)),
/**
- * Returns the names of all preferences.
+ * Returns an array of all preference names in this branch or the
+ * given sub-branch.
*
- * @param {string} branch The branch in which to search preferences.
- * @default ""
+ * @param {string} branch The sub-branch for which to return preferences.
+ * @optional
*/
- getNames: function (branch) this.branch.getChildList(branch || "", { value: 0 }),
+ getNames: function getNames(branch) this.branch.getChildList(branch || "", { value: 0 }),
- _checkSafe: function (name, message, value) {
+ /**
+ * Returns true if the given preference exists in this branch.
+ *
+ * @param {string} name The name of the preference to check.
+ */
+ has: function has(name) this.branch.getPrefType(name) !== 0,
+
+ /**
+ * Returns true if the given preference is set to its default value.
+ *
+ * @param {string} name The name of the preference to check.
+ */
+ isDefault: function isDefault(name) !this.branch.prefHasUserValue(name),
+
+ _checkSafe: function _checkSafe(name, message, value) {
let curval = this.get(name, null);
+
+ if (this.branches.original.get(name) == null && !this.branches.saved.has(name))
+ this.branches.original.set(name, curval, true);
+
if (arguments.length > 2 && curval === value)
return;
+
let defval = this.defaults.get(name, null);
- let saved = this.get(this.SAVED + name);
+ let saved = this.branches.saved.get(name);
if (saved == null && curval != defval || saved != null && curval != saved) {
- let msg = "Warning: setting preference " + name + ", but it's changed from its default value.";
+ let msg = _("pref.safeSet.warnChanged", name);
if (message)
msg = template.linkifyHelp(msg + " " + message);
util.dactyl.warn(msg);
*
* @param {string} name The preference name.
* @param {value} value The new preference value.
+ * @param {boolean} silent Ignore errors.
*/
- safeReset: function (name, message) {
+ safeReset: function safeReset(name, message, silent) {
this._checkSafe(name, message);
- this.reset(name);
- this.reset(this.SAVED + name);
+ this.set(name, this.branches.original.get(name), silent);
+ this.branches.original.reset(name);
+ this.branches.saved.reset(name);
},
/**
* @param {string} name The preference name.
* @param {value} value The new preference value.
*/
- safeSet: function (name, value, message, skipSave) {
+ safeSet: function safeSet(name, value, message, skipSave) {
this._checkSafe(name, message, value);
this.set(name, value);
- this[skipSave ? "reset" : "set"](this.SAVED + name, value);
+ this.branches.saved[skipSave ? "reset" : "set"](name, value);
},
/**
- * Sets the preference *name* to *value*.
+ * Sets the preference *name* to *value*. If the preference already
+ * exists, it must have the same type as the given value.
*
- * @param {string} name The preference name.
- * @param {value} value The new preference value.
+ * @param {name} name The name of the preference to change.
+ * @param {string|number|boolean} value The value to set.
+ * @param {boolean} silent Ignore errors.
*/
- set: function (name, value) {
- if (this._prefContexts.length) {
- let val = this.get(name, null);
- if (val != null)
- this._prefContexts[this._prefContexts.length - 1][name] = val;
- }
+ set: function set(name, value, silent) {
+ if (this._prefContexts.length)
+ this._prefContexts[this._prefContexts.length - 1][name] = this.get(name, null);
function assertType(needType)
util.assert(type === Ci.nsIPrefBranch.PREF_INVALID || type === needType,
type === Ci.nsIPrefBranch.PREF_INT
- ? "E521: Number required after =: " + name + "=" + value
- : "E474: Invalid argument: " + name + "=" + value);
+ ? /*L*/"E521: Number required after =: " + name + "=" + value
+ : /*L*/"E474: Invalid argument: " + name + "=" + value);
let type = this.branch.getPrefType(name);
- switch (typeof value) {
- case "string":
- assertType(Ci.nsIPrefBranch.PREF_STRING);
-
- this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
- break;
- case "number":
- assertType(Ci.nsIPrefBranch.PREF_INT);
-
- this.branch.setIntPref(name, value);
- break;
- case "boolean":
- assertType(Ci.nsIPrefBranch.PREF_BOOL);
-
- this.branch.setBoolPref(name, value);
- break;
- default:
- throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
+ try {
+ switch (typeof value) {
+ case "string":
+ assertType(Ci.nsIPrefBranch.PREF_STRING);
+
+ this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
+ break;
+ case "number":
+ assertType(Ci.nsIPrefBranch.PREF_INT);
+
+ this.branch.setIntPref(name, value);
+ break;
+ case "boolean":
+ assertType(Ci.nsIPrefBranch.PREF_BOOL);
+
+ this.branch.setBoolPref(name, value);
+ break;
+ default:
+ if (value == null && this != this.defaults)
+ this.reset(name);
+ else
+ throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
+ }
}
+ catch (e if silent) {}
+ return value;
},
/**
*
* @param {string} name The preference to save.
*/
- save: function (name) {
+ save: function save(name) {
let val = this.get(name);
this.set(this.RESTORE + name, val);
this.safeSet(name, val);
* @param {string} branch The branch from which to restore
* preferences. @optional
*/
- restore: function (branch) {
+ restore: function restore(branch) {
this.getNames(this.RESTORE + (branch || "")).forEach(function (pref) {
this.safeSet(pref.substr(this.RESTORE.length), this.get(pref), null, true);
this.reset(pref);
/**
* Resets the preference *name* to its default value.
*
- * @param {string} name The preference name.
+ * @param {string} name The name of the preference to reset.
*/
- reset: function (name) {
- try {
+ reset: function reset(name) {
+ if (this.branch.prefHasUserValue(name))
this.branch.clearUserPref(name);
- }
- catch (e) {} // ignore - thrown if not a user set value
+ },
+
+ /**
+ * Resets the preference branch *branch* to its default value.
+ *
+ * @param {string} branch The preference name. @optional
+ */
+ resetBranch: function resetBranch(branch) {
+ this.getNames(branch).forEach(this.closure.reset);
},
/**
*
* @param {string} name The preference name.
*/
- toggle: function (name) {
+ toggle: function toggle(name) {
util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL,
- _("error.trailing", name + "!"));
+ _("error.trailingCharacters", name + "!"));
this.set(name, !this.get(name));
},
*
* @see #withContext
*/
- pushContext: function () {
+ pushContext: function pushContext() {
this._prefContexts.push({});
},
*
* @see #withContext
*/
- popContext: function () {
+ popContext: function popContext() {
for (let [k, v] in Iterator(this._prefContexts.pop()))
this.set(k, v);
},
* @see #pushContext
* @see #popContext
*/
- withContext: function (func, self) {
+ withContext: function withContext(func, self) {
try {
this.pushContext();
return func.call(self);
finally {
this.popContext();
}
- }
+ },
+
+ observe: null,
+ observers: {
+ "nsPref:changed": function (subject, data) {
+ let observers = this._observers[data];
+ if (observers) {
+ let value = this.get(data, false);
+ this._observers[data] = observers.filter(function (callback) {
+ if (!callback.get())
+ return false;
+ util.trapErrors(callback.get(), null, value);
+ return true;
+ });
+ }
+ }
+ },
+
+ /**
+ * Adds a new preference observer for the given preference.
+ *
+ * @param {string} pref The preference to observe.
+ * @param {function(object)} callback The callback, called with the
+ * new value of the preference whenever it changes.
+ */
+ watch: function watch(pref, callback, strong) {
+ if (!this.observe) {
+ util.addObserver(this);
+ this.branch.addObserver("", this, false);
+ }
+
+ if (!this._observers[pref])
+ this._observers[pref] = [];
+ this._observers[pref].push(!strong ? util.weakReference(callback) : { get: function () callback });
+ },
+
+ /**
+ * Lists all preferences matching *filter* or only those with changed
+ * values if *onlyNonDefault* is specified.
+ *
+ * @param {boolean} onlyNonDefault Limit the list to prefs with a
+ * non-default value.
+ * @param {string} filter The list filter. A null filter lists all
+ * prefs.
+ * @optional
+ */
+ list: function list(onlyNonDefault, filter) {
+ if (!filter)
+ filter = "";
+
+ let prefArray = this.getNames();
+ prefArray.sort();
+ function prefs() {
+ for (let [, pref] in Iterator(prefArray)) {
+ let userValue = services.pref.prefHasUserValue(pref);
+ if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
+ continue;
+
+ let value = this.get(pref);
+
+ let option = {
+ isDefault: !userValue,
+ default: this.defaults.get(pref, null),
+ value: <>={template.highlight(value, true, 100)}</>,
+ name: pref,
+ pre: "\u00a0\u00a0" // Unicode nonbreaking space.
+ };
+
+ yield option;
+ }
+ };
+
+ return template.options(_("pref.hostPreferences", config.host), prefs.call(this));
+ },
}, {
}, {
- completion: function (dactyl, modules) {
+ completion: function init_completion(dactyl, modules) {
modules.completion.preference = function preference(context) {
context.anchored = false;
context.title = [config.host + " Preference", "Value"];
context.completions = prefs.getNames();
};
},
- javascript: function (dactyl, modules) {
+ javascript: function init_javascript(dactyl, modules) {
modules.JavaScript.setCompleter([this.get, this.safeSet, this.set, this.reset, this.toggle],
[function (context) (context.anchored=false, this.getNames().map(function (pref) [pref, ""]))]);
}