This article needs a technical review. How you can help.
If you are looking for Add-on SDK solution for XPCOM JavaScript components then check out platform/xpcom module first.
This is a "Hello World" tutorial for creating an XPCOM component in JavaScript. This tutorial does not describe how and why XPCOM works the way it does, or what every bit of the example code does. That's been detailed elsewhere. This tutorial will show you what you need to do to get a component working in as few and as simple steps as possible.
Implementation
This example component will expose a single method, which returns the string "Hello World!".
Defining the Interface
If you want to use your component in other XPCOM components, you must define the interfaces that you want exposed. If you want to use your component only from JavaScript, you can skip to the next section.
There are many interfaces already defined in Mozilla applications, so you may not need to define a new one. You can browse existing XPCOM interfaces at various locations in the Mozilla source code, or using XPCOMViewer, a GUI for browsing registered interfaces and components. You can download an old version of XPCOMViewer that works with Firefox 1.5 from mozdev mirrors.
If an interface exists that meets your needs, then you do not need to write an IDL, or compile a typelib, and may skip to the next section.
If you don't find a suitable pre-existing interface, then you must define your own. XPCOM uses a dialect of IDL to define interfaces, called XPIDL. Here's the XPIDL definition for our HelloWorld component:
HelloWorld.idl
#include "nsISupports.idl" [scriptable, uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)] interface nsIHelloWorld : nsISupports { string hello(); };
Note that you must generate a new UUID for each XPCOM component that you create. See Generating GUIDs for more information.
Compiling the Typelib
Your interface definition must be compiled into a binary format (XPT) in order to be registered and used within Mozilla applications. The compilation can be done using the Gecko SDK. You can learn how to get Mac, Linux, and Windows versions of the Gecko SDK by reading the article Gecko SDK.
For code outside the tree
windows\bin\x86
within the zip into the bin
directory of the Gecko SDK.Execute this command to compile the typelib
. Here, {sdk_dir}
is the directory in which you unpacked the Gecko SDK.
{sdk_dir}/bin/xpidl -m typelib -w -v -I {sdk_dir}/idl -e HelloWorld.xpt HelloWorld.idl
This will create the typelib
file HelloWorld.xpt
in the current working directory.
For a new component in Firefox/Thunderbird/B2G
If you are adding a new feature to applications in the mozilla-central repository, you can create a Makefile listing the IDL files and the build system will automatically generate the typelib. A sample Makefile.in
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
# MODULE specifies where header files from this Makefile are installed
# Use dom if your component implements a DOM API
MODULE = dom
# Name of the typelib
XPIDL_MODULE = dom_apps
# Set to 1 if the module should be part of the Gecko Runtime common to all applications
GRE_MODULE = 1
# The IDL sources
XPIDLSRCS = \
HelloWorld.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk
XPIDL_FLAGS += \
-I$(topsrcdir)/dom/interfaces/base \
-I$(topsrcdir)/dom/interfaces/events \
$(NULL)
Creating the Component Using XPCOMUtils
In Firefox 3 and later you can use import XPCOMUtils.jsm using Components.utils.import to simplify the process of writing your component slightly. The imported library contains functions for generating the module, factory, and the NSGetModule and QueryInterface functions for you. Note: it doesn't do the work of creating your interface definition file or the type library for you, so you still have to go through those steps above if they haven't been done. The library provides a simple example of its use in the source code (js/xpconnect/loader/XPCOMUtils.jsm
), but here's another using this example. To begin, include a line at the top of your interface to import the XPCOMUtils
library:
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
then implement your interface the same way you did above, except with a few modifications so that XPCOMUtils
can set it up properly:
/*********************************************************** class definition ***********************************************************/ //class constructor function HelloWorld() { // If you only need to access your component from JavaScript, uncomment the following line: //this.wrappedJSObject = this; } // class definition HelloWorld.prototype = { // properties required for XPCOM registration: classDescription: "My Hello World JavaScript XPCOM Component", classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), contractID: "@dietrich.ganx4.com/helloworld;1", // [optional] custom factory (an object implementing nsIFactory). If not // provided, the default factory is used, which returns // |(new MyComponent()).QueryInterface(iid)| in its createInstance(). _xpcom_factory: { ... }, // [optional] an array of categories to register this component in. _xpcom_categories: [{ // Each object in the array specifies the parameters to pass to // nsICategoryManager.addCategoryEntry(). 'true' is passed for both // aPersist and aReplace params. category: "some-category", // optional, defaults to the object's classDescription entry: "entry name", // optional, defaults to the object's contractID (unless 'service' is specified) value: "...", // optional, defaults to false. When set to true, and only if 'value' is not // specified, the concatenation of the string "service," and the object's contractID // is passed as aValue parameter of addCategoryEntry. service: true }], // QueryInterface implementation, e.g. using the generateQI helper (remove argument if skipped steps above) QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIHelloWorld]), // Optional, but required if you want your component to be exposed to DOM classInfo: XPCOMUtils.generateCI({classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), contractID: "@dietrich.ganx4.com/helloworld;1", interfaces: [Ci.nsIHelloWorld], flags: Ci.nsIClassInfo.DOM_OBJECT}), // ...component implementation... // define the function we want to expose in our interface hello: function() { return "Hello World!"; }, };
XPCOMUtils
does the work of creating the module and factory for you after this. Finally, you create an array of your components to be created:
var components = [HelloWorld];
and replace NSGetFactory
/NSGetModule
to use this array and XPCOMUtils
:
if ("generateNSGetFactory" in XPCOMUtils) var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); // Firefox 4.0 and higher else var NSGetModule = XPCOMUtils.generateNSGetModule(components); // Firefox 3.x
So the total simplified version of your component now looks like (of course documentation and comments aren't a bad thing, but as a template something smaller is nice to have):
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); function HelloWorld() { } HelloWorld.prototype = { classDescription: "My Hello World JavaScript XPCOM Component", classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), contractID: "@dietrich.ganx4.com/helloworld;1", QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIHelloWorld]), hello: function() { return "Hello World!"; } }; var components = [HelloWorld]; if ("generateNSGetFactory" in XPCOMUtils) var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); // Firefox 4.0 and higher else var NSGetModule = XPCOMUtils.generateNSGetModule(components); // Firefox 3.x
chrome.manifest
; classDescription
and contractID
properties on the other hand are no longer required. See XPCOM changes in Gecko 2.0 for more details.Installation
For extensions:
- Copy
HelloWorld.js
, andHelloWorld.xpt
(only if you defined and compiled the IDL) to{extensiondir}/components/
. - Delete
compreg.dat
andxpti.dat
from your profile directory. - Restart application.
For Firefox:
- Copy
HelloWorld.js
, andHelloWorld.xpt
(only if you defined and compiled the IDL) to the{objdir}/dist/bin/components
directory, if running from the source. - Delete
compreg.dat
andxpti.dat
from the components directory. - Delete
compreg.dat
andxpti.dat
from your profile directory. - Restart application.
Using Your Component
Using wrappedJSObject
If you only intend to access your component from JavaScript, i.e. you skipped the sections "Defining the Interface" and "Compiling the Typelib" above, you uncommented the "wrappedJSObject" line in the class constructor, and you removed the "[Components.interfaces.nsIHelloWorld]" argument for the call to XPCOMUtils.generateQI() in QueryInterface, then you can access your component like this:
try { var myComponent = Components.classes['@dietrich.ganx4.com/helloworld;1'] .getService().wrappedJSObject; alert(myComponent.hello()); } catch (anError) { dump("ERROR: " + anError); }
For more information about wrappedJSObject, see here.
Using XPCOM instantiation
try { var myComponent = Components.classes['@dietrich.ganx4.com/helloworld;1'] .createInstance(Components.interfaces.nsIHelloWorld); alert(myComponent.hello()); } catch (anError) { dump("ERROR: " + anError); }
Other resources
- Two mozillazine forums threads about implementing XPCOM components in JS with some explanations, example code, and troubleshooting tips:
- Implementing XPCOM components in JavaScript at kb.mozillazine.org
- Using XPCOM in JavaScript without leaking - A must-read.
- An example component
- Older JS+XPCOM notes - includes some wrappedJSObject information.
- Writing an XPCOM Service in JavaScript