xpcom

Module xpcom provides low level API for implementing and registering / unregistering various XCOM interfaces.

Implementing XPCOM interfaces

Module exports Unknown exemplar object, that may be extended to implement specific XCOM interface(s). For example nsIObserver may be implemented as follows:

const { Class } = require('api-utils/heritage');
const { Unknown } = require('api-utils/xpcom');
const { Cc, Ci } = require('chrome')
const observerService = Cc["@mozilla.org/observer-service;1"].
                        getService(Ci.nsIObserverService);

// Create my observer exemplar
const SleepObserver = Class({
  extends: Unknown,
  interfaces: [ 'nsIObserver' ],    // Interfaces component implements
  topic: 'sleep_notification',
  initialize: function(fn) { this.fn = fn },
  register: function register() {
    observerService.addObserver(this, this.topic, false);
  },
  unregister: function() {
    observerService.removeObserver(this, this.topic);
  },
  observe: function observe(subject, topic, data) {
    this.fn({
      subject: subject,
      type: topic,
      data: data
    });
  }
});

// create instances of observers
let observer = SleepObserver.new(function(event) {
  console.log('Going to sleep!')
});
// register observer
observer.register();

Implementing XCOM factories

Module exports Factory exemplar, object that may be used to create objects implementing nsIFactory interface:

const { Class } = require('api-utils/heritage');
const { Factory } = require('api-utils/xpcom');
let Request = Class({
  extends: Unknown,
  interfaces: [ 'nsIRequest' ],
  initialize: function initialize() {
    this.pending = false;
    // Do some initialization
  },
  isPending: function() { return this.pending },
  resume: function() { /* Implementation */ },
  suspend: function() { /* Implementation */ },
  cancel: function() { /* Implementation */ },
  initiate: function() {
    this.pending = true;
    /* Implementation */
  }
});

let requestFactory = Factory.new({ Component: Request });

Factories registered into a runtime may be accessed from the rest of the application via standard XPCOM API using factory's auto generated id (optionally you could specify specific id by passing it as an option):

let request = Components.classesByID[requestFactory.id].
  createInstance(Ci.nsIRequest);
request.isPending()     // => false

Be aware that traditional XPCOM API will always return a wrapped JS objects exposing only properties defined by a given interface (nsIRequest) in our case:

request.initiate()      // TypeError: request.initiate is not a function

You still can expose unwrapped JS object, by a special wrappedJSObject property of the component:

let Request = Class({
  extends: Unknown,
  get wrappedJSObject() this,
  interfaces: [ 'nsIRequest' ],
  initialize: function initialize() {
    this.pending = false;
    // Do some initialization
  },
  isPending: function() { return this.pending },
  resume: function() { /* Implementation */ },
  suspend: function() { /* Implementation */ },
  cancel: function() { /* Implementation */ },
  initiate: function() {
    this.pending = true;
    /* Implementation */
  }
});

let requestFactory = Factory.new({ Component: Request });
let request = Components.classesByID[requestFactory.id].
  createInstance(Ci.nsIRequest);
request.isPending();     // => false
request.wrappedJSObject.initiate();
request.isPending();     // => true

Optionally Factory.new may be passed globally unique string in a format of: '@domain.com/unique/identifier;1' as a contract option in order to associate it with it:

let namedFactory = Factory.new({
  contract: '@examples.com/request/factory;1',
  component: Request
});

Such factories when registered can be accessed form rest of the application by human readable contract strings:

let request = Components.classes['@examples.com/request/factory;1'].
               createInstance(Components.interfaces.nsIRequest);

In addition factories associated with a given contract may be replaced at runtime:

let renewedFactory = Factory.new({
  contract: '@examples.com/request/factory;1',
  Component: Class({ extends: Unknown, /* Implementation */ })
})

Unfortunately commonly used Components.classes won't get updated at runtime but there is an alternative, more verbose way to access last registered factory for a given contract:

let id = Components.manager.QueryInterface(Ci.nsIComponentRegistrar).
  contractIDToCID('@examples.com/request/factory;1');
Components.classesByID[requestFactory.id].
  createInstance(Ci.nsISupports);

Module also exports factoryByContract function to simplify this:

factoryByContract('@examples.com/request/factory;1').
  createInstance(Ci.nsISupports);

It's also recommended to construct factories with an optional description property, providing human readable description of it:

let factory = Factory.new({
  contract: '@examples.com/request/factory;1',
  description: 'Super duper request factory',
  Component: Request
});

Registering / Unregistering factories

All factories created using Factory.new get automatically registered into runtime unless explicitly specified otherwise by setting register option to false:

var factoryToRegister = Factory.new({
  register: false,
  Component: Class({ extends: Unknown, /* Implementation */ })
});

Such factories still may be registered manually using exported register function:

const { register } = require('api-utils/xpcom');
register(factoryToRegister);

All factories created using Factory.new also get unregistered automatically when add-on is unloaded. This also can be disabled by setting unregister option to false.

var factoryToUnregister = Service.new({
  unregister: false,
  Component: Class({ extends: Unknown, /* Implementation */ })
});

All registered services may be unregistered at any time using exported unregister function:

unregister(factoryToUnregister);

Implementing XCOM services

Module exports Service exemplar object, that has exact same API as Factory and can be used to register services:

const { Service } = require('api-utils/xpcom');
let service = Service.new({
  contract: '@examples/demo/service;1',
  description: 'My demo service',
  Component: Class({
    extends: Unknown,
    // Implementation
    get wrappedJSObject() this
  })
});

Registered services can be accessed through the rest of the application via standard XPCOM API:

let s = Components.classes['@examples/demo/service;1'].
  getService(Components.interfaces.nsISupports);

In contrast to factories, services do not create instances of enclosed components, they expose component itself. Also please note that idiomatic way to work with a service is via getService method:

s.wrappedJSObject === service.component // => true