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