1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 this.EXPORTED_SYMBOLS = ["Preferences"];
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cr = Components.results;
10 const Cu = Components.utils;
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
14 // The minimum and maximum integers that can be set as preferences.
15 // The range of valid values is narrower than the range of valid JS values
16 // because the native preferences code treats integers as NSPR PRInt32s,
17 // which are 32-bit signed integers on all platforms.
18 const MAX_INT = Math.pow(2, 31) - 1;
19 const MIN_INT = -MAX_INT;
22 function Preferences(args) {
25 this._prefBranch = args.branch;
26 if (args.defaultBranch)
27 this._defaultBranch = args.defaultBranch;
28 if (args.privacyContext)
29 this._privacyContext = args.privacyContext;
32 this._prefBranch = args;
35 Preferences.prototype = {
37 * Get the value of a pref, if any; otherwise return the default value.
39 * @param prefName {String|Array}
40 * the pref to get, or an array of prefs to get
43 * the default value, if any, for prefs that don't have one
45 * @returns the value of the pref, if any; otherwise the default value
47 get: function(prefName, defaultValue) {
48 if (Array.isArray(prefName))
49 return prefName.map(function(v) this.get(v, defaultValue), this);
51 return this._get(prefName, defaultValue);
54 _get: function(prefName, defaultValue) {
55 switch (this._prefSvc.getPrefType(prefName)) {
56 case Ci.nsIPrefBranch.PREF_STRING:
57 return this._prefSvc.getComplexValue(prefName, Ci.nsISupportsString).data;
59 case Ci.nsIPrefBranch.PREF_INT:
60 return this._prefSvc.getIntPref(prefName);
62 case Ci.nsIPrefBranch.PREF_BOOL:
63 return this._prefSvc.getBoolPref(prefName);
65 case Ci.nsIPrefBranch.PREF_INVALID:
69 // This should never happen.
70 throw "Error getting pref " + prefName + "; its value's type is " +
71 this._prefSvc.getPrefType(prefName) + ", which I don't know " +
77 * Set a preference to a value.
79 * You can set multiple prefs by passing an object as the only parameter.
80 * In that case, this method will treat the properties of the object
81 * as preferences to set, where each property name is the name of a pref
82 * and its corresponding property value is the value of the pref.
84 * @param prefName {String|Object}
85 * the name of the pref to set; or an object containing a set
88 * @param prefValue {String|Number|Boolean}
89 * the value to which to set the pref
91 * Note: Preferences cannot store non-integer numbers or numbers outside
92 * the signed 32-bit range -(2^31-1) to 2^31-1, If you have such a number,
93 * store it as a string by calling toString() on the number before passing
94 * it to this method, i.e.:
95 * Preferences.set("pi", 3.14159.toString())
96 * Preferences.set("big", Math.pow(2, 31).toString()).
98 set: function(prefName, prefValue) {
99 if (isObject(prefName)) {
100 for (let [name, value] in Iterator(prefName))
101 this.set(name, value);
105 this._set(prefName, prefValue);
108 _set: function(prefName, prefValue) {
110 if (typeof prefValue != "undefined" && prefValue != null)
111 prefType = prefValue.constructor.name;
116 let string = Cc["@mozilla.org/supports-string;1"].
117 createInstance(Ci.nsISupportsString);
118 string.data = prefValue;
119 this._prefSvc.setComplexValue(prefName, Ci.nsISupportsString, string);
124 // We throw if the number is outside the range, since the result
125 // will never be what the consumer wanted to store, but we only warn
126 // if the number is non-integer, since the consumer might not mind
127 // the loss of precision.
128 if (prefValue > MAX_INT || prefValue < MIN_INT)
129 throw("you cannot set the " + prefName + " pref to the number " +
130 prefValue + ", as number pref values must be in the signed " +
131 "32-bit integer range -(2^31-1) to 2^31-1. To store numbers " +
132 "outside that range, store them as strings.");
133 this._prefSvc.setIntPref(prefName, prefValue);
134 if (prefValue % 1 != 0)
135 Cu.reportError("Warning: setting the " + prefName + " pref to the " +
136 "non-integer number " + prefValue + " converted it " +
137 "to the integer number " + this.get(prefName) +
138 "; to retain fractional precision, store non-integer " +
139 "numbers as strings.");
143 this._prefSvc.setBoolPref(prefName, prefValue);
147 throw "can't set pref " + prefName + " to value '" + prefValue +
148 "'; it isn't a String, Number, or Boolean";
153 * Whether or not the given pref has a value. This is different from isSet
154 * because it returns true whether the value of the pref is a default value
155 * or a user-set value, while isSet only returns true if the value
156 * is a user-set value.
158 * @param prefName {String|Array}
159 * the pref to check, or an array of prefs to check
161 * @returns {Boolean|Array}
162 * whether or not the pref has a value; or, if the caller provided
163 * an array of pref names, an array of booleans indicating whether
164 * or not the prefs have values
166 has: function(prefName) {
167 if (Array.isArray(prefName))
168 return prefName.map(this.has, this);
170 return this._has(prefName);
173 _has: function(prefName) {
174 return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID);
178 * Whether or not the given pref has a user-set value. This is different
179 * from |has| because it returns true only if the value of the pref is a user-
180 * set value, while |has| returns true if the value of the pref is a default
181 * value or a user-set value.
183 * @param prefName {String|Array}
184 * the pref to check, or an array of prefs to check
186 * @returns {Boolean|Array}
187 * whether or not the pref has a user-set value; or, if the caller
188 * provided an array of pref names, an array of booleans indicating
189 * whether or not the prefs have user-set values
191 isSet: function(prefName) {
192 if (Array.isArray(prefName))
193 return prefName.map(this.isSet, this);
195 return (this.has(prefName) && this._prefSvc.prefHasUserValue(prefName));
199 * Whether or not the given pref has a user-set value. Use isSet instead,
200 * which is equivalent.
203 modified: function(prefName) { return this.isSet(prefName) },
205 reset: function(prefName) {
206 if (Array.isArray(prefName)) {
207 prefName.map(function(v) this.reset(v), this);
211 this._prefSvc.clearUserPref(prefName);
215 * Lock a pref so it can't be changed.
217 * @param prefName {String|Array}
218 * the pref to lock, or an array of prefs to lock
220 lock: function(prefName) {
221 if (Array.isArray(prefName))
222 prefName.map(this.lock, this);
224 this._prefSvc.lockPref(prefName);
228 * Unlock a pref so it can be changed.
230 * @param prefName {String|Array}
231 * the pref to lock, or an array of prefs to lock
233 unlock: function(prefName) {
234 if (Array.isArray(prefName))
235 prefName.map(this.unlock, this);
237 this._prefSvc.unlockPref(prefName);
241 * Whether or not the given pref is locked against changes.
243 * @param prefName {String|Array}
244 * the pref to check, or an array of prefs to check
246 * @returns {Boolean|Array}
247 * whether or not the pref has a user-set value; or, if the caller
248 * provided an array of pref names, an array of booleans indicating
249 * whether or not the prefs have user-set values
251 locked: function(prefName) {
252 if (Array.isArray(prefName))
253 return prefName.map(this.locked, this);
255 return this._prefSvc.prefIsLocked(prefName);
259 * Start observing a pref.
261 * The callback can be a function or any object that implements nsIObserver.
262 * When the callback is a function and thisObject is provided, it gets called
263 * as a method of thisObject.
265 * @param prefName {String}
266 * the name of the pref to observe
268 * @param callback {Function|Object}
269 * the code to notify when the pref changes;
271 * @param thisObject {Object} [optional]
272 * the object to use as |this| when calling a Function callback;
274 * @returns the wrapped observer
276 observe: function(prefName, callback, thisObject) {
277 let fullPrefName = this._prefBranch + (prefName || "");
279 let observer = new PrefObserver(fullPrefName, callback, thisObject);
280 Preferences._prefSvc.addObserver(fullPrefName, observer, true);
281 observers.push(observer);
287 * Stop observing a pref.
289 * You must call this method with the same prefName, callback, and thisObject
290 * with which you originally registered the observer. However, you don't have
291 * to call this method on the same exact instance of Preferences; you can call
292 * it on any instance. For example, the following code first starts and then
293 * stops observing the "foo.bar.baz" preference:
295 * let observer = function() {...};
296 * Preferences.observe("foo.bar.baz", observer);
297 * new Preferences("foo.bar.").ignore("baz", observer);
299 * @param prefName {String}
300 * the name of the pref being observed
302 * @param callback {Function|Object}
303 * the code being notified when the pref changes
305 * @param thisObject {Object} [optional]
306 * the object being used as |this| when calling a Function callback
308 ignore: function(prefName, callback, thisObject) {
309 let fullPrefName = this._prefBranch + (prefName || "");
311 // This seems fairly inefficient, but I'm not sure how much better we can
312 // make it. We could index by fullBranch, but we can't index by callback
313 // or thisObject, as far as I know, since the keys to JavaScript hashes
314 // (a.k.a. objects) can apparently only be primitive values.
315 let [observer] = observers.filter(function(v) v.prefName == fullPrefName &&
316 v.callback == callback &&
317 v.thisObject == thisObject);
320 Preferences._prefSvc.removeObserver(fullPrefName, observer);
321 observers.splice(observers.indexOf(observer), 1);
325 resetBranch: function(prefBranch = "") {
327 this._prefSvc.resetBranch(prefBranch);
330 // The current implementation of nsIPrefBranch in Mozilla
331 // doesn't implement resetBranch, so we do it ourselves.
332 if (ex.result == Cr.NS_ERROR_NOT_IMPLEMENTED)
333 this.reset(this._prefSvc.getChildList(prefBranch, []));
340 * The branch of the preferences tree to which this instance provides access.
346 * Preferences Service
350 let prefSvc = Cc["@mozilla.org/preferences-service;1"]
351 .getService(Ci.nsIPrefService);
352 if (this._defaultBranch) {
353 prefSvc = prefSvc.getDefaultBranch(this._prefBranch);
355 prefSvc = prefSvc.getBranch(this._prefBranch);
358 this.__defineGetter__("_prefSvc", function() prefSvc);
359 return this._prefSvc;
367 let ioSvc = Cc["@mozilla.org/network/io-service;1"].
368 getService(Ci.nsIIOService);
369 this.__defineGetter__("_ioSvc", function() ioSvc);
375 // Give the constructor the same prototype as its instances, so users can access
376 // preferences directly via the constructor without having to create an instance
378 Preferences.__proto__ = Preferences.prototype;
381 * A cache of pref observers.
383 * We use this to remove observers when a caller calls Preferences::ignore.
385 * All Preferences instances share this object, because we want callers to be
386 * able to remove an observer using a different Preferences object than the one
387 * with which they added it. That means we have to identify the observers
388 * in this object by their complete pref name, not just their name relative to
389 * the root branch of the Preferences object with which they were created.
393 function PrefObserver(prefName, callback, thisObject) {
394 this.prefName = prefName;
395 this.callback = callback;
396 this.thisObject = thisObject;
399 PrefObserver.prototype = {
400 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
402 observe: function(subject, topic, data) {
403 // The pref service only observes whole branches, but we only observe
404 // individual preferences, so we check here that the pref that changed
405 // is the exact one we're observing (and not some sub-pref on the branch).
406 if (data.indexOf(this.prefName) != 0)
409 if (typeof this.callback == "function") {
410 let prefValue = Preferences.get(data);
413 this.callback.call(this.thisObject, prefValue);
415 this.callback(prefValue);
417 else // typeof this.callback == "object" (nsIObserver)
418 this.callback.observe(subject, topic, data);
422 function isObject(val) {
423 // We can't check for |val.constructor == Object| here, since the value
424 // might be from a different context whose Object constructor is not the same
425 // as ours, so instead we match based on the name of the constructor.
426 return (typeof val != "undefined" && val != null && typeof val == "object" &&
427 val.constructor.name == "Object");