Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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/.
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
35 // TODO(tc): Maybe remove this class and just call natively since we're no
36 // longer an extension.
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;
54 this.startPoint_ = opt_startPoint || null;
55 }
57 G_Preferences.setterMap_ = { "string": "setCharPref",
58 "boolean": "setBoolPref",
59 "number": "setIntPref" };
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";
66 G_Preferences.prototype.__defineGetter__('prefs_', function() {
67 var prefs;
68 var prefSvc = Cc["@mozilla.org/preferences-service;1"]
69 .getService(Ci.nsIPrefService);
71 if (this.getDefaultBranch_) {
72 prefs = prefSvc.getDefaultBranch(this.startPoint_);
73 } else {
74 prefs = prefSvc.getBranch(this.startPoint_);
75 }
77 // QI to prefs in case we want to add observers
78 prefs.QueryInterface(Ci.nsIPrefBranchInternal);
79 return prefs;
80 });
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);
90 if (datatype == "number" && (val % 1 != 0)) {
91 throw new Error("Cannot store non-integer numbers in preferences.");
92 }
94 var meth = G_Preferences.setterMap_[datatype];
96 if (!meth) {
97 throw new Error("Pref datatype {" + datatype + "} not supported.");
98 }
100 return this.prefs_[meth](key, val);
101 }
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);
111 // zero means that the specified pref didn't exist
112 if (type == Ci.nsIPrefBranch.PREF_INVALID) {
113 return opt_default;
114 }
116 var meth = G_Preferences.getterMap_[type];
118 if (!meth) {
119 throw new Error("Pref datatype {" + type + "} not supported.");
120 }
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 }
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 }
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: [] };
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 }
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 }
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 }
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 }
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 }
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 }
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");
240 var p = new G_Preferences();
242 var testPref = "test-preferences-unittest";
243 var noSuchPref = "test-preferences-unittest-aypabtu";
245 // Used to test observing
246 var observeCount = 0;
247 function observe(prefChanged) {
248 G_Assert(z, prefChanged == testPref, "observer broken");
249 observeCount++;
250 };
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");
258 p.removeObserver(testPref, observe);
260 p.setPref(testPref, false);
261 G_Assert(z, observeCount == 1, "observer removal not working");
262 G_Assert(z, !p.getPref(testPref), "get broken");
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");
269 p.clearPref(testPref);
271 G_Debug(z, "PASSED");
272 }
273 }
274 #endif