Creating Extensions in Firefox: an Example Toolbar

This tutorial details everything some of the stuff you need to know to start creating Firefox toolbars (more properly called extensions). It's not nearly as hard as you might expect, and if you already know some javascript and CSS, you'll breeze through it.

My first encounter with Firefox toolbars was when I was asked to develop one for a busy internet forum. So rather than cover a useless 'hello world' style plugin, I'll be detailing how I created this plugin. Yes it's diving in at the deep end, but ultimately you should find it more useful (read 'more code you can steal')

Note that Internet Explorer toolbars are a completely different beast, and aren't covered here.

Overview

Before we dive in to the nitty gritty, it's helpful to have an overview of the structure of an extension/toolbar.

The first part is an overlay file, written in XUL (a subset of XML); if you know HTML, you'll pick up XUL easily. The overlay file controls the elements that will appear on your toolbar - text entry boxes, icons, menus etc. Again, it's pretty similar to a HTML form

A HTML form looks rather plain without any styling; most likely you would create an external style sheet to prettify, and Firefox extensions use a similar method. So the second piece of your plugin would be a CSS file containing formatting information

Finally, you need all those buttons to do something useful, and for this javascript is used. You would typically assign onclick events to elements in the overlay file, making them call functions stored in the .js file. Is this tutorial we'll be looking at a few fancier techniques too including using AJAX to dynamically populate menus with data from a web server.

XPI Files

The .xpi format used by Firefox extensions is nothing more than a zip archive consisting of some CSS, Javascript, a couple of meta files, and a few images.

A great way to learn is to read the source for other plugins, so after you've finished this tutorial you may wish to browse the Firefox extension repository and download a few of the smaller ones. Firefox won't let you download .xpi files (since it will recognise such files as extensions and prompt you to install them), but if you're on Linux or similar, you can use wget to download them.

The CAG Toolbar

The extension around which this tutorial is written is one that I created in 2007 for the Consumer Action Group, a busy web forum dealing with consumer issues in the UK. The idea of the toolbar was that it would make navigation of the forum easier by providing quick links to important threads, alerting the user if they had a new private message, searching the site etc. The image below gives you an idea of how it looks, but you can also download a copy if you'd like to dissect it (or just think it would be useful to have).

firefox extension image

The toolbar has a couple of features which make it interesting...

The main problem with a toolbar is that once your users have installed it, you have no control over it - if your toolbar contains a menu of 'quick links' and these links change, your only option is to release a new version and urge users to upgrade (or put up 301s if you control the domain that the links are on). This may sound obviously, but as a web developer it's easy to forget.

To cut down on this problem, I wanted to make some of the CAG toolbar menus populate dynamically, pulling their content from a file hosted on a web server; and that meant using AJAX. We'll see how to do this shortly

Extension Structure

Lets look at the directory structure and files of a typical extension:
myplugin/
    chrome.manifest
    install.rdf
    chrome/
       content/
            myplugin.js
            myplugin.xul
       skin/
            myplugin.css

We'll cover each of these files in turn.

install.rdf

install.rdf is a short XML file that defines the name and version of the plugin, along with other meta data such as the author's name, a description, and the versions of Firefox which it is compatible with (more on version compatibility at the end of the tutorial). Here's the install.rdf from the CAG plugin:
<&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>cag@consumeractiongroup.co.uk</em:id>
        <em:name>CAG toolbar</em:name>
        <em:version>0.9</em:version>

        <em:targetApplication>
            <Description>
                <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                <em:minVersion>1.5</em:minVersion>
                <em:maxVersion>3.0.*</em:maxVersion>
            </Description>
        </em:targetApplication>

        <!-- Optional Items -->
        <em:creator>Consumer Action Group</em:creator>
        <em:description>The CAG Firefox Extension</em:description>
        <em:homepageURL>http://www.consumeractiongroup.co.uk</em:homepageURL>

    </Description>
</RDF>

Hopefully everything here is self-explanatory.

chrome.manifest

According the Mozilla docs,

Chrome is the set of user interface elements of the application window that are outside of a window's content area. Toolbars, menu bars, progress bars, and window title bars are all examples of elements that are typically part of the chrome.

