XUL Tutorial - 8.1 - XPCOM Interfaces
Previous Contents Reference Next

XUL Tutorial - XPCOM Interfaces

In this section, we'll take a brief look at XPCOM (Cross-platform Component Object Model), which is the Object system that Mozilla uses.

Calling Native Objects

By using XUL we can build a complex user interface. We can attach scripts which modify the interface and perform tasks. However, there are quite a number of things that cannot be performed directly with JavaScript. For example, if we wanted to create a mail application, we would need to write scripts which would connect to mail servers to retrieve and send mail. JavaScript does not have the capability to do such things.

The only way to handle this would be to write native code that would get mail. We also need to have a way for our scripts to call the native code easily. Mozilla provides such a method. It involves using XPCOM (Cross-platform Component Object Model).

About XPCOM

Mozilla is constructed from a collection of components, each of which performs a certain task. For example, there is an component for each menu, button and element. The components are constructed from a number of definitions called interfaces.

An interface in Mozilla is a definition of a set of functionality that could be implemented by components. Components are what implement the code in Mozilla that does things. Each component implements the functionality as described by interfaces. A single component might implement multiple interfaces. And multiple components might implement the same interface.

Let's take an example of a file component. An interface would need to be created which describes properties and functions that can be performed on files. A file would need properties for its name, modification date and its size. Functions of a file would include moving, copying and deleting it.

The File interface only describes the characteristics of a file, it does not implement it. The implementation of the File interface is left to a component. The component will have code which can retrieve the file's name, date and size. In addition, it will have code which copies and renames it.

We don't care how the component implements it, as long as it implements the interface correctly. Of course, we'll have different implementations anyway, one for each platform. The Windows and Macintosh versions of a file component would be significantly different. However, they would both implement the same interface. Thus, we can use a component by accessing it using the functions we know from the interface.

In Mozilla, interfaces a preceded by 'nsI' so that they are easily recognized as interfaces. For example, the nsIAddressBook is the interface for interacting with an address book, nsISound is used for playing files and nsILocalFile is used for files.

XPCOM components are typically implemented natively, which means that they generally do things that JavaScript cannot do itself. However, there is a way in which you can call them, which we will see shortly. We can call any of the functions provided by the component as described by the interfaces it implements. For example, once we have a component, we can check if it implements nsISound, and then we can play sound through it.

Creating XPCOM Objects

There are three steps to calling an XPCOM component.

  1. Get a component
  2. Get the part of the component that implements the interface that we want to use.
  3. Call the function we need

Once you've done the first two steps, you can repeat the last step as often as necessary. Let's say we want to rename a file. For this we can use the nsILocalFile interface. The first step is getting a file component. Second, we query the file component and get the portion of it that implements the nsILocalFile interface. Finally, we call functions provided by the interface. This interface is used to represent a single file.

We've seen that interfaces are always named starting with 'nsI'. Components, however, are refered to using a URI syntax. Mozilla stores a list of all the components that are available in its own registry. A particular user can install new components as needed. It works much like plug-ins.

Mozilla provides a file component, that is, a component that implements nsILocalFile. This component can be refered to using the URI 'component://mozilla/file/local'. The component: URI scheme is used to specify a component. Other components can be refered to in a similar way.

The URI of the component can be used to get the component. With JavaScript, you can get a component using JavaScript code like that below:

var aFile = Components.classes["component://mozilla/file/local"].createInstance();

The file component is retreived and stored in the aFile variable. The Components in the above example refers to a general object that provides some component related functions. Here, we get a component class from the classes property. The classes property is an array of all the available components. To get a different component, just replace the URI inside the square brackets with the URI of the component you want to use. Finally, an instance is created with the createInstance function.

However, at this point, we only have a reference to the file component itself. In order to call the functions of it we need to get one of its interfaces, in this case nsILocalFile. A second line of code needs to be added as follows:

var aFile = Components.classes["component://mozilla/file/local"].createInstance();
var aLocalFile = aFile.QueryInterface(Components.interfaces.nsILocalFile);

The function QueryInterface is a function provided by all components which can be used to get a specific interface of that component. This function takes one parameter, the interface that you want to get. The interfaces property of the Components object contains a list of all the interfaces that are available. Here, we use the nsILocalFile interface and pass it as a parameter to QueryInterface. The result is that aLocalFile will be set to a reference to the part of the component that implements the nsILocalFile interface.

The two JavaScript lines above can be used to get any interface of any component. Just replace the component name with the name of the component you want to use and change the interface name. You can also use any variable names of course. For example, to get a sound interface, you can do the following:

var sound = Components.classes["component://netscape/sound"].createInstance();
var nsisound = sound.QueryInterface(Components.interfaces.nsISound);

