js/xpconnect/loader/XPCOMUtils.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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 }

mercurial