1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
2 * vim: sw=2 ts=2 sts=2 et filetype=javascript
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
9 let global = Cu.getGlobalForObject({});
11 // Some global imports expose additional symbols; for example,
12 // `Cu.importGlobalProperties(["MessageChannel"])` imports `MessageChannel`
13 // and `MessagePort`. This table maps those extra symbols to the main
15 const EXTRA_GLOBAL_NAME_TO_IMPORT_NAME = {
16 MessagePort: "MessageChannel",
20 * Redefines the given property on the given object with the given
21 * value. This can be used to redefine getter properties which do not
24 function redefine(object, prop, value) {
25 Object.defineProperty(object, prop, {
35 * XPCOMUtils contains helpers to make lazily loading scripts, modules, prefs
36 * and XPCOM services more ergonomic for JS consumers.
40 export var XPCOMUtils = {
44 * Use ChromeUtils.defineLazyGetter instead.
46 * Defines a getter on a specified object that will be created upon first use.
48 * @param {object} aObject
49 * The object to define the lazy getter on.
50 * @param {string} aName
51 * The name of the getter to define on aObject.
52 * @param {Function} aLambda
53 * A function that returns what the getter should return. This will
54 * only ever be called once.
56 defineLazyGetter(aObject, aName, aLambda) {
58 "Please use ChromeUtils.defineLazyGetter instead of XPCOMUtils.defineLazyGetter. XPCOMUtils.defineLazyGetter will be removed soon."
60 ChromeUtils.defineLazyGetter(aObject, aName, aLambda);
64 * Defines a getter on a specified object for a script. The script will not
65 * be loaded until first use.
67 * @param {object} aObject
68 * The object to define the lazy getter on.
69 * @param {string|string[]} aNames
70 * The name of the getter to define on aObject for the script.
71 * This can be a string if the script exports only one symbol,
72 * or an array of strings if the script can be first accessed
73 * from several different symbols.
74 * @param {string} aResource
75 * The URL used to obtain the script.
77 defineLazyScriptGetter(aObject, aNames, aResource) {
78 if (!Array.isArray(aNames)) {
81 for (let name of aNames) {
82 Object.defineProperty(aObject, name, {
84 XPCOMUtils._scriptloader.loadSubScript(aResource, aObject);
88 redefine(aObject, name, value);
97 * Overrides the scriptloader definition for tests to help with globals
98 * tracking. Should only be used for tests.
100 * @param {object} aObject
101 * The alternative script loader object to use.
103 overrideScriptLoaderForTests(aObject) {
104 Cu.crashIfNotInAutomation();
105 delete this._scriptloader;
106 this._scriptloader = aObject;
110 * Defines a getter property on the given object for each of the given
111 * global names as accepted by Cu.importGlobalProperties. These
112 * properties are imported into the shared system global, and then
113 * copied onto the given object, no matter which global the object
116 * @param {object} aObject
117 * The object on which to define the properties.
118 * @param {string[]} aNames
119 * The list of global properties to define.
121 defineLazyGlobalGetters(aObject, aNames) {
122 for (let name of aNames) {
123 ChromeUtils.defineLazyGetter(aObject, name, () => {
124 if (!(name in global)) {
125 let importName = EXTRA_GLOBAL_NAME_TO_IMPORT_NAME[name] || name;
126 // eslint-disable-next-line mozilla/reject-importGlobalProperties, no-unused-vars
127 Cu.importGlobalProperties([importName]);
135 * Defines a getter on a specified object for a service. The service will not
136 * be obtained until first use.
138 * @param {object} aObject
139 * The object to define the lazy getter on.
140 * @param {string} aName
141 * The name of the getter to define on aObject for the service.
142 * @param {string} aContract
143 * The contract used to obtain the service.
144 * @param {string} aInterfaceName
145 * The name of the interface to query the service to.
147 defineLazyServiceGetter(aObject, aName, aContract, aInterfaceName) {
148 ChromeUtils.defineLazyGetter(aObject, aName, () => {
149 if (aInterfaceName) {
150 return Cc[aContract].getService(Ci[aInterfaceName]);
152 return Cc[aContract].getService().wrappedJSObject;
157 * Defines a lazy service getter on a specified object for each
158 * property in the given object.
160 * @param {object} aObject
161 * The object to define the lazy getter on.
162 * @param {object} aServices
163 * An object with a property for each service to be
164 * imported, where the property name is the name of the
165 * symbol to define, and the value is a 1 or 2 element array
166 * containing the contract ID and, optionally, the interface
167 * name of the service, as passed to defineLazyServiceGetter.
169 defineLazyServiceGetters(aObject, aServices) {
170 for (let [name, service] of Object.entries(aServices)) {
171 // Note: This is hot code, and cross-compartment array wrappers
172 // are not JIT-friendly to destructuring or spread operators, so
173 // we need to use indexed access instead.
174 this.defineLazyServiceGetter(
184 * Defines a getter on a specified object for preference value. The
185 * preference is read the first time that the property is accessed,
186 * and is thereafter kept up-to-date using a preference observer.
188 * @param {object} aObject
189 * The object to define the lazy getter on.
190 * @param {string} aName
191 * The name of the getter property to define on aObject.
192 * @param {string} aPreference
193 * The name of the preference to read.
194 * @param {any} aDefaultPrefValue
195 * The default value to use, if the preference is not defined.
196 * This is the default value of the pref, before applying aTransform.
197 * @param {Function} aOnUpdate
198 * A function to call upon update. Receives as arguments
199 * `(aPreference, previousValue, newValue)`
200 * @param {Function} aTransform
201 * An optional function to transform the value. If provided,
202 * this function receives the new preference value as an argument
203 * and its return value is used by the getter.
205 defineLazyPreferenceGetter(
209 aDefaultPrefValue = null,
211 aTransform = val => val
213 if (AppConstants.DEBUG && aDefaultPrefValue !== null) {
214 let prefType = Services.prefs.getPrefType(aPreference);
215 if (prefType != Ci.nsIPrefBranch.PREF_INVALID) {
216 // The pref may get defined after the lazy getter is called
217 // at which point the code here won't know the expected type.
218 let prefTypeForDefaultValue = {
219 boolean: Ci.nsIPrefBranch.PREF_BOOL,
220 number: Ci.nsIPrefBranch.PREF_INT,
221 string: Ci.nsIPrefBranch.PREF_STRING,
222 }[typeof aDefaultPrefValue];
223 if (prefTypeForDefaultValue != prefType) {
225 `Default value does not match preference type (Got ${prefTypeForDefaultValue}, expected ${prefType}) for ${aPreference}`
231 // Note: We need to keep a reference to this observer alive as long
232 // as aObject is alive. This means that all of our getters need to
233 // explicitly close over the variable that holds the object, and we
234 // cannot define a value in place of a getter after we read the
237 QueryInterface: XPCU_lazyPreferenceObserverQI,
241 observe(subject, topic, data) {
242 if (data == aPreference) {
244 let previous = this.value;
246 // Fetch and cache value.
247 this.value = undefined;
248 let latest = lazyGetter();
249 aOnUpdate(data, previous, latest);
251 // Empty cache, next call to the getter will cause refetch.
252 this.value = undefined;
258 let defineGetter = get => {
259 Object.defineProperty(aObject, aName, {
266 function lazyGetter() {
267 if (observer.value === undefined) {
269 switch (Services.prefs.getPrefType(aPreference)) {
270 case Ci.nsIPrefBranch.PREF_STRING:
271 prefValue = Services.prefs.getStringPref(aPreference);
274 case Ci.nsIPrefBranch.PREF_INT:
275 prefValue = Services.prefs.getIntPref(aPreference);
278 case Ci.nsIPrefBranch.PREF_BOOL:
279 prefValue = Services.prefs.getBoolPref(aPreference);
282 case Ci.nsIPrefBranch.PREF_INVALID:
283 prefValue = aDefaultPrefValue;
287 // This should never happen.
289 `Error getting pref ${aPreference}; its value's type is ` +
290 `${Services.prefs.getPrefType(aPreference)}, which I don't ` +
291 `know how to handle.`
295 observer.value = aTransform(prefValue);
297 return observer.value;
301 Services.prefs.addObserver(aPreference, observer, true);
303 defineGetter(lazyGetter);
309 * Defines a non-writable property on an object.
311 * @param {object} aObj
312 * The object to define the property on.
314 * @param {string} aName
315 * The name of the non-writable property to define on aObject.
317 * @param {any} aValue
318 * The value of the non-writable property.
320 defineConstant(aObj, aName, aValue) {
321 Object.defineProperty(aObj, aName, {
329 ChromeUtils.defineLazyGetter(XPCOMUtils, "_scriptloader", () => {
330 return Services.scriptloader;
333 var XPCU_lazyPreferenceObserverQI = ChromeUtils.generateQI([
335 "nsISupportsWeakReference",