| Also feel free to contact me if you have any questions or suggestions for improvement: |
You can begin by downloading the tutorial files. Here is an outline of the tutorial:
- Firefox extensibility model
- Setting up a development environment
- Extension directory structure
- Loading an extension into Firefox for development
- Finding the element you want to modify
- Your first extension
- Styling elements with CSS
- Incorporating user preferences
- Packaging and distribution
- Debugging and development resources
Firefox extensibility model
Let's start by providing an overview of the Mozilla Firefox extensibility model. Firefox interestingly allows third-party code modules, called extensions, to modify the core browser's functionality or appearance and add new features or user interface elements. Roughly speaking, such an extension consists of three parts: XUL documents, which describe the layout of the user interface, CSS directives that define the apperance of interface elements, and JavaScript code that determines the behavior of such elements, e.g. what should happen when a user presses a button or types something in a textbox. In the following, I will be mainly focusing on XUL and JavaScript, although I will mention a couple of CSS examples to showcase its use.
Setting up a development environment
Before we start hacking, it is a good idea to set up a development environment that will make our life easier in the long term. The following instructions apply to a Linux environment; the situation for other platforms, including Windows, is similar. First of all, Firefox does not by default allow you to open two separate instances of the browser at the same time. If you try launching Firefox a second time, a new window of the first instance will be opened instead. Nevertheless, it is beneficial to be able to use a separate Firefox instance for testing purposes during extension development, while also running your regular browser at the same time. This is indeed possible; you just have to set an environment variable called MOZ_NO_REMOTE to 1 before launching the second instance. In addition, it is a good idea to create a new testing profile that will be used during development, so that you don't accidentally tramp over your existing browser settings when testing your extension. To do that, just launch Firefox with the ProfileManager flag (<firefox_directory>/firefox -ProfileManager) and create your new profile. You can then launch Firefox with that profile by using the P flag: <firefox_directory>/firefox -P <profile_name>. I use variations of this bash script to further automate this process.
Another useful thing to do is to turn on JavaScript warnings and errors for the Error Console. These can give you more information when something goes wrong during development. Type "about:config" in an empty tab, then find and set preference javascript.options.showInConsole to true. A related preference is javascript.options.strict, which enables even stricter warnings, but I have empirically found that this produces a whole lot of error messages from Web pages and other installed extensions, so many that it renders the Error Console useless. I typically leave it disabled myself. It is good to know that you can open the Error Console in a separate tab inside the browser, by entering "chrome://global/content/console.xul" on the location bar. This comes in handy when you frequently restart the browser (more on why you need to do that below) and want to have easy access to errors without going through the extra step of opening the console from the Tools menu. Lastly, the choice of a source code editor is a personal one. Feel free to use whatever you are most comfortable with on your platform of choice; if you use Emacs, like I do, you might be interested in these bindings that enable syntax highlighting for all extension-related source files. I would also recommend Komodo Edit, another popular editor that is specifically geared towards development with dynamic languages.
Extension directory structure
Firefox extensions follow a pre-defined directory structure. This is not cast in stone and there are ways around it, but it is a good idea to stick to it, at least for now. First, download the tutorial files and unzip them. You will see a number of directories and files. The development directory contains the finished code for the extension will be building, in the directory structure that should be used while developing an extension. The package directory on the other hand contains the same files, but in the structure needed for creating the installation package (.xpi file) that can be distributed to users to install on their own Firefox instance. The other directories contain snapshots of the extension development process we will be going through, and we will be explaining their contents in turn. For now, create the directory structure in development, but with empty directories. For your reference, defaults will contain the default user preferences for the extension, content the XUL and JavaScript files, while skin will hold the CSS files and images. The chrome.manifest and install.rdf files at the top level include instructions to Firefox for installing and registering your extension at startup.
Loading an extension into Firefox for development
Now that we have the empty directory structure, let's create a blank extension, one that does nothing, and load it into Firefox, i.e. make Firefox aware of it, by registering it with the Extension Manager. Later on, we will add functionality. The extension we will be developing here is called "lugext" (from LUG extension), and let's assume that we have created the directory structure under /home/<user>/lugext/development. You could place your extension files anywhere though, not necessarily in your home directory. Now, if you look in your testing profile directory , the one you created before (~/.mozilla/firefox/<profile_name> on Linux), you will see an extensions subdirectory with some other directories in it, one for each extension installed for this profile. To register our own extension with Firefox, the only thing we need to do is create a "pointer" text file in the extensions subdirectory, containing the path to our development directory. Here is the corresponding file for the lugext extension. This file should be named after the extension ID, which is a unique identifier that you generate for your extension. This can be either a long hexadecimal number or, more recently, a string of the form <extension_name>@<organization>. I suggest using the latter, since it is simpler and more readable. For instance, I will be using the ID "lugext@ucla" for the extension we are going to be building. In summary, I would copy this file into the extensions subdirectory of the profile I would like to add my extension to.
We also need to tell Firefox where to find the extension files and provide other information necessary for registration. This is what the chrome.manifest and install.rdf files are for. The former simply points Firefox to your extension's content and skin subdirectories, while the latter contains required information such as extension ID, extension author and version, as well as which Firefox versions this extension is compatible with. These two files are in the blank directory; there are comments in them that should give you a good idea of what each field is for. Copy these files from blank to development. Then launch a Firefox instance with the testing profile and open the Add-ons Manager (Tools->Add-ons). Under Extensions you should be able to see the new extension; it is of course still feature-less, since we have not implemented any functionality yet.
Finding the element you want to modify
The first step has been taken! We now have a way of loading our extension code into Firefox. Let's now add a new menu item under the Tools menu, right before the Clear Private Data... item, which, when clicked, will display a "Hello world" message. Before adding the new item, we need to figure out how the Tools menu is represented in the Firefox codebase. I mentioned before that the user interface layout is represented by XUL documents. A helper application called the DOM Inspector can help us identify the specific XUL document that contains any element we are interested in. It is an optional component that gets installed along with Firefox. You can launch it from Tools->DOM Inspector. Go to File->Inspect a Window and select the browser window (the one ending in"Mozilla Firefox"), then click on the leftmost icon on the toolbar (with the tooltip "Find a node to inspect by clicking on it"). This allows us to click on any interface element and have the DOM Inspector look up its representation in the appropriate XUL document. Switch back to the browser window, hit Alt+T to open the Tools popup and click on the "Clear Private Data..." menu item. The item is briefly highlighted with a red border, and a new dialog opens. Close the dialog and switch back to the DOM inspector. You will notice that the file chrome://browser/content/browser.xul has been loaded and an element with id sanitizeItem is selected, which is of course the XUL element representing the Clear Private Data... menu item. Note that even the separator has an associated XUL element; its id is sanitizeSeparator. Browse around the DOM Inspector for a while. See if you can locate other interface elements, such as the location bar or the search textbox.
Your first extension
We are now ready to add the new menu item. To do that we will need to write an overlay. This is simply a XUL document that tells the browser which elements to add or replace in the interface. Take a look at the hello directory in the tutorial files. The overlay we will use is lugext-overlay.xul. Copy that to the content subdirectory of your extension development directory. Some of the code in the overlay is boilerplate; in addition, we create a new menuitem XUL element, add it under the Tools menu and define keyboard shortcuts for it. The overlay also associates the JavaScript function lug_hello() with the menu item, so that, when it is clicked, that function will get called. This function is located in file lugext.js, which should also be copied into the content directory. Note that, JavaScript does not support separate namespaces (yet). Thus, you have to make sure that all your function and global variable names do not collide with other names in other extensions or the Firefox code itself. A good rule of thumb is to prepend all such names with an extension-specific prefix. That is why I have added "lug_" at the beginning of the function name. To make Firefox aware of the overlay, we have to a add a directive in chrome.manifest instructing the browser to "merge" this overlay in the main browser XUL document at startup. Copy the updated chrome.manifest file from hello to the development directory, overwriting the previous one. Lastly, I have added some more information in install.rdf, such as the extension author and contributors, a homepage URL and the path to the extension icon. Copy the updated install.rdf file into development, and also copy lugext-icon.png into the skin subdirectory.
That's all! For our changes to take effect, we need to restart Firefox, so that all the XUL documents are reloaded. This is common practice when developing extensions; very often, the only way to test your changes is to restart the browser. You should now see a new menu item under the Tools menu titled "LUG hello". Clicking on it brings up a dialog with the familiar greeting. The dialog can be also invoked using the Ctrl+Shift+L shortcut from anywhere in the browser, or by pressing 'L' when the Tools menu is open. If you open the Add-ons Manager, you will see the icon we added next to the extension name. Right-clicking on the extension will allow you to either visit the homepage specified in install.rdf or open the About box which displays the extension author and contributors, among other things.
Styling elements with CSS
Let's now see how we can use CSS to change the appearance of interface elements. In this instance, we are going to paint menu item we added in red. Copy acmanager.css from the hello-colored subdirectory into the skin directory. It contains a single CSS directive that instructs Firefox to paint the element with id lugExtension with a red color. That is the id we assigned to our new menu item in lugext-overlay.xul. If you restart Firefox, you should see the menu item colored in red. Everything else remains the same. Using this method you can make text elements bold or italic, or change margins and dimensions of textboxes. The possibilities are endless!
You now know all you need to create your own extensions. In the rest of the turorial, we will be discussing more advanced stuff, such as incorporating user preferences and more complex JavaScript code. You may want to jump to the section on packaging your extension for distribution or read about debugging and development resources. Feel free to follow along or go off and start implementing your own cool idea!
Incorporating user preferences
All user preferences in Firefox, even those that do not appear in the graphical preferences dialog, can be seen by typing "about:config" in the location bar. We will now augment the extension we have created so far to use such user preferences that persist across browsing sessions. In particular, we will implement an accumulator, which, given a certain number, will count up to that number adding the intermediate values in the process. So, for example, if the ceiling (the number to count up to) is 5, our accumulator should return the value 1+2+3+4+5=15. We will also provide an option to add the cubes of numbers instead of the numbers themselves (e.g. 13+23+33+43+53=225), First, we need to update our overlay tp rename the menu item to LUG accumulator and associate it with a new function, namely lug_addThemUp(). Copy the updated lugext-overlay.xul from the accumulator-options subdirectory into the content directory, overwriting the old file.
The options we will offer our users are shown in this snapshot. Users are able to specify the ceiling number, whether to ask for confirmation before performing the calculation, and what method to use to compute the result. There are three alternative methods: the "hard" way, the "easy" way, and the "functional" way (more on each of these later). We will first design the XUL document that implements the dialog box that will offer these options to the user. Subsequently, we will write some JavaScript code to implement the behavior of the elements on the dialog box, and to actually perform the computation when the menu item is clicked. Look at options.xul and copy it to the content subdirectory. This XUL document defines a preference window with two panes, one for setting user preferences and the other for an About box. The file is well-commented and you should be able to get a good idea of what I'm doing. If you have any questions, you can look on XULPlanet for more details on the different XUL elements being used. Along with the preferences dialog, a new lugext.css file specifies the icons used at the top toolbar, while the image used in the About box is uclalug-logo.jpg. Copy both of these to the skin directory. We also have to define the default preferences, which Firefox will use the first time the extension is loaded, and before the user has had a chance to change them. These are defined in lugext-prefs.js, which should be copied into development/defaults/preferences/.
You will notice that there is a new install.rdf in the accumulator-options subdirectory. In that, we specify that the preferences dialog for our extension will be the one we just created; this dialog will be shown when clicking on the extension's Options/Preferences button in the Add-ons Manager. Note that we would normally enter string of the form "chrome://lugext/content/options.xul" here. The reason I have slightly deviated from that rule is to work around a bug in Firefox on Windows that does not allow the preferences dialog to be resizable. In addition, you will notice that I have added an updateURL property to install.rdf. Firefox supports automatic updates for extensions, by performing periodic checks and notifying the user when a newer extension version is available. To include that capability in our extension we need two things: 1. an update manifest, in our case named lugext_update.rdf, which specifies the latest version of the extension and the name of the installation package; we would upload this to a Web server, along with the extension installation package to make it publicly available for download. 2. the Web server itself has to be "educated" as to the correct MIME types for update manifest (.rdf) and installation package (.xpi) files. The required directives for the Apache Web server are in this file. It should be renamed to .htaccess (note the dot at the beginning) and uploaded to the same Web server directory as the update manifest and installation package. Make sure the permissions of this file on the server are -rw-r--r--. You can also read more about the automatic update mechanism here.
We are all set. Restart Firefox and verify that the menu item has changed to LUG accumulator (it should still be red). Then go to the Add-ons Manager and click on the extension and then on its Options/Preferences button. The preferences dialog we designed should appear and you should be able to check/uncheck the checkboxes, and change the selection of the radiogroup. If you also open up about:config and type 'lugext' in the filter textbox, you should see all the extension preferences (which we named in lugext-prefs.js). A preference will be bold when it has been changed from its default value.
We are now ready to write the JavaScript code that actually performs the desired calculation. Go to accumulator-final and copy lugext.js and options.xul into the content subdirectory. The former file contains all the code to read the user preferences and perform the calculation, while the latter only contains a couple of minor modifications I have made with respect to selecting radio buttons. The code uses the Firefox preferences service to retrieve the user preferences and then uses a switch statement to redirect control to one of three functions that compute the result. The "hard" way works by using a loop to add up the values, the "easy" way uses the appropriate mathematical formulas), and the "functional" way utilizes the well-known paradigm of map/reduce from functional languages to cube all the numbers in an array, if needed, and then collapse all the values into their sum. By restarting Firefox we have the opportunity to check all these out. Feel free to play with the different options. The result should be the same regardless of which method is used for the computation.
Now that we have successfully written our extension, we would like to prepare it so that it is ready for distribution. We cannot just distribute our development directory, since target browsers will not have a shortcut file in their extensions subdirectory. Fortunately, Firefox takes care of the extension registration without a shortcut file, as long as our extension files follow a certain directory structure (slightly different from the one during development), and they are packaged up in a single file. You might have noticed from your experience that extensions are typically deployed in a single installation package with a .xpi file extension. Let's see now how we can create such a package. The following instructions apply to Linux, but the procedure for other platforms is very similar. The directory structure we need can be seen under the package subdirectory. The structure is similar to the one in the development directory, but the content and skin subdirectories have been moved under a chrome directory. Copy all the files from the development directories into their respective package directories. Then go to the chrome subdirectory and issue the following command: jar cf lugext.jar *. This will create a .jar file inside chrome that contains all the code for the extension. Note that we are just using the Java jar utility for packaging all the files here; there is no Java code anywhere in our extension. We can now safely remove the content and skin subdirectories from inside chrome to reduce the size of our extension package, since all the code is already contained in lugext.jar.
The defaults directory and the chrome.manifest and install.rdf files have to be outside the chrome directory. Before packaging all those up, we need to slightly modify chrome.manifest to have it point inside the jar file, instead of the content and skin directories. You can find the updated chrome.manifest in the package directory of the tutorial files. Lastly, issue the following command while in the package directory of your extension: zip -r lugext_0.1.xpi *. This will package everything up in a zip file (or zippie file or .xpi file), which can be distributed and used for installing the extension in unmodified browsers. That's it! Your new extension is ready to be deployed. You might even want to publish it to public repositories, in order to make it easier for interested users to find it. The most popular repositories at this time are the official Mozilla Add-ons website and the Add-ons Mirror (formerly Extensions Mirror).
Debugging and development resources
Debugging is an important part of the development's process, and building Firefox extensions is no exception. For simple debugging tasks, one can use graphical alerts or console messages. The former is simply accomplished by using the built-in alert() function, while the latter by utilizing a Firefox service that writes messages to the Error Console. I have included code for using this service in lugext.js. For more advanced debugging, such as setting breakpoints, I would recommend Firebug; however, this excellent tool has been designed with a focus on debugging Web applications that run in the main browser window, so some of its functionality may not work for extensions that typically operate on the user interface of the browser itself.
In addition, there is plenty of free online documentation to get you started. This page contains some basic information for building extensions, much of which we covered here. XULPlanet is by far the most complete source of documentation for the XUL language and the Firefox APIs, while the MozillaZine Knowledge Base and the Mozilla Developer Center are also rich sources of information. A good reference site for JavaScript is here. There are also a couple of free online books that explain the Firefox development platform, notably Creating Applications with Mozilla and Rapid Application Development with Mozilla. Lastly, you can find answers to questions and even ask your own questions on the Mozilla newsgroups on news.mozilla.org and in the numerous MozillaZine forums.