prefs.js (5977B)
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"; 5 6 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 7 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); 36 37 const cache = new Map(); 38 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 } 51 52 const observer = makeObserver(this, cache, prefsRoot, prefsBlueprint); 53 this.registerObserver = () => observer.register(); 54 this.unregisterObserver = () => observer.unregister(); 55 } 56 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; 78 } 79 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); 95 } 96 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). 102 * 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 } 151 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 }); 159 } 160 161 /** 162 * Finds the accessor for the provided pref, based on the blueprint object 163 * used in the constructor. 164 * 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 ""; 177 } 178 179 /** 180 * Creates a pref observer for `self`. 181 * 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 }; 208 } 209 210 exports.PrefsHelper = PrefsHelper; 211 212 /** 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(); 219 220 this.#branchName = branchName; 221 this.#branch = Services.prefs.getBranch(branchName); 222 this.#branch.addObserver("", this); 223 } 224 225 #branchName; 226 #branch; 227 228 observe(subject, topic, data) { 229 if (topic == "nsPref:changed") { 230 this.emit(this.#branchName + data); 231 } 232 } 233 234 destroy() { 235 this.#branch.removeObserver("", this); 236 } 237 } 238 239 exports.PrefObserver = PrefObserver;