Bug 1943761 - Add class alignment to the mozsearch analysis file. r=asuth
[gecko.git] / js / xpconnect / loader / XPCOMUtils.sys.mjs
blobcb22349ceb9cb9c84b73791740d40b6e0aa1340b
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
14 // import name.
15 const EXTRA_GLOBAL_NAME_TO_IMPORT_NAME = {
16   MessagePort: "MessageChannel",
19 /**
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
22  * implement setters.
23  */
24 function redefine(object, prop, value) {
25   Object.defineProperty(object, prop, {
26     configurable: true,
27     enumerable: true,
28     value,
29     writable: true,
30   });
31   return value;
34 /**
35  * XPCOMUtils contains helpers to make lazily loading scripts, modules, prefs
36  * and XPCOM services more ergonomic for JS consumers.
37  *
38  * @class
39  */
40 export var XPCOMUtils = {
41   /**
42    * DEPRECATED!
43    *
44    * Use ChromeUtils.defineLazyGetter instead.
45    *
46    * Defines a getter on a specified object that will be created upon first use.
47    *
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.
55    */
56   defineLazyGetter(aObject, aName, aLambda) {
57     console.warn(
58       "Please use ChromeUtils.defineLazyGetter instead of XPCOMUtils.defineLazyGetter. XPCOMUtils.defineLazyGetter will be removed soon."
59     );
60     ChromeUtils.defineLazyGetter(aObject, aName, aLambda);
61   },
63   /**
64    * Defines a getter on a specified object for a script.  The script will not
65    * be loaded until first use.
66    *
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.
76    */
77   defineLazyScriptGetter(aObject, aNames, aResource) {
78     if (!Array.isArray(aNames)) {
79       aNames = [aNames];
80     }
81     for (let name of aNames) {
82       Object.defineProperty(aObject, name, {
83         get() {
84           XPCOMUtils._scriptloader.loadSubScript(aResource, aObject);
85           return aObject[name];
86         },
87         set(value) {
88           redefine(aObject, name, value);
89         },
90         configurable: true,
91         enumerable: true,
92       });
93     }
94   },
96   /**
97    * Overrides the scriptloader definition for tests to help with globals
98    * tracking. Should only be used for tests.
99    *
100    * @param {object} aObject
101    *        The alternative script loader object to use.
102    */
103   overrideScriptLoaderForTests(aObject) {
104     Cu.crashIfNotInAutomation();
105     delete this._scriptloader;
106     this._scriptloader = aObject;
107   },
109   /**
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
114    * belongs to.
115    *
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.
120    */
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]);
128         }
129         return global[name];
130       });
131     }
132   },
134   /**
135    * Defines a getter on a specified object for a service.  The service will not
136    * be obtained until first use.
137    *
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.
146    */
147   defineLazyServiceGetter(aObject, aName, aContract, aInterfaceName) {
148     ChromeUtils.defineLazyGetter(aObject, aName, () => {
149       if (aInterfaceName) {
150         return Cc[aContract].getService(Ci[aInterfaceName]);
151       }
152       return Cc[aContract].getService().wrappedJSObject;
153     });
154   },
156   /**
157    * Defines a lazy service getter on a specified object for each
158    * property in the given object.
159    *
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.
168    */
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(
175         aObject,
176         name,
177         service[0],
178         service[1] || null
179       );
180     }
181   },
183   /**
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.
187    *
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.
204    */
205   defineLazyPreferenceGetter(
206     aObject,
207     aName,
208     aPreference,
209     aDefaultPrefValue = null,
210     aOnUpdate = null,
211     aTransform = val => val
212   ) {
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) {
224           throw new Error(
225             `Default value does not match preference type (Got ${prefTypeForDefaultValue}, expected ${prefType}) for ${aPreference}`
226           );
227         }
228       }
229     }
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
235     // preference.
236     let observer = {
237       QueryInterface: XPCU_lazyPreferenceObserverQI,
239       value: undefined,
241       observe(subject, topic, data) {
242         if (data == aPreference) {
243           if (aOnUpdate) {
244             let previous = this.value;
246             // Fetch and cache value.
247             this.value = undefined;
248             let latest = lazyGetter();
249             aOnUpdate(data, previous, latest);
250           } else {
251             // Empty cache, next call to the getter will cause refetch.
252             this.value = undefined;
253           }
254         }
255       },
256     };
258     let defineGetter = get => {
259       Object.defineProperty(aObject, aName, {
260         configurable: true,
261         enumerable: true,
262         get,
263       });
264     };
266     function lazyGetter() {
267       if (observer.value === undefined) {
268         let prefValue;
269         switch (Services.prefs.getPrefType(aPreference)) {
270           case Ci.nsIPrefBranch.PREF_STRING:
271             prefValue = Services.prefs.getStringPref(aPreference);
272             break;
274           case Ci.nsIPrefBranch.PREF_INT:
275             prefValue = Services.prefs.getIntPref(aPreference);
276             break;
278           case Ci.nsIPrefBranch.PREF_BOOL:
279             prefValue = Services.prefs.getBoolPref(aPreference);
280             break;
282           case Ci.nsIPrefBranch.PREF_INVALID:
283             prefValue = aDefaultPrefValue;
284             break;
286           default:
287             // This should never happen.
288             throw new Error(
289               `Error getting pref ${aPreference}; its value's type is ` +
290                 `${Services.prefs.getPrefType(aPreference)}, which I don't ` +
291                 `know how to handle.`
292             );
293         }
295         observer.value = aTransform(prefValue);
296       }
297       return observer.value;
298     }
300     defineGetter(() => {
301       Services.prefs.addObserver(aPreference, observer, true);
303       defineGetter(lazyGetter);
304       return lazyGetter();
305     });
306   },
308   /**
309    * Defines a non-writable property on an object.
310    *
311    * @param {object} aObj
312    *        The object to define the property on.
313    *
314    * @param {string} aName
315    *        The name of the non-writable property to define on aObject.
316    *
317    * @param {any} aValue
318    *        The value of the non-writable property.
319    */
320   defineConstant(aObj, aName, aValue) {
321     Object.defineProperty(aObj, aName, {
322       value: aValue,
323       enumerable: true,
324       writable: false,
325     });
326   },
329 ChromeUtils.defineLazyGetter(XPCOMUtils, "_scriptloader", () => {
330   return Services.scriptloader;
333 var XPCU_lazyPreferenceObserverQI = ChromeUtils.generateQI([
334   "nsIObserver",
335   "nsISupportsWeakReference",