1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/modules/Preferences.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,428 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = ["Preferences"]; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cr = Components.results; 1.13 +const Cu = Components.utils; 1.14 + 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 + 1.17 +// The minimum and maximum integers that can be set as preferences. 1.18 +// The range of valid values is narrower than the range of valid JS values 1.19 +// because the native preferences code treats integers as NSPR PRInt32s, 1.20 +// which are 32-bit signed integers on all platforms. 1.21 +const MAX_INT = Math.pow(2, 31) - 1; 1.22 +const MIN_INT = -MAX_INT; 1.23 + 1.24 +this.Preferences = 1.25 + function Preferences(args) { 1.26 + if (isObject(args)) { 1.27 + if (args.branch) 1.28 + this._prefBranch = args.branch; 1.29 + if (args.defaultBranch) 1.30 + this._defaultBranch = args.defaultBranch; 1.31 + if (args.privacyContext) 1.32 + this._privacyContext = args.privacyContext; 1.33 + } 1.34 + else if (args) 1.35 + this._prefBranch = args; 1.36 +} 1.37 + 1.38 +Preferences.prototype = { 1.39 + /** 1.40 + * Get the value of a pref, if any; otherwise return the default value. 1.41 + * 1.42 + * @param prefName {String|Array} 1.43 + * the pref to get, or an array of prefs to get 1.44 + * 1.45 + * @param defaultValue 1.46 + * the default value, if any, for prefs that don't have one 1.47 + * 1.48 + * @returns the value of the pref, if any; otherwise the default value 1.49 + */ 1.50 + get: function(prefName, defaultValue) { 1.51 + if (Array.isArray(prefName)) 1.52 + return prefName.map(function(v) this.get(v, defaultValue), this); 1.53 + 1.54 + return this._get(prefName, defaultValue); 1.55 + }, 1.56 + 1.57 + _get: function(prefName, defaultValue) { 1.58 + switch (this._prefSvc.getPrefType(prefName)) { 1.59 + case Ci.nsIPrefBranch.PREF_STRING: 1.60 + return this._prefSvc.getComplexValue(prefName, Ci.nsISupportsString).data; 1.61 + 1.62 + case Ci.nsIPrefBranch.PREF_INT: 1.63 + return this._prefSvc.getIntPref(prefName); 1.64 + 1.65 + case Ci.nsIPrefBranch.PREF_BOOL: 1.66 + return this._prefSvc.getBoolPref(prefName); 1.67 + 1.68 + case Ci.nsIPrefBranch.PREF_INVALID: 1.69 + return defaultValue; 1.70 + 1.71 + default: 1.72 + // This should never happen. 1.73 + throw "Error getting pref " + prefName + "; its value's type is " + 1.74 + this._prefSvc.getPrefType(prefName) + ", which I don't know " + 1.75 + "how to handle."; 1.76 + } 1.77 + }, 1.78 + 1.79 + /** 1.80 + * Set a preference to a value. 1.81 + * 1.82 + * You can set multiple prefs by passing an object as the only parameter. 1.83 + * In that case, this method will treat the properties of the object 1.84 + * as preferences to set, where each property name is the name of a pref 1.85 + * and its corresponding property value is the value of the pref. 1.86 + * 1.87 + * @param prefName {String|Object} 1.88 + * the name of the pref to set; or an object containing a set 1.89 + * of prefs to set 1.90 + * 1.91 + * @param prefValue {String|Number|Boolean} 1.92 + * the value to which to set the pref 1.93 + * 1.94 + * Note: Preferences cannot store non-integer numbers or numbers outside 1.95 + * the signed 32-bit range -(2^31-1) to 2^31-1, If you have such a number, 1.96 + * store it as a string by calling toString() on the number before passing 1.97 + * it to this method, i.e.: 1.98 + * Preferences.set("pi", 3.14159.toString()) 1.99 + * Preferences.set("big", Math.pow(2, 31).toString()). 1.100 + */ 1.101 + set: function(prefName, prefValue) { 1.102 + if (isObject(prefName)) { 1.103 + for (let [name, value] in Iterator(prefName)) 1.104 + this.set(name, value); 1.105 + return; 1.106 + } 1.107 + 1.108 + this._set(prefName, prefValue); 1.109 + }, 1.110 + 1.111 + _set: function(prefName, prefValue) { 1.112 + let prefType; 1.113 + if (typeof prefValue != "undefined" && prefValue != null) 1.114 + prefType = prefValue.constructor.name; 1.115 + 1.116 + switch (prefType) { 1.117 + case "String": 1.118 + { 1.119 + let string = Cc["@mozilla.org/supports-string;1"]. 1.120 + createInstance(Ci.nsISupportsString); 1.121 + string.data = prefValue; 1.122 + this._prefSvc.setComplexValue(prefName, Ci.nsISupportsString, string); 1.123 + } 1.124 + break; 1.125 + 1.126 + case "Number": 1.127 + // We throw if the number is outside the range, since the result 1.128 + // will never be what the consumer wanted to store, but we only warn 1.129 + // if the number is non-integer, since the consumer might not mind 1.130 + // the loss of precision. 1.131 + if (prefValue > MAX_INT || prefValue < MIN_INT) 1.132 + throw("you cannot set the " + prefName + " pref to the number " + 1.133 + prefValue + ", as number pref values must be in the signed " + 1.134 + "32-bit integer range -(2^31-1) to 2^31-1. To store numbers " + 1.135 + "outside that range, store them as strings."); 1.136 + this._prefSvc.setIntPref(prefName, prefValue); 1.137 + if (prefValue % 1 != 0) 1.138 + Cu.reportError("Warning: setting the " + prefName + " pref to the " + 1.139 + "non-integer number " + prefValue + " converted it " + 1.140 + "to the integer number " + this.get(prefName) + 1.141 + "; to retain fractional precision, store non-integer " + 1.142 + "numbers as strings."); 1.143 + break; 1.144 + 1.145 + case "Boolean": 1.146 + this._prefSvc.setBoolPref(prefName, prefValue); 1.147 + break; 1.148 + 1.149 + default: 1.150 + throw "can't set pref " + prefName + " to value '" + prefValue + 1.151 + "'; it isn't a String, Number, or Boolean"; 1.152 + } 1.153 + }, 1.154 + 1.155 + /** 1.156 + * Whether or not the given pref has a value. This is different from isSet 1.157 + * because it returns true whether the value of the pref is a default value 1.158 + * or a user-set value, while isSet only returns true if the value 1.159 + * is a user-set value. 1.160 + * 1.161 + * @param prefName {String|Array} 1.162 + * the pref to check, or an array of prefs to check 1.163 + * 1.164 + * @returns {Boolean|Array} 1.165 + * whether or not the pref has a value; or, if the caller provided 1.166 + * an array of pref names, an array of booleans indicating whether 1.167 + * or not the prefs have values 1.168 + */ 1.169 + has: function(prefName) { 1.170 + if (Array.isArray(prefName)) 1.171 + return prefName.map(this.has, this); 1.172 + 1.173 + return this._has(prefName); 1.174 + }, 1.175 + 1.176 + _has: function(prefName) { 1.177 + return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID); 1.178 + }, 1.179 + 1.180 + /** 1.181 + * Whether or not the given pref has a user-set value. This is different 1.182 + * from |has| because it returns true only if the value of the pref is a user- 1.183 + * set value, while |has| returns true if the value of the pref is a default 1.184 + * value or a user-set value. 1.185 + * 1.186 + * @param prefName {String|Array} 1.187 + * the pref to check, or an array of prefs to check 1.188 + * 1.189 + * @returns {Boolean|Array} 1.190 + * whether or not the pref has a user-set value; or, if the caller 1.191 + * provided an array of pref names, an array of booleans indicating 1.192 + * whether or not the prefs have user-set values 1.193 + */ 1.194 + isSet: function(prefName) { 1.195 + if (Array.isArray(prefName)) 1.196 + return prefName.map(this.isSet, this); 1.197 + 1.198 + return (this.has(prefName) && this._prefSvc.prefHasUserValue(prefName)); 1.199 + }, 1.200 + 1.201 + /** 1.202 + * Whether or not the given pref has a user-set value. Use isSet instead, 1.203 + * which is equivalent. 1.204 + * @deprecated 1.205 + */ 1.206 + modified: function(prefName) { return this.isSet(prefName) }, 1.207 + 1.208 + reset: function(prefName) { 1.209 + if (Array.isArray(prefName)) { 1.210 + prefName.map(function(v) this.reset(v), this); 1.211 + return; 1.212 + } 1.213 + 1.214 + this._prefSvc.clearUserPref(prefName); 1.215 + }, 1.216 + 1.217 + /** 1.218 + * Lock a pref so it can't be changed. 1.219 + * 1.220 + * @param prefName {String|Array} 1.221 + * the pref to lock, or an array of prefs to lock 1.222 + */ 1.223 + lock: function(prefName) { 1.224 + if (Array.isArray(prefName)) 1.225 + prefName.map(this.lock, this); 1.226 + 1.227 + this._prefSvc.lockPref(prefName); 1.228 + }, 1.229 + 1.230 + /** 1.231 + * Unlock a pref so it can be changed. 1.232 + * 1.233 + * @param prefName {String|Array} 1.234 + * the pref to lock, or an array of prefs to lock 1.235 + */ 1.236 + unlock: function(prefName) { 1.237 + if (Array.isArray(prefName)) 1.238 + prefName.map(this.unlock, this); 1.239 + 1.240 + this._prefSvc.unlockPref(prefName); 1.241 + }, 1.242 + 1.243 + /** 1.244 + * Whether or not the given pref is locked against changes. 1.245 + * 1.246 + * @param prefName {String|Array} 1.247 + * the pref to check, or an array of prefs to check 1.248 + * 1.249 + * @returns {Boolean|Array} 1.250 + * whether or not the pref has a user-set value; or, if the caller 1.251 + * provided an array of pref names, an array of booleans indicating 1.252 + * whether or not the prefs have user-set values 1.253 + */ 1.254 + locked: function(prefName) { 1.255 + if (Array.isArray(prefName)) 1.256 + return prefName.map(this.locked, this); 1.257 + 1.258 + return this._prefSvc.prefIsLocked(prefName); 1.259 + }, 1.260 + 1.261 + /** 1.262 + * Start observing a pref. 1.263 + * 1.264 + * The callback can be a function or any object that implements nsIObserver. 1.265 + * When the callback is a function and thisObject is provided, it gets called 1.266 + * as a method of thisObject. 1.267 + * 1.268 + * @param prefName {String} 1.269 + * the name of the pref to observe 1.270 + * 1.271 + * @param callback {Function|Object} 1.272 + * the code to notify when the pref changes; 1.273 + * 1.274 + * @param thisObject {Object} [optional] 1.275 + * the object to use as |this| when calling a Function callback; 1.276 + * 1.277 + * @returns the wrapped observer 1.278 + */ 1.279 + observe: function(prefName, callback, thisObject) { 1.280 + let fullPrefName = this._prefBranch + (prefName || ""); 1.281 + 1.282 + let observer = new PrefObserver(fullPrefName, callback, thisObject); 1.283 + Preferences._prefSvc.addObserver(fullPrefName, observer, true); 1.284 + observers.push(observer); 1.285 + 1.286 + return observer; 1.287 + }, 1.288 + 1.289 + /** 1.290 + * Stop observing a pref. 1.291 + * 1.292 + * You must call this method with the same prefName, callback, and thisObject 1.293 + * with which you originally registered the observer. However, you don't have 1.294 + * to call this method on the same exact instance of Preferences; you can call 1.295 + * it on any instance. For example, the following code first starts and then 1.296 + * stops observing the "foo.bar.baz" preference: 1.297 + * 1.298 + * let observer = function() {...}; 1.299 + * Preferences.observe("foo.bar.baz", observer); 1.300 + * new Preferences("foo.bar.").ignore("baz", observer); 1.301 + * 1.302 + * @param prefName {String} 1.303 + * the name of the pref being observed 1.304 + * 1.305 + * @param callback {Function|Object} 1.306 + * the code being notified when the pref changes 1.307 + * 1.308 + * @param thisObject {Object} [optional] 1.309 + * the object being used as |this| when calling a Function callback 1.310 + */ 1.311 + ignore: function(prefName, callback, thisObject) { 1.312 + let fullPrefName = this._prefBranch + (prefName || ""); 1.313 + 1.314 + // This seems fairly inefficient, but I'm not sure how much better we can 1.315 + // make it. We could index by fullBranch, but we can't index by callback 1.316 + // or thisObject, as far as I know, since the keys to JavaScript hashes 1.317 + // (a.k.a. objects) can apparently only be primitive values. 1.318 + let [observer] = observers.filter(function(v) v.prefName == fullPrefName && 1.319 + v.callback == callback && 1.320 + v.thisObject == thisObject); 1.321 + 1.322 + if (observer) { 1.323 + Preferences._prefSvc.removeObserver(fullPrefName, observer); 1.324 + observers.splice(observers.indexOf(observer), 1); 1.325 + } 1.326 + }, 1.327 + 1.328 + resetBranch: function(prefBranch = "") { 1.329 + try { 1.330 + this._prefSvc.resetBranch(prefBranch); 1.331 + } 1.332 + catch(ex) { 1.333 + // The current implementation of nsIPrefBranch in Mozilla 1.334 + // doesn't implement resetBranch, so we do it ourselves. 1.335 + if (ex.result == Cr.NS_ERROR_NOT_IMPLEMENTED) 1.336 + this.reset(this._prefSvc.getChildList(prefBranch, [])); 1.337 + else 1.338 + throw ex; 1.339 + } 1.340 + }, 1.341 + 1.342 + /** 1.343 + * The branch of the preferences tree to which this instance provides access. 1.344 + * @private 1.345 + */ 1.346 + _prefBranch: "", 1.347 + 1.348 + /** 1.349 + * Preferences Service 1.350 + * @private 1.351 + */ 1.352 + get _prefSvc() { 1.353 + let prefSvc = Cc["@mozilla.org/preferences-service;1"] 1.354 + .getService(Ci.nsIPrefService); 1.355 + if (this._defaultBranch) { 1.356 + prefSvc = prefSvc.getDefaultBranch(this._prefBranch); 1.357 + } else { 1.358 + prefSvc = prefSvc.getBranch(this._prefBranch); 1.359 + } 1.360 + 1.361 + this.__defineGetter__("_prefSvc", function() prefSvc); 1.362 + return this._prefSvc; 1.363 + }, 1.364 + 1.365 + /** 1.366 + * IO Service 1.367 + * @private 1.368 + */ 1.369 + get _ioSvc() { 1.370 + let ioSvc = Cc["@mozilla.org/network/io-service;1"]. 1.371 + getService(Ci.nsIIOService); 1.372 + this.__defineGetter__("_ioSvc", function() ioSvc); 1.373 + return this._ioSvc; 1.374 + } 1.375 + 1.376 +}; 1.377 + 1.378 +// Give the constructor the same prototype as its instances, so users can access 1.379 +// preferences directly via the constructor without having to create an instance 1.380 +// first. 1.381 +Preferences.__proto__ = Preferences.prototype; 1.382 + 1.383 +/** 1.384 + * A cache of pref observers. 1.385 + * 1.386 + * We use this to remove observers when a caller calls Preferences::ignore. 1.387 + * 1.388 + * All Preferences instances share this object, because we want callers to be 1.389 + * able to remove an observer using a different Preferences object than the one 1.390 + * with which they added it. That means we have to identify the observers 1.391 + * in this object by their complete pref name, not just their name relative to 1.392 + * the root branch of the Preferences object with which they were created. 1.393 + */ 1.394 +let observers = []; 1.395 + 1.396 +function PrefObserver(prefName, callback, thisObject) { 1.397 + this.prefName = prefName; 1.398 + this.callback = callback; 1.399 + this.thisObject = thisObject; 1.400 +} 1.401 + 1.402 +PrefObserver.prototype = { 1.403 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 1.404 + 1.405 + observe: function(subject, topic, data) { 1.406 + // The pref service only observes whole branches, but we only observe 1.407 + // individual preferences, so we check here that the pref that changed 1.408 + // is the exact one we're observing (and not some sub-pref on the branch). 1.409 + if (data.indexOf(this.prefName) != 0) 1.410 + return; 1.411 + 1.412 + if (typeof this.callback == "function") { 1.413 + let prefValue = Preferences.get(data); 1.414 + 1.415 + if (this.thisObject) 1.416 + this.callback.call(this.thisObject, prefValue); 1.417 + else 1.418 + this.callback(prefValue); 1.419 + } 1.420 + else // typeof this.callback == "object" (nsIObserver) 1.421 + this.callback.observe(subject, topic, data); 1.422 + } 1.423 +}; 1.424 + 1.425 +function isObject(val) { 1.426 + // We can't check for |val.constructor == Object| here, since the value 1.427 + // might be from a different context whose Object constructor is not the same 1.428 + // as ours, so instead we match based on the name of the constructor. 1.429 + return (typeof val != "undefined" && val != null && typeof val == "object" && 1.430 + val.constructor.name == "Object"); 1.431 +}