The chrome.manifest defines the user interface elements which will be used in the plugin.

content cag jar:chrome/cag.jar!/content/
overlay chrome://browser/content/browser.xul chrome://cag/content/cag.xul
skin cag classic/0.1 jar:chrome/cag.jar!/skin/
overlay chrome://cag/content/options.xul chrome://cag/content/cag.xul

We'll cover the details of this file later, but for now note that it it references the XUL overlay file mentioned in the previous section

The Overlay

As mentioned earlier, the overlay file controls the layout of elements in the toolbar. Rather than take you through the syntax and attributes for each available XUL markup tag (the documents at developer.mozilla.org already do a good job of this), I'll present my overlay file along with some screen shots, and brief explanations of some of the key points

cag.xul

Quick Links

The first item in the toolbar is a drop down menu providing quick links to threads on the CAG forum. We create this by first defining a toolbaritem, then placing a toolbarbutton inside it

<toolbaritem flex="0">
  <toolbarbutton type="menu" id="cag-homepage" tooltiptext="Links to consumer websites" label=" Links ">
    ...
  </toolbarbutton>
</toolbaritem>

At the moment this is simply a clickable button. To make it a drop down menu, we use a group of menuitem tags enclosed in menupopup:

  <menupopup id="cag-newsmenu">

    <menuitem label="Consumer Action Group" class="menuitem-iconic" id='dd_cag'
              tooltiptext="http://www.consumeractiongroup.co.uk"
              onclick="cag_loadurl('http://www.consumeractiongroup.co.uk/', event)" />

    <menuitem label="CAG Forums"  class="menuitem-iconic" id='dd_forum'
              tooltiptext="http://www.consumeractiongroup.co.uk/forums"
              onclick="cag_loadurl('http://www.consumeractiongroup.co.uk/forums', event)" />

    <menuitem label="Consumer Wiki" class="menuitem-iconic" id='dd_wiki'
              tooltiptext="http://www.consumerwiki.co.uk"
              onclick="cag_loadurl('http://www.consumerwiki.co.uk', event)" />

   </menupopup>

Note the user of onclick attributes to cause a custom javascript function to be called when the menu item is clicked. We'll look at these in more detail when we get to the javascript file

Here's the finished product:

linux server support

You'll notice that the toolbar button and each menu item have icons next to them. These are defined in the CSS file, which we'll come to later

Options

One of the features of the toolbar is that it can alert users if they have a new private message on the CAG forums. To do this the extension needs to know their forum login details, and the next button is where they can set these.

Once again we use the toolbaritem and toolbarbutton tags, but since there is no dropdown menu, the code is a lot shorter:

<toolbaritem flex="0">
<toolbarbutton id="cag-settings" tooltiptext="Options"  label="" oncommand="cag_settings(event)"/>
</toolbaritem>

firefox toolbar image

Notice again how we use the oncommand attribute to specify a javascript function which should be called when the user clicks the button. In this case the function causes a popup window to appear into which the user can enter his username and password

Message Icon

