platform/xpcom

Unstable

Implement XPCOM objects, factories, and services.

Usage

The xpcom module makes it simpler to perform three main tasks:

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 Unknown, although it may return a custom object that satisfies the nsISupports interface.

Optional options:

Name Type
contract string

A contract ID. Users of XPCOM can use this value to retrieve the factory from Components.classes:

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 contract property.

id string

A class ID. Users of XPCOM can use this value to retrieve the factory using Components.classesByID:

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 id property.

register boolean

By default, the factory is registered as soon as it is constructed. By including this option, set to false, the factory is not automatically registered and you must register it manually using the register() function.

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 false, the factory is not automatically unregistered and you must unregister it manually using the unregister() function.

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 the interfaces property.
  • initializes pending in initialize()
  • 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 the implements option defined in heritage).

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:

// 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 the nsISupports 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 and id 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/or unregister options, set to false. If you do this, you can use the register() and unregister() 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:

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.