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/. */
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
91this.EXPORTED_SYMBOLS = [ "XPCOMUtils" ];
92
93const Cc = Components.classes;
94const Ci = Components.interfaces;
95const Cr = Components.results;
96const Cu = Components.utils;
97
98this.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    let a = [];
111    if (interfaces) {
112      for (let i = 0; i < interfaces.length; i++) {
113        let iface = interfaces[i];
114        if (Ci[iface]) {
115          a.push(Ci[iface].name);
116        }
117      }
118    }
119    return makeQI(a);
120  },
121
122  /**
123   * Generate a ClassInfo implementation for a component. The returned object
124   * must be assigned to the 'classInfo' property of a JS object. The first and
125   * only argument should be an object that contains a number of optional
126   * properties: "interfaces", "contractID", "classDescription", "classID" and
127   * "flags". The values of the properties will be returned as the values of the
128   * various properties of the nsIClassInfo implementation.
129   */
130  generateCI: function XPCU_generateCI(classInfo)
131  {
132    if (QueryInterface in classInfo)
133      throw Error("In generateCI, don't use a component for generating classInfo");
134    /* Note that Ci[Ci.x] == Ci.x for all x */
135    let _interfaces = [];
136    for (let i = 0; i < classInfo.interfaces.length; i++) {
137      let iface = classInfo.interfaces[i];
138      if (Ci[iface]) {
139        _interfaces.push(Ci[iface]);
140      }
141    }
142    return {
143      getInterfaces: function XPCU_getInterfaces(countRef) {
144        countRef.value = _interfaces.length;
145        return _interfaces;
146      },
147      getScriptableHelper: function XPCU_getScriptableHelper() {
148        return null;
149      },
150      contractID: classInfo.contractID,
151      classDescription: classInfo.classDescription,
152      classID: classInfo.classID,
153      flags: classInfo.flags,
154      QueryInterface: this.generateQI([Ci.nsIClassInfo])
155    };
156  },
157
158  /**
159   * Generate a NSGetFactory function given an array of components.
160   */
161  generateNSGetFactory: function XPCU_generateNSGetFactory(componentsArray) {
162    let classes = {};
163    for (let i = 0; i < componentsArray.length; i++) {
164        let component = componentsArray[i];
165        if (!(component.prototype.classID instanceof Components.ID))
166          throw Error("In generateNSGetFactory, classID missing or incorrect for component " + component);
167
168        classes[component.prototype.classID] = this._getFactory(component);
169    }
170    return function NSGetFactory(cid) {
171      let cidstring = cid.toString();
172      if (cidstring in classes)
173        return classes[cidstring];
174      throw Cr.NS_ERROR_FACTORY_NOT_REGISTERED;
175    }
176  },
177
178  /**
179   * Defines a getter on a specified object that will be created upon first use.
180   *
181   * @param aObject
182   *        The object to define the lazy getter on.
183   * @param aName
184   *        The name of the getter to define on aObject.
185   * @param aLambda
186   *        A function that returns what the getter should return.  This will
187   *        only ever be called once.
188   */
189  defineLazyGetter: function XPCU_defineLazyGetter(aObject, aName, aLambda)
190  {
191    Object.defineProperty(aObject, aName, {
192      get: function () {
193        // Redefine this accessor property as a data property.
194        // Delete it first, to rule out "too much recursion" in case aObject is
195        // a proxy whose defineProperty handler might unwittingly trigger this
196        // getter again.
197        delete aObject[aName];
198        let value = aLambda.apply(aObject);
199        Object.defineProperty(aObject, aName, {
200          value,
201          writable: true,
202          configurable: true,
203          enumerable: true
204        });
205        return value;
206      },
207      configurable: true,
208      enumerable: true
209    });
210  },
211
212  /**
213   * Defines a getter on a specified object for a service.  The service will not
214   * be obtained until first use.
215   *
216   * @param aObject
217   *        The object to define the lazy getter on.
218   * @param aName
219   *        The name of the getter to define on aObject for the service.
220   * @param aContract
221   *        The contract used to obtain the service.
222   * @param aInterfaceName
223   *        The name of the interface to query the service to.
224   */
225  defineLazyServiceGetter: function XPCU_defineLazyServiceGetter(aObject, aName,
226                                                                 aContract,
227                                                                 aInterfaceName)
228  {
229    this.defineLazyGetter(aObject, aName, function XPCU_serviceLambda() {
230      return Cc[aContract].getService(Ci[aInterfaceName]);
231    });
232  },
233
234  /**
235   * Defines a getter on a specified object for a module.  The module will not
236   * be imported until first use. The getter allows to execute setup and
237   * teardown code (e.g.  to register/unregister to services) and accepts
238   * a proxy object which acts on behalf of the module until it is imported.
239   *
240   * @param aObject
241   *        The object to define the lazy getter on.
242   * @param aName
243   *        The name of the getter to define on aObject for the module.
244   * @param aResource
245   *        The URL used to obtain the module.
246   * @param aSymbol
247   *        The name of the symbol exported by the module.
248   *        This parameter is optional and defaults to aName.
249   * @param aPreLambda
250   *        A function that is executed when the proxy is set up.
251   *        This will only ever be called once.
252   * @param aPostLambda
253   *        A function that is executed when the module has been imported to
254   *        run optional teardown procedures on the proxy object.
255   *        This will only ever be called once.
256   * @param aProxy
257   *        An object which acts on behalf of the module to be imported until
258   *        the module has been imported.
259   */
260  defineLazyModuleGetter: function XPCU_defineLazyModuleGetter(
261                                   aObject, aName, aResource, aSymbol,
262                                   aPreLambda, aPostLambda, aProxy)
263  {
264    let proxy = aProxy || {};
265
266    if (typeof(aPreLambda) === "function") {
267      aPreLambda.apply(proxy);
268    }
269
270    this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
271      var temp = {};
272      try {
273        Cu.import(aResource, temp);
274
275        if (typeof(aPostLambda) === "function") {
276          aPostLambda.apply(proxy);
277        }
278      } catch (ex) {
279        Cu.reportError("Failed to load module " + aResource + ".");
280        throw ex;
281      }
282      return temp[aSymbol || aName];
283    });
284  },
285
286  /**
287   * Defines a getter on a specified object for preference value. The
288   * preference is read the first time that the property is accessed,
289   * and is thereafter kept up-to-date using a preference observer.
290   *
291   * @param aObject
292   *        The object to define the lazy getter on.
293   * @param aName
294   *        The name of the getter property to define on aObject.
295   * @param aPreference
296   *        The name of the preference to read.
297   * @param aDefaultValue
298   *        The default value to use, if the preference is not defined.
299   */
300  defineLazyPreferenceGetter: function XPCU_defineLazyPreferenceGetter(
301                                   aObject, aName, aPreference, aDefaultValue = null)
302  {
303    // Note: We need to keep a reference to this observer alive as long
304    // as aObject is alive. This means that all of our getters need to
305    // explicitly close over the variable that holds the object, and we
306    // cannot define a value in place of a getter after we read the
307    // preference.
308    let observer = {
309      QueryInterface: this.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
310
311      value: undefined,
312
313      observe(subject, topic, data) {
314        if (data == aPreference) {
315          this.value = undefined;
316        }
317      },
318    }
319
320    let defineGetter = get => {
321      Object.defineProperty(aObject, aName, {
322        configurable: true,
323        enumerable: true,
324        get,
325      });
326    };
327
328    function lazyGetter() {
329      if (observer.value === undefined) {
330        observer.value = Preferences.get(aPreference, aDefaultValue);
331      }
332      return observer.value;
333    }
334
335    defineGetter(() => {
336      Services.prefs.addObserver(aPreference, observer, true);
337
338      defineGetter(lazyGetter);
339      return lazyGetter();
340    });
341  },
342
343  /**
344   * Helper which iterates over a nsISimpleEnumerator.
345   * @param e The nsISimpleEnumerator to iterate over.
346   * @param i The expected interface for each element.
347   */
348  IterSimpleEnumerator: function* XPCU_IterSimpleEnumerator(e, i)
349  {
350    while (e.hasMoreElements())
351      yield e.getNext().QueryInterface(i);
352  },
353
354  /**
355   * Helper which iterates over a string enumerator.
356   * @param e The string enumerator (nsIUTF8StringEnumerator or
357   *          nsIStringEnumerator) over which to iterate.
358   */
359  IterStringEnumerator: function* XPCU_IterStringEnumerator(e)
360  {
361    while (e.hasMore())
362      yield e.getNext();
363  },
364
365  /**
366   * Helper which iterates over the entries in a category.
367   * @param aCategory The name of the category over which to iterate.
368   */
369  enumerateCategoryEntries: function* XPCOMUtils_enumerateCategoryEntries(aCategory)
370  {
371    let category = this.categoryManager.enumerateCategory(aCategory);
372    for (let entry of this.IterSimpleEnumerator(category, Ci.nsISupportsCString)) {
373      yield [entry.data, this.categoryManager.getCategoryEntry(aCategory, entry.data)];
374    }
375  },
376
377  /**
378   * Returns an nsIFactory for |component|.
379   */
380  _getFactory: function XPCOMUtils__getFactory(component) {
381    var factory = component.prototype._xpcom_factory;
382    if (!factory) {
383      factory = {
384        createInstance: function(outer, iid) {
385          if (outer)
386            throw Cr.NS_ERROR_NO_AGGREGATION;
387          return (new component()).QueryInterface(iid);
388        },
389        QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
390      }
391    }
392    return factory;
393  },
394
395  /**
396   * Allows you to fake a relative import. Expects the global object from the
397   * module that's calling us, and the relative filename that we wish to import.
398   */
399  importRelative: function XPCOMUtils__importRelative(that, path, scope) {
400    if (!("__URI__" in that))
401      throw Error("importRelative may only be used from a JSM, and its first argument "+
402                  "must be that JSM's global object (hint: use this)");
403    let uri = that.__URI__;
404    let i = uri.lastIndexOf("/");
405    Components.utils.import(uri.substring(0, i+1) + path, scope || that);
406  },
407
408  /**
409   * generates a singleton nsIFactory implementation that can be used as
410   * the _xpcom_factory of the component.
411   * @param aServiceConstructor
412   *        Constructor function of the component.
413   */
414  generateSingletonFactory:
415  function XPCOMUtils_generateSingletonFactory(aServiceConstructor) {
416    return {
417      _instance: null,
418      createInstance: function XPCU_SF_createInstance(aOuter, aIID) {
419        if (aOuter !== null) {
420          throw Cr.NS_ERROR_NO_AGGREGATION;
421        }
422        if (this._instance === null) {
423          this._instance = new aServiceConstructor();
424        }
425        return this._instance.QueryInterface(aIID);
426      },
427      lockFactory: function XPCU_SF_lockFactory(aDoLock) {
428        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
429      },
430      QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
431    };
432  },
433
434  /**
435   * Defines a non-writable property on an object.
436   */
437  defineConstant: function XPCOMUtils__defineConstant(aObj, aName, aValue) {
438    Object.defineProperty(aObj, aName, {
439      value: aValue,
440      enumerable: true,
441      writable: false
442    });
443  },
444};
445
446XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
447                                  "resource://gre/modules/Preferences.jsm");
448XPCOMUtils.defineLazyModuleGetter(this, "Services",
449                                  "resource://gre/modules/Services.jsm");
450
451XPCOMUtils.defineLazyServiceGetter(XPCOMUtils, "categoryManager",
452                                   "@mozilla.org/categorymanager;1",
453                                   "nsICategoryManager");
454
455/**
456 * Helper for XPCOMUtils.generateQI to avoid leaks - see bug 381651#c1
457 */
458function makeQI(interfaceNames) {
459  return function XPCOMUtils_QueryInterface(iid) {
460    if (iid.equals(Ci.nsISupports))
461      return this;
462    if (iid.equals(Ci.nsIClassInfo) && "classInfo" in this)
463      return this.classInfo;
464    for (let i = 0; i < interfaceNames.length; i++) {
465      if (Ci[interfaceNames[i]].equals(iid))
466        return this;
467    }
468
469    throw Cr.NS_ERROR_NO_INTERFACE;
470  };
471}
472