michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: michael@0: // Class for manipulating preferences. Aside from wrapping the pref michael@0: // service, useful functionality includes: michael@0: // michael@0: // - abstracting prefobserving so that you can observe preferences michael@0: // without implementing nsIObserver michael@0: // michael@0: // - getters that return a default value when the pref doesn't exist michael@0: // (instead of throwing) michael@0: // michael@0: // - get-and-set getters michael@0: // michael@0: // Example: michael@0: // michael@0: // var p = new PROT_Preferences(); michael@0: // dump(p.getPref("some-true-pref")); // shows true michael@0: // dump(p.getPref("no-such-pref", true)); // shows true michael@0: // dump(p.getPref("no-such-pref", null)); // shows null michael@0: // michael@0: // function observe(prefThatChanged) { michael@0: // dump("Pref changed: " + prefThatChanged); michael@0: // }; michael@0: // michael@0: // p.addObserver("somepref", observe); michael@0: // p.setPref("somepref", true); // dumps michael@0: // p.removeObserver("somepref", observe); michael@0: // michael@0: // TODO: should probably have the prefobserver pass in the new and old michael@0: // values michael@0: michael@0: // TODO(tc): Maybe remove this class and just call natively since we're no michael@0: // longer an extension. michael@0: michael@0: /** michael@0: * A class that wraps the preferences service. michael@0: * michael@0: * @param opt_startPoint A starting point on the prefs tree to resolve michael@0: * names passed to setPref and getPref. michael@0: * michael@0: * @param opt_useDefaultPranch Set to true to work against the default michael@0: * preferences tree instead of the profile one. michael@0: * michael@0: * @constructor michael@0: */ michael@0: function G_Preferences(opt_startPoint, opt_getDefaultBranch) { michael@0: this.debugZone = "prefs"; michael@0: this.observers_ = {}; michael@0: this.getDefaultBranch_ = !!opt_getDefaultBranch; michael@0: michael@0: this.startPoint_ = opt_startPoint || null; michael@0: } michael@0: michael@0: G_Preferences.setterMap_ = { "string": "setCharPref", michael@0: "boolean": "setBoolPref", michael@0: "number": "setIntPref" }; michael@0: michael@0: G_Preferences.getterMap_ = {}; michael@0: G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_STRING] = "getCharPref"; michael@0: G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_BOOL] = "getBoolPref"; michael@0: G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_INT] = "getIntPref"; michael@0: michael@0: G_Preferences.prototype.__defineGetter__('prefs_', function() { michael@0: var prefs; michael@0: var prefSvc = Cc["@mozilla.org/preferences-service;1"] michael@0: .getService(Ci.nsIPrefService); michael@0: michael@0: if (this.getDefaultBranch_) { michael@0: prefs = prefSvc.getDefaultBranch(this.startPoint_); michael@0: } else { michael@0: prefs = prefSvc.getBranch(this.startPoint_); michael@0: } michael@0: michael@0: // QI to prefs in case we want to add observers michael@0: prefs.QueryInterface(Ci.nsIPrefBranchInternal); michael@0: return prefs; michael@0: }); michael@0: michael@0: /** michael@0: * Stores a key/value in a user preference. Valid types for val are string, michael@0: * boolean, and number. Complex values are not yet supported (but feel free to michael@0: * add them!). michael@0: */ michael@0: G_Preferences.prototype.setPref = function(key, val) { michael@0: var datatype = typeof(val); michael@0: michael@0: if (datatype == "number" && (val % 1 != 0)) { michael@0: throw new Error("Cannot store non-integer numbers in preferences."); michael@0: } michael@0: michael@0: var meth = G_Preferences.setterMap_[datatype]; michael@0: michael@0: if (!meth) { michael@0: throw new Error("Pref datatype {" + datatype + "} not supported."); michael@0: } michael@0: michael@0: return this.prefs_[meth](key, val); michael@0: } michael@0: michael@0: /** michael@0: * Retrieves a user preference. Valid types for the value are the same as for michael@0: * setPref. If the preference is not found, opt_default will be returned michael@0: * instead. michael@0: */ michael@0: G_Preferences.prototype.getPref = function(key, opt_default) { michael@0: var type = this.prefs_.getPrefType(key); michael@0: michael@0: // zero means that the specified pref didn't exist michael@0: if (type == Ci.nsIPrefBranch.PREF_INVALID) { michael@0: return opt_default; michael@0: } michael@0: michael@0: var meth = G_Preferences.getterMap_[type]; michael@0: michael@0: if (!meth) { michael@0: throw new Error("Pref datatype {" + type + "} not supported."); michael@0: } michael@0: michael@0: // If a pref has been cleared, it will have a valid type but won't michael@0: // be gettable, so this will throw. michael@0: try { michael@0: return this.prefs_[meth](key); michael@0: } catch(e) { michael@0: return opt_default; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Delete a preference. michael@0: * michael@0: * @param which Name of preference to obliterate michael@0: */ michael@0: G_Preferences.prototype.clearPref = function(which) { michael@0: try { michael@0: // This throws if the pref doesn't exist, which is fine because a michael@0: // nonexistent pref is cleared michael@0: this.prefs_.clearUserPref(which); michael@0: } catch(e) {} michael@0: } michael@0: michael@0: /** michael@0: * Add an observer for a given pref. michael@0: * michael@0: * @param which String containing the pref to listen to michael@0: * @param callback Function to be called when the pref changes. This michael@0: * function will receive a single argument, a string michael@0: * holding the preference name that changed michael@0: */ michael@0: G_Preferences.prototype.addObserver = function(which, callback) { michael@0: // Need to store the observer we create so we can eventually unregister it michael@0: if (!this.observers_[which]) michael@0: this.observers_[which] = { callbacks: [], observers: [] }; michael@0: michael@0: /* only add an observer if the callback hasn't been registered yet */ michael@0: if (this.observers_[which].callbacks.indexOf(callback) == -1) { michael@0: var observer = new G_PreferenceObserver(callback); michael@0: this.observers_[which].callbacks.push(callback); michael@0: this.observers_[which].observers.push(observer); michael@0: this.prefs_.addObserver(which, observer, false /* strong reference */); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Remove an observer for a given pref. michael@0: * michael@0: * @param which String containing the pref to stop listening to michael@0: * @param callback Function to remove as an observer michael@0: */ michael@0: G_Preferences.prototype.removeObserver = function(which, callback) { michael@0: var ix = this.observers_[which].callbacks.indexOf(callback); michael@0: G_Assert(this, ix != -1, "Tried to unregister a nonexistent observer"); michael@0: this.observers_[which].callbacks.splice(ix, 1); michael@0: var observer = this.observers_[which].observers.splice(ix, 1)[0]; michael@0: this.prefs_.removeObserver(which, observer); michael@0: } michael@0: michael@0: /** michael@0: * Remove all preference observers registered through this object. michael@0: */ michael@0: G_Preferences.prototype.removeAllObservers = function() { michael@0: for (var which in this.observers_) { michael@0: for each (var observer in this.observers_[which].observers) { michael@0: this.prefs_.removeObserver(which, observer); michael@0: } michael@0: } michael@0: this.observers_ = {}; michael@0: } michael@0: michael@0: /** michael@0: * Helper class that knows how to observe preference changes and michael@0: * invoke a callback when they do michael@0: * michael@0: * @constructor michael@0: * @param callback Function to call when the preference changes michael@0: */ michael@0: function G_PreferenceObserver(callback) { michael@0: this.debugZone = "prefobserver"; michael@0: this.callback_ = callback; michael@0: } michael@0: michael@0: /** michael@0: * Invoked by the pref system when a preference changes. Passes the michael@0: * message along to the callback. michael@0: * michael@0: * @param subject The nsIPrefBranch that changed michael@0: * @param topic String "nsPref:changed" (aka michael@0: * NS_PREFBRANCH_PREFCHANGE_OBSERVER_ID -- but where does it michael@0: * live???) michael@0: * @param data Name of the pref that changed michael@0: */ michael@0: G_PreferenceObserver.prototype.observe = function(subject, topic, data) { michael@0: G_Debug(this, "Observed pref change: " + data); michael@0: this.callback_(data); michael@0: } michael@0: michael@0: /** michael@0: * XPCOM cruft michael@0: * michael@0: * @param iid Interface id of the interface the caller wants michael@0: */ michael@0: G_PreferenceObserver.prototype.QueryInterface = function(iid) { michael@0: if (iid.equals(Ci.nsISupports) || michael@0: iid.equals(Ci.nsIObserver) || michael@0: iid.equals(Ci.nsISupportsWeakReference)) michael@0: return this; michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // UNITTESTS michael@0: function TEST_G_Preferences() { michael@0: if (G_GDEBUG) { michael@0: var z = "preferences UNITTEST"; michael@0: G_debugService.enableZone(z); michael@0: G_Debug(z, "Starting"); michael@0: michael@0: var p = new G_Preferences(); michael@0: michael@0: var testPref = "test-preferences-unittest"; michael@0: var noSuchPref = "test-preferences-unittest-aypabtu"; michael@0: michael@0: // Used to test observing michael@0: var observeCount = 0; michael@0: function observe(prefChanged) { michael@0: G_Assert(z, prefChanged == testPref, "observer broken"); michael@0: observeCount++; michael@0: }; michael@0: michael@0: // Test setting, getting, and observing michael@0: p.addObserver(testPref, observe); michael@0: p.setPref(testPref, true); michael@0: G_Assert(z, p.getPref(testPref), "get or set broken"); michael@0: G_Assert(z, observeCount == 1, "observer adding not working"); michael@0: michael@0: p.removeObserver(testPref, observe); michael@0: michael@0: p.setPref(testPref, false); michael@0: G_Assert(z, observeCount == 1, "observer removal not working"); michael@0: G_Assert(z, !p.getPref(testPref), "get broken"); michael@0: michael@0: // Remember to clean up the prefs we've set, and test removing prefs michael@0: // while we're at it michael@0: p.clearPref(noSuchPref); michael@0: G_Assert(z, !p.getPref(noSuchPref, false), "clear broken"); michael@0: michael@0: p.clearPref(testPref); michael@0: michael@0: G_Debug(z, "PASSED"); michael@0: } michael@0: } michael@0: #endif