How to Add an XPCOM Component in Javascript and Expose it to DOM

From MozillaWiki
Jump to: navigation, search

Introduction

This article is based on experience of programming in B2G environment. Whether it applies to firefox desktop edition has not been tested.

All directory structures are by default under gecko directory, if not specified.

Basic Procedure

Say the component name is "Example". Go through this basic procedure and at the end you should be able to see navigator.mozExample in DOM and access its attributes and methods. Then you can continue to add permission control and add test.

  1. Add code to the files listed below following example patches:
    • b2g/installer/package-manifest.in
    • browser/installer/package-manifest.in
    • dom/Makefile.in
    • dom/dom-config.mk
    • layout/build/Makefile.in
    • toolkit/toolkit-makefiles.sh
  2. Create dom/example/ as the new component's directory.
  3. Create these files under dom/example/:
    • Makefile.in
    • nsIDOMExample.idl
    • Example.js
    • Example.manifest

nsIDOMExample.idl

Define the IDL following the example patches. Here's a simplified one with just one attribute and one function.

#include "nsISupports.idl"

[scriptable, uuid(<uuid>)]
interface nsIDOMMozExample: nsISupports
{
    readonly attribute DOMString test;
    DOMString hello();
};

Example.manifest

component <uuid> Example.js
contract @mozilla.org/example;1 <uuid>
category Javascript-navigator-property mozExample @mozilla.org/example;1

This builds the connection between uuid, js file and navigator attribute we want to expose. By referencing navigator.mozExample, we eventually reach Example.js

Example.js

This is the implementation of the this component. It need to realize all the interfaces defined in nsIDOMExample.idl.

  1. Copy code from the example patches.
  2. Replace the all interface functions and private functions to yours while keeping the nsIDOMGlobalPropertyInitializer implementation part.
  3. Empty the init() function.
  4. Modify remaining code carefully to avoid name mismatching.

Here's the simplest version.

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

function Example() { }

Example.prototype = {
    classDescription: "My Example Javascript XPCOM Component",
    classID:          Components.ID("{<uuid>}"),
    contractID:       "@mozilla.org/example;1",
    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIDOMMozExample]),
    get test() { return "This is a test."; },
    hello: function() { return "Hello World!"; },
    init: function(aWindow) {},
    uninit: function unit() {}
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Example]);

Here's a more practical one. You can use it as a template.

"use strict";

const DEBUG = false;

function debug(aStr) {
  if (DEBUG)
    dump("Example: " + aStr + "\n");
}

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");

const EXAMPLE_CONTRACTID = "@mozilla.org/example;1";
const EXAMPLE_CID        = Components.ID("{<uuid>}");
const nsIDOMMozExample   = Ci.nsIDOMMozExample;
const nsIClassInfo             = Ci.nsIClassInfo;

function Example() {
    debug("Constructor");
}

Example.prototype = {

    __proto__: DOMRequestIpcHelper.prototype,

    classID : EXAMPLE_CID,

    QueryInterface : XPCOMUtils.generateQI([nsIDOMExample, Ci.nsIDOMGlobalPropertyInitializer]),

    classInfo : XPCOMUtils.generateCI({ classID: EXAMPLE_CID,
                                     contractID: EXAMPLE_CONTRACTID,
                                     classDescription: "Example",
                                     interfaces: [nsIDOMMozExample],
                                     flags: nsIClassInfo.DOM_OBJECT }),

    get test() {
        return "This is a test.";
    },

    hello: function() {
        debug("hello");
        return "Hello world!";
    },

    // nsIDOMGlobalPropertyInitializer implementation
    init: function(aWindow) {
        debug("init()");
    },

    // Called from DOMRequestIpcHelper.
    uninit: function() {
        debug("uninit()");
    }
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Example])

Makefile.in

Copy code from the example patches and remove test-related code. Here's a simplified version:

DEPTH       = @DEPTH@
topsrcdir   = @top_srcdir@
srcdir      = @srcdir@
VPATH       = @srcdir@

include $(DEPTH)/config/autoconf.mk

MODULE              = dom
XPIDL_MODULE        = dom_example
LIBRARY_NAME        = domexample_s
LIBXUL_LIBRARY      = 1
FORCE_STATIC_LIB    = 1
GRE_MODULE          = 1
FAIL_ON_WARNINGS := 1

include $(topsrcdir)/dom/dom-config.mk

EXPORTS_NAMESPACES = mozilla/dom/example

EXTRA_COMPONENTS =       \
  Example.js       \
  Example.manifest \
  $(NULL)

XPIDLSRCS =               \
  nsIDOMExample.idl \
  $(NULL)

include $(topsrcdir)/config/config.mk
include $(topsrcdir)/ipc/chromium/chromium-config.mk
include $(topsrcdir)/config/rules.mk

XPIDL_FLAGS += \
  -I$(topsrcdir)/dom/interfaces/base \
  $(NULL)

Add Permission Control

TBW

Add Test

TBW

Resources

Documents

How to build an XPCOM component in Javascript

Adding APIs to the navigator object

B2G tutorial slides, 3_Gecko, How to add components to XPCOM

Example Patches

Example of AlarmsManager

Example of WebFM