XPCOM interfaces can inherit from other interfaces. The interfaces that inherit from others have their own functions and the functions of all the interfaces that they inherit from. All interfaces inherit from another interface. The top-level interface is called nsISupports. All interfaces are descendants of this interface. It has one function supplied to JavaScript. This is QueryInterface which we have already seen. Since the interface nsISupports is implemented by all components, the function QueryInterface function is available in every component.

Several components may implement the same interface. Typically, they might be subclasses of the original but not necessarily. Any component may implement the functionality of nsILocalFile. In addition, a component may implement several interfaces. It is for these reasons why two steps are involved in getting an interface to call functions through.

However, there is a shortcut we can use since we'll often use both of these lines together:

var aLocalFile = Components.classes["component://mozilla/file/local"].createInstance(Components.interfaces.nsILocalFile);

This will do the same thing as the two lines but in one line of code. It eliminates the need to create the instance and then query it for an interface in two separate steps.

If you call QueryInterface on an object and the requested interface is not supported by an object, null is returned. You should always check to ensure that a non-null value is returned as in the following:

var aFile = Components.classes["component://mozilla/file/local"].createInstance();
if (!aFile) return false;
aLocalFile=aFile.QueryInterface(Components.interfaces.nsILocalFile);
if (!aLocalFile) return false;

Calling the Functions of an Interface

Now that we have a object that refers to a component with the nsILocalFile interface, we can call the functions of nsILocalFile through it. We want to rename a file. There are two things we need to do. First, we need to tell the file component which file to rename, then we need to rename the file. Note that the nsILocalFile is used to specify a single file and isn't a general purpose file manipulation object.

The table below shows some of the properties and methods of the nsILocalFile interface.

initWithPath This method is used to initialize the path and filename for the nsILocalFile. The first parameter should be the file path, such as /usr/local/mozilla
leafName The filename without the directory part
fileSize The size of the file
isDirectory() Returns true if the nsILocalFile represents a directory
delete(recursive) Deletes a file. If the recursive parameter is true, a directory and all of its files and subdirectories will also be deleted.
copyTo(nsILocalFile dir,newname) Copies a file to another directory, optionally renaming the file
moveTo(nsILocalFile dir,newname) Moves a file to another directory, or renames a file

In order to delete a file we first need to assign a file to the nsILocalFile. We can call the method initWithPath to indicate which file we mean. Just assign the path of the file to this property. Next, we call the delete function. It takes one parameter which is the new filename. The code below demonstrates these two steps:

var aFile = Components.classes["component://mozilla/file/local"].createInstance();
var aLocalFile = aFile.QueryInterface(Components.interfaces.nsILocalFile);
if (!aLocalFile) return false;

aLocalFile.initWithPath("/mozilla/testfile.txt");
aLocalFile.delete(false);

This code will take the file at /mozilla/testfile.txt and delete it. Try this example by adding this code to an event handler. You should change the filename to an existing file that you have that you would like to delete.

In the functions table above, you will see two functions copyTo and moveTo. These two functions can be used to copy files and move files respectively. Note that they do not take a string parameter for the directory to copy or move to, but instead take an nsILocalFile. That means that you'll need to get two file components. The example below shows how to copy a file:

// get a component for the file to copy
var aFile = Components.classes["component://mozilla/file/local"].createInstance(Components.interfaces.nsILocalFile);
if (!aFile) return false;

// get a component for the directory to copy to
var aDir = Components.classes["component://mozilla/file/local"].createInstance(Components.interfaces.nsILocalFile);
if (!aDir) return false;

// next, assign URLs to the file components
aFile.initWithPath("/mozilla/testfile.txt");
aDir.initWithPath("/etc/");

// finally, copy the file, without renaming it
aFile.copyTo(aDir,null);

XPCOM Services

Some XPCOM components are special components called services. You do not create instances of them because only one should exist. Services provide general functions which either get or set global data or perform operations on other objects. Instead of calling createInstance, you call getService to get a reference to the service component. Other than that, services are not very different from other components.

One such service provided with Mozilla is a bookmarks service. It allows you to add bookmarks to the user's current bookmark list. An example is shown below:

var bmarks = Components.classes["component://netscape/browser/bookmarks-service"].getService();
bmarks = bmarks.QueryInterface(Components.interfaces.nsIBookmarksService);
bmarks.AddBookmark("http://www.mozilla.org","Mozilla");

First, the component "component://netscape/browser/bookmarks-service" is retreived and its service is placed in the variable bmarks. We use QueryInterface to get the nsIBookmarksService interface. The function AddBookmark provided by this interface can be used to add bookmarks. The two parameters to this function are the bookmark's URL and its title.


(Next) Next, we would see some of the interfaces provided with Mozilla that we can use.

XUL Tutorial - 8.1 - XPCOM Interfaces
Previous Contents Reference Next