Your First Javascript XPCOM Component in 10 Minutes

Steps

  • Prepare the skeleton for an extension. (The Firefox extension wizard can do it for you. If you use it, call the extension “myextension” otherwise the code below won’t work.)
  • Only if you used the extension wizard: create a components/ directory in your tree and change the ROOT_DIRS line in config_build.sh so that it reads:

ROOT_DIRS="defaults components" 

  • In the components/ directory, create MyComponent.idl:

#include "nsISupports.idl" 

[scriptable, uuid(86939149-65a1-4777-8133-b97506250963)]
interface nsIMyComponent: nsISupports
{
    void reload();
    void sayHello();
};

  • In the components/ directory, create MyComponent.js:

/* ---------------------------------------------------------------------- */
/* Component specific code.                                               */

const CLASS_ID = Components.ID('{86939149-65a1-4777-8133-b97506250963}');
const CLASS_NAME = 'My XPCOM Component';
const CONTRACT_ID = '@example.org/mycomponent;1';
const SOURCE = 'chrome://myextension/content/xpcom.js';
const INTERFACE = Components.interfaces.nsIMyComponent;

/* ---------------------------------------------------------------------- */
/* Template.  No need to modify the code below.                           */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const loader = Cc['@mozilla.org/moz/jssubscript-loader;1']
    .getService(Ci.mozIJSSubScriptLoader);

function Component() {
    this.wrappedJSObject = this;
}

Component.prototype = {
    reload: function() {
        loader.loadSubScript(SOURCE, this.__proto__);
    },

    QueryInterface: function(aIID) {
        if(!aIID.equals(INTERFACE) &&
           !aIID.equals(Ci.nsISupports))
            throw Cr.NS_ERROR_NO_INTERFACE;
        return this;
    }
};
loader.loadSubScript(SOURCE, Component.prototype);

var Factory = {
    createInstance: function(aOuter, aIID) {
        if(aOuter != null)
            throw Cr.NS_ERROR_NO_AGGREGATION;
        var component = new Component();
        if(typeof(component.init) == 'function')
            component.init();

        return component.QueryInterface(aIID);
    }
};

var Module = {
    _firstTime: true,

    registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) {
        if (this._firstTime) {
            this._firstTime = false;
            throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
        };
        aCompMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
        aCompMgr.registerFactoryLocation(
            CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType);
    },

    unregisterSelf: function(aCompMgr, aLocation, aType) {
        aCompMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
        aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);
    },

    getClassObject: function(aCompMgr, aCID, aIID) {
        if (!aIID.equals(Ci.nsIFactory))
            throw Cr.NS_ERROR_NOT_IMPLEMENTED;

        if (aCID.equals(CLASS_ID))
            return Factory;

        throw Cr.NS_ERROR_NO_INTERFACE;        
    },

    canUnload: function(aCompMgr) { return true; }
};

function NSGetModule(aCompMgr, aFileSpec) { return Module; }

  • Generate an UUID that will identify the component uniquely and use it to replace 86939149-65a1-4777-8133-b97506250963 in MyComponent.idl and MyComponent.js. (In Debian-based Linux distros, the uuidgen tool is found in the the e2fsprogs package. A Firefox Extension/Theme GUI Generator is also available on the web.)
  • Compile the IDL. Have the paths of the xpidl tool and of the Mozilla IDL directory handy. Below, those found in the mozilla-dev package from Debian-based Linux distros are used.

$ /usr/lib/mozilla/xpidl -m typelib -w -v -I /usr/share/idl/mozilla -e MyComponent.xpt MyComponent.idl
  • In the content/ directory create xpcom.js:

function init() {
    this._message = 'Hello, XPCOM world!';
}

function sayHello() {
    Components
        .classes["@mozilla.org/embedcomp/prompt-service;1"]
        .getService(Components.interfaces.nsIPromptService)
        .alert(null, 'Greeting...', this._message);
}

  • In the content/ directory create a test file test.xul:

<?xml version="1.0"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <script type="application/x-javascript">
    function sayHello() {
        Components
            .classes['@example.org/mycomponent;1']
            .getService(Components.interfaces.nsIMyComponent)
            .sayHello();
    }
  </script>

  <button label="Test" oncommand="sayHello();"/>
</window>

  • Register the extension, either by packaging and installing it or by creating a pointer to its directory.
  • (Re)start Firefox and open chrome://myextension/content/test.xul. Clicking on the button should have a greeting pop up. Done!

Explanation

This technique moves the parts common to Javascript XPCOM components (factory, registration, etc.) to a single place that can be left alone (second part of MyComponent.js), the ones related to XPCOM bookkeeping (class ID, contract ID, etc.) to another that can be customized (first part of MyComponent.js), and the code that actually does something to yet another place (xpcom.js) which is where the 99% of the writing is done.

