summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/prefs.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/prefs.js')
-rw-r--r--devtools/client/shared/prefs.js238
1 files changed, 238 insertions, 0 deletions
diff --git a/devtools/client/shared/prefs.js b/devtools/client/shared/prefs.js
new file mode 100644
index 0000000000..5bfe115c7e
--- /dev/null
+++ b/devtools/client/shared/prefs.js
@@ -0,0 +1,238 @@
+/* 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 EventEmitter = require("resource://devtools/shared/event-emitter.js");
+
+/**
+ * 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,
+ "String",
+ 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() {
+ this._branch = Services.prefs.getBranch(prefsRoot + ".");
+ this._branch.addObserver("", this);
+ },
+ unregister() {
+ this._branch.removeObserver("", this);
+ },
+ observe(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(subject, topic, data) {
+ if (topic == "nsPref:changed") {
+ this.emit(this.branchName + data);
+ }
+ },
+
+ destroy() {
+ if (this.branch) {
+ this.branch.removeObserver("", this);
+ }
+ },
+};