|
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/. */ |
|
6 |
|
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 */ |
|
89 |
|
90 |
|
91 this.EXPORTED_SYMBOLS = [ "XPCOMUtils" ]; |
|
92 |
|
93 const Cc = Components.classes; |
|
94 const Ci = Components.interfaces; |
|
95 const Cr = Components.results; |
|
96 const Cu = Components.utils; |
|
97 |
|
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 }, |
|
112 |
|
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 }, |
|
141 |
|
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); |
|
150 |
|
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 }, |
|
160 |
|
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 }, |
|
183 |
|
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 }, |
|
205 |
|
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 }, |
|
230 |
|
231 /** |
|
232 * Convenience access to category manager |
|
233 */ |
|
234 get categoryManager() { |
|
235 return Components.classes["@mozilla.org/categorymanager;1"] |
|
236 .getService(Ci.nsICategoryManager); |
|
237 }, |
|
238 |
|
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 }, |
|
249 |
|
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 }, |
|
260 |
|
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 }, |
|
277 |
|
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 }, |
|
290 |
|
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 }; |
|
317 |
|
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 } |
|
331 |
|
332 throw Cr.NS_ERROR_NO_INTERFACE; |
|
333 }; |
|
334 } |