--- a/devtools/client/shared/shim/Services.js
+++ b/devtools/client/shared/shim/Services.js
@@ -14,151 +14,39 @@ const PREF_STRING = 32;
const PREF_INT = 64;
const PREF_BOOL = 128;
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
// We prefix all our local storage items with this.
const PREFIX = "Services.prefs:";
/**
- * Create a new preference object.
- *
- * @param {PrefBranch} branch the branch holding this preference
- * @param {String} name the base name of this preference
- * @param {String} fullName the fully-qualified name of this preference
- */
-function Preference(branch, name, fullName) {
- this.branch = branch;
- this.name = name;
- this.fullName = fullName;
- this.defaultValue = null;
- this.hasUserValue = false;
- this.userValue = null;
- this.type = null;
-}
-
-Preference.prototype = {
- /**
- * Return this preference's current value.
- *
- * @return {Any} The current value of this preference. This may
- * return a string, a number, or a boolean depending on the
- * preference's type.
- */
- get: function () {
- if (this.hasUserValue) {
- return this.userValue;
- }
- return this.defaultValue;
- },
-
- /**
- * Set the preference's value. The new value is assumed to be a
- * user value. After setting the value, this function emits a
- * change notification.
- *
- * @param {Any} value the new value
- */
- set: function (value) {
- if (!this.hasUserValue || value !== this.userValue) {
- this.userValue = value;
- this.hasUserValue = true;
- this.saveAndNotify();
- }
- },
-
- /**
- * Set the default value for this preference, and emit a
- * notification if this results in a visible change.
- *
- * @param {Any} value the new default value
- */
- setDefault: function (value) {
- if (this.defaultValue !== value) {
- this.defaultValue = value;
- if (!this.hasUserValue) {
- this.saveAndNotify();
- }
- }
- },
-
- /**
- * If this preference has a user value, clear it. If a change was
- * made, emit a change notification.
- */
- clearUserValue: function () {
- if (this.hasUserValue) {
- this.userValue = null;
- this.hasUserValue = false;
- this.saveAndNotify();
- }
- },
-
- /**
- * Helper function to write the preference's value to local storage
- * and then emit a change notification.
- */
- saveAndNotify: function () {
- let store = {
- type: this.type,
- defaultValue: this.defaultValue,
- hasUserValue: this.hasUserValue,
- userValue: this.userValue,
- };
-
- localStorage.setItem(PREFIX + this.fullName, JSON.stringify(store));
- this.branch._notify(this.name);
- },
-
- /**
- * Change this preference's value without writing it back to local
- * storage. This is used to handle changes to local storage that
- * were made externally.
- *
- * @param {Number} type one of the PREF_* values
- * @param {Any} userValue the user value to use if the pref does not exist
- * @param {Any} defaultValue the default value to use if the pref
- * does not exist
- * @param {Boolean} hasUserValue if a new pref is created, whether
- * the default value is also a user value
- * @param {Object} store the new value of the preference. It should
- * be of the form {type, defaultValue, hasUserValue, userValue};
- * where |type| is one of the PREF_* type constants; |defaultValue|
- * and |userValue| are the default and user values, respectively;
- * and |hasUserValue| is a boolean indicating whether the user value
- * is valid
- */
- storageUpdated: function (type, userValue, hasUserValue, defaultValue) {
- this.type = type;
- this.defaultValue = defaultValue;
- this.hasUserValue = hasUserValue;
- this.userValue = userValue;
- // There's no need to write this back to local storage, since it
- // came from there; and this avoids infinite event loops.
- this.branch._notify(this.name);
- },
-};
-
-/**
* Create a new preference branch. This object conforms largely to
* nsIPrefBranch and nsIPrefService, though it only implements the
- * subset needed by devtools.
+ * subset needed by devtools. A preference branch can hold child
+ * preferences while also holding a preference value itself.
*
* @param {PrefBranch} parent the parent branch, or null for the root
* branch.
* @param {String} name the base name of this branch
* @param {String} fullName the fully-qualified name of this branch
*/
function PrefBranch(parent, name, fullName) {
this._parent = parent;
this._name = name;
this._fullName = fullName;
this._observers = {};
this._children = {};
+ // Properties used when this branch has a value as well.
+ this._defaultValue = null;
+ this._hasUserValue = false;
+ this._userValue = null;
+ this._type = PREF_INVALID;
+
if (!parent) {
this._initializeRoot();
}
}
PrefBranch.prototype = {
PREF_INVALID: PREF_INVALID,
PREF_STRING: PREF_STRING,
@@ -167,92 +55,92 @@ PrefBranch.prototype = {
/** @see nsIPrefBranch.root. */
get root() {
return this._fullName;
},
/** @see nsIPrefBranch.getPrefType. */
getPrefType: function (prefName) {
- return this._findPref(prefName).type;
+ return this._findPref(prefName)._type;
},
/** @see nsIPrefBranch.getBoolPref. */
getBoolPref: function (prefName) {
let thePref = this._findPref(prefName);
- if (thePref.type !== PREF_BOOL) {
+ if (thePref._type !== PREF_BOOL) {
throw new Error(`${prefName} does not have bool type`);
}
- return thePref.get();
+ return thePref._get();
},
/** @see nsIPrefBranch.setBoolPref. */
setBoolPref: function (prefName, value) {
if (typeof value !== "boolean") {
throw new Error("non-bool passed to setBoolPref");
}
let thePref = this._findOrCreatePref(prefName, value, true, value);
- if (thePref.type !== PREF_BOOL) {
+ if (thePref._type !== PREF_BOOL) {
throw new Error(`${prefName} does not have bool type`);
}
- thePref.set(value);
+ thePref._set(value);
},
/** @see nsIPrefBranch.getCharPref. */
getCharPref: function (prefName) {
let thePref = this._findPref(prefName);
- if (thePref.type !== PREF_STRING) {
+ if (thePref._type !== PREF_STRING) {
throw new Error(`${prefName} does not have string type`);
}
- return thePref.get();
+ return thePref._get();
},
/** @see nsIPrefBranch.setCharPref. */
setCharPref: function (prefName, value) {
if (typeof value !== "string") {
throw new Error("non-string passed to setCharPref");
}
let thePref = this._findOrCreatePref(prefName, value, true, value);
- if (thePref.type !== PREF_STRING) {
+ if (thePref._type !== PREF_STRING) {
throw new Error(`${prefName} does not have string type`);
}
- thePref.set(value);
+ thePref._set(value);
},
/** @see nsIPrefBranch.getIntPref. */
getIntPref: function (prefName) {
let thePref = this._findPref(prefName);
- if (thePref.type !== PREF_INT) {
+ if (thePref._type !== PREF_INT) {
throw new Error(`${prefName} does not have int type`);
}
- return thePref.get();
+ return thePref._get();
},
/** @see nsIPrefBranch.setIntPref. */
setIntPref: function (prefName, value) {
if (typeof value !== "number") {
throw new Error("non-number passed to setIntPref");
}
let thePref = this._findOrCreatePref(prefName, value, true, value);
- if (thePref.type !== PREF_INT) {
+ if (thePref._type !== PREF_INT) {
throw new Error(`${prefName} does not have int type`);
}
- thePref.set(value);
+ thePref._set(value);
},
/** @see nsIPrefBranch.clearUserPref */
clearUserPref: function (prefName) {
let thePref = this._findPref(prefName);
- thePref.clearUserValue();
+ thePref._clearUserValue();
},
/** @see nsIPrefBranch.prefHasUserValue */
prefHasUserValue: function (prefName) {
let thePref = this._findPref(prefName);
- return thePref.hasUserValue;
+ return thePref._hasUserValue;
},
/** @see nsIPrefBranch.addObserver */
addObserver: function (domain, observer, holdWeak) {
if (holdWeak) {
throw new Error("shim prefs only supports strong observers");
}
@@ -290,16 +178,116 @@ PrefBranch.prototype = {
prefRoot = prefRoot.slice(0, -1);
}
// This is a bit weird since it could erroneously return a pref,
// not a pref branch.
return this._findPref(prefRoot);
},
/**
+ * Return this preference's current value.
+ *
+ * @return {Any} The current value of this preference. This may
+ * return a string, a number, or a boolean depending on the
+ * preference's type.
+ */
+ _get: function () {
+ if (this._hasUserValue) {
+ return this._userValue;
+ }
+ return this._defaultValue;
+ },
+
+ /**
+ * Set the preference's value. The new value is assumed to be a
+ * user value. After setting the value, this function emits a
+ * change notification.
+ *
+ * @param {Any} value the new value
+ */
+ _set: function (value) {
+ if (!this._hasUserValue || value !== this._userValue) {
+ this._userValue = value;
+ this._hasUserValue = true;
+ this._saveAndNotify();
+ }
+ },
+
+ /**
+ * Set the default value for this preference, and emit a
+ * notification if this results in a visible change.
+ *
+ * @param {Any} value the new default value
+ */
+ _setDefault: function (value) {
+ if (this._defaultValue !== value) {
+ this._defaultValue = value;
+ if (!this._hasUserValue) {
+ this._saveAndNotify();
+ }
+ }
+ },
+
+ /**
+ * If this preference has a user value, clear it. If a change was
+ * made, emit a change notification.
+ */
+ _clearUserValue: function () {
+ if (this._hasUserValue) {
+ this._userValue = null;
+ this._hasUserValue = false;
+ this._saveAndNotify();
+ }
+ },
+
+ /**
+ * Helper function to write the preference's value to local storage
+ * and then emit a change notification.
+ */
+ _saveAndNotify: function () {
+ let store = {
+ type: this._type,
+ defaultValue: this._defaultValue,
+ hasUserValue: this._hasUserValue,
+ userValue: this._userValue,
+ };
+
+ localStorage.setItem(PREFIX + this.fullName, JSON.stringify(store));
+ this._parent._notify(this._name);
+ },
+
+ /**
+ * Change this preference's value without writing it back to local
+ * storage. This is used to handle changes to local storage that
+ * were made externally.
+ *
+ * @param {Number} type one of the PREF_* values
+ * @param {Any} userValue the user value to use if the pref does not exist
+ * @param {Any} defaultValue the default value to use if the pref
+ * does not exist
+ * @param {Boolean} hasUserValue if a new pref is created, whether
+ * the default value is also a user value
+ * @param {Object} store the new value of the preference. It should
+ * be of the form {type, defaultValue, hasUserValue, userValue};
+ * where |type| is one of the PREF_* type constants; |defaultValue|
+ * and |userValue| are the default and user values, respectively;
+ * and |hasUserValue| is a boolean indicating whether the user value
+ * is valid
+ */
+ _storageUpdated: function (type, userValue, hasUserValue, defaultValue) {
+ this._type = type;
+ this._defaultValue = defaultValue;
+ this._hasUserValue = hasUserValue;
+ this._userValue = userValue;
+ // There's no need to write this back to local storage, since it
+ // came from there; and this avoids infinite event loops.
+ this._parent._notify(this._name);
+ },
+
+ /**
* Helper function to find either a Preference or PrefBranch object
* given its name. If the name is not found, throws an exception.
*
* @param {String} prefName the fully-qualified preference name
* @return {Object} Either a Preference or PrefBranch object
*/
_findPref: function (prefName) {
let branchNames = prefName.split(".");
@@ -373,46 +361,44 @@ PrefBranch.prototype = {
* This is also the name of the key in local storage.
* @param {Any} userValue the user value to use if the pref does not exist
* @param {Any} defaultValue the default value to use if the pref
* does not exist
* @param {Boolean} hasUserValue if a new pref is created, whether
* the default value is also a user value
*/
_findOrCreatePref: function (keyName, userValue, hasUserValue, defaultValue) {
- let branchName = keyName.split(".");
- let prefName = branchName.pop();
-
- let branch = this._createBranch(branchName);
- if (!(prefName in branch._children)) {
- if (hasUserValue && typeof (userValue) !== typeof (defaultValue)) {
- throw new Error("inconsistent values when creating " + keyName);
- }
+ let branch = this._createBranch(keyName.split("."));
- let type;
- switch (typeof (defaultValue)) {
- case "boolean":
- type = PREF_BOOL;
- break;
- case "number":
- type = PREF_INT;
- break;
- case "string":
- type = PREF_STRING;
- break;
- default:
- throw new Error("unhandled argument type: " + typeof (defaultValue));
- }
-
- let thePref = new Preference(branch, prefName, keyName);
- thePref.storageUpdated(type, userValue, hasUserValue, defaultValue);
- branch._children[prefName] = thePref;
+ if (hasUserValue && typeof (userValue) !== typeof (defaultValue)) {
+ throw new Error("inconsistent values when creating " + keyName);
}
- return branch._children[prefName];
+ let type;
+ switch (typeof (defaultValue)) {
+ case "boolean":
+ type = PREF_BOOL;
+ break;
+ case "number":
+ type = PREF_INT;
+ break;
+ case "string":
+ type = PREF_STRING;
+ break;
+ default:
+ throw new Error("unhandled argument type: " + typeof (defaultValue));
+ }
+
+ if (branch._type === PREF_INVALID) {
+ branch._storageUpdated(type, userValue, hasUserValue, defaultValue);
+ } else if (branch._type !== type) {
+ throw new Error("attempt to change type of pref " + keyName);
+ }
+
+ return branch;
},
/**
* Helper function that is called when local storage changes. This
* updates the preferences and notifies pref observers as needed.
*
* @param {StorageEvent} event the event representing the local
* storage change
@@ -427,17 +413,17 @@ PrefBranch.prototype = {
}
let {type, userValue, hasUserValue, defaultValue} =
JSON.parse(event.newValue);
if (event.oldValue === null) {
this._findOrCreatePref(event.key, userValue, hasUserValue, defaultValue);
} else {
let thePref = this._findPref(event.key);
- thePref.storageUpdated(type, userValue, hasUserValue, defaultValue);
+ thePref._storageUpdated(type, userValue, hasUserValue, defaultValue);
}
},
/**
* Helper function to initialize the root PrefBranch.
*/
_initializeRoot: function () {
if (localStorage.length === 0) {
@@ -587,16 +573,16 @@ const Services = {
* devtools/client/preferences/devtools.js) to install the
* default preferences.
*
* @param {String} name the name of the preference
* @param {Any} value the default value of the preference
*/
function pref(name, value) {
let thePref = Services.prefs._findOrCreatePref(name, value, true, value);
- thePref.setDefault(value);
+ thePref._setDefault(value);
}
module.exports = Services;
// This is exported to silence eslint and, at some point, perhaps to
// provide it when loading devtools.js in order to install the default
// preferences.
exports.pref = pref;