The third element in the toolbar is a PM (private message) notification icon. If the user has unread messages it shows green, if they have no messages is shows yellow, and if there was an error (eg user is offline, username/password haven't been set) it shows red.

<toolbaritem flex="0">
<toolbarbutton id="cag-newpm" tooltiptext="New Message! Click to visit your inbox"  label="" oncommand="cag_inbox(event)" hidden="true"/>
</toolbaritem>

<toolbaritem flex="0">
<toolbarbutton id="cag-nopm" tooltiptext="No new messages"  label=""  oncommand="cag_inbox(event)" hidden="false"/>
</toolbaritem>

<toolbaritem flex="0">
<toolbarbutton id="cag-errpm" tooltiptext="Perhaps you didn't enter your forums username and password? Click the Options button to configure the
plugin"  label="" hidden="true" oncommand="cag_inbox(event)"/>
</toolbaritem>

By default the first and last are hidden. We'll be using javascript to control which of these icons is visible.

Threads

This element is another dropdown menu providing quick links to any forum threads that the user is subscribed to. Obviously this is dynamic data and needs to be pulled from the webserver; so for the moment we just start off with an empty menu and bind a javascript function to the onpopupshowing event:

 <toolbaritem flex="0">
 <toolbarbutton type="menu" id="cag-subs-tb" tooltiptext="Latest News from CAG" label=" Threads ">
        <menupopup id="cag-subsmenu" onpopupshowing="cag_get_subs()" />
 </toolbarbutton>
 </toolbaritem>

cag_get_subs, which will be discussed later, deals with making the AJAX request and formatting the reply into a series of menuitem tags

News

Next up is the news button, which is identical to threads: a menupopup element with a javascript function called by an onpopupshowing handler

Search

The final group of elements are a search box and two buttons. These allow the user to search the CAG website, consumerwiki.co.uk, and Google

The first element, the text entry box:

<toolbaritem id="cag-SearchTerms-wrap" persist="width">
    <menulist id="cag-SearchTerms" editable="true" flex="1"
              minwidth="100" width="250"
              onkeypress="cag_KeyHandler(event);">
    </menulist>
</toolbaritem>


For the second element the 'web search button' we use a toolbarbutton, while the 'search' button is a drop down, allowing the user to choose where to perform the search. You should be well aquainted with menuitem, menupopup etc by now, so I won't show the code. If you need to see how it's done, follow the link at the top of this section to download the XUL file.

Styling the Overlay

Now that the overlay file has been created, we can use CSS to style it. In my case all I really wanted was a few pretty icons, so my CSS file is quite basic. You can view a copy here

If you know any CSS, the syntax will be familiar:

#cag-homepage {
    list-style-image: url("chrome://cag/skin/cag_title.png");
}

If you refer back to cag.xul, you'll see that cag-homepage was the id that we gave to the very first element in the toolbar, the 'Links' button:


All our CSS does is assign the cag_title.png icon to this button. In a similar fashion we can assign images to items in the menus too

I'd assume that it's also possible to changes fonts and foreground/background colours using the CSS too, but this isn't really something I've looked into: it seems desirable that the fonts and colours used should be the default, so that the toolbar fits into the style of the rest of the browser

Javascript

The next big part is the javascript, which is used to make all those pretty little buttons actually do something useful. Again, if you already know javascript, it should all be straighforward.

Quick Links

Refer back to the very first element in the toolbar, the quick links dropdown. Each menuitem used the onclick attribute to specifiy a javascript funcion to call when the link was clicked:
    <menuitem label="Consumer Action Group" class="menuitem-iconic" id='dd_cag'
              tooltiptext="http://www.consumeractiongroup.co.uk"
              onclick="cag_loadurl('http://www.consumeractiongroup.co.uk/', event)" />

Let's create that function...

function cag_loadurl(url, event) {
   if (event.button == 0) {
    window._content.document.location = url;
    window.content.focus();
   } else {
    gBrowser.selectedTab = gBrowser.addTab(url);
   }
}

The default action is to load the url in the tab which currently has focus; but if the right mouse button is clicked instead, firefox will open a new tab, load the url into that tab, and switch the focus to that tab. Nice.

Search box

The search box is a little more complicated, but not much. The main addition is that we do some work contructing the URL. Referring back to the overlay, you'll see that the text entry box has the id cag-SearchTerms, while each of the search buttons calls cag_search, passing it the name of the search type to perform.


function cag_search(type)
{
    var URL = "";
    var isEmpty = false;

    var searchTermsBox = document.getElementById("cag-SearchTerms");
        var searchTerms = cag_TrimString(searchTermsBox.value);

    if(searchTerms.length == 0) // Is the search terms box empty?
        isEmpty = true;         // If so, set the isEmpty flag to true
    else                        // If not, convert the terms to a URL-safe string
        searchTerms = cag_ConvertTermsToURI(searchTerms);

After cleaning up searchTerms with a couple of helper functions (listed in cag.js if you really want to see them), we can do the search:

   switch(type)  {

    case "web":
    default:

        gBrowser.selectedTab = gBrowser.addTab(
		"http://www.cag.mediajump.co.uk/search.php?searchphrase=" + searchTerms);
        break;

    case "cag":
        gBrowser.selectedTab = gBrowser.addTab(
                "http://www.cag.mediajump.co.uk/search.php?searchphrase=site:consumeractiongroup.co.uk%20"
		 + searchTerms);
        break;

    case "wiki":
        gBrowser.selectedTab = gBrowser.addTab(
                "http://www.cag.mediajump.co.uk/search.php?searchphrase=site:consumerwiki.co.uk%20"
		 + searchTerms);
        break;
  }

}

