Introduction
Here's my first cut at a "Hello World" tutorial for creating an XPCOM component in Javascript. The tutorials, wiki pages and knowledge-base entries that exist on this subject are mostly from the perspective of a C++ developer, not a front-end developer, and therefore seemed to always leave out basic steps and information. Slay me with corrections in the comments :)
UPDATE: I've moved this tutorial to the Mozilla Developer Center, and made a some of the changes recommended in the comments. Please read the updated version instead of this one.
This is a very utilitarian tutorial (say that 10x fast!): I'm not going to describe how and why XPCOM works the way it does, or what every bit of the example code does. That's been detailed elsewhere. I'm just going to show you what you need to do to get things working, in as few and as simple steps as possible.
Caveat: This was done on a Mac. YMMV with Windows.
Prerequisites
Please read the updated version of this tutorial!
Implementation
This example component will expose a single method, which returns the string "Hello World!".
Defining the Interface
XPCOM uses a dialect of IDL to define interfaces, called XPIDL. Here's the XPIDL for our HelloWorld component:
HelloWorld.idl
#include "nsISupports.idl" [scriptable, uuid(1C0E8D86-B661-40d0-AE3D-CA012FADF170)] interface nsIHelloWorld: nsISupports { string hello(); };
Note that you must generate a new UUID for each XPCOM component that you create. A web-based UUID generator is available here.
Creating the Component
HelloWorld.js
/*********************************************************** constants ***********************************************************/ // reference to the interface defined in nsIHelloWorld.idl const nsIHelloWorld = Components.interfaces.nsIHelloWorld; // reference to the required base interface that all components must support const nsISupports = Components.interfaces.nsISupports; // guid uniquely identifying our component const CLASS_ID = Components.ID("{1C0E8D86-B661-40d0-AE3D-CA012FADF170}"); // description const CLASS_NAME = "My Hello World Javascript XPCOM Component"; // textual unique identifier const CONTRACT_ID = "@dietrich.ganx4.com/helloworld;1"; /*********************************************************** class definition ***********************************************************/ //class constructor function HelloWorld() { }; // class definition HelloWorld.prototype = { // define the function we want to expose in our interface hello: function() { return "Hello World!"; }, QueryInterface: function(aIID) { if (!aIID.equals(nsIHelloWorld) && !aIID.equals(nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } }; /*********************************************************** class factory This object is a member of the global-scope Components.classes. It is keyed off of the contract ID. Eg: myHelloWorld = Components.classes["@dietrich.ganx4.com/helloworld;1"]. createInstance(Components.interfaces.nsIHelloWorld); ***********************************************************/ var HelloWorldFactory = { createInstance: function (aOuter, aIID) { if (aOuter != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new HelloWorld()).QueryInterface(aIID); } }; /*********************************************************** module definition (xpcom registration) ***********************************************************/ var HelloWorldModule = { _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(Components.interfaces.nsIComponentRegistrar); aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); }, unregisterSelf: function(aCompMgr, aLocation, aType) { aCompMgr = aCompMgr. QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation); }, getClassObject: function(aCompMgr, aCID, aIID) { if (!aIID.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; if (aCID.equals(CLASS_ID)) return HelloWorldFactory; throw Components.results.NS_ERROR_NO_INTERFACE; }, canUnload: function(aCompMgr) { return true; } }; /*********************************************************** module initialization When the application registers the component, this function is called. ***********************************************************/ function NSGetModule(aCompMgr, aFileSpec) { return HelloWorldModule; }
Usage
Compile Typelib
Here {objdir} is the directory specified for the MOZ_OBJDIR option in your .mozconfig file.
- Copy HelloWorld.idl to {objdir}/dist/idl/
- Copy HelloWorld.js to {objdir}/dist/bin/components/
- Change directories to {objdir}/dist/idl/
- $../bin/xpidl -m typelib -w -v -e ../bin/components/HelloWorld.xpt HelloWorld.idl
This will create a typelib file at {objdir}/dist/bin/compontents/HelloWorld.xpt.
Installation
For extensions:
- Copy HelloWorld.js and HelloWorld.xpt to {extensiondir}/components/
- Delete compreg.dat and xpti.dat from your profile directory.
- Restart application
For Firefox core, using the application that you just built:
- Copy HelloWorld.js and HelloWorld.xpt to the {objdir}/dist/bin/components directory
- Delete compreg.dat and xpti.dat from the components directory.
- Delete compreg.dat and xpti.dat from your profile directory.
- Restart application
Using Your Component
var oMyComponent = Components.classes['@dietrich.ganx4.com/helloworld;1']. createInstance(Components.interfaces.nsIHelloWorld); alert(oMyComponent.hello());