Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 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 /**
8 * Utilities for JavaScript components loaded by the JS component
9 * loader.
10 *
11 * Import into a JS component using
12 * 'Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");'
13 *
14 * Exposing a JS 'class' as a component using these utility methods consists
15 * of several steps:
16 * 0. Import XPCOMUtils, as described above.
17 * 1. Declare the 'class' (or multiple classes) implementing the component(s):
18 * function MyComponent() {
19 * // constructor
20 * }
21 * MyComponent.prototype = {
22 * // properties required for XPCOM registration:
23 * classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"),
24 *
25 * // [optional] custom factory (an object implementing nsIFactory). If not
26 * // provided, the default factory is used, which returns
27 * // |(new MyComponent()).QueryInterface(iid)| in its createInstance().
28 * _xpcom_factory: { ... },
29 *
30 * // QueryInterface implementation, e.g. using the generateQI helper
31 * QueryInterface: XPCOMUtils.generateQI(
32 * [Components.interfaces.nsIObserver,
33 * Components.interfaces.nsIMyInterface,
34 * "nsIFoo",
35 * "nsIBar" ]),
36 *
37 * // [optional] classInfo implementation, e.g. using the generateCI helper.
38 * // Will be automatically returned from QueryInterface if that was
39 * // generated with the generateQI helper.
40 * classInfo: XPCOMUtils.generateCI(
41 * {classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"),
42 * contractID: "@example.com/xxx;1",
43 * classDescription: "unique text description",
44 * interfaces: [Components.interfaces.nsIObserver,
45 * Components.interfaces.nsIMyInterface,
46 * "nsIFoo",
47 * "nsIBar"],
48 * flags: Ci.nsIClassInfo.SINGLETON}),
49 *
50 * // The following properties were used prior to Mozilla 2, but are no
51 * // longer supported. They may still be included for compatibility with
52 * // prior versions of XPCOMUtils. In Mozilla 2, this information is
53 * // included in the .manifest file which registers this JS component.
54 * classDescription: "unique text description",
55 * contractID: "@example.com/xxx;1",
56 *
57 * // [optional] an array of categories to register this component in.
58 * _xpcom_categories: [{
59 * // Each object in the array specifies the parameters to pass to
60 * // nsICategoryManager.addCategoryEntry(). 'true' is passed for
61 * // both aPersist and aReplace params.
62 * category: "some-category",
63 * // optional, defaults to the object's classDescription
64 * entry: "entry name",
65 * // optional, defaults to the object's contractID (unless
66 * // 'service' is specified)
67 * value: "...",
68 * // optional, defaults to false. When set to true, and only if 'value'
69 * // is not specified, the concatenation of the string "service," and the
70 * // object's contractID is passed as aValue parameter of addCategoryEntry.
71 * service: true,
72 * // optional, it can be an array of applications' IDs in the form:
73 * // [ "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}", ... ]
74 * // If defined the component will be registered in this category only for
75 * // the provided applications.
76 * apps: [...]
77 * }],
78 *
79 * // ...component implementation...
80 * };
81 *
82 * 2. Create an array of component constructors (like the one
83 * created in step 1):
84 * var components = [MyComponent];
85 *
86 * 3. Define the NSGetFactory entry point:
87 * this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
88 */
91 this.EXPORTED_SYMBOLS = [ "XPCOMUtils" ];
93 const Cc = Components.classes;
94 const Ci = Components.interfaces;
95 const Cr = Components.results;
96 const Cu = Components.utils;
98 this.XPCOMUtils = {
99 /**
100 * Generate a QueryInterface implementation. The returned function must be
101 * assigned to the 'QueryInterface' property of a JS object. When invoked on
102 * that object, it checks if the given iid is listed in the |interfaces|
103 * param, and if it is, returns |this| (the object it was called on).
104 * If the JS object has a classInfo property it'll be returned for the
105 * nsIClassInfo IID, generateCI can be used to generate the classInfo
106 * property.
107 */
108 generateQI: function XPCU_generateQI(interfaces) {
109 /* Note that Ci[Ci.x] == Ci.x for all x */
110 return makeQI([Ci[i].name for each (i in interfaces) if (Ci[i])]);
111 },
113 /**
114 * Generate a ClassInfo implementation for a component. The returned object
115 * must be assigned to the 'classInfo' property of a JS object. The first and
116 * only argument should be an object that contains a number of optional
117 * properties: "interfaces", "contractID", "classDescription", "classID" and
118 * "flags". The values of the properties will be returned as the values of the
119 * various properties of the nsIClassInfo implementation.
120 */
121 generateCI: function XPCU_generateCI(classInfo)
122 {
123 if (QueryInterface in classInfo)
124 throw Error("In generateCI, don't use a component for generating classInfo");
125 /* Note that Ci[Ci.x] == Ci.x for all x */
126 var _interfaces = [Ci[i] for each (i in classInfo.interfaces) if (Ci[i])];
127 return {
128 getInterfaces: function XPCU_getInterfaces(countRef) {
129 countRef.value = _interfaces.length;
130 return _interfaces;
131 },
132 getHelperForLanguage: function XPCU_getHelperForLanguage(language) null,
133 contractID: classInfo.contractID,
134 classDescription: classInfo.classDescription,
135 classID: classInfo.classID,
136 implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
137 flags: classInfo.flags,
138 QueryInterface: this.generateQI([Ci.nsIClassInfo])
139 };
140 },
142 /**
143 * Generate a NSGetFactory function given an array of components.
144 */
145 generateNSGetFactory: function XPCU_generateNSGetFactory(componentsArray) {
146 let classes = {};
147 for each (let component in componentsArray) {
148 if (!(component.prototype.classID instanceof Components.ID))
149 throw Error("In generateNSGetFactory, classID missing or incorrect for component " + component);
151 classes[component.prototype.classID] = this._getFactory(component);
152 }
153 return function NSGetFactory(cid) {
154 let cidstring = cid.toString();
155 if (cidstring in classes)
156 return classes[cidstring];
157 throw Cr.NS_ERROR_FACTORY_NOT_REGISTERED;
158 }
159 },
161 /**
162 * Defines a getter on a specified object that will be created upon first use.
163 *
164 * @param aObject
165 * The object to define the lazy getter on.
166 * @param aName
167 * The name of the getter to define on aObject.
168 * @param aLambda
169 * A function that returns what the getter should return. This will
170 * only ever be called once.
171 */
172 defineLazyGetter: function XPCU_defineLazyGetter(aObject, aName, aLambda)
173 {
174 Object.defineProperty(aObject, aName, {
175 get: function () {
176 delete aObject[aName];
177 return aObject[aName] = aLambda.apply(aObject);
178 },
179 configurable: true,
180 enumerable: true
181 });
182 },
184 /**
185 * Defines a getter on a specified object for a service. The service will not
186 * be obtained until first use.
187 *
188 * @param aObject
189 * The object to define the lazy getter on.
190 * @param aName
191 * The name of the getter to define on aObject for the service.
192 * @param aContract
193 * The contract used to obtain the service.
194 * @param aInterfaceName
195 * The name of the interface to query the service to.
196 */
197 defineLazyServiceGetter: function XPCU_defineLazyServiceGetter(aObject, aName,
198 aContract,
199 aInterfaceName)
200 {
201 this.defineLazyGetter(aObject, aName, function XPCU_serviceLambda() {
202 return Cc[aContract].getService(Ci[aInterfaceName]);
203 });
204 },
206 /**
207 * Defines a getter on a specified object for a module. The module will not
208 * be imported until first use.
209 *
210 * @param aObject
211 * The object to define the lazy getter on.
212 * @param aName
213 * The name of the getter to define on aObject for the module.
214 * @param aResource
215 * The URL used to obtain the module.
216 * @param aSymbol
217 * The name of the symbol exported by the module.
218 * This parameter is optional and defaults to aName.
219 */
220 defineLazyModuleGetter: function XPCU_defineLazyModuleGetter(aObject, aName,
221 aResource,
222 aSymbol)
223 {
224 this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
225 var temp = {};
226 Cu.import(aResource, temp);
227 return temp[aSymbol || aName];
228 });
229 },
231 /**
232 * Convenience access to category manager
233 */
234 get categoryManager() {
235 return Components.classes["@mozilla.org/categorymanager;1"]
236 .getService(Ci.nsICategoryManager);
237 },
239 /**
240 * Helper which iterates over a nsISimpleEnumerator.
241 * @param e The nsISimpleEnumerator to iterate over.
242 * @param i The expected interface for each element.
243 */
244 IterSimpleEnumerator: function XPCU_IterSimpleEnumerator(e, i)
245 {
246 while (e.hasMoreElements())
247 yield e.getNext().QueryInterface(i);
248 },
250 /**
251 * Helper which iterates over a string enumerator.
252 * @param e The string enumerator (nsIUTF8StringEnumerator or
253 * nsIStringEnumerator) over which to iterate.
254 */
255 IterStringEnumerator: function XPCU_IterStringEnumerator(e)
256 {
257 while (e.hasMore())
258 yield e.getNext();
259 },
261 /**
262 * Returns an nsIFactory for |component|.
263 */
264 _getFactory: function XPCOMUtils__getFactory(component) {
265 var factory = component.prototype._xpcom_factory;
266 if (!factory) {
267 factory = {
268 createInstance: function(outer, iid) {
269 if (outer)
270 throw Cr.NS_ERROR_NO_AGGREGATION;
271 return (new component()).QueryInterface(iid);
272 }
273 }
274 }
275 return factory;
276 },
278 /**
279 * Allows you to fake a relative import. Expects the global object from the
280 * module that's calling us, and the relative filename that we wish to import.
281 */
282 importRelative: function XPCOMUtils__importRelative(that, path, scope) {
283 if (!("__URI__" in that))
284 throw Error("importRelative may only be used from a JSM, and its first argument "+
285 "must be that JSM's global object (hint: use this)");
286 let uri = that.__URI__;
287 let i = uri.lastIndexOf("/");
288 Components.utils.import(uri.substring(0, i+1) + path, scope || that);
289 },
291 /**
292 * generates a singleton nsIFactory implementation that can be used as
293 * the _xpcom_factory of the component.
294 * @param aServiceConstructor
295 * Constructor function of the component.
296 */
297 generateSingletonFactory:
298 function XPCOMUtils_generateSingletonFactory(aServiceConstructor) {
299 return {
300 _instance: null,
301 createInstance: function XPCU_SF_createInstance(aOuter, aIID) {
302 if (aOuter !== null) {
303 throw Cr.NS_ERROR_NO_AGGREGATION;
304 }
305 if (this._instance === null) {
306 this._instance = new aServiceConstructor();
307 }
308 return this._instance.QueryInterface(aIID);
309 },
310 lockFactory: function XPCU_SF_lockFactory(aDoLock) {
311 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
312 },
313 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
314 };
315 },
316 };
318 /**
319 * Helper for XPCOMUtils.generateQI to avoid leaks - see bug 381651#c1
320 */
321 function makeQI(interfaceNames) {
322 return function XPCOMUtils_QueryInterface(iid) {
323 if (iid.equals(Ci.nsISupports))
324 return this;
325 if (iid.equals(Ci.nsIClassInfo) && "classInfo" in this)
326 return this.classInfo;
327 for each(let interfaceName in interfaceNames) {
328 if (Ci[interfaceName].equals(iid))
329 return this;
330 }
332 throw Cr.NS_ERROR_NO_INTERFACE;
333 };
334 }