If an init() function is provided in xpcom.js, it will be run each time an instance is created (or, in the case of a service, the first time a reference is acquired).

Customizations

  • In MyComponent.js, changing the first five lines should be enough for most situations.
  • Add more method definitions to MyComponent.idl and method implementations in xpcom.js.
  • Of course, none of the MyComponent.idl, MyComponent.js, and xpcom.js files needs to be named that way, choose what is appropriate.

Optimizations

  • When developing XPCOM services (i.e. components that are only instantiated the first time, then reused), particularly with MozRepl, the reload() method will come in handy; with reload() you can make changes to xpcom.js and reload it without restarting Firefox. Sample MozRepl session:

> var c = Components.classes['@example.org/mycomponent;1'].getService(Components.interfaces.nsIMyComponent);
> c.sayHello();
> // Modify and save xpcom.js
> c.reload();
> c.sayHello();

Changes to the MyComponent.idl will still require an IDL recompilation and a restart.

  • For non-service components, a trick to avoid restarting Firefox is to cause a fresh component definition to be loaded each time an instance is created. Delete the function Component() { ... } definition and the loader.loadSubscript(SOURCE, Component.prototype) instruction from global scope and replace Factory with the following code:

var Factory = {
    createInstance: function(aOuter, aIID) {
        if(aOuter != null)
            throw Cr.NS_ERROR_NO_AGGREGATION;

        function Component() {
           this.wrappedJSObject = this;
        }
        loader.loadSubScript(SOURCE, Component.prototype);
        var component = new Component();
        component.init();

        return component.QueryInterface(aIID);
    }
};

Loading the definition each time is a performance hit, you’ll want to only use this during development or on components not too frequently instantiated.

  • Here is a Makefile to automate compilation of the IDL. Put it in the components/ directory and change IDLC, INC and XPTS according to your setup. Whenever you change the IDL, just issue make.

IDLC=/usr/lib/firefox/xpidl
INC=/usr/share/idl/mozilla/
XPTS=MyComponent.xpt

all: $(XPTS)

%.xpt: %.idl
    $(IDLC) -m typelib -w -v -I $(INC) -e $(@) $(<)

.PHONY: all

Its excellent tutorials for beginners

Hi,

It was a very good article up till the last point but it would have been great if you could have taken some time and explained about regestring the componets .

So, please do that looking forward to your response.

Thanks!

Registration happens automatically when you install the extension. Or did you mean something else?

Hi, your article was so great and helpful for me.

Can your programm run on xulrunner applications?

I had tried it on xulrunner and window. Using Gecko-SDK, i compiled the “MyComponent.idl” xpidl -m typelib -w -v -I c:\xulrunner\gecko-sdk\idl -e MyComponent.xpt f:\temp\components\MyComponent.idl and then got the “MyComponent.xpt” in the “temp” folder. so I copied it to “temp\components\” or “xulrunner\components\”, and incremented the “buildID” in “application.ini” file. finally I executed my programm with “xulrunner application.ini”. but it didn’t work. and threw an error:”Components.classes[’@example.org/mycomponent;1’] has no property.” I was very confused on it! looking forward to your response. Thanks a lot!

shine,

glad you found it helpful. By compiling the IDL and placing the xpt under components/, you make the component’s interface available, but you still need the component’s implementation. That’s what MyComponent.js provides; place that along with MyComponent.xpt under components/.

Thanks! I had found the problem. since I had made a stupid mistake: wrong path! The path must be accordant. After changeing the name of “temp” folder. it works.

For non-service components, to me, it worked only under placing the Component.prototype={...} into the definition of Factory!

Thanks , dude,,,
It's greate for beginners......

I'm using MozRepl with Emacs. I setted

var c = Components.classes['@myext/service;1'].getService(Components.interfaces.nsIMyComponent);

using my own component data.
When I run "c.reload();" to reload XPCOM, avoiding to restart FF, I got an error:


!!! [Exception... "'[JavaScript Error: "redeclaration of const XXX" {file: "chrome://myext/content/service.js" line: 26}]' when calling method: [nsIXPCOMComponent::reload]" nsresult: "0x80570021 (NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS)" location: "JS frame :: chrome://mozlab/content/mozrepl/repl.js -> file:///home/hamen/.mozilla/firefox/tnbxv0qa.profile/mozrepl.tmp.js :: :: line 1" data: yes]

Any help?
Thanks
hamen

@hamen: yes, don't declare XXX as a const!

This is an excellent beginner tutorial for building xpcom components using javascript, would it be possible to do the same using java (javascritpt isn't enough to 'cause I need to interact with some libraries written in java). I already know that through xpidl it's possible to generate the java interfaces to be extended,but i'm not understanding how to register those as xpcom...any suggestion?

Excellent tutorial, thanks a lot!

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

Captcha
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Syndicate content