XPCOM Objects

XPCOM

XPCOM is a cross platform component object model, similar to Microsoft COM.

Taken from the XPCOM page.

Firefox can be seen as composed of two layers. The largest of the two is a compiled platform, mostly written in C++. On top of it lies the chrome, mostly written in XML, Javascript and CSS. In fact, you can separate the two. We often mention other "Mozilla based applications". Well, those are applications that, simply put, take the underlying platform with perhaps a few changes and additions, and then write their own chrome layer. This lower layer is called XULRunner, and it is a very powerful platform, providing a very robust development base for web-enabled, cross-platform applications. The fact that it allows to easily create OS-independent applications is a big selling point for XULRunner.

XPCOM is the way in which the two layers (XULRunner and chrome) communicate. Most of the objects and functions in the lower layers are hidden from the chrome; those that need to be publicized are exposed through XPCOM components and interfaces. You can think of XPCOM as a reference to all the capabilities available on the lower layers of Firefox.

Using XPCOM components is relatively simple, as you've seen in previous examples.

this.obsService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);

The Cc object (Components.classes) is an index to static objects and class definitions available through XPCOM. The string between the brackets is just an identifier, in this case corresponding to the Observer service. You'll usually know what string to use by reading examples and documentation. There is no comprehensive list of these (that we know of), and that's understandable since it would be a very long list, and it can be extended by add-ons. If you want to see the list in your current Firefox installation, just run the following code in the Error Console:

var str = ""; for (var i in Components.classes) { str += i + "\n" }; str

A run on Firefox 3.6.2 with a few extensions installed yields 876 strings. That's quite a lot. Luckily, you'll only need to know a handful of those for extension development. The @mozilla.org/ prefix is just a way to keep things namespaced. We would use something like @xulschool.com/ to make our own components.

Components are either services (static objects) or instances of classes, just like the objects we handle in JS. The method you call on Cc["some-string"] should either be getService or createInstance, depending on what you're asking for. In most cases it is very clear which one to call, but in case of doubt, look for documentation on it. Those two methods always receive the interface identifier as an argument.

Similarly to Cc, Ci (Components.interfaces) is an index of available interfaces. A modified version of the last code snippet produces an even longer list of available interfaces. Just like in component identifiers, the nsI prefix is just a way of keeping things in order. The NS stands for Netscape, Mozilla's predecessor. The "I" stands for interface. Our interfaces should begin with something like xsIHello.

An interface is just a definition of a set of attributes and methods that an object implementing it should have. XPCOM components can implement multiple interfaces, and they often do. Let's look at the Preference service as an example of this. We'll look at its documentation in a very old XUL site called XUL Planet. All of its documentation was planned to be migrated to MDC, but it looks like it was never finished and XUL Planet was discontinued. Their XPCOM documentation is better in terms of seeing the relationships between components and interfaces, so we'll use that.

Another useful resource is this XPCOM reference. This is generated from source, and it's kept relatively up to date. It shows the relationships between components and interfaces, but it's more of a source browser than a documentation reference.

Stepping into the time machine, we see the Preferences Service component page. Right at the top you can see a list of the interfaces it implements, with a link to a documentation page for each one of them. Then you'll see a list of all members of this object, with some documentation about it. It is particularly important to note that, for every member in the component, you'll see in what interface this member is defined. Clicking on the link for the getBranch method takes you to the nsIPrefService documentation page, where you can see more details on the interface and the method. You can also see a list of what components implement this interface. All of this documentation is generated from the one present in the Firefox source files, so it's in general very complete and well written. It's a shame XUL Planet is no longer with us.

Interfaces can be awkward to handle. If you want to call a method or use an attribute of interface X in a component, you first need to "cast" the component to interface X. This is done via the QueryInterface method that is included in all XPCOM components.

this._prefService =
  Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);

this._prefValue = this._prefService.getBoolPref("somePreferenceName");

this._prefService.QueryInterface(Ci.nsIPrefBranch2);
this._prefService.addObserver("somePreferenceName", this, false);
this._prefService.QueryInterface(Ci.nsIPrefBranch);

This is a common piece of code you'll see when initializing components or JSM that rely on preferences. We use the Preferences Service to get and set preference values, such as the preference value we're getting on the fourth line of code. These methods are in the nsIPrefBranch interface. The getService and createInstance methods allow you to get the component already set to an interface. In many cases you only need to use one interface, and you won't have to worry about QueryInterface. But in this case we need to change the interface to nsIPrefBranch2, which is the one that includes the method that adds a preference observer. Then we change it back, because after that we only need to get and set preferences, and those methods are in nsIPrefBranch.

Trying to access methods or attributes without having the right interface set will result in an exception being thrown.

Passing parameters

Passing parameters to XPCOM methods is no different from other JS objects, with some exceptions. In general, you can rely on JavaScript's ability to transform values to the correct type, but it's usually best to pass the right type in the first place. This section is a quick guide on how to read XPCOM documentation, which basically amounts to understanding the syntax of XPIDL, the language used to specify XPCOM interfaces.

At MDC, you'll see stuff like this:

