michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 sts=2 et filetype=javascript michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * Utilities for JavaScript components loaded by the JS component michael@0: * loader. michael@0: * michael@0: * Import into a JS component using michael@0: * 'Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");' michael@0: * michael@0: * Exposing a JS 'class' as a component using these utility methods consists michael@0: * of several steps: michael@0: * 0. Import XPCOMUtils, as described above. michael@0: * 1. Declare the 'class' (or multiple classes) implementing the component(s): michael@0: * function MyComponent() { michael@0: * // constructor michael@0: * } michael@0: * MyComponent.prototype = { michael@0: * // properties required for XPCOM registration: michael@0: * classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), michael@0: * michael@0: * // [optional] custom factory (an object implementing nsIFactory). If not michael@0: * // provided, the default factory is used, which returns michael@0: * // |(new MyComponent()).QueryInterface(iid)| in its createInstance(). michael@0: * _xpcom_factory: { ... }, michael@0: * michael@0: * // QueryInterface implementation, e.g. using the generateQI helper michael@0: * QueryInterface: XPCOMUtils.generateQI( michael@0: * [Components.interfaces.nsIObserver, michael@0: * Components.interfaces.nsIMyInterface, michael@0: * "nsIFoo", michael@0: * "nsIBar" ]), michael@0: * michael@0: * // [optional] classInfo implementation, e.g. using the generateCI helper. michael@0: * // Will be automatically returned from QueryInterface if that was michael@0: * // generated with the generateQI helper. michael@0: * classInfo: XPCOMUtils.generateCI( michael@0: * {classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), michael@0: * contractID: "@example.com/xxx;1", michael@0: * classDescription: "unique text description", michael@0: * interfaces: [Components.interfaces.nsIObserver, michael@0: * Components.interfaces.nsIMyInterface, michael@0: * "nsIFoo", michael@0: * "nsIBar"], michael@0: * flags: Ci.nsIClassInfo.SINGLETON}), michael@0: * michael@0: * // The following properties were used prior to Mozilla 2, but are no michael@0: * // longer supported. They may still be included for compatibility with michael@0: * // prior versions of XPCOMUtils. In Mozilla 2, this information is michael@0: * // included in the .manifest file which registers this JS component. michael@0: * classDescription: "unique text description", michael@0: * contractID: "@example.com/xxx;1", michael@0: * michael@0: * // [optional] an array of categories to register this component in. michael@0: * _xpcom_categories: [{ michael@0: * // Each object in the array specifies the parameters to pass to michael@0: * // nsICategoryManager.addCategoryEntry(). 'true' is passed for michael@0: * // both aPersist and aReplace params. michael@0: * category: "some-category", michael@0: * // optional, defaults to the object's classDescription michael@0: * entry: "entry name", michael@0: * // optional, defaults to the object's contractID (unless michael@0: * // 'service' is specified) michael@0: * value: "...", michael@0: * // optional, defaults to false. When set to true, and only if 'value' michael@0: * // is not specified, the concatenation of the string "service," and the michael@0: * // object's contractID is passed as aValue parameter of addCategoryEntry. michael@0: * service: true, michael@0: * // optional, it can be an array of applications' IDs in the form: michael@0: * // [ "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}", ... ] michael@0: * // If defined the component will be registered in this category only for michael@0: * // the provided applications. michael@0: * apps: [...] michael@0: * }], michael@0: * michael@0: * // ...component implementation... michael@0: * }; michael@0: * michael@0: * 2. Create an array of component constructors (like the one michael@0: * created in step 1): michael@0: * var components = [MyComponent]; michael@0: * michael@0: * 3. Define the NSGetFactory entry point: michael@0: * this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components); michael@0: */ michael@0: michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "XPCOMUtils" ]; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: this.XPCOMUtils = { michael@0: /** michael@0: * Generate a QueryInterface implementation. The returned function must be michael@0: * assigned to the 'QueryInterface' property of a JS object. When invoked on michael@0: * that object, it checks if the given iid is listed in the |interfaces| michael@0: * param, and if it is, returns |this| (the object it was called on). michael@0: * If the JS object has a classInfo property it'll be returned for the michael@0: * nsIClassInfo IID, generateCI can be used to generate the classInfo michael@0: * property. michael@0: */ michael@0: generateQI: function XPCU_generateQI(interfaces) { michael@0: /* Note that Ci[Ci.x] == Ci.x for all x */ michael@0: return makeQI([Ci[i].name for each (i in interfaces) if (Ci[i])]); michael@0: }, michael@0: michael@0: /** michael@0: * Generate a ClassInfo implementation for a component. The returned object michael@0: * must be assigned to the 'classInfo' property of a JS object. The first and michael@0: * only argument should be an object that contains a number of optional michael@0: * properties: "interfaces", "contractID", "classDescription", "classID" and michael@0: * "flags". The values of the properties will be returned as the values of the michael@0: * various properties of the nsIClassInfo implementation. michael@0: */ michael@0: generateCI: function XPCU_generateCI(classInfo) michael@0: { michael@0: if (QueryInterface in classInfo) michael@0: throw Error("In generateCI, don't use a component for generating classInfo"); michael@0: /* Note that Ci[Ci.x] == Ci.x for all x */ michael@0: var _interfaces = [Ci[i] for each (i in classInfo.interfaces) if (Ci[i])]; michael@0: return { michael@0: getInterfaces: function XPCU_getInterfaces(countRef) { michael@0: countRef.value = _interfaces.length; michael@0: return _interfaces; michael@0: }, michael@0: getHelperForLanguage: function XPCU_getHelperForLanguage(language) null, michael@0: contractID: classInfo.contractID, michael@0: classDescription: classInfo.classDescription, michael@0: classID: classInfo.classID, michael@0: implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, michael@0: flags: classInfo.flags, michael@0: QueryInterface: this.generateQI([Ci.nsIClassInfo]) michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Generate a NSGetFactory function given an array of components. michael@0: */ michael@0: generateNSGetFactory: function XPCU_generateNSGetFactory(componentsArray) { michael@0: let classes = {}; michael@0: for each (let component in componentsArray) { michael@0: if (!(component.prototype.classID instanceof Components.ID)) michael@0: throw Error("In generateNSGetFactory, classID missing or incorrect for component " + component); michael@0: michael@0: classes[component.prototype.classID] = this._getFactory(component); michael@0: } michael@0: return function NSGetFactory(cid) { michael@0: let cidstring = cid.toString(); michael@0: if (cidstring in classes) michael@0: return classes[cidstring]; michael@0: throw Cr.NS_ERROR_FACTORY_NOT_REGISTERED; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Defines a getter on a specified object that will be created upon first use. michael@0: * michael@0: * @param aObject michael@0: * The object to define the lazy getter on. michael@0: * @param aName michael@0: * The name of the getter to define on aObject. michael@0: * @param aLambda michael@0: * A function that returns what the getter should return. This will michael@0: * only ever be called once. michael@0: */ michael@0: defineLazyGetter: function XPCU_defineLazyGetter(aObject, aName, aLambda) michael@0: { michael@0: Object.defineProperty(aObject, aName, { michael@0: get: function () { michael@0: delete aObject[aName]; michael@0: return aObject[aName] = aLambda.apply(aObject); michael@0: }, michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Defines a getter on a specified object for a service. The service will not michael@0: * be obtained until first use. michael@0: * michael@0: * @param aObject michael@0: * The object to define the lazy getter on. michael@0: * @param aName michael@0: * The name of the getter to define on aObject for the service. michael@0: * @param aContract michael@0: * The contract used to obtain the service. michael@0: * @param aInterfaceName michael@0: * The name of the interface to query the service to. michael@0: */ michael@0: defineLazyServiceGetter: function XPCU_defineLazyServiceGetter(aObject, aName, michael@0: aContract, michael@0: aInterfaceName) michael@0: { michael@0: this.defineLazyGetter(aObject, aName, function XPCU_serviceLambda() { michael@0: return Cc[aContract].getService(Ci[aInterfaceName]); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Defines a getter on a specified object for a module. The module will not michael@0: * be imported until first use. michael@0: * michael@0: * @param aObject michael@0: * The object to define the lazy getter on. michael@0: * @param aName michael@0: * The name of the getter to define on aObject for the module. michael@0: * @param aResource michael@0: * The URL used to obtain the module. michael@0: * @param aSymbol michael@0: * The name of the symbol exported by the module. michael@0: * This parameter is optional and defaults to aName. michael@0: */ michael@0: defineLazyModuleGetter: function XPCU_defineLazyModuleGetter(aObject, aName, michael@0: aResource, michael@0: aSymbol) michael@0: { michael@0: this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() { michael@0: var temp = {}; michael@0: Cu.import(aResource, temp); michael@0: return temp[aSymbol || aName]; michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Convenience access to category manager michael@0: */ michael@0: get categoryManager() { michael@0: return Components.classes["@mozilla.org/categorymanager;1"] michael@0: .getService(Ci.nsICategoryManager); michael@0: }, michael@0: michael@0: /** michael@0: * Helper which iterates over a nsISimpleEnumerator. michael@0: * @param e The nsISimpleEnumerator to iterate over. michael@0: * @param i The expected interface for each element. michael@0: */ michael@0: IterSimpleEnumerator: function XPCU_IterSimpleEnumerator(e, i) michael@0: { michael@0: while (e.hasMoreElements()) michael@0: yield e.getNext().QueryInterface(i); michael@0: }, michael@0: michael@0: /** michael@0: * Helper which iterates over a string enumerator. michael@0: * @param e The string enumerator (nsIUTF8StringEnumerator or michael@0: * nsIStringEnumerator) over which to iterate. michael@0: */ michael@0: IterStringEnumerator: function XPCU_IterStringEnumerator(e) michael@0: { michael@0: while (e.hasMore()) michael@0: yield e.getNext(); michael@0: }, michael@0: michael@0: /** michael@0: * Returns an nsIFactory for |component|. michael@0: */ michael@0: _getFactory: function XPCOMUtils__getFactory(component) { michael@0: var factory = component.prototype._xpcom_factory; michael@0: if (!factory) { michael@0: factory = { michael@0: createInstance: function(outer, iid) { michael@0: if (outer) michael@0: throw Cr.NS_ERROR_NO_AGGREGATION; michael@0: return (new component()).QueryInterface(iid); michael@0: } michael@0: } michael@0: } michael@0: return factory; michael@0: }, michael@0: michael@0: /** michael@0: * Allows you to fake a relative import. Expects the global object from the michael@0: * module that's calling us, and the relative filename that we wish to import. michael@0: */ michael@0: importRelative: function XPCOMUtils__importRelative(that, path, scope) { michael@0: if (!("__URI__" in that)) michael@0: throw Error("importRelative may only be used from a JSM, and its first argument "+ michael@0: "must be that JSM's global object (hint: use this)"); michael@0: let uri = that.__URI__; michael@0: let i = uri.lastIndexOf("/"); michael@0: Components.utils.import(uri.substring(0, i+1) + path, scope || that); michael@0: }, michael@0: michael@0: /** michael@0: * generates a singleton nsIFactory implementation that can be used as michael@0: * the _xpcom_factory of the component. michael@0: * @param aServiceConstructor michael@0: * Constructor function of the component. michael@0: */ michael@0: generateSingletonFactory: michael@0: function XPCOMUtils_generateSingletonFactory(aServiceConstructor) { michael@0: return { michael@0: _instance: null, michael@0: createInstance: function XPCU_SF_createInstance(aOuter, aIID) { michael@0: if (aOuter !== null) { michael@0: throw Cr.NS_ERROR_NO_AGGREGATION; michael@0: } michael@0: if (this._instance === null) { michael@0: this._instance = new aServiceConstructor(); michael@0: } michael@0: return this._instance.QueryInterface(aIID); michael@0: }, michael@0: lockFactory: function XPCU_SF_lockFactory(aDoLock) { michael@0: throw Cr.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) michael@0: }; michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Helper for XPCOMUtils.generateQI to avoid leaks - see bug 381651#c1 michael@0: */ michael@0: function makeQI(interfaceNames) { michael@0: return function XPCOMUtils_QueryInterface(iid) { michael@0: if (iid.equals(Ci.nsISupports)) michael@0: return this; michael@0: if (iid.equals(Ci.nsIClassInfo) && "classInfo" in this) michael@0: return this.classInfo; michael@0: for each(let interfaceName in interfaceNames) { michael@0: if (Ci[interfaceName].equals(iid)) michael@0: return this; michael@0: } michael@0: michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: }; michael@0: }