toolkit/modules/Preferences.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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;
    33 }
    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   }
   373 };
   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;
   380 /**
   381  * A cache of pref observers.
   382  *
   383  * We use this to remove observers when a caller calls Preferences::ignore.
   384  *
   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;
   397 }
   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   }
   420 };
   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");
   428 }

mercurial