Developer

Getting started with Firefox Extensions

No browser is perfect -- eventually enough annoyances will build up to the point that you wish you could do something about it. Thanks to Firefox's extendable architecture and a healthy dose of JavaScript knowledge, you can begin bending the browser to your will.

No browser is perfect — eventually enough annoyances will build up to the point that you wish you could do something about it. Thanks to Firefox's extendable architecture and a healthy dose of JavaScript knowledge, you can begin bending the browser to your will.

Despite recent improvements to its JavaScript engine, Mozilla browsers have been regarded as slower than their WebKit or Opera brethren. The overpowering reason that Firefox is so successful is the ability to extend the browser with XUL; if performance was the only reason people choose browsers, Opera would have cleaned up the competition years ago.

There is only XUL

XUL (XML User Interface Language) is what allows Firefox to be so extensible. The useful part of the specification to extension developers is that XUL allows you to overwrite (or overlay) parts of the UI with your own code.

There's plenty of documentation for XUL on its project page. But enough background, it's time to get straight into creating extensions.

Essential details

First thing that we are going to need is a reason to have an extension. I'm going to attempt to reproduce the start-up page that appears in Chrome/Opera with the nine most visited sites appearing as thumbnails in a page.

Next we'll need a name for the extension, I'll choose to call it chromenewtab, as I want this extension to create a new tab with the most visited sites in it every time that the extension is invoked.

Now we need to create a directory called chromenewtab, which will hold all versions of our code, and within it we'll create a directory called chromenewtab-0.1.

install.rdf

Every extension needs an install.rdf file to provide metadata about the extension. Inside the chromenewtab-0.1 directory, create an install.rdf file and put the following information:

<?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">
                   <em:id>chromenewtab@builderau.com.au</em:id>
                   <em:version>0.1</em:version>
                   <em:type>2</em:type>
                   <em:name>Chrome-like New Tab</em:name>
                   <em:description>A new tab interface like Opera, which Chrome then "borrowed"</em:description>
                   <em:creator>Chris Duckett</em:creator>
                   <em:homepageURL>http://www.builderau.com.au/labs/cnt/chromenewtab.htm</em:homepageURL>
                   <em:iconURL>chrome://chromenewtab/skin/icon.png</em:iconURL>
                   <em:updateURL>http://www.builderau.com.au/labs/cnt/update.htm</em:updateURL>
                   <em:targetApplication>
                      <Description>
                        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                        <em:minVersion>3.0</em:minVersion>
                        <em:maxVersion>3.*</em:maxVersion>
                      </Description>
                  </em:targetApplication>
  <Description>
</RDF>

The above XML needs some explanation:

  • The first bit can be taken as information that is needed:
     <?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">
  • The id of an extension must be unique. Prior to Firefox 1.5 it had to be a GUID, but nowadays you can use an email-like format. The suggested formatting is extensionname@organisation.tld.
  • The type property is 2, as that is what Firefox expects for extensions.
  • The name property is the name that will appear in the Add-Ons dailog within Firefox.
  • updateURL is needed if we wish to update the extension — which given that our final code will not be feature complete and probably has bugs, we will need. If you host your extension on addons.mozilla.org, you do not need this property.
  • targetApplication specifies which Mozilla applications and the versions of them to target. {ec8030f7-c20a-464f-9b0e-13a3a9e97384} is the id for Firefox and we are targeting from version 3.0 to any version of the Firefox 3 series. If we wish to restrict the extension to the Firefox 3.0.x branch, we would use:
    <em:minVersion>3.0</em:minVersion>
    <em:maxVersion>3.0.*</em:maxVersion>
  • Version, description, creator and homepageURL should all be self-explanatory.
  • iconURL is the 32x32 icon that will be shown on the Add-Ons dialog, it uses a chrome:// protocol address, which will be explained in the next section.

chrome.manifest

Inside the chromenewtab-0.1 folder, we need to create a chrome.manifest file and paste in the following:

content         chromenewtab chrome/content/
skin            chromenewtab classic chrome/skin/
overlay         chrome://browser/content/browser.xul chrome://chromenewtab/content/overlay.xul
style           chrome://global/content/customizeToolbar.xul    chrome://chromenewtab/content/chromenewtab.css

The first line tells Firefox that a content package named chromenewtab will have its content directory linked to the chrome/content directory within the package — so that chrome://content/ will link to chromenewtab-0.1/chrome/content/.

Similarly the next line links chrome://skin to chromenewtab-0.1/chrome/skin/ with the addition of classic telling Firefox to make it part of the theme named classic.

For the manifest to make any sense though, we will need to create the directories and files to which we refer. Inside of the chromenewtab-0.1 folder, we run the following commands in a terminal (Linux and OS X only — on Windows create the directories and empty files however you choose).

mkdir -p chrome/content
mkdir -p chrome/skin
touch chrome/content/overlay.xul
touch chrome/content/overlay.js
touch chrome/content/chromenewtab.css

The last two lines in the chrome.manifest tell Firefox that it should merge our overlay.xul with Firefox's own browser.xul. It is via these overlays that extensions do the things that they want to do.

overlay.xul

Now that the groundwork is complete, it's time to actually make our extension do things.

Copy into the overlay.xul file the following code:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="chrome://chromenewtab/content/chromenewtab.css"?>