void setCharPref(in string aPrefName, in string aValue);

One of the most important details to notice is that both paratemers have the in keyword. This specifies that these are input parameters, values that the method will use to perform its actions. When is a parameter not an in parameter? In some methods the out keyword is used for parameters that are return values in reality. This is done for certain value types that are not valid as return values in IDL, such as typed arrays.

void getChildList(in string aStartingAt, out unsigned long aCount,[array, size_is(aCount), retval] out string aChildArray);

This method returns an array of strings. The first parameter is an input that tells the method where to start looking. The second one will hold the length of the return array, and the third parameter will hold the array itself. Note the metadata included in the square brackets, indicating that the parameter is an array, and that its size is determined by the aCount parameter.  Here's one way to invoke this method:

let childArrayObj = new Object();
let childArray;

this._prefService.getChildList("", {}, childArrayObj);

// .value holds the actual array.
childArray = childArrayObj.value;

The general rule for out parameters is that you can pass an empty object, and then you can get the result by accessing the value attribute in this object after the method call. The method will set value for you. Also, since JS arrays have the length attribute to get their length, there's no need for the second parameter to be used, so we just pass it an empty object that we won't use. The second parameter is only necessary for callers from within C++ code that use pointers instead of high-level arrays.

Some commonly used XPCOM methods require other XPCOM types as parameters. The addObserver method in nsIPrefBranch2 is an example of this.

void addObserver(in string aDomain, in nsIObserver aObserver, in boolean aHoldWeak);

Luckily, you don't have to do anything special if you want to register your JS object as a preference observer. The nsIObserver has a single method observe, so all you need to do is have an observe method in your object and you'll be OK.

XULSchool.PrefObserver = {
  init: function() {

    this._prefService =
      Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2);
    // pass 'this' as if it implemented nsIObserver.
    this._prefService.addObserver(
      "extensions.xulschoolhello.somePref", this, false);
  },

  observe : function(aSubject, aTopic, aData) {
    // do stuff here.
  }
};

Finally, here's a table summarizing the types you will most likely encounter in XPCOM interfaces, and how to handle them:

JS type IDL types Notes
Strings AUTF8String, string, wstring, char*, others Historically there have been several string types in XPCOM. The currently favored type for most cases is AUTF8String. You can read more about it in the XPCOM String Guide.
Integers short, unsigned short, long, unsigned long, PRInt32, PRUInt32 PRInt32 is the equivalent to long. Most PR* types have an easier to read equivalent, so it is better to use those.
Floating point float  
Boolean boolean, PRBool  
Void void  
Timestamps PRTime This type is used to pass timestamps measured in milliseconds, such as the output of the getTime() method in a Javascript Date object.

There are more details about XPIDL in the XPDIL Syntax definition.

Creating Your Own Components

JavaScript XPCOM Components

As we've said before, we recommend using JSM whenever you can. Yet there are some cases where you don't have a choice and you have to create XPCOM components to add a specific feature. In these cases you can choose between compiled XPCOM components, written in C++, or JS XPCOM components. You should favor the latter, they are much less complicated to make and maintain.

Most of the time you'll need 2 source files for a JS XPCOM component: the IDL interface file, and the implementation JS file. In your final extension XPI you'll need to include the JS implementation file, and the XPT file, which is a compiled version of your IDL file. You won't need the IDL or XPT files if your components only use pre-existing Firefox interfaces. In this case you may also find it easier to implement your component using JSM and the XPCOMUtils module.

