Discussion of Using XPCOM Components

From CDOT Wiki
Jump to: navigation, search

Introduction

The following is a discussion with examples of how to use XPCOM components from C++ and JavaScript. It elaborates on an excellent chapter from the Creating XPCOM Components, Using XPCOM Components. All code samples below are taken from the following revision of mozilla-central: http://hg.mozilla.org/mozilla-central/file/0cd41f599080/

The Cookie Manager

Cookie Manager UI

Let's look at how Cookies are managed via XPCOM, and in so doing, learn about how to use XPCOM components from C++ and JS. You can obtain see the Cookie Manager by doing the following in Firefox:

  • Windows - Tools > Options > Privacy > Show Cookies...
  • Mac - Preferences > Privacy > Show Cookies...

This preference dialog gives users access to the underlying functionality of the Cookie Manager. This dialog is written in XUL (/browser/components/preferences/cookies.xul) and JavaScript (/browser/components/preferences/cookies.js).

Cookie Manager Source

This UI obtains its underlying functionality from the code in netwerk/cookie, which is where the code for nsICookieManager lives.

As you would expect from our XPCOM lab, nsICookieManager is really two parts:

Notice that the C++ code is not named the same as the IDL. In fact, nsCookieService.cpp/h defines a single class that implements all of nsICookieService and nsICookieManager2, which includes nsICookieManger through inheritance.

Why have nsICookieManger2? Why not simply add more to nsICookieManager? The nsICookieManager interface is a Frozen Interface, which means that it is guaranteed not to change in the future, and developers can use it knowing that nothing will happen to it down the road. Any component that implements this interface must also guarantee that the method and attributes don't change their signature.

What if you need to add something? It will happen eventually. The typical answer is to add a new interface that inherits from the old one, bumping a version number by one. This explains the existence of nsICookieManager2, and is a common design pattern in Mozilla.

Using nsICookieManager in C++

Here's how you use nsICookieManager in code:

#include "nsICookieManager.h"
...
nsresult rv;
nsCOMPtr<nsICookieManager> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
...
cookieManager->RemoveAll();

What's happening here? First, we create a variable (rv) to hold the error/success result code (nsresult is simply an unsigned long). Mozilla does not use exceptions, so all XPCOM methods return result codes indicating that things worked (NS_OK) or that there was an error (NS_ERROR_FAILURE) of some kind (i.e., different result codes mean different errors.

Next we create a pointer to a nsICookieManager--that's what cookieManager is, but it doesn't look like that. In order to make working with XPCOM easier, there is a utility class called nsCOMPtr.

nsCOMPtr

nsCOMPtr is designed to help developers not leak memory, and to make it easy to work with interfaces. It is a so-called smart pointer: a template class that acts just like a regular pointer, but which adds AddRef, Release, and QueryInterface.

An XPCOM object is accessed via a pointer to an abstract base class (the interface type). Each interface pointer keeps track of how many references to it are being held. As new variables are created and clients get new references, AddRef is called, and the number goes up. When interface pointers go out of scope, the nsCOMPtr decreases the reference count, and when that goes to 0, deletes the object automatically.

The QueryInterface method of nsCOMPtr is used to do runtime type discovery, allowing us to see if a type implements an interface, and if it does, obtain a pointer to that interface.

For example, we know that nsICookieManager2 inherits from nsICookieManager, so given an nsICookieManager we might wish to obtain a pointer to an nsICookieManager2 in order to get extra functionality:

nsCOMPtr<nsICookieManager> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
if (cookieManager)
{
  nsCOMPtr<nsICookieManager2> cookieManager2 = do_QueryInterface(cookieManager);
  if (cookieManager2)
  {
   ...
  }
}

NOTE: it is more common (and advisable) to call the helper do_QueryInterface instead of using QueryInterface directly. See the nsCOMPtr Reference Manual.

Services

Next we use a helper function, do_GetService, to get an XPCOM service (i.e., there is only one shared instance of a service) using the Contract ID of the cookie manager, see http://hg.mozilla.org/mozilla-central/file/0cd41f599080/netwerk/build/nsNetCID.h#l780.

Sometimes you'll see the same code above done in a longer form, like this:

nsCOMPtr<nsIServiceManager> servMan;
nsresult rv = NS_GetServiceManager(getter_AddRefs(servMan));
if (NS_FAILED(rv))
  return -1;

nsCOMPtr<nsICookieManager> cookieManager;
rv = servMan->GetServiceByContractID("@mozilla.org/cookiemanager",
                                     NS_GET_IID(nsICookieManager),
                                     getter_AddRefs(cookieManager));

if (NS_FAILED(rv))
  return -1;

Behind the scenes do_GetService uses the Service Manager for us, so we can reduce our code. The use of getter_AddRefs means, "this pointer is already ref counted for you, so don't do it again." See a discussion of getter_AddRefs here. Notice too that NS_ENSURE_SUCCESS has been expanded (see see http://hg.mozilla.org/mozilla-central/file/0cd41f599080/xpcom/glue/nsDebug.h#l222). The former method of doing this is more common, because it is shorter.

You'll even sometimes see it shortened to this:

nsCOMPtr<nsICookieManager> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
if (cookieManager)
{
  ...
}

Using nsICookieManger in JavaScript

Doing the same thing from JavaScript is even easier, but requires some different steps and sytax:

var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
                              .getService(Components.interfaces.nsICookieManager);
cookieManager.removeAll();

It's not uncommon to find a C++ component used from JS, or used exclusively from JS. This is possible because of XPConnect and Mozilla IDL's scriptability, that is, the ability to specify that an interface is scriptable and can be used from JS. You can see various XPConnect objects at work here, in the form of Components.classes and Components.interfaces. Components.classes is an array that is keyed on Contract IDs. When you request a new class, you either ask for a singleton (i.e., a service) or an instance of the component (e.g., a regular object). This is what .getService and .getInstance do.

You might see the previous code done like this:

var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"].getService();
cookieManager = cookieManager.QueryInterface(Components.interfaces.nsICookieManager);

The object returned by Components.classes is an nsISupports, the most basic type in XPCOM. Before we can use it, we need to pick an interface through which to all its functionality. QueryInterface allows us to ask for another interface for the given object. Once we have that interface, we can start accessing/calling its members.