<!DOCTYPE overlay >
<overlay id="chromenewtab-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<script type="application/x-javascript" src="chrome://chromenewtab/content/chromenewtab.js"/>

<toolbarpalette id="BrowserToolbarPalette">
  <toolbarbutton id="custom-button-1"
  label="New Tab with Chrome-like bookmarks"
  tooltiptext="New Tab with Chrome-like bookmarks"
  oncommand="create_chromenewtab()"
  class="toolbarbutton-1 chromeclass-toolbar-additional chromenewtabbutton"
/>
</toolbarpalette>
</overlay>

In this file, we are telling Firefox that we are going to create a toolbar button and style it with the CSS in chromenewtab.css, and the method to perform when the button is pressed is found in chromenewtab.js.

Before we look at the CSS, we need to add the images we will use. Copy the below images into the skin directory.

This is the icon image that will appear in the Add-Ons dialog:

Below is the image that we will use on our toolbar:

Now we can add the stylings — add the following code to chromenewtab.css:

 #chromenewtab-button
  {list-style-image: url("chrome://chromenewtab/skin/chromenewtab_button.png");}

/* common style for all custom buttons */
.chromenewtabbutton                  
  {-moz-image-region: rect( 0px 24px 24px  0px);}

.chromenewtabbutton:hover     
  {-moz-image-region: rect(24px 24px 48px  0px);}

[iconsize="small"] .chromenewtabbutton
  {-moz-image-region: rect( 0px 40px 16px 24px);}

[iconsize="small"] .chromenewtabbutton:hover
  {-moz-image-region: rect(24px 40px 40px 24px);}
All the CSS does, is restrict the size of the region shown in our button image. Hover images being on the bottom row, default icon sizes on the left and small icon images on the right. Now we need to add some functionality to the button. All we'll do in the first instance, is create an alert so that we know that everything is behaving as it should. Add the following code to overlay.js
function create_chromenewtab() {
  alert("Do stuff here");
}

Setting up Firefox

To make Firefox more useful in helping us debug any bugs we find, we need to set some preferences. Thankfully, there is a list on the Mozilla Developer site to help us.

Beyond setting the preferences to make Firefox more verbose, the one thing that you should do is create a link from your Firefox profile extension directory to the directory where your code is stored.

To do this, create a new file in your extensions directory with the name of your extension used in the install.rdf file, and as its contents use the fully qualified path name to your code folder. In my file I have:

/Users/chris.duckett/tmp/chromenewtab/chromenewtab-0.1

Now we are ready to restart Firefox and see our extension in action.

Hopefully, everything will have worked as it should, and you should see the Add-Ons dialog informing you that our extension is installed.

To test it, we need to add the button to the toolbar. Do to this we customise our toolbar and drag our extension button onto it.

When we press the button, we should get an alert box appearing.

That's all we need to do for our button to do what we want.

Searching Firefox's History

In order to do what we set out to do, recreate the Chrome/Opera start page, we will need to find a way to extract the most visited sites from Firefox. With version 3 of Firefox we can use the HistoryService to expose the data we want.

Replace the contents of overlay.js to the following code:

function create_chromenewtab() {
  // Add tab, then make active
  gBrowser.selectedTab = gBrowser.addTab("about:blank");
  var newTab = gBrowser.getBrowserForTab(gBrowser.selectedTab);
  newTab.addEventListener("load", function() { 
    var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"].getService(Components.interfaces.nsINavHistoryService);

    // no query parameters will get all history
    var options = historyService.getNewQueryOptions();
    options.queryType=0;
    options.sortingMode = options.SORT_BY_VISITCOUNT_DESCENDING;
    options.maxResults=10;

    var query = historyService.getNewQuery();

    // execute the query
    var result = historyService.executeQuery(query, options);

    result.root.containerOpen=true;
    for(var i=0;i<result.root.childCount;i++){
      newTab.contentDocument.body.innerHTML += "<p><a href=\""+result.root.getChild(i).uri+"\">"+(result.root.getChild(i).title==""?result.root.getChild(i).uri:result.root.getChild(i).title)+"</a></p>";
    }
  
  }, true);
}

This code begins with creating a new tab that shows a blank page and makes it the selected tab. Then a listener is added to the tab that is called every time the tab loads a new page — that means that if you navigate away from the page that is shown, the code will be executed on each and every page loaded.

The internal function calls the HistoryService, constructs a query to fetch the top 10 pages by visit count, and outputs the results as a series of links.

The bugs and wrap-up

Clearly the resultant page is far removed from our original vision, to load each of the most visited pages we would need thumbnails for each page or to use a canvas object that loads the pages.

Thumbnails are a bad approach and the Canvas object failed abysmally when I tried to implement it — even when I used the code from the MDC site. Theoretically, the Canvas implementation should work.

And as noted above, the internal function runs on each and every load, but that can be very easily fixed.

But on the other side of the coin, we do have a working extension, and your knowledge of how to create and write your own should be enough to get you started.

Good luck and welcome to the wider world of extensions.

About Chris Duckett

Some would say that it is a long way from software engineering to journalism, others would correctly argue that it is a mere 10 metres according to the floor plan.During his first five years with CBS Interactive, Chris started his journalistic advent...

Editor's Picks

Free Newsletters, In your Inbox