|
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 |
|
6 // Class for manipulating preferences. Aside from wrapping the pref |
|
7 // service, useful functionality includes: |
|
8 // |
|
9 // - abstracting prefobserving so that you can observe preferences |
|
10 // without implementing nsIObserver |
|
11 // |
|
12 // - getters that return a default value when the pref doesn't exist |
|
13 // (instead of throwing) |
|
14 // |
|
15 // - get-and-set getters |
|
16 // |
|
17 // Example: |
|
18 // |
|
19 // var p = new PROT_Preferences(); |
|
20 // dump(p.getPref("some-true-pref")); // shows true |
|
21 // dump(p.getPref("no-such-pref", true)); // shows true |
|
22 // dump(p.getPref("no-such-pref", null)); // shows null |
|
23 // |
|
24 // function observe(prefThatChanged) { |
|
25 // dump("Pref changed: " + prefThatChanged); |
|
26 // }; |
|
27 // |
|
28 // p.addObserver("somepref", observe); |
|
29 // p.setPref("somepref", true); // dumps |
|
30 // p.removeObserver("somepref", observe); |
|
31 // |
|
32 // TODO: should probably have the prefobserver pass in the new and old |
|
33 // values |
|
34 |
|
35 // TODO(tc): Maybe remove this class and just call natively since we're no |
|
36 // longer an extension. |
|
37 |
|
38 /** |
|
39 * A class that wraps the preferences service. |
|
40 * |
|
41 * @param opt_startPoint A starting point on the prefs tree to resolve |
|
42 * names passed to setPref and getPref. |
|
43 * |
|
44 * @param opt_useDefaultPranch Set to true to work against the default |
|
45 * preferences tree instead of the profile one. |
|
46 * |
|
47 * @constructor |
|
48 */ |
|
49 function G_Preferences(opt_startPoint, opt_getDefaultBranch) { |
|
50 this.debugZone = "prefs"; |
|
51 this.observers_ = {}; |
|
52 this.getDefaultBranch_ = !!opt_getDefaultBranch; |
|
53 |
|
54 this.startPoint_ = opt_startPoint || null; |
|
55 } |
|
56 |
|
57 G_Preferences.setterMap_ = { "string": "setCharPref", |
|
58 "boolean": "setBoolPref", |
|
59 "number": "setIntPref" }; |
|
60 |
|
61 G_Preferences.getterMap_ = {}; |
|
62 G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_STRING] = "getCharPref"; |
|
63 G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_BOOL] = "getBoolPref"; |
|
64 G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_INT] = "getIntPref"; |
|
65 |
|
66 G_Preferences.prototype.__defineGetter__('prefs_', function() { |
|
67 var prefs; |
|
68 var prefSvc = Cc["@mozilla.org/preferences-service;1"] |
|
69 .getService(Ci.nsIPrefService); |
|
70 |
|
71 if (this.getDefaultBranch_) { |
|
72 prefs = prefSvc.getDefaultBranch(this.startPoint_); |
|
73 } else { |
|
74 prefs = prefSvc.getBranch(this.startPoint_); |
|
75 } |
|
76 |
|
77 // QI to prefs in case we want to add observers |
|
78 prefs.QueryInterface(Ci.nsIPrefBranchInternal); |
|
79 return prefs; |
|
80 }); |
|
81 |
|
82 /** |
|
83 * Stores a key/value in a user preference. Valid types for val are string, |
|
84 * boolean, and number. Complex values are not yet supported (but feel free to |
|
85 * add them!). |
|
86 */ |
|
87 G_Preferences.prototype.setPref = function(key, val) { |
|
88 var datatype = typeof(val); |
|
89 |
|
90 if (datatype == "number" && (val % 1 != 0)) { |
|
91 throw new Error("Cannot store non-integer numbers in preferences."); |
|
92 } |
|
93 |
|
94 var meth = G_Preferences.setterMap_[datatype]; |
|
95 |
|
96 if (!meth) { |
|
97 throw new Error("Pref datatype {" + datatype + "} not supported."); |
|
98 } |
|
99 |
|
100 return this.prefs_[meth](key, val); |
|
101 } |
|
102 |
|
103 /** |
|
104 * Retrieves a user preference. Valid types for the value are the same as for |
|
105 * setPref. If the preference is not found, opt_default will be returned |
|
106 * instead. |
|
107 */ |
|
108 G_Preferences.prototype.getPref = function(key, opt_default) { |
|
109 var type = this.prefs_.getPrefType(key); |
|
110 |
|
111 // zero means that the specified pref didn't exist |
|
112 if (type == Ci.nsIPrefBranch.PREF_INVALID) { |
|
113 return opt_default; |
|
114 } |
|
115 |
|
116 var meth = G_Preferences.getterMap_[type]; |
|
117 |
|
118 if (!meth) { |
|
119 throw new Error("Pref datatype {" + type + "} not supported."); |
|
120 } |
|
121 |
|
122 // If a pref has been cleared, it will have a valid type but won't |
|
123 // be gettable, so this will throw. |
|
124 try { |
|
125 return this.prefs_[meth](key); |
|
126 } catch(e) { |
|
127 return opt_default; |
|
128 } |
|
129 } |
|
130 |
|
131 /** |
|
132 * Delete a preference. |
|
133 * |
|
134 * @param which Name of preference to obliterate |
|
135 */ |
|
136 G_Preferences.prototype.clearPref = function(which) { |
|
137 try { |
|
138 // This throws if the pref doesn't exist, which is fine because a |
|
139 // nonexistent pref is cleared |
|
140 this.prefs_.clearUserPref(which); |
|
141 } catch(e) {} |
|
142 } |
|
143 |
|
144 /** |
|
145 * Add an observer for a given pref. |
|
146 * |
|
147 * @param which String containing the pref to listen to |
|
148 * @param callback Function to be called when the pref changes. This |
|
149 * function will receive a single argument, a string |
|
150 * holding the preference name that changed |
|
151 */ |
|
152 G_Preferences.prototype.addObserver = function(which, callback) { |
|
153 // Need to store the observer we create so we can eventually unregister it |
|
154 if (!this.observers_[which]) |
|
155 this.observers_[which] = { callbacks: [], observers: [] }; |
|
156 |
|
157 /* only add an observer if the callback hasn't been registered yet */ |
|
158 if (this.observers_[which].callbacks.indexOf(callback) == -1) { |
|
159 var observer = new G_PreferenceObserver(callback); |
|
160 this.observers_[which].callbacks.push(callback); |
|
161 this.observers_[which].observers.push(observer); |
|
162 this.prefs_.addObserver(which, observer, false /* strong reference */); |
|
163 } |
|
164 } |
|
165 |
|
166 /** |
|
167 * Remove an observer for a given pref. |
|
168 * |
|
169 * @param which String containing the pref to stop listening to |
|
170 * @param callback Function to remove as an observer |
|
171 */ |
|
172 G_Preferences.prototype.removeObserver = function(which, callback) { |
|
173 var ix = this.observers_[which].callbacks.indexOf(callback); |
|
174 G_Assert(this, ix != -1, "Tried to unregister a nonexistent observer"); |
|
175 this.observers_[which].callbacks.splice(ix, 1); |
|
176 var observer = this.observers_[which].observers.splice(ix, 1)[0]; |
|
177 this.prefs_.removeObserver(which, observer); |
|
178 } |
|
179 |
|
180 /** |
|
181 * Remove all preference observers registered through this object. |
|
182 */ |
|
183 G_Preferences.prototype.removeAllObservers = function() { |
|
184 for (var which in this.observers_) { |
|
185 for each (var observer in this.observers_[which].observers) { |
|
186 this.prefs_.removeObserver(which, observer); |
|
187 } |
|
188 } |
|
189 this.observers_ = {}; |
|
190 } |
|
191 |
|
192 /** |
|
193 * Helper class that knows how to observe preference changes and |
|
194 * invoke a callback when they do |
|
195 * |
|
196 * @constructor |
|
197 * @param callback Function to call when the preference changes |
|
198 */ |
|
199 function G_PreferenceObserver(callback) { |
|
200 this.debugZone = "prefobserver"; |
|
201 this.callback_ = callback; |
|
202 } |
|
203 |
|
204 /** |
|
205 * Invoked by the pref system when a preference changes. Passes the |
|
206 * message along to the callback. |
|
207 * |
|
208 * @param subject The nsIPrefBranch that changed |
|
209 * @param topic String "nsPref:changed" (aka |
|
210 * NS_PREFBRANCH_PREFCHANGE_OBSERVER_ID -- but where does it |
|
211 * live???) |
|
212 * @param data Name of the pref that changed |
|
213 */ |
|
214 G_PreferenceObserver.prototype.observe = function(subject, topic, data) { |
|
215 G_Debug(this, "Observed pref change: " + data); |
|
216 this.callback_(data); |
|
217 } |
|
218 |
|
219 /** |
|
220 * XPCOM cruft |
|
221 * |
|
222 * @param iid Interface id of the interface the caller wants |
|
223 */ |
|
224 G_PreferenceObserver.prototype.QueryInterface = function(iid) { |
|
225 if (iid.equals(Ci.nsISupports) || |
|
226 iid.equals(Ci.nsIObserver) || |
|
227 iid.equals(Ci.nsISupportsWeakReference)) |
|
228 return this; |
|
229 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
230 } |
|
231 |
|
232 #ifdef DEBUG |
|
233 // UNITTESTS |
|
234 function TEST_G_Preferences() { |
|
235 if (G_GDEBUG) { |
|
236 var z = "preferences UNITTEST"; |
|
237 G_debugService.enableZone(z); |
|
238 G_Debug(z, "Starting"); |
|
239 |
|
240 var p = new G_Preferences(); |
|
241 |
|
242 var testPref = "test-preferences-unittest"; |
|
243 var noSuchPref = "test-preferences-unittest-aypabtu"; |
|
244 |
|
245 // Used to test observing |
|
246 var observeCount = 0; |
|
247 function observe(prefChanged) { |
|
248 G_Assert(z, prefChanged == testPref, "observer broken"); |
|
249 observeCount++; |
|
250 }; |
|
251 |
|
252 // Test setting, getting, and observing |
|
253 p.addObserver(testPref, observe); |
|
254 p.setPref(testPref, true); |
|
255 G_Assert(z, p.getPref(testPref), "get or set broken"); |
|
256 G_Assert(z, observeCount == 1, "observer adding not working"); |
|
257 |
|
258 p.removeObserver(testPref, observe); |
|
259 |
|
260 p.setPref(testPref, false); |
|
261 G_Assert(z, observeCount == 1, "observer removal not working"); |
|
262 G_Assert(z, !p.getPref(testPref), "get broken"); |
|
263 |
|
264 // Remember to clean up the prefs we've set, and test removing prefs |
|
265 // while we're at it |
|
266 p.clearPref(noSuchPref); |
|
267 G_Assert(z, !p.getPref(noSuchPref, false), "clear broken"); |
|
268 |
|
269 p.clearPref(testPref); |
|
270 |
|
271 G_Debug(z, "PASSED"); |
|
272 } |
|
273 } |
|
274 #endif |