News

Since I'm covering the javascript file in order of complexity, next up is the code for the News menu; and - since we want this menu to be dynamically populated - this time we'll be using a bit of AJAX.

A text file sits on the CAG server containing a list of news headlines, one per line. The format is:

headline:link
headline:link
headline:link

Using XMLHttpRequest we'll fetch this file when the user tries to view the menu, then display each line of the file as a clickable menuitem:


function cag_get_news() {

var menu = document.getElementById("cag-newsmenu");

var req = new XMLHttpRequest();
req.onreadystatechange = function (event) {
var lines = new Array;
lines = req.responseText.split('\n');


    for(var i=menu.childNodes.length - 1; i >= 0; i--)
    {
        menu.removeChild(menu.childNodes.item(i));
    }

    for(key in lines)
    {
        var bits = new Array;
        bits = lines[key].split(',');
        var tempItem = document.createElement("menuitem");

        tempItem.setAttribute("label", bits[0]);
        tempItem.setAttribute("oncommand", "cag_loadurl('" + bits[1] + "')");
        menu.appendChild(tempItem);
    }
}

req.open('GET', 'http://www.consumeractiongroup.co.uk/firefox_plugin/firefox_news.txt', true);
req.send(null);

}

Notice how the processing of the text file is done by a callback function which triggers when a response is received (req.onreadystatechange), this means that the script won't block in the (hopefully very short) period between the AJAX request being sent, and a response being received. The rest of the code simply parses the text file and appends an item to the menu for each item.

Incidentally, an improvement to this code would be to cache the text file. We could hold the contents in a string and use another variable to hold the date at which the AJAX request was made. When the user viewed the menu at a later date, we could then skip the AJAX request and read the contents back from the string. If the timestamp in our variable indicated that the cache was more than a few hours old, we'd fall back on AJAX. This should cut down on server load and improve the responsiveness of the toolbar. Hindsight is a great thing.

Threads

The threads button, which shows a list of forum threads to which the user is subscribed, works in a very similar way to news - we use AJAX to request a PHP script from the server. This script spits out a list of subscribed threads (title:link), one per line, and our javascript then renders these in a menu.

The main difference from the news code is that this thread list will be user specific, so we need to send the user's username and password in the query string of the request. The one remaining toolbar element that we haven't looked at (private message notification) needs to authenticate itself too, so we'd better look at this next...

Options

We mentioned the options element briefly in the overlay section of this article. It consists of a toolbar button, which when clicked opens an options window:

firefox toolbar options

We implement this in the same way as we did the toolbar - via an XUL overlay file:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>


<dialog id="donothing" title="Do Nothing"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        buttons="accept,cancel"
        ondialogaccept="return doOK();"
        ondialogcancel="return doCancel();">


</dialog>

This file is referenced in the chrome.manifest in the same way as the main overlay

To open the window, we use some javascript which is called by the oncommand attribute of the button (see section 3 above). The window itself is just a standard XPCOM component made available by the Mozilla Components class:


var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                        .getService(Components.interfaces.nsIPromptService);

username = {value:cag_username};
password = {value:cag_password};
check = {value:true};

okorcancel = prompts.promptUsernameAndPassword(window, 'CAG Options',
'Please enter your CAG forum login details. This will allow the plugin to periodically' +
 'alert you to new private messages and thread replies', username, password, 'Save', check);

This generates the elements: two text input boxes username and password), two check buttons (ok and cancel), and a save checkbox. The three middle lines set the values to be shown in these fields (in the case of the checkbox, for it to be checked by default). We'll cover where cag_username and cag_password came from shortly

