Wed, 31 Dec 2014 06:09:35 +0100
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 | } |