Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / devtools / client / shared / prefs.js
blob226038d5ecafb2f5ae3c535210e910116556786d
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/. */
4 "use strict";
6 const EventEmitter = require("resource://devtools/shared/event-emitter.js");
8 /**
9  * Shortcuts for lazily accessing and setting various preferences.
10  * Usage:
11  *   let prefs = new Prefs("root.path.to.branch", {
12  *     myIntPref: ["Int", "leaf.path.to.my-int-pref"],
13  *     myCharPref: ["Char", "leaf.path.to.my-char-pref"],
14  *     myJsonPref: ["Json", "leaf.path.to.my-json-pref"],
15  *     myFloatPref: ["Float", "leaf.path.to.my-float-pref"]
16  *     ...
17  *   });
18  *
19  * Get/set:
20  *   prefs.myCharPref = "foo";
21  *   let aux = prefs.myCharPref;
22  *
23  * Observe:
24  *   prefs.registerObserver();
25  *   prefs.on("pref-changed", (prefValue) => {
26  *     ...
27  *   });
28  *
29  * @param string prefsRoot
30  *        The root path to the required preferences branch.
31  * @param object prefsBlueprint
32  *        An object containing { accessorName: [prefType, prefName] } keys.
33  */
34 function PrefsHelper(prefsRoot = "", prefsBlueprint = {}) {
35   EventEmitter.decorate(this);
37   const cache = new Map();
39   for (const accessorName in prefsBlueprint) {
40     const [prefType, prefName, fallbackValue] = prefsBlueprint[accessorName];
41     map(
42       this,
43       cache,
44       accessorName,
45       prefType,
46       prefsRoot,
47       prefName,
48       fallbackValue
49     );
50   }
52   const observer = makeObserver(this, cache, prefsRoot, prefsBlueprint);
53   this.registerObserver = () => observer.register();
54   this.unregisterObserver = () => observer.unregister();
57 /**
58  * Helper method for getting a pref value.
59  *
60  * @param Map cache
61  * @param string prefType
62  * @param string prefsRoot
63  * @param string prefName
64  * @param string|int|boolean fallbackValue
65  * @return any
66  */
67 function get(cache, prefType, prefsRoot, prefName, fallbackValue) {
68   const cachedPref = cache.get(prefName);
69   if (cachedPref !== undefined) {
70     return cachedPref;
71   }
72   const value = Services.prefs["get" + prefType + "Pref"](
73     [prefsRoot, prefName].join("."),
74     fallbackValue
75   );
76   cache.set(prefName, value);
77   return value;
80 /**
81  * Helper method for setting a pref value.
82  *
83  * @param Map cache
84  * @param string prefType
85  * @param string prefsRoot
86  * @param string prefName
87  * @param any value
88  */
89 function set(cache, prefType, prefsRoot, prefName, value) {
90   Services.prefs["set" + prefType + "Pref"](
91     [prefsRoot, prefName].join("."),
92     value
93   );
94   cache.set(prefName, value);
97 /**
98  * Maps a property name to a pref, defining lazy getters and setters.
99  * Supported types are "Bool", "Char", "Int", "Float" (sugar around "Char"
100  * type and casting), and "Json" (which is basically just sugar for "Char"
101  * using the standard JSON serializer).
103  * @param PrefsHelper self
104  * @param Map cache
105  * @param string accessorName
106  * @param string prefType
107  * @param string prefsRoot
108  * @param string prefName
109  * @param string|int|boolean fallbackValue
110  * @param array serializer [optional]
111  */
112 function map(
113   self,
114   cache,
115   accessorName,
116   prefType,
117   prefsRoot,
118   prefName,
119   fallbackValue,
120   serializer = { in: e => e, out: e => e }
121 ) {
122   if (prefName in self) {
123     throw new Error(
124       `Can't use ${prefName} because it overrides a property` +
125         "on the instance."
126     );
127   }
128   if (prefType == "Json") {
129     map(
130       self,
131       cache,
132       accessorName,
133       "String",
134       prefsRoot,
135       prefName,
136       fallbackValue,
137       {
138         in: JSON.parse,
139         out: JSON.stringify,
140       }
141     );
142     return;
143   }
144   if (prefType == "Float") {
145     map(self, cache, accessorName, "Char", prefsRoot, prefName, fallbackValue, {
146       in: Number.parseFloat,
147       out: n => n + "",
148     });
149     return;
150   }
152   Object.defineProperty(self, accessorName, {
153     get: () =>
154       serializer.in(get(cache, prefType, prefsRoot, prefName, fallbackValue)),
155     set: e => {
156       set(cache, prefType, prefsRoot, prefName, serializer.out(e));
157     },
158   });
162  * Finds the accessor for the provided pref, based on the blueprint object
163  * used in the constructor.
165  * @param PrefsHelper self
166  * @param object prefsBlueprint
167  * @return string
168  */
169 function accessorNameForPref(somePrefName, prefsBlueprint) {
170   for (const accessorName in prefsBlueprint) {
171     const [, prefName] = prefsBlueprint[accessorName];
172     if (somePrefName == prefName) {
173       return accessorName;
174     }
175   }
176   return "";
180  * Creates a pref observer for `self`.
182  * @param PrefsHelper self
183  * @param Map cache
184  * @param string prefsRoot
185  * @param object prefsBlueprint
186  * @return object
187  */
188 function makeObserver(self, cache, prefsRoot, prefsBlueprint) {
189   return {
190     register() {
191       this._branch = Services.prefs.getBranch(prefsRoot + ".");
192       this._branch.addObserver("", this);
193     },
194     unregister() {
195       this._branch.removeObserver("", this);
196     },
197     observe(subject, topic, prefName) {
198       // If this particular pref isn't handled by the blueprint object,
199       // even though it's in the specified branch, ignore it.
200       const accessorName = accessorNameForPref(prefName, prefsBlueprint);
201       if (!(accessorName in self)) {
202         return;
203       }
204       cache.delete(prefName);
205       self.emit("pref-changed", accessorName, self[accessorName]);
206     },
207   };
210 exports.PrefsHelper = PrefsHelper;
213  * A PreferenceObserver observes a pref branch for pref changes.
214  * It emits an event for each preference change.
215  */
216 class PrefObserver extends EventEmitter {
217   constructor(branchName) {
218     super();
220     this.#branchName = branchName;
221     this.#branch = Services.prefs.getBranch(branchName);
222     this.#branch.addObserver("", this);
223   }
225   #branchName;
226   #branch;
228   observe(subject, topic, data) {
229     if (topic == "nsPref:changed") {
230       this.emit(this.#branchName + data);
231     }
232   }
234   destroy() {
235     this.#branch.removeObserver("", this);
236   }
239 exports.PrefObserver = PrefObserver;