Bumping manifests a=b2g-bump
[gecko.git] / toolkit / modules / Preferences.jsm
blob44d1708725aae8ce1e9b7b154cc895763dffa16d
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;
21 this.Preferences =
22   function Preferences(args) {
23     if (isObject(args)) {
24       if (args.branch)
25         this._prefBranch = args.branch;
26       if (args.defaultBranch)
27         this._defaultBranch = args.defaultBranch;
28       if (args.privacyContext)
29         this._privacyContext = args.privacyContext;
30     }
31     else if (args)
32       this._prefBranch = args;
35 Preferences.prototype = {
36   /**
37    * Get the value of a pref, if any; otherwise return the default value.
38    *
39    * @param   prefName  {String|Array}
40    *          the pref to get, or an array of prefs to get
41    *
42    * @param   defaultValue
43    *          the default value, if any, for prefs that don't have one
44    *
45    * @returns the value of the pref, if any; otherwise the default value
46    */
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);
52   },
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:
66         return defaultValue;
68       default:
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 " +
72               "how to handle.";
73     }
74   },
76   /**
77    * Set a preference to a value.
78    *
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.
83    *
84    * @param   prefName  {String|Object}
85    *          the name of the pref to set; or an object containing a set
86    *          of prefs to set
87    *
88    * @param   prefValue {String|Number|Boolean}
89    *          the value to which to set the pref
90    *
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()).
97    */
98   set: function(prefName, prefValue) {
99     if (isObject(prefName)) {
100       for (let [name, value] in Iterator(prefName))
101         this.set(name, value);
102       return;
103     }
105     this._set(prefName, prefValue);
106   },
108   _set: function(prefName, prefValue) {
109     let prefType;
110     if (typeof prefValue != "undefined" && prefValue != null)
111       prefType = prefValue.constructor.name;
113     switch (prefType) {
114       case "String":
115         {
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);
120         }
121         break;
123       case "Number":
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.");
140         break;
142       case "Boolean":
143         this._prefSvc.setBoolPref(prefName, prefValue);
144         break;
146       default:
147         throw "can't set pref " + prefName + " to value '" + prefValue +
148               "'; it isn't a String, Number, or Boolean";
149     }
150   },
152   /**
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.
157    *
158    * @param   prefName  {String|Array}
159    *          the pref to check, or an array of prefs to check
160    *
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
165    */
166   has: function(prefName) {
167     if (Array.isArray(prefName))
168       return prefName.map(this.has, this);
170     return this._has(prefName);
171   },
173   _has: function(prefName) {
174     return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID);
175   },
177   /**
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.
182    *
183    * @param   prefName  {String|Array}
184    *          the pref to check, or an array of prefs to check
185    *
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
190    */
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));
196   },
198   /**
199    * Whether or not the given pref has a user-set value. Use isSet instead,
200    * which is equivalent.
201    * @deprecated
202    */
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);
208       return;
209     }
211     this._prefSvc.clearUserPref(prefName);
212   },
214   /**
215    * Lock a pref so it can't be changed.
216    *
217    * @param   prefName  {String|Array}
218    *          the pref to lock, or an array of prefs to lock
219    */
220   lock: function(prefName) {
221     if (Array.isArray(prefName))
222       prefName.map(this.lock, this);
224     this._prefSvc.lockPref(prefName);
225   },
227   /**
228    * Unlock a pref so it can be changed.
229    *
230    * @param   prefName  {String|Array}
231    *          the pref to lock, or an array of prefs to lock
232    */
233   unlock: function(prefName) {
234     if (Array.isArray(prefName))
235       prefName.map(this.unlock, this);
237     this._prefSvc.unlockPref(prefName);
238   },
240   /**
241    * Whether or not the given pref is locked against changes.
242    *
243    * @param   prefName  {String|Array}
244    *          the pref to check, or an array of prefs to check
245    *
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
250    */
251   locked: function(prefName) {
252     if (Array.isArray(prefName))
253       return prefName.map(this.locked, this);
255     return this._prefSvc.prefIsLocked(prefName);
256   },
258   /**
259    * Start observing a pref.
260    *
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.
264    *
265    * @param   prefName    {String}
266    *          the name of the pref to observe
267    *
268    * @param   callback    {Function|Object}
269    *          the code to notify when the pref changes;
270    *
271    * @param   thisObject  {Object}  [optional]
272    *          the object to use as |this| when calling a Function callback;
273    *
274    * @returns the wrapped observer
275    */
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);
283     return observer;
284   },
286   /**
287    * Stop observing a pref.
288    *
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:
294    *
295    *   let observer = function() {...};
296    *   Preferences.observe("foo.bar.baz", observer);
297    *   new Preferences("foo.bar.").ignore("baz", observer);
298    *
299    * @param   prefName    {String}
300    *          the name of the pref being observed
301    *
302    * @param   callback    {Function|Object}
303    *          the code being notified when the pref changes
304    *
305    * @param   thisObject  {Object}  [optional]
306    *          the object being used as |this| when calling a Function callback
307    */
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);
319     if (observer) {
320       Preferences._prefSvc.removeObserver(fullPrefName, observer);
321       observers.splice(observers.indexOf(observer), 1);
322     }
323   },
325   resetBranch: function(prefBranch = "") {
326     try {
327       this._prefSvc.resetBranch(prefBranch);
328     }
329     catch(ex) {
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, []));
334       else
335         throw ex;
336     }
337   },
339   /**
340    * The branch of the preferences tree to which this instance provides access.
341    * @private
342    */
343   _prefBranch: "",
345   /**
346    * Preferences Service
347    * @private
348    */
349   get _prefSvc() {
350     let prefSvc = Cc["@mozilla.org/preferences-service;1"]
351                   .getService(Ci.nsIPrefService);
352     if (this._defaultBranch) {
353       prefSvc = prefSvc.getDefaultBranch(this._prefBranch);
354     } else {
355       prefSvc = prefSvc.getBranch(this._prefBranch);
356     }
358     this.__defineGetter__("_prefSvc", function() prefSvc);
359     return this._prefSvc;
360   },
362   /**
363    * IO Service
364    * @private
365    */
366   get _ioSvc() {
367     let ioSvc = Cc["@mozilla.org/network/io-service;1"].
368                 getService(Ci.nsIIOService);
369     this.__defineGetter__("_ioSvc", function() ioSvc);
370     return this._ioSvc;
371   }
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
377 // first.
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.
390  */
391 let observers = [];
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)
407       return;
409     if (typeof this.callback == "function") {
410       let prefValue = Preferences.get(data);
412       if (this.thisObject)
413         this.callback.call(this.thisObject, prefValue);
414       else
415         this.callback(prefValue);
416     }
417     else // typeof this.callback == "object" (nsIObserver)
418       this.callback.observe(subject, topic, data);
419   }
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");