Once the user has filled in his login details, we'll need somewhere to store them, and the Firefox preferences fits the bill nicely (the preferences can be seen by entering about:config into the address bar). Again, we use a Mozilla/Netscape component for this:

var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                    getService(Components.interfaces.nsIPrefService);

We can now read or write preferences (including creating our own) using the prefs object. Eg to read a preference named extensions.cagplugin.username:

prefs = prefs.getBranch("extensions.cagplugin.");
if (prefs.prefHasUserValue("username")) {
  var cag_username = prefs.getCharPref("username");
  var cag_password = prefs.getCharPref("password");
} else {
  prefs.setCharPref("username", "");
  prefs.setCharPref("password", "");
}

Similarly, to set preferences:


prefs.setCharPref("username", username.value);
prefs.setCharPref("password", MD5(password.value));

Notice that we're storing the password as an MD5 hash. As well as guarding against (well detering anyway) someone looking through the preferences, it also means that the password isn't being sent over the network in plaintext.

Putting it all together, the flow of the function is this:

Rather than show the full function here, please refer back to cag.js

Note: MD5() is a custom function defined towards the end of cag.js

Note: There's a flaw in the methodology here. Since the password is being stored as an MD5 hash, if the user clicks the options button again, the value shown in the password field is the hash of his password. If he doesn't change the value, but then clicks 'ok', we end up storing the hash of the hash in the preferences. I can't think of a good way around this.

Threads revisited

After that digression into components and preferences we return to the toolbar, and the last item, the private message notification.

This element consists of a button which changes colour to indicate whether or not the user has unread private messages on the forum. Actually, that's not strictly true. To create the illusion of the button changing colour, we created 3 buttons in the overlay, but hid two of them by default (see 'Message Icons' in section 3).

We want our toolbar to periodically check the webserver for new private messages and change the icon accordingly, so rather than bind to an event, we'll use a javascript timer. Again, please refer to cag.js for all the gory details - I haven't included them here, since timers are just standard javscript.

As before we make an AJAX request to the web server, the query string of which contains the user's username/password. Some PHP code on the server checks these credentials, queries the database, then spits out the number of unread private messages (or "ERR" in the case of authentication failure). Here's a fragment of the code for handling the response

req.onreadystatechange = function (event) {

lines = req.responseText;

if (lines == "ERR") {
  document.getElementById("cag-newpm").setAttribute("hidden", true);
  document.getElementById("cag-nopm").setAttribute("hidden", true);
  document.getElementById("cag-errpm").setAttribute("hidden", false);
} else if (lines == "0") {
  document.getElementById("cag-nopm").setAttribute("hidden", false);
  document.getElementById("cag-errpm").setAttribute("hidden", true);
  document.getElementById("cag-newpm").setAttribute("hidden", true);
} else {
  document.getElementById("cag-errpm").setAttribute("hidden", true);
  document.getElementById("cag-newpm").setAttribute("hidden", false);
  document.getElementById("cag-nopm").setAttribute("hidden", true);
}

Nothing too exciting here

Summary and Links

Building

During development of this extension, I kept a little shell script in the top level directory for building. This should illustrate the steps involved

cd chrome
zip -r cag.jar content/* skin/*
cd ..
zip cag.xpi install.rdf chrome.manifest chrome/cag.jar

Conclusion

I hope you've found this guide useful. As you're probably realised by now, my intention was not to provide a comprehensive list of every option or attribute available, or even to exhaustively explain what every line in my code does. Rather, I wanted to cover a real-world plugin, and concentrate on some of the fancy features - things like AJAX, XPCOM components, and preferences; things which tend to be missing from most "introduction to Firefox extensions" tutorials. Hopefully a lot of the code (in particular the overlays) should have been faily self-explanitory, but don't forget that the Mozilla docs give more information that you could ever want

Links

Linux Services

Books

Code

vBulletin

Fun Stuff

Blog

Pete's Shed




linux support email pete@linuxbox.co.uk
(+44) 07890 592198