Unstable
Implement XPCOM objects, factories, and services.
Usage
The xpcom
module makes it simpler to perform three main tasks:
- Implement XPCOM object interfaces
- Implement and register XPCOM factories
- Implement and register XPCOM services
If all you need to do is use XPCOM objects that someone else has implemented, then you don't need to use this module. You can just use require("chrome")
to get direct access to the Components
object, and access XPCOM objects from there.
Implementing XPCOM Interfaces
This module exports a class called Unknown
which implements the fundamental XPCOM interface nsISupports
. By subclassing Unknown
, either using standard JavaScript inheritance or using the SDK's heritage
module, you can provide your own implementations of XPCOM interfaces.
"Unknown
" is named after the "IUnknown
" interface in COM.
For example, the add-on below implements the nsIObserver
interface to listen for and log all topic notifications:
var { Class } = require('sdk/core/heritage'); var { Unknown } = require('sdk/platform/xpcom'); var { Cc, Ci } = require('chrome') var observerService = Cc['@mozilla.org/observer-service;1']. getService(Ci.nsIObserverService); var StarObserver = Class({ extends: Unknown, interfaces: [ 'nsIObserver' ], topic: '*', register: function register() { observerService.addObserver(this, this.topic, false); }, unregister: function() { observerService.removeObserver(this, this.topic); }, observe: function observe(subject, topic, data) { console.log('star observer:', subject, topic, data); } }); var starobserver = StarObserver(); starobserver.register();
Implementing XPCOM Factories
The xpcom
module exports a class called Factory
which implements the nsIFactory
interface. You can use this class to register factories for XPCOM components you have defined.
For example, this add-on defines a subclass of Unknown
called HelloWorld
that implements a function called hello
. By creating a Factory
and passing it the contract ID for the HelloWorld
component and the HelloWorld
constructor, we enable XPCOM clients to access the HelloWorld
component, given its contract ID.
In this example the HelloWorld
component is available to JavaScript only, so we use the technique documented under the "Using wrappedJSObject" section of How to Build an XPCOM Component in JavaScript.
var { Class } = require('sdk/core/heritage'); var { Unknown, Factory } = require('sdk/platform/xpcom'); var { Cc, Ci } = require('chrome'); var contractId = '@me.org/helloworld'; // Define a component var HelloWorld = Class({ extends: Unknown, get wrappedJSObject() this, hello: function() {return 'Hello World';} }); // Create and register the factory var factory = Factory({ contract: contractId, Component: HelloWorld }); // XPCOM clients can retrieve and use this new // component in the normal way var wrapper = Cc[contractId].createInstance(Ci.nsISupports); var helloWorld = wrapper.wrappedJSObject; console.log(helloWorld.hello());
Using class ID
You can specify a class ID for the factory by setting the id
option in the factory's constructor. If you don't specify a class ID, then the factory will generate one. Either way, it will be accessible as the value of the factory's id
property.
XPCOM users can look up the factory using the class ID instead of the contract ID. Here's the example above, rewritten to use class ID instead of contract ID for lookup:
var { Class } = require('sdk/core/heritage'); var { Unknown, Factory } = require('sdk/platform/xpcom'); var { Cc, Ci, components } = require('chrome'); // Define a component var HelloWorld = Class({ extends: Unknown, get wrappedJSObject() this, hello: function() {return 'Hello World';} }); // Create and register the factory var factory = Factory({ Component: HelloWorld }); var id = factory.id; // Retrieve the factory by class ID var wrapper = components.classesByID[id].createInstance(Ci.nsISupports); var helloWorld = wrapper.wrappedJSObject; console.log(helloWorld.hello());
Replacing Factories
If the factory you create has the same contract ID as an existing registered factory, then your factory will replace the existing one. However, the Components.classes
object commonly used to look up factories by contract ID will not be updated at run time. To access the replacement factory you need to do something like this:
var id = Components.manager.QueryInterface(Ci.nsIComponentRegistrar). contractIDToCID('@me.org/helloworld'); var wrapper = Components.classesByID[id].createInstance(Ci.nsISupports);
The xpcom
module exports a function factoryByContract
to simplify this technique:
var wrapper = xpcom.factoryByContract('@me.org/helloworld').createInstance(Ci.nsISupports);
Registration
By default, factories are registered and unregistered automatically. To learn more about this, see Registering and Unregistering.
Implementing XPCOM Services
The xpcom
module exports a class called Service
which you can use to define XPCOM services, making them available to all XPCOM users.
This example implements a logging service that just appends a timestamp to all logged messages. The logger itself is implemented by subclassing the Unknown
class, then we create a service which associates the logger's constructor with its contract ID. After this, XPCOM users can access the service using the getService()
API:
var { Class } = require('sdk/core/heritage'); var { Unknown, Service } = require('sdk/platform/xpcom'); var { Cc, Ci } = require('chrome'); var contractId = '@me.org/timestampedlogger'; // Implement the service by subclassing Unknown var TimeStampedLogger = Class({ extends: Unknown, get wrappedJSObject() this, log: function(message) { console.log(new Date().getTime() + ' : ' + message); } }); // Register the service using the contract ID var service = Service({ contract: contractId, Component: TimeStampedLogger }); // Access the service using getService() var wrapper = Cc[contractId].getService(Ci.nsISupports); var logger = wrapper.wrappedJSObject; logger.log('a timestamped message');
By default, services are registered and unregistered automatically. To learn more about this, see Registering and Unregistering.
Registering and Unregistering
By default, factories and services are registered with XPCOM automatically when they are created, and unregistered automatically when the add-on that created them is unloaded.
You can override this behavior using the register
and unregister
options to the factory or service constructor:
var xpcom = require('sdk/platform/xpcom'); var factory = xpcom.Factory({ contract: contractId, Component: HelloWorld, register: false, unregister: false, });
If you disable automatic registration in this way, you can use the register()
function to register factories and services:
xpcom.register(factory);
You can use the corresponding unregister()
function to unregister them, whether or not you have disabled automatic unregistration:
xpcom.unregister(factory);
You can find out whether a factory or service has been registered by using the isRegistered()
function:
if (xpcom.isRegistered(factory)) xpcom.unregister(factory);
Globals
Constructors
Factory(options)
Parameters
options : object
Required options:
Name | Type | |
---|---|---|
Component | constructor |
Constructor for the component this factory creates. This will typically return a descendant of |
Optional options:
Name | Type | |
---|---|---|
contract | string |
A contract ID. Users of XPCOM can use this value to retrieve the factory from var factory = Components.classes['@me.org/request'] var component = factory.createInstance(Ci.nsIRequest); This parameter is formally optional, but if you don't specify it, users won't be able to retrieve your factory using a contract ID. If specified, the contract ID is accessible as the value of the factory's |
id | string |
A class ID. Users of XPCOM can use this value to retrieve the factory using var factory = components.classesByID[id]; var component = factory.createInstance(Ci.nsIRequest); This parameter is optional. If you don't supply it, the factory constructor will generate a fresh ID. Either way, it's accessible as the value of the factory's |
register | boolean |
By default, the factory is registered as soon as it is constructed. By including this option, set to |
unregister | boolean |
By default, the factory is unregistered as soon as the add-on which created it is unloaded. By including this option, set to |
Functions
register(factory)
Register the factory or service supplied. If the factory or service is already registered, this function throws Components.results.NS_ERROR_FACTORY_EXISTS
.
By default, factories and services are registered automatically, so you should only call register()
if you have overridden the default behavior.
Parameters
factory : object
The factory or service to register.
unregister(factory)
Unregister the factory or service supplied. If the factory or service is not registered, this function does nothing.
By default, factories and services are unregistered automatically when the add-on that registered them is unloaded.
Parameters
factory : object
The factory or service to unregister.
isRegistered(factory)
Find out whether a factory or service is registered.
Parameters
factory : object
Returns
boolean : True if the factory or service is registered, false otherwise.
autoRegister(path)
Register a component (.manifest) file or all component files in a directory. See nsIComponentRegistrar.autoRegister()
for details.
Parameters
path : string
Path to a component file to be registered or a directory containing component files to be registered.
factoryByID(id)
Given a class ID, this function returns the associated factory or service. If the factory or service isn't registered, this function returns null
.
This function wraps Components.ClassesByID.
Parameters
id : string
A class ID.
Returns
object : The factory or service identified by the class ID.
factoryByContract(contract)
Given a contract ID this function returns the associated factory or service. If the factory or service isn't registered, this function throws Components.results.NS_ERROR_FACTORY_NOT_REGISTERED
.
This function is similar to the standard Components.classes[contractID]
with one significant difference: that Components.classes
is not updated at runtime.
So if a factory is registered with the contract ID "@me.org/myComponent", and another factory is already registered with that contract ID, then:
Components.classes["@me.org/myComponent"]
will return the old factory, while:
xpcom.factoryByContract("@me.org/myComponent")
will return the new one.
Parameters
contract : string
Contract ID of the factory or service to retrieve.
Returns
object : The factory or service identified by the contract ID.
Unknown
This is the base class for all XPCOM objects. It is not intended to be used directly but you can subclass it, either using standard JavaScript inheritance or using the SDK's heritage
module, to create new implementations of XPCOM interfaces. For example, this subclass implements the nsIRequest
interface:
var { Class } = require('sdk/core/heritage'); var { Unknown } = require('sdk/platform/xpcom'); var Request = Class({ extends: Unknown, interfaces: [ 'nsIRequest' ], initialize: function initialize() { this.pending = false; }, isPending: function() { return this.pending; }, resume: function() { console.log('resuming...'); }, suspend: function() { console.log('suspending...'); }, cancel: function() { console.log('canceling...'); } });
This component definition:
- specifies that we support
nsIRequest
using theinterfaces
property. - initializes
pending
ininitialize()
- adds our implementation of the
nsIRequest
interface
Although Request
also implements nsISupports
, there is no need to add it here, because the base class Unknown
declares support for nsISupports
and this is accounted for when retrieving objects.
We can register a factory for this component by using the Factory
class to associate its constructor with its contract ID:
var { Factory } = require('sdk/platform/xpcom'); var { Cc, Ci } = require('chrome'); var contractId = '@me.org/request' // Create and register the factory var factory = Factory({ contract: contractId, Component: Request });
Now XPCOM users can access our implementation in the normal way:
var request = Cc[contractId].createInstance(Ci.nsIRequest); request.resume();
Methods
QueryInterface(interface)
This method is called automatically by XPCOM, so usually you don't need to call it yourself. It is passed an interface identifier and searches for the identifier in the interfaces
property of:
- this object
- any of this object's ancestors
- any classes in the
implements
array property of the instance (for example, any classes added to this object via theimplements
option defined inheritage
).
If it finds a match, it returns this
, otherwise it throws Components.results.NS_ERROR_NO_INTERFACE
.
Parameters
interface : iid
The interface to ask for. This is typically given as a property of the Components.interfaces
object.
Returns
object : The object itself(this
).
Properties
interfaces
The set of interfaces supported by this class. Unknown
sets this to nsISupports
.
Factory
Use this class to register an XPCOM factory. To register a factory for a component, construct a Factory
, giving it:
- a constructor for the component
- a contract ID and/or a class ID
// Create and register the factory var factory = Factory({ contract: '@me.org/myComponent', Component: MyComponent });
- The component constructor typically returns a descendant of
Unknown
, although it may return a custom object that satisfies thensISupports
interface. - The contract ID and/or class ID may be used by XPCOM clients to retrieve the factory. If a class ID is not given, a new one will be generated. The contract ID and class ID are accessible as the values of the
contract
andid
properties, respectively. - By default, the factory is registered when it is created and unregistered when the add-on that created it is unloaded. To override this behavior, you can pass
register
and/orunregister
options, set tofalse
. If you do this, you can use theregister()
andunregister()
functions to register and unregister.
Methods
createInstance(outer, iid)
Creates an instance of the component associated with this factory.
Parameters
outer : null
This argument must be null
, or the function throws Cr.NS_ERROR_NO_AGGREGATION
.
iid : iid
Interface identifier. These objects are usually accessed through the Components.interfaces
, or Ci
, object. The methods of this interface will be callable on the returned object.
If the object implements an interface that's already defined in XPCOM, you can pass that in here:
var about = aboutFactory.createInstance(null, Ci.nsIAboutModule); // You can now access the nsIAboutModule interface of the 'about' object
If you will be getting the wrappedJSObject
property from the returned object to access its JavaScript implementation, pass Ci.nsISupports
here:
var custom = factory.createInstance(null, Ci.nsISupports).wrappedJSObject; // You can now access the interface defined for the 'custom' object
Returns
object : The component created by the factory.
lockFactory()
This method is required by the nsIFactory
interface, but as in most implementations it does nothing interesting.
QueryInterface()
See the documentation for Unknown.QueryInterface()
.
Properties
interfaces
The set of interfaces supported by this object. Factory
sets this to nsIFactory
.
id
This factory's class ID.
contract
This factory's contract ID.
Service
Use this class to register an XPCOM service. To register a service for a component, construct a Service
, giving it:
- a constructor for the object implementing the service
- a contract ID and/or a class ID
var service = Service({ contract: contractId, Component: AlertService });
After this, XPCOM users can access the service implementation by supplying the contract ID:
var alertService = Cc[contractId].getService(Ci.nsIAlertsService); alertService.showAlertNotification(...);
The Service
interface is identical to the Factory
interface, so refer to the Factory
interface documentation for details.