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.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 this.EXPORTED_SYMBOLS = ["Preferences"];
michael@0 6
michael@0 7 const Cc = Components.classes;
michael@0 8 const Ci = Components.interfaces;
michael@0 9 const Cr = Components.results;
michael@0 10 const Cu = Components.utils;
michael@0 11
michael@0 12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 13
michael@0 14 // The minimum and maximum integers that can be set as preferences.
michael@0 15 // The range of valid values is narrower than the range of valid JS values
michael@0 16 // because the native preferences code treats integers as NSPR PRInt32s,
michael@0 17 // which are 32-bit signed integers on all platforms.
michael@0 18 const MAX_INT = Math.pow(2, 31) - 1;
michael@0 19 const MIN_INT = -MAX_INT;
michael@0 20
michael@0 21 this.Preferences =
michael@0 22 function Preferences(args) {
michael@0 23 if (isObject(args)) {
michael@0 24 if (args.branch)
michael@0 25 this._prefBranch = args.branch;
michael@0 26 if (args.defaultBranch)
michael@0 27 this._defaultBranch = args.defaultBranch;
michael@0 28 if (args.privacyContext)
michael@0 29 this._privacyContext = args.privacyContext;
michael@0 30 }
michael@0 31 else if (args)
michael@0 32 this._prefBranch = args;
michael@0 33 }
michael@0 34
michael@0 35 Preferences.prototype = {
michael@0 36 /**
michael@0 37 * Get the value of a pref, if any; otherwise return the default value.
michael@0 38 *
michael@0 39 * @param prefName {String|Array}
michael@0 40 * the pref to get, or an array of prefs to get
michael@0 41 *
michael@0 42 * @param defaultValue
michael@0 43 * the default value, if any, for prefs that don't have one
michael@0 44 *
michael@0 45 * @returns the value of the pref, if any; otherwise the default value
michael@0 46 */
michael@0 47 get: function(prefName, defaultValue) {
michael@0 48 if (Array.isArray(prefName))
michael@0 49 return prefName.map(function(v) this.get(v, defaultValue), this);
michael@0 50
michael@0 51 return this._get(prefName, defaultValue);
michael@0 52 },
michael@0 53
michael@0 54 _get: function(prefName, defaultValue) {
michael@0 55 switch (this._prefSvc.getPrefType(prefName)) {
michael@0 56 case Ci.nsIPrefBranch.PREF_STRING:
michael@0 57 return this._prefSvc.getComplexValue(prefName, Ci.nsISupportsString).data;
michael@0 58
michael@0 59 case Ci.nsIPrefBranch.PREF_INT:
michael@0 60 return this._prefSvc.getIntPref(prefName);
michael@0 61
michael@0 62 case Ci.nsIPrefBranch.PREF_BOOL:
michael@0 63 return this._prefSvc.getBoolPref(prefName);
michael@0 64
michael@0 65 case Ci.nsIPrefBranch.PREF_INVALID:
michael@0 66 return defaultValue;
michael@0 67
michael@0 68 default:
michael@0 69 // This should never happen.
michael@0 70 throw "Error getting pref " + prefName + "; its value's type is " +
michael@0 71 this._prefSvc.getPrefType(prefName) + ", which I don't know " +
michael@0 72 "how to handle.";
michael@0 73 }
michael@0 74 },
michael@0 75
michael@0 76 /**
michael@0 77 * Set a preference to a value.
michael@0 78 *
michael@0 79 * You can set multiple prefs by passing an object as the only parameter.
michael@0 80 * In that case, this method will treat the properties of the object
michael@0 81 * as preferences to set, where each property name is the name of a pref
michael@0 82 * and its corresponding property value is the value of the pref.
michael@0 83 *
michael@0 84 * @param prefName {String|Object}
michael@0 85 * the name of the pref to set; or an object containing a set
michael@0 86 * of prefs to set
michael@0 87 *
michael@0 88 * @param prefValue {String|Number|Boolean}
michael@0 89 * the value to which to set the pref
michael@0 90 *
michael@0 91 * Note: Preferences cannot store non-integer numbers or numbers outside
michael@0 92 * the signed 32-bit range -(2^31-1) to 2^31-1, If you have such a number,
michael@0 93 * store it as a string by calling toString() on the number before passing
michael@0 94 * it to this method, i.e.:
michael@0 95 * Preferences.set("pi", 3.14159.toString())
michael@0 96 * Preferences.set("big", Math.pow(2, 31).toString()).
michael@0 97 */
michael@0 98 set: function(prefName, prefValue) {
michael@0 99 if (isObject(prefName)) {
michael@0 100 for (let [name, value] in Iterator(prefName))
michael@0 101 this.set(name, value);
michael@0 102 return;
michael@0 103 }
michael@0 104
michael@0 105 this._set(prefName, prefValue);
michael@0 106 },
michael@0 107
michael@0 108 _set: function(prefName, prefValue) {
michael@0 109 let prefType;
michael@0 110 if (typeof prefValue != "undefined" && prefValue != null)
michael@0 111 prefType = prefValue.constructor.name;
michael@0 112
michael@0 113 switch (prefType) {
michael@0 114 case "String":
michael@0 115 {
michael@0 116 let string = Cc["@mozilla.org/supports-string;1"].
michael@0 117 createInstance(Ci.nsISupportsString);
michael@0 118 string.data = prefValue;
michael@0 119 this._prefSvc.setComplexValue(prefName, Ci.nsISupportsString, string);
michael@0 120 }
michael@0 121 break;
michael@0 122
michael@0 123 case "Number":
michael@0 124 // We throw if the number is outside the range, since the result
michael@0 125 // will never be what the consumer wanted to store, but we only warn
michael@0 126 // if the number is non-integer, since the consumer might not mind
michael@0 127 // the loss of precision.
michael@0 128 if (prefValue > MAX_INT || prefValue < MIN_INT)
michael@0 129 throw("you cannot set the " + prefName + " pref to the number " +
michael@0 130 prefValue + ", as number pref values must be in the signed " +
michael@0 131 "32-bit integer range -(2^31-1) to 2^31-1. To store numbers " +
michael@0 132 "outside that range, store them as strings.");
michael@0 133 this._prefSvc.setIntPref(prefName, prefValue);
michael@0 134 if (prefValue % 1 != 0)
michael@0 135 Cu.reportError("Warning: setting the " + prefName + " pref to the " +
michael@0 136 "non-integer number " + prefValue + " converted it " +
michael@0 137 "to the integer number " + this.get(prefName) +
michael@0 138 "; to retain fractional precision, store non-integer " +
michael@0 139 "numbers as strings.");
michael@0 140 break;
michael@0 141
michael@0 142 case "Boolean":
michael@0 143 this._prefSvc.setBoolPref(prefName, prefValue);
michael@0 144 break;
michael@0 145
michael@0 146 default:
michael@0 147 throw "can't set pref " + prefName + " to value '" + prefValue +
michael@0 148 "'; it isn't a String, Number, or Boolean";
michael@0 149 }
michael@0 150 },
michael@0 151
michael@0 152 /**
michael@0 153 * Whether or not the given pref has a value. This is different from isSet
michael@0 154 * because it returns true whether the value of the pref is a default value
michael@0 155 * or a user-set value, while isSet only returns true if the value
michael@0 156 * is a user-set value.
michael@0 157 *
michael@0 158 * @param prefName {String|Array}
michael@0 159 * the pref to check, or an array of prefs to check
michael@0 160 *
michael@0 161 * @returns {Boolean|Array}
michael@0 162 * whether or not the pref has a value; or, if the caller provided
michael@0 163 * an array of pref names, an array of booleans indicating whether
michael@0 164 * or not the prefs have values
michael@0 165 */
michael@0 166 has: function(prefName) {
michael@0 167 if (Array.isArray(prefName))
michael@0 168 return prefName.map(this.has, this);
michael@0 169
michael@0 170 return this._has(prefName);
michael@0 171 },
michael@0 172
michael@0 173 _has: function(prefName) {
michael@0 174 return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID);
michael@0 175 },
michael@0 176
michael@0 177 /**
michael@0 178 * Whether or not the given pref has a user-set value. This is different
michael@0 179 * from |has| because it returns true only if the value of the pref is a user-
michael@0 180 * set value, while |has| returns true if the value of the pref is a default
michael@0 181 * value or a user-set value.
michael@0 182 *
michael@0 183 * @param prefName {String|Array}
michael@0 184 * the pref to check, or an array of prefs to check
michael@0 185 *
michael@0 186 * @returns {Boolean|Array}
michael@0 187 * whether or not the pref has a user-set value; or, if the caller
michael@0 188 * provided an array of pref names, an array of booleans indicating
michael@0 189 * whether or not the prefs have user-set values
michael@0 190 */
michael@0 191 isSet: function(prefName) {
michael@0 192 if (Array.isArray(prefName))
michael@0 193 return prefName.map(this.isSet, this);
michael@0 194
michael@0 195 return (this.has(prefName) && this._prefSvc.prefHasUserValue(prefName));
michael@0 196 },
michael@0 197
michael@0 198 /**
michael@0 199 * Whether or not the given pref has a user-set value. Use isSet instead,
michael@0 200 * which is equivalent.
michael@0 201 * @deprecated
michael@0 202 */
michael@0 203 modified: function(prefName) { return this.isSet(prefName) },
michael@0 204
michael@0 205 reset: function(prefName) {
michael@0 206 if (Array.isArray(prefName)) {
michael@0 207 prefName.map(function(v) this.reset(v), this);
michael@0 208 return;
michael@0 209 }
michael@0 210
michael@0 211 this._prefSvc.clearUserPref(prefName);
michael@0 212 },
michael@0 213
michael@0 214 /**
michael@0 215 * Lock a pref so it can't be changed.
michael@0 216 *
michael@0 217 * @param prefName {String|Array}
michael@0 218 * the pref to lock, or an array of prefs to lock
michael@0 219 */
michael@0 220 lock: function(prefName) {
michael@0 221 if (Array.isArray(prefName))
michael@0 222 prefName.map(this.lock, this);
michael@0 223
michael@0 224 this._prefSvc.lockPref(prefName);
michael@0 225 },
michael@0 226
michael@0 227 /**
michael@0 228 * Unlock a pref so it can be changed.
michael@0 229 *
michael@0 230 * @param prefName {String|Array}
michael@0 231 * the pref to lock, or an array of prefs to lock
michael@0 232 */
michael@0 233 unlock: function(prefName) {
michael@0 234 if (Array.isArray(prefName))
michael@0 235 prefName.map(this.unlock, this);
michael@0 236
michael@0 237 this._prefSvc.unlockPref(prefName);
michael@0 238 },
michael@0 239
michael@0 240 /**
michael@0 241 * Whether or not the given pref is locked against changes.
michael@0 242 *
michael@0 243 * @param prefName {String|Array}
michael@0 244 * the pref to check, or an array of prefs to check
michael@0 245 *
michael@0 246 * @returns {Boolean|Array}
michael@0 247 * whether or not the pref has a user-set value; or, if the caller
michael@0 248 * provided an array of pref names, an array of booleans indicating
michael@0 249 * whether or not the prefs have user-set values
michael@0 250 */
michael@0 251 locked: function(prefName) {
michael@0 252 if (Array.isArray(prefName))
michael@0 253 return prefName.map(this.locked, this);
michael@0 254
michael@0 255 return this._prefSvc.prefIsLocked(prefName);
michael@0 256 },
michael@0 257
michael@0 258 /**
michael@0 259 * Start observing a pref.
michael@0 260 *
michael@0 261 * The callback can be a function or any object that implements nsIObserver.
michael@0 262 * When the callback is a function and thisObject is provided, it gets called
michael@0 263 * as a method of thisObject.
michael@0 264 *
michael@0 265 * @param prefName {String}
michael@0 266 * the name of the pref to observe
michael@0 267 *
michael@0 268 * @param callback {Function|Object}
michael@0 269 * the code to notify when the pref changes;
michael@0 270 *
michael@0 271 * @param thisObject {Object} [optional]
michael@0 272 * the object to use as |this| when calling a Function callback;
michael@0 273 *
michael@0 274 * @returns the wrapped observer
michael@0 275 */
michael@0 276 observe: function(prefName, callback, thisObject) {
michael@0 277 let fullPrefName = this._prefBranch + (prefName || "");
michael@0 278
michael@0 279 let observer = new PrefObserver(fullPrefName, callback, thisObject);
michael@0 280 Preferences._prefSvc.addObserver(fullPrefName, observer, true);
michael@0 281 observers.push(observer);
michael@0 282
michael@0 283 return observer;
michael@0 284 },
michael@0 285
michael@0 286 /**
michael@0 287 * Stop observing a pref.
michael@0 288 *
michael@0 289 * You must call this method with the same prefName, callback, and thisObject
michael@0 290 * with which you originally registered the observer. However, you don't have
michael@0 291 * to call this method on the same exact instance of Preferences; you can call
michael@0 292 * it on any instance. For example, the following code first starts and then
michael@0 293 * stops observing the "foo.bar.baz" preference:
michael@0 294 *
michael@0 295 * let observer = function() {...};
michael@0 296 * Preferences.observe("foo.bar.baz", observer);
michael@0 297 * new Preferences("foo.bar.").ignore("baz", observer);
michael@0 298 *
michael@0 299 * @param prefName {String}
michael@0 300 * the name of the pref being observed
michael@0 301 *
michael@0 302 * @param callback {Function|Object}
michael@0 303 * the code being notified when the pref changes
michael@0 304 *
michael@0 305 * @param thisObject {Object} [optional]
michael@0 306 * the object being used as |this| when calling a Function callback
michael@0 307 */
michael@0 308 ignore: function(prefName, callback, thisObject) {
michael@0 309 let fullPrefName = this._prefBranch + (prefName || "");
michael@0 310
michael@0 311 // This seems fairly inefficient, but I'm not sure how much better we can
michael@0 312 // make it. We could index by fullBranch, but we can't index by callback
michael@0 313 // or thisObject, as far as I know, since the keys to JavaScript hashes
michael@0 314 // (a.k.a. objects) can apparently only be primitive values.
michael@0 315 let [observer] = observers.filter(function(v) v.prefName == fullPrefName &&
michael@0 316 v.callback == callback &&
michael@0 317 v.thisObject == thisObject);
michael@0 318
michael@0 319 if (observer) {
michael@0 320 Preferences._prefSvc.removeObserver(fullPrefName, observer);
michael@0 321 observers.splice(observers.indexOf(observer), 1);
michael@0 322 }
michael@0 323 },
michael@0 324
michael@0 325 resetBranch: function(prefBranch = "") {
michael@0 326 try {
michael@0 327 this._prefSvc.resetBranch(prefBranch);
michael@0 328 }
michael@0 329 catch(ex) {
michael@0 330 // The current implementation of nsIPrefBranch in Mozilla
michael@0 331 // doesn't implement resetBranch, so we do it ourselves.
michael@0 332 if (ex.result == Cr.NS_ERROR_NOT_IMPLEMENTED)
michael@0 333 this.reset(this._prefSvc.getChildList(prefBranch, []));
michael@0 334 else
michael@0 335 throw ex;
michael@0 336 }
michael@0 337 },
michael@0 338
michael@0 339 /**
michael@0 340 * The branch of the preferences tree to which this instance provides access.
michael@0 341 * @private
michael@0 342 */
michael@0 343 _prefBranch: "",
michael@0 344
michael@0 345 /**
michael@0 346 * Preferences Service
michael@0 347 * @private
michael@0 348 */
michael@0 349 get _prefSvc() {
michael@0 350 let prefSvc = Cc["@mozilla.org/preferences-service;1"]
michael@0 351 .getService(Ci.nsIPrefService);
michael@0 352 if (this._defaultBranch) {
michael@0 353 prefSvc = prefSvc.getDefaultBranch(this._prefBranch);
michael@0 354 } else {
michael@0 355 prefSvc = prefSvc.getBranch(this._prefBranch);
michael@0 356 }
michael@0 357
michael@0 358 this.__defineGetter__("_prefSvc", function() prefSvc);
michael@0 359 return this._prefSvc;
michael@0 360 },
michael@0 361
michael@0 362 /**
michael@0 363 * IO Service
michael@0 364 * @private
michael@0 365 */
michael@0 366 get _ioSvc() {
michael@0 367 let ioSvc = Cc["@mozilla.org/network/io-service;1"].
michael@0 368 getService(Ci.nsIIOService);
michael@0 369 this.__defineGetter__("_ioSvc", function() ioSvc);
michael@0 370 return this._ioSvc;
michael@0 371 }
michael@0 372
michael@0 373 };
michael@0 374
michael@0 375 // Give the constructor the same prototype as its instances, so users can access
michael@0 376 // preferences directly via the constructor without having to create an instance
michael@0 377 // first.
michael@0 378 Preferences.__proto__ = Preferences.prototype;
michael@0 379
michael@0 380 /**
michael@0 381 * A cache of pref observers.
michael@0 382 *
michael@0 383 * We use this to remove observers when a caller calls Preferences::ignore.
michael@0 384 *
michael@0 385 * All Preferences instances share this object, because we want callers to be
michael@0 386 * able to remove an observer using a different Preferences object than the one
michael@0 387 * with which they added it. That means we have to identify the observers
michael@0 388 * in this object by their complete pref name, not just their name relative to
michael@0 389 * the root branch of the Preferences object with which they were created.
michael@0 390 */
michael@0 391 let observers = [];
michael@0 392
michael@0 393 function PrefObserver(prefName, callback, thisObject) {
michael@0 394 this.prefName = prefName;
michael@0 395 this.callback = callback;
michael@0 396 this.thisObject = thisObject;
michael@0 397 }
michael@0 398
michael@0 399 PrefObserver.prototype = {
michael@0 400 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
michael@0 401
michael@0 402 observe: function(subject, topic, data) {
michael@0 403 // The pref service only observes whole branches, but we only observe
michael@0 404 // individual preferences, so we check here that the pref that changed
michael@0 405 // is the exact one we're observing (and not some sub-pref on the branch).
michael@0 406 if (data.indexOf(this.prefName) != 0)
michael@0 407 return;
michael@0 408
michael@0 409 if (typeof this.callback == "function") {
michael@0 410 let prefValue = Preferences.get(data);
michael@0 411
michael@0 412 if (this.thisObject)
michael@0 413 this.callback.call(this.thisObject, prefValue);
michael@0 414 else
michael@0 415 this.callback(prefValue);
michael@0 416 }
michael@0 417 else // typeof this.callback == "object" (nsIObserver)
michael@0 418 this.callback.observe(subject, topic, data);
michael@0 419 }
michael@0 420 };
michael@0 421
michael@0 422 function isObject(val) {
michael@0 423 // We can't check for |val.constructor == Object| here, since the value
michael@0 424 // might be from a different context whose Object constructor is not the same
michael@0 425 // as ours, so instead we match based on the name of the constructor.
michael@0 426 return (typeof val != "undefined" && val != null && typeof val == "object" &&
michael@0 427 val.constructor.name == "Object");
michael@0 428 }

mercurial