|
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/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = ["Preferences"]; |
|
6 |
|
7 const Cc = Components.classes; |
|
8 const Ci = Components.interfaces; |
|
9 const Cr = Components.results; |
|
10 const Cu = Components.utils; |
|
11 |
|
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
13 |
|
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; |
|
20 |
|
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 } |
|
34 |
|
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); |
|
50 |
|
51 return this._get(prefName, defaultValue); |
|
52 }, |
|
53 |
|
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; |
|
58 |
|
59 case Ci.nsIPrefBranch.PREF_INT: |
|
60 return this._prefSvc.getIntPref(prefName); |
|
61 |
|
62 case Ci.nsIPrefBranch.PREF_BOOL: |
|
63 return this._prefSvc.getBoolPref(prefName); |
|
64 |
|
65 case Ci.nsIPrefBranch.PREF_INVALID: |
|
66 return defaultValue; |
|
67 |
|
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 }, |
|
75 |
|
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 } |
|
104 |
|
105 this._set(prefName, prefValue); |
|
106 }, |
|
107 |
|
108 _set: function(prefName, prefValue) { |
|
109 let prefType; |
|
110 if (typeof prefValue != "undefined" && prefValue != null) |
|
111 prefType = prefValue.constructor.name; |
|
112 |
|
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; |
|
122 |
|
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; |
|
141 |
|
142 case "Boolean": |
|
143 this._prefSvc.setBoolPref(prefName, prefValue); |
|
144 break; |
|
145 |
|
146 default: |
|
147 throw "can't set pref " + prefName + " to value '" + prefValue + |
|
148 "'; it isn't a String, Number, or Boolean"; |
|
149 } |
|
150 }, |
|
151 |
|
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); |
|
169 |
|
170 return this._has(prefName); |
|
171 }, |
|
172 |
|
173 _has: function(prefName) { |
|
174 return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID); |
|
175 }, |
|
176 |
|
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); |
|
194 |
|
195 return (this.has(prefName) && this._prefSvc.prefHasUserValue(prefName)); |
|
196 }, |
|
197 |
|
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) }, |
|
204 |
|
205 reset: function(prefName) { |
|
206 if (Array.isArray(prefName)) { |
|
207 prefName.map(function(v) this.reset(v), this); |
|
208 return; |
|
209 } |
|
210 |
|
211 this._prefSvc.clearUserPref(prefName); |
|
212 }, |
|
213 |
|
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); |
|
223 |
|
224 this._prefSvc.lockPref(prefName); |
|
225 }, |
|
226 |
|
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); |
|
236 |
|
237 this._prefSvc.unlockPref(prefName); |
|
238 }, |
|
239 |
|
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); |
|
254 |
|
255 return this._prefSvc.prefIsLocked(prefName); |
|
256 }, |
|
257 |
|
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 || ""); |
|
278 |
|
279 let observer = new PrefObserver(fullPrefName, callback, thisObject); |
|
280 Preferences._prefSvc.addObserver(fullPrefName, observer, true); |
|
281 observers.push(observer); |
|
282 |
|
283 return observer; |
|
284 }, |
|
285 |
|
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 || ""); |
|
310 |
|
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); |
|
318 |
|
319 if (observer) { |
|
320 Preferences._prefSvc.removeObserver(fullPrefName, observer); |
|
321 observers.splice(observers.indexOf(observer), 1); |
|
322 } |
|
323 }, |
|
324 |
|
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 }, |
|
338 |
|
339 /** |
|
340 * The branch of the preferences tree to which this instance provides access. |
|
341 * @private |
|
342 */ |
|
343 _prefBranch: "", |
|
344 |
|
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 } |
|
357 |
|
358 this.__defineGetter__("_prefSvc", function() prefSvc); |
|
359 return this._prefSvc; |
|
360 }, |
|
361 |
|
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 } |
|
372 |
|
373 }; |
|
374 |
|
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; |
|
379 |
|
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 = []; |
|
392 |
|
393 function PrefObserver(prefName, callback, thisObject) { |
|
394 this.prefName = prefName; |
|
395 this.callback = callback; |
|
396 this.thisObject = thisObject; |
|
397 } |
|
398 |
|
399 PrefObserver.prototype = { |
|
400 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), |
|
401 |
|
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; |
|
408 |
|
409 if (typeof this.callback == "function") { |
|
410 let prefValue = Preferences.get(data); |
|
411 |
|
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 }; |
|
421 |
|
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 } |