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 theROOT_DIRSline inconfig_build.shso that it reads:
ROOT_DIRS="defaults components"
- In the
components/directory, createMyComponent.idl:
#include "nsISupports.idl"
[scriptable, uuid(86939149-65a1-4777-8133-b97506250963)]
interface nsIMyComponent: nsISupports
{
void reload();
void sayHello();
};
- In the
components/directory, createMyComponent.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-b97506250963inMyComponent.idlandMyComponent.js. (In Debian-based Linux distros, theuuidgentool is found in the thee2fsprogspackage. A Firefox Extension/Theme GUI Generator is also available on the web.) - Compile the IDL. Have the paths of the
xpidltool and of the Mozilla IDL directory handy. Below, those found in themozilla-devpackage 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 createxpcom.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 filetest.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.idland method implementations inxpcom.js. - Of course, none of the
MyComponent.idl,MyComponent.js, andxpcom.jsfiles 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; withreload()you can make changes toxpcom.jsand 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 theloader.loadSubscript(SOURCE, Component.prototype)instruction from global scope and replaceFactorywith 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
Makefileto automate compilation of the IDL. Put it in thecomponents/directory and changeIDLC,INCandXPTSaccording to your setup. Whenever you change the IDL, just issuemake.
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