Mozilla's XML User Interface Language (XUL) is a versatile language for developing stand-alone applications or browser extensions. In the first part of this series I showed the basic building blocks for a XUL extension, and in this article I show how to assemble these building blocks into a cross-platform Firefox extension. The code in the first article was limited because of the higher security barrier for XUL files in the browser, and such restrictions are much fewer for extensions, so I will also be providing some of the missing functionality for the code.
The first step is to build an extension with the XUL and support files as developed in the previous article. If you've installed other Firefox add-ons, you might notice that they come in a package with a .xpi filename extension, which refers to XUL's Cross-Platform Install (XPI) package format (pronounced "zippy"). XPI defines bundles, which are .zip files with a specific layout and manifest files. This is similar to Java's JAR format, which also combines .zip content with manifest files. Listing 1 is the .zip file directory for an extension I prepared using the XUL, JavaScript, and CSS from the previous article.
Listing 1. .zip file directory for a Firefox extension XPI bundle
Archive: /Users/uche/stats.xpi
Length Method Size Ratio Date Time CRC-32 Name
-------- ------ ------- ----- ---- ---- ------ ----
0 Stored 0 0% 10-08-07 23:10 00000000 chrome/
0 Stored 0 0% 10-08-07 23:10 00000000 chrome/content/
271 Defl:N 184 32% 10-09-07 00:13 44bb6d65 chrome/content/overlay.js
464 Defl:N 284 39% 10-09-07 00:13 1d873c24 chrome/content/overlay.xul
909 Defl:N 366 60% 10-09-07 00:13 67869f05 chrome/content/stats.js
1581 Defl:N 544 66% 10-09-07 00:13 49def584 chrome/content/stats.xul
0 Stored 0 0% 10-08-07 23:10 00000000 chrome/skin/
230 Defl:N 147 36% 10-09-07 00:13 a732aecb chrome/skin/stats.css
181 Defl:N 105 42% 10-09-07 00:13 f7317c0a chrome.manifest
0 Stored 0 0% 10-08-07 23:10 00000000 defaults/
0 Stored 0 0% 10-08-07 23:10 00000000 defaults/preferences/
948 Defl:N 440 54% 10-09-07 00:13 50ee7971 install.rdf
0 Stored 0 0% 10-08-07 23:10 00000000 locale/
0 Stored 0 0% 10-08-07 23:10 00000000 locale/en-US/
43 Stored 43 0% 10-09-07 00:13 d9c38400 locale/en-US/overlay.dtd
-------- ------- --- -------
4627 2113 54% 15 files
|
Pay close attention to the file layout, because problems in this area are the biggest pain when developing extensions. You will notice some new files that were not covered in the last article, and I'll cover these in the next section.
In the previous article the new XUL window was stand-alone, but now that it is bundled as an extension it has to be invoked from Firefox somehow. This generally involves tweaking the browser user interface to give the user a means of invoking the extension. XUL makes it easy to extend user interfaces in this way using what are called overlays. Listing 2 (overlay.xul) is an overlay to add a menu item for launching the stats viewer window.
Listing 2 (overlay.xul). XUL overlay to add a menu item
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://stats/skin/overlay.css" type="text/css"?>
<!DOCTYPE overlay SYSTEM "chrome://stats/locale/overlay.dtd">
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="stats-overlay">
<script src="overlay.js"/>
<menupopup id="menu_ToolsPopup">
<menuitem id="stats" label="&statsmenuitemname;"
oncommand="Stats.onMenuItemCommand(event);"/>
</menupopup>
</overlay>
|
I'll discuss chrome URLs further in a later section. Notice the document type
declaration, which is used to load a DTD, which contains any strings that should
be localized (basically any natural language strings presented to the user). The
next section covers a bit more about this neat approach to localization (l10n) in
Mozilla chrome applications. Within the overlay XUL proper, I specify a menu
addition. The outer element specifies the existing target menu item in Firefox,
by ID—
menu_ToolsPopup is the "Tools" menu. By default the new item will be appended as the last menu item. The new menu item,
stats, uses a localized string for the label the user sees, provided as an entity in the DTD I mentioned. The
oncommand attribute is key, because it establishes what
happens when the user selects the menu item. The JavaScript function specified is provided in the separate script file overlay.js, which is Listing 3.
Listing 3 (overlay.js). Script providing the action for the added menu item
var Stats = {
onLoad: function() {
//You can place set-up code for the extension here
},
onMenuItemCommand: function() {
window.open("chrome://stats/content/stats.xul", "", "chrome, resizable=yes");
}
};
window.addEventListener("load", function(e) { Stats.onLoad(e); }, false);
|
Most of this script is defining a single structure, Stats, to wrap all the functions. You would also want to place any
global variables in this structure. This reduces the footprint of the extension by only using up one name in the global namespace (the name of the outer structure itself), and this is standard behavior of all well-behaved extensions, to avoid clashing with object names used by other extensions or even content scripts. You've already seen how to access the wrapped objects in Listing 2, in the JavaScript snippet
Stats.onMenuItemCommand(event);, which accesses the
second structure item in Listing 3. I included a Stats.onLoad function for completeness, though it's not really used
in this extension. You would use this to run any extension initialization code.
The last line of the script sets up the Stats.onLoad
function to run as soon as the chrome has completed loading. The Stats.onMenuItemCommand function simply launches a window with the
XUL example applet from the last article. The second attribute is a window ID,
which I don't use. The third is a list of flags, chrome, indicating that the new window has its own chrome, and should
not be opened within the browser's chrome (so for example the new window doesn't
inherit Firefox's menu, toolbars, status bar, tabs, and so on). The
resizable=yes enables controls for the user to resize the window.
You can already see the files proliferating for this extension, and an important aspect of XPI is finding files within the bundle. This is where the manifest file comes in, chrome.manifest, provided in Listing 4.
Listing 4. The chrome manifest file for the Stats extension
content stats chrome/content/
overlay chrome://browser/content/browser.xul chrome://stats/content/overlay.xul
locale stats en-US locale/en-US/
skin stats classic/1.0 chrome/skin/
|
The first line determines where the main content lives, such as XUL files and scripts. It uses the stats alias to refer to the
"chrome/content/" folder within the bundle, so for example, if you use the chrome URL
chrome://stats/content/stats.xul it resolves this by mapping the
stats alias and looking in the "chrome/content/" folder
for the "stats.xul" file. The second line establishes an overlay on the main browser chrome using overlay.xul (Listing 2). The third line supports l10n by establishing the location of DTDs that contain natural language strings. The example only has a US English version of these strings, but you could translate them to French and place them within a
fr-FR folder, to German and use a
de-DE folder, and so on. The browser will automatically use the one that corresponds to the language preference set by the user. The final line establishes a location for CSS files that are to be combined with the standard browser chrome.
Another key information file for XPI is install.rdf, Listing 5, which contains instructions for installing the extension.
Listing 5 (install.rdf). Installation parameters for the Stats extension
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<!-- Required Items -->
<em:id>xulapp@example.com</em:id>
<em:name>Stats viewer</em:name>
<em:version>1.0</em:version>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>1.5</em:minVersion>
<em:maxVersion>2.0.0.*</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Optional Items -->
<em:creator>Uche Ogbuji</em:creator>
<em:description>
Opens a window in which you can view simple stats about Web pages
</em:description>
<em:homepageURL>http://www.ibm.com/developerworks/web/</em:homepageURL>
</Description>
</RDF>
|
Most of this is self explanatory and I won't go into too much detail except to mention that the em:targetApplication element I use specifies any Firefox version greater than 1.5 and less than 2.0.1. The long
em:id string is the application UUID of Firefox itself.
You should be able to just drop in the sample files from the last article to the
locations indicated in Listing 1 with the only exception that you should adjust the CSS link in "stats.xul", which used to assume "stats.css" was in the same directory. Instead you would use the chrome link chrome://stats/skin/stats.css.
Completing the extension functionality
Once again I had to dummy out some of the functionality in stats.js XUL file because of security restrictions. Now that I have it packaged as an extension, I can put back in the missing pieces. Listing 6 is an updated stats.js that truly does execute the counting operations expected in the extension.
Listing 6. Complete JavaScript for the Stats viewer window
//Invoked in response to a click on the "Go!" button
function change_url(event)
{
//Variables for convenient access to specific elements in the XUL
var urlbox = document.getElementById("url");
var contentview = document.getElementById("contentview");
var wordcountbox = document.getElementById("wordcount");
var charcountbox = document.getElementById("charcount");
var elemcountbox = document.getElementById("elemcount");
alert(urlbox.value);
contentview.contentDocument.location.href = urlbox.value;
//Use Firefox XPath to get the raw text of the document
var doctext = contentview.contentDocument.evaluate(
"string(.)", document, null, XPathResult.STRING_TYPE, null).stringValue;
var wordcount = doctext.split(" ").length()
alert(wordcount);
wordcountbox.value = String(wordcount);
charcountbox.value = String(doctext.length());
//Use Firefox XPath to count elements in the document
var elemcount = contentview.contentDocument.evaluate(
"count(//*)", document, null, XPathResult.NUMBER_TYPE, null).numberValue;
elemcountbox.value = String(elemcount);
}
|
This time I use two XPath expressions on the Web page loaded by the user into the contentview. From the first expression
string(.), I can get the character count (from the string length) and the word count (by splitting the string by spaces and counting the results). From the second expression
//* I get the number of elements. I then use
these values to update the appropriate text boxes.
Creating Firefox extensions can be tricky, but luckily the community has produced a lot of extensions that help in various ways. The sidebar lists a few of these. In this pair of articles you've learned that with some care you can develop Firefox extensions, and that you can use this knowledge for easier development of some things that seemed obvious territory for a full application. There is an impressive amount of power in XUL, JavaScript, and the Mozilla platform overall. And you can target more than just the classic Web browser. It's usually not very hard to adapt Firefox extensions for Mozilla (and generally XUL-based) applications such as the Songbird music management application and the Flock social Web browser.
Learn
-
Check out some Mozilla-based applications such as Songbird and Flock, a Web browser with special
features for social Web sites such as Flickr, del.icio.us, and Weblogs.
-
OperaView
and
IEView
are not only terribly handy, they're also Firefox extensions. Check out
20
Firefox Extensions That Every Web Designer Should Know About or go
ahead and browse the great
list of all Firefox extensions.
-
Localization is important in almost any application development, and Mozilla has gone
to great lengths to define best practices for XUL. Learn more about these in "XUL Coding Style Guidelines."
-
Learn more about localization in general in "Introducing XML Internationalization" (developerWorks, January 2007).
-
The Mozilla Development Center hosts a very comprehensive XUL Tutorial, which is especially useful because of its many links to the reference guide for XUL elements.
-
Sort through several technologies comparable to XUL in Technology options for Rich Internet Applications, by Mr. Vaibhav V. Gadge.
-
Expand your site development skills with articles and tutorials that specialize
in Web technologies in the developerWorks Web development zone.
Discuss
-
Participate in developerWorks
blogs and get involved in the developerWorks community.

Uche Ogbuji is Partner at Zepheira, LLC, a solutions firm specializing in the next generation of Web technologies. Mr. Ogbuji is lead developer of 4Suite, an open source platform for XML, RDF and knowledge-management applications and lead developer of the Versa RDF query language. He is a Computer Engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can find more about Mr. Ogbuji at his Weblog Copia.



