/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const Services = require("Services"); const EventEmitter = require("devtools/shared/event-emitter"); /** * Shortcuts for lazily accessing and setting various preferences. * Usage: * let prefs = new Prefs("root.path.to.branch", { * myIntPref: ["Int", "leaf.path.to.my-int-pref"], * myCharPref: ["Char", "leaf.path.to.my-char-pref"], * myJsonPref: ["Json", "leaf.path.to.my-json-pref"], * myFloatPref: ["Float", "leaf.path.to.my-float-pref"] * ... * }); * * Get/set: * prefs.myCharPref = "foo"; * let aux = prefs.myCharPref; * * Observe: * prefs.registerObserver(); * prefs.on("pref-changed", (prefValue) => { * ... * }); * * @param string prefsRoot * The root path to the required preferences branch. * @param object prefsBlueprint * An object containing { accessorName: [prefType, prefName] } keys. */ function PrefsHelper(prefsRoot = "", prefsBlueprint = {}) { EventEmitter.decorate(this); const cache = new Map(); for (const accessorName in prefsBlueprint) { const [prefType, prefName, fallbackValue] = prefsBlueprint[accessorName]; map( this, cache, accessorName, prefType, prefsRoot, prefName, fallbackValue ); } const observer = makeObserver(this, cache, prefsRoot, prefsBlueprint); this.registerObserver = () => observer.register(); this.unregisterObserver = () => observer.unregister(); } /** * Helper method for getting a pref value. * * @param Map cache * @param string prefType * @param string prefsRoot * @param string prefName * @param string|int|boolean fallbackValue * @return any */ function get(cache, prefType, prefsRoot, prefName, fallbackValue) { const cachedPref = cache.get(prefName); if (cachedPref !== undefined) { return cachedPref; } const value = Services.prefs["get" + prefType + "Pref"]( [prefsRoot, prefName].join("."), fallbackValue ); cache.set(prefName, value); return value; } /** * Helper method for setting a pref value. * * @param Map cache * @param string prefType * @param string prefsRoot * @param string prefName * @param any value */ function set(cache, prefType, prefsRoot, prefName, value) { Services.prefs["set" + prefType + "Pref"]( [prefsRoot, prefName].join("."), value ); cache.set(prefName, value); } /** * Maps a property name to a pref, defining lazy getters and setters. * Supported types are "Bool", "Char", "Int", "Float" (sugar around "Char" * type and casting), and "Json" (which is basically just sugar for "Char" * using the standard JSON serializer). * * @param PrefsHelper self * @param Map cache * @param string accessorName * @param string prefType * @param string prefsRoot * @param string prefName * @param string|int|boolean fallbackValue * @param array serializer [optional] */ function map( self, cache, accessorName, prefType, prefsRoot, prefName, fallbackValue, serializer = { in: e => e, out: e => e } ) { if (prefName in self) { throw new Error( `Can't use ${prefName} because it overrides a property` + "on the instance." ); } if (prefType == "Json") { map(self, cache, accessorName, "Char", prefsRoot, prefName, fallbackValue, { in: JSON.parse, out: JSON.stringify, }); return; } if (prefType == "Float") { map(self, cache, accessorName, "Char", prefsRoot, prefName, fallbackValue, { in: Number.parseFloat, out: n => n + "", }); return; } Object.defineProperty(self, accessorName, { get: () => serializer.in(get(cache, prefType, prefsRoot, prefName, fallbackValue)), set: e => set(cache, prefType, prefsRoot, prefName, serializer.out(e)), }); } /** * Finds the accessor for the provided pref, based on the blueprint object * used in the constructor. * * @param PrefsHelper self * @param object prefsBlueprint * @return string */ function accessorNameForPref(somePrefName, prefsBlueprint) { for (const accessorName in prefsBlueprint) { const [, prefName] = prefsBlueprint[accessorName]; if (somePrefName == prefName) { return accessorName; } } return ""; } /** * Creates a pref observer for `self`. * * @param PrefsHelper self * @param Map cache * @param string prefsRoot * @param object prefsBlueprint * @return object */ function makeObserver(self, cache, prefsRoot, prefsBlueprint) { return { register: function() { this._branch = Services.prefs.getBranch(prefsRoot + "."); this._branch.addObserver("", this); }, unregister: function() { this._branch.removeObserver("", this); }, observe: function(subject, topic, prefName) { // If this particular pref isn't handled by the blueprint object, // even though it's in the specified branch, ignore it. const accessorName = accessorNameForPref(prefName, prefsBlueprint); if (!(accessorName in self)) { return; } cache.delete(prefName); self.emit("pref-changed", accessorName, self[accessorName]); }, }; } exports.PrefsHelper = PrefsHelper; /** * A PreferenceObserver observes a pref branch for pref changes. * It emits an event for each preference change. */ function PrefObserver(branchName) { this.branchName = branchName; this.branch = Services.prefs.getBranch(branchName); this.branch.addObserver("", this); EventEmitter.decorate(this); } exports.PrefObserver = PrefObserver; PrefObserver.prototype = { observe: function(subject, topic, data) { if (topic == "nsPref:changed") { this.emit(this.branchName + data); } }, destroy: function() { if (this.branch) { this.branch.removeObserver("", this); } }, };