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.
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 }