Now that you have PyXPCOM installed, you'll find Mozilla scripting and general XPCOM development easier than ever. At this point I suggest you read through a good Mozilla scripting tutorial, especially Neil Deakin's excellent tutorial on Mozilla's XML-based User-interface Language (XUL). Among many other things, Deakin's tutorial covers how to connect to components of the Mozilla project itself. His tutorial is out of date in places, so I would still spend some time in the official documentation of the particular components you're using.
Interfaces with a touch of class
Neil Deakin's XUL tutorial deals with Javascript rather than Python as scripting language, but the PyXPCOM developers purposefully made the Python binding as similar as possible to the Javascript binding. This similarity is a great boon to Python users familiar with Mozilla Javascript programming, and to those who have been through Mozilla Javascript tutorials. First of all, we'll look at the Python form of some of the Javascript examples in Neil Deakin's tutorial.
We'll start off in the Python interactive console. By simply entering:
>>> from xpcom import components |
we obtain the components singleton object, which is
equivalent to the Components object in Javascript (note the
different capitalization). This object represents the XPCOM component
registry, allowing access to all the factories and interfaces. The
classes attribute of the components object is a
dictionary of all the classes registered with XPCOM. The key is the
contract ID, and the value is the class object, which also acts as a
factory. Therefore, we can invoke the createInstance()
method on the class object to create a new instance of the class.
Working with classes and interfaces
Mozilla comes with a class representing local file objects. As an example, a script could use this interface to save downloaded data to files for the user. In the following example we retrieve the class object according to the appropriate contract ID, then create a new instance from the class object.
>>> file_cls = components.classes["@mozilla.org/file/local;1"] >>> print file_cls <xpcom.components._Class instance at 0x823c504> >>> file_abs = file_cls.createInstance() >>> print file_abs <XPCOM interface 'nsISupports'> |
Notice that the resulting instance is identified as "interface
nsISupports". Actually, what we have is a Python proxy object that
carries this interface. Remember that this is simply a generic interface
that all XPCOM objects provide. To actually call file methods on the
object we've created, we must get the appropriate interface. We do this
by calling queryInterface(), which returns an object with an
interface that allows us to call methods for a local file object. To do
this we have to pass in the interface object for local files. The
components object also has an interfaces attribute which has
one attribute for each registered interface; the attribute name is the
interface name used in the IDL. The interface for a local file is
nsILocalFile, and its interface ID is the universally unique
ID (UUID) "aa610f20-a889-11d3-8c81-000064657374".
>>> file_if = components.interfaces.nsILocalFile
>>> print file_if
{aa610f20-a889-11d3-8c81-000064657374}
>>> file = file_abs.queryInterface(file_if)
>>> print file
<XPCOM interface 'nsILocalFile'>
|
Now that we have a local file object with the proper interface, we
can put it to work. A local file object isn't tied to any particular
system file at first. You make this association by invoking the
initWithPath() method. So, for instance, if we want to
delete the file "/tmp/spam.msg", we would create a local file instance, as
above, and then initialize it with the file name of interest.
>>> file.initWithPath("/tmp/spam.msg")
|
If you're experimenting, note that the argument to
initWithPath() must be a complete file path. If you pass a
relative file path such as "spam.msg", you'll get an error. Now we can
invoke the delete method to do our deed.
>>> file.delete(0) |
The delete method from the nsILocalFile IDL is as follows:
/**
* This will try to delete this file. The 'recursive' flag
* must be PR_TRUE to delete directories which are not empty.
*
* This will not resolve any symlinks.
*/
void delete(in boolean recursive);
|
The recursive argument makes sense when you realize that
the nsILocalFile interface can represent directories as well
as files. As you can see, we passed in the integer 0 for this boolean
value. PyXPCOM does a great job of representing the XPCOM data model in a
natural way for Python. Python does not have a boolean type, but it
treats certain values as false (such as None; empty strings,
sequences and mappings; and numbers with zero value). It treats all other values as true. PyXPCOM uses these familiar rules to coerce Python arguments and return values to appropriate boolean values for XPCOM. See the PyXPCOM documentation for the full set of data model rules.
One of the lovely things about doing any development in Python is the flexibility and cleanliness of its support for dynamic programming. Python is famous for making rapid development in Java actually possible, and PyXPCOM brings the same benefit to XPCOM. One particular aspect of this is the way it makes experimentation at the interactive console so simple.
We have a local file object, but how do we know what to do with it? We can read the IDL and all the documents, but we can also take a quick peek directly at the object. PyXPCOM objects have an
_interface_methods_ attribute which is a dictionary of all the available methods. So, for instance, we can do the following:
>>> print file._interface_methods_.keys() ['delete', 'isFile', 'initWithPath', 'isReadable', 'reveal', 'QueryInterface', 'copyTo', 'copyToFollowingLinksUnicode', 'contains', 'isWritable', 'launch', 'isSpecial', 'isHidden', 'normalize', 'append', 'isExecutable', 'create', 'moveToUnicode', 'createUnique', 'appendRelativeUnicodePath', 'initWithUnicodePath', 'isDirectory', 'appendRelativePath', 'equals', 'copyToFollowingLinks', 'clone', 'copyToUnicode', 'isSymlink', 'exists', 'appendUnicode', 'moveTo', 'spawn'] |
And presto! We at least know the name of all the available methods. By getting the value from this dictionary, you get a function object with attributes that you can access for further introspective goodies.
I must warn, however, that these attributes with leading underscore are, as in the Python tradition, private, and unsafe to use for anything more than occasional, interactive prodding. They may change name without notice, and they may change behaviour without notice. Of course you can always use the magic function dir() and the magic attribute __dict__ to find out what private variables are available for probing.
XPCOM services are accessible through PyXPCOM as special singleton objects. To access a service you first get the class object using the contract ID. But you can't call createInstance() on the
classes because there is only one true instance against which all requests are made. Instead, you use the getService() method to retrieve a proxy to the service. You pass this method the appropriate
interface according to your needs.
As an example, let's access the Mozilla service that provides an interface to user preferences:
>>> from xpcom import components >>> ps_cls = components.classes["@mozilla.org/preferences-service;1"] >>> ps = ps_cls.getService(components.interfaces.nsIPrefService) |
Note that Neil Deakin's tutorial wrongly omits the interface argument to getService() in his example. Now we can piggyback on all the great code the Mozilla developers wrote for user preference
management. First of all we read the current user preferences from the configuration file:
>>> ps.readConfigFile() |
The configuration settings are arranged in a hierarchy, and we must access its branch to read or update a particular entry. The branches are identified by identifiers separated by dots.
>>> branch = ps.getBranch("browser.startup.")
>>> print branch.getChildList("")
['browser.startup.homepage', 'browser.startup.license_accepted',
'browser.startup.page', 'browser.startup.autoload_homepage',
'browser.startup.homepage_override.1']
|
As we can see from the "browser.startup." root, which deals with the behavior of a browser window when first launched, we get information such as the initial home page and whether or not the user has accepted the Mozilla license. We used the getChildList() method which lists all the names of settings within the branch. Let's have a look at one of them:
>>> print branch.getCharPref("homepage")
chrome://navigator-region/locale/region.properties
|
You may get a different URL if you have changed Mozilla's initial home page. The getCharPref() method is used to get the value of settings with string value; it takes the name (without root) of the setting as its only argument. Note that you can determine the type of a
setting by using the getPrefType() method, which returns an integer representing the type of the given setting. You can add or update a setting in similar fashion:
>>> branch.setCharPref("homepage", "http://ibm.com/developer")
|
File component + preference component = preference file
As an example of how XPCOM components work together, listing 1 is a Python program that reads in the Mozilla configuration, modifies the browser startup screen, and writes the result of the update to a temporary file ("/tmp/foo.prefs").
Listing 1: Update a user preference settings and write the update to a temporary file
from xpcom import components
#Get the Mozilla preference service
ps_cls = components.classes["@mozilla.org/preferences-service;1"]
ps = ps_cls.getService(components.interfaces.nsIPrefService)
#Use the service to read the default configuration file
ps.readConfigFile()
#Set the setting "browser.startup.homepage" to
"http://ibm.com/developer"
branch = ps.getBranch("browser.startup.")
branch.setCharPref("homepage", "http://ibm.com/developer")
#Get a local file instance
file_cls = components.classes["@mozilla.org/file/local;1"]
file_abs = file_cls.createInstance()
file = file_abs.queryInterface(components.interfaces.nsILocalFile)
#Set up the new file path
file.initWithPath("/tmp/foo.prefs")
#Create the file using UNIX permissions mode of "775" ("-rw-rw-r--")
file.create(file.NORMAL_FILE_TYPE, 511)
#Save the updated preferences to the new file
ps.savePrefFile(file)
|
Next: roll your own components
Of course, as with any rich technology, there is always more to discover with PyXPCOM, but this discussion gives you a sound footing on which to continue your further exploration with the PyXPCOM and Mozilla docs and other available resources. In the next and final article in this series we'll discuss how to implement XPCOM objects in Python.
- The first part of this series covered installation and set up of PyXPCOM.
- Rick Parrish's series on XPCOM provides excellent background.
- XPCOM Part 1: An introduction to XPCOM
- XPCOM Part 2: XPCOM component basics
- XPCOM Part 3: Setting up XPCOM
- XPCOM Part 4: Component development
- Neil Deakin's superb XUL tutorial has a section on Javascript access to XPCOM objects.
- The embedded Mozilla public API documentation is a great listing of the various services that come with Mozilla.
- The Mozilla project's home page for XPConnect scriptable components is a starting place for relevant docs.

Uche Ogbuji is a consultant and co-founder of Fourthought, Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management. Fourthought has developed 4Suite, an open source platform for XML, RDF, and knowledge-management applications. Uche is a computer engineer and writer born in Nigeria; he lives and works in Boulder, Colorado. You can contact Uche at uche@ogbuji.net.
Comments (Undergoing maintenance)