Download this version of the Hello World project with XPCOM to see how XPCOM files are structured in the project and built. (Your build will probably break, we'll cover this later on.)

In the components directory, the file xsIHelloCounter.idl has the following contents:

#include "nsISupports.idl"

/**
 * Counter for the Hello World extension. Keeps track of how many times the
 * hello world message has been shown.
 */
[scriptable, uuid(BD46F689-6C1D-47D0-BC07-BB52B546B8B5)]
interface xsIHelloCounter : nsISupports
{
  /* The maximum allowed count. */
  const short MAX_COUNT = 100;

  /* The current count. */
  readonly attribute short count;

  /**
   * Increments the display count and returns the new count.
   * @return the incremented count.
   */
  short increment();
};

The bits about nsISupports are common to most XPCOM interface definitions. nsISupports is the base interface for all interfaces, so it should always be included, except for cases where your interface extends another interface. In those cases you just need to replace nsISupports with the interface you're extending. You can also extend from multiple interfaces, by including a comma-separated list of interfaces instead of only one.

[scriptable, uuid(BD46F689-6C1D-47D0-BC07-BB52B546B8B5)]

The scriptable qualifier says that this component can be accessed from JS code. This can also be specified on a per-method basis, which is something you'll see in some of the interfaces in Firefox, but it's not likely you'll have to do it in your own components. The second part defines a UUID for the interface. You must generate a new one for each interface, and you should change it every time the interface changes. In this case you're forced to use UUID, the email address format used for extension ids won't work.

We included a constant, an attribute and a method to display examples of the 3, but this is clearly an overly elaborate way to keep a simple counter.

You can define numeric and boolean constants in IDL files, but not string constants. This is a known limitation of XPIDL, and a simple workaround is to define a readonly attribute instead. This means you have to define a getter in the implementation file, though. You can access constants through a reference of the component, or directly from the interface:

// these are equivalent.
max = Ci.xsIHelloCounter.MAX_COUNT;
max = counterReference.MAX_COUNT;

The implementation file, xsHelloCounter.js, is much longer. We'll analyze it piece by piece.

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Ce = Components.Exception;

You should be familiar with this already, although there are a couple of additions, Components.results and Components.Exception. They'll be used further ahead.

const CLASS_ID = Components.ID("{37ED5D2A-E223-4386-9854-B64FD38932BF}");
const CLASS_NAME = "Hello World Counter";
const CONTRACT_ID = "@xulschool.com/counter;1";

These constants are used at the bottom, in the component registration code. They specify the details of the component, such as a unique UUID (you have to generate it too and it must be different from the IDL UUID), a descriptive name (this isn't used anywhere that we know of), and the contract ID, which is the string you use to get a reference to the component. The ";1" at the end of the string is supposed to indicate the version of the component, although it shouldn't change much. It can be useful if there are multiple incompatible versions of the component installed at the same time.

The implementation object itself should be easy to understand. The only aspects to take into account are that methods and attributes must have the same names as their IDL counterparts, and that the QueryInterface method is implemented:

QueryInterface : function(aIID) {
  if (!aIID.equals(Ci.xsIHelloCounter) &&
      !aIID.equals(Ci.nsISupports)) {
    throw Cr.NS_ERROR_NO_INTERFACE;
 }

  return this;
}

The method is very simple, it validates that the caller is requesting a supported interface, otherwise it throws an exception.

The rest of the code looks long and complicated, but it is pretty much the same for all components, so you shouldn't worry too much about it. All you have to do to use it in other components is copy it and change some names. The purpose of this code is to register the component so that you can get references to it just like all other Firefox components. It is better read from bottom to top.

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

This piece of code is the first one that Firefox looks for in all implementation files in the components directory. It simply returns the object that precedes it.

var CounterModule = {
  // registerSelf, unregisterSelf, getClassObject, canUnload
};

The only thing you may need to change here is when you need to use the Category Manager. The Category Manager is a service that allows you to register your component under categories that are either pre-existing or you make up. The service also allows you to get all components registered in a category and invoke methods on them. One common use for this service is registering a component as a Content Policy. With it you can detect and filter URL loads. This is covered further ahead in another section of the tutorial.

The add and delete calls to the Category Manager would have to be done in the registerSelf and unregisterSelf methods:

registerSelf : function(aCompMgr, aLocation, aLoaderStr, aType) {

  let categoryManager =
    Cc[@mozilla.org/categorymanager;1].getService(Ci.nsICategoryManager);

  aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  aCompMgr.registerFactoryLocation(
    CLASS_ID, CLASS_NAME, CONTRACT_ID, aLocation, aLoaderStr, aType);
  categoryManager.addCategoryEntry(
    "content-policy", "XULSchool Hello World", CONTRACT_ID, true, true);
},

In this case the component would need to implement nsIContentPolicy.

And, finally, the factory object.

var CounterFactory = {
  /* Single instance of the component. */
  _singletonObj: null,

  createInstance: function(aOuter, aIID) {
    if (aOuter != null) {
      throw Cr.NS_ERROR_NO_AGGREGATION;
    }
    // in this case we need a unique instance of the service.
    if (!this._singletonObj) {
      this._singletonObj = MessageCounter;
    }

    return this._singletonObj.QueryInterface(aIID);
  }
};

If we wanted a class that can be instantiated, instead of a singleton service, the Factory would look like this:

var CounterFactory = {

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

    return (new Counter()).QueryInterface(aIID);
  }
};

The instructions on how to build an IDL file are included in the section Setting up a Development Environment.

C++ XPCOM Components

You do not want to do this unless it's really necessary.

There are few reasons you might need to use binary XPCOM. One of them is adding functionality to Firefox that it doesn't support natively. In that, you would either need to implement this feature for every platform, or limit your extension compatibility to the ones you'll support. You'll need to build a library file for each one of them: DLL for Windows, dylib for Mac (Intel and PPC) and .so for Linux and similar.

We won't get into details about this because it's certainly not tutorial material. This blog post details the XPCOM build set up. And you'll need to read the Build Documentation thoroughly to understand how this all works.

If you need to interact with system libraries without really needing to create one of your own, you should consider using c-types instead. The c-types module is a new bridge between JavaScript and native binaries, introduced in Firefox 3.7. With it, you can interact with existing system libraries without using XPCOM at all.

This tutorial was kindly donated to Mozilla by Appcoast.

Attachments

File Size Date Attached by
HelloWorld4.zip
11684 bytes 2011-04-15 22:05:20 Jorge.villalobos

Document Tags and Contributors

Contributors to this page: wbamberg, Jorge.villalobos, teoli
Last updated by: wbamberg,