322
Part VI — Creating Extensions and Themes
Most text editors won’t recognize XUL files as XML by default. You should look for an editor
that can highlight XML and in which you can specify that XUL files are actually XML files.
Automatic indentation: If you want to create readable XML documents, JavaScript programs, or any other structured text files, you should use indentation to emphasize the
hierarchical structure of the document elements. For example, in XML files, the child
elements should have greater indentation than their parent element:
Editors with an autoindentation feature automatically indent the elements of your document according to its type.
Parentheses matching: A good text editor warns you if you forget to close a previously
opened parenthesis. This can save you a lot of debugging time further along the way.
Some editors also allow you to see the correspondence between the opening and the
closing parentheses, which can be a great troubleshooting tool.
Line numbering: If there are problems or errors in your document, the browser (or a
validation program) typically reports them along with the line number on which the
error was encountered. A good text editor will display the current cursor position within
the document and allows jumping directly to any given line number.
There are many excellent freeware text editors available:
JEdit: A multiplatform programmer’s text editor
VIM: A flexible text editor that works on many operating systems
Crimson Editor: Source editor for Windows
Nedit: A multipurpose text editor for the X Window System
Dozens of additional text editors are available on the Web.
ZIP Format Compression Tool
Extensions are packaged using the ZIP compression format. There are a lot of great ZIP compression tools, many of which are free. If you are on Windows, you might want to look at one
of the following programs:
7-Zip
WinZip
WinRar
Chapter 17 — Creating Extensions
If you want to automate the packaging process, you should get a ZIP tool with a command-line
interface. For example, the free 7-Zip file archiver has a command-line utility called 7z.exe. You
will see an example of how it can be used to automatically package your extension later in the
chapter.
Graphics Editor
If you want your user interface to have icons, images, or any other graphic elements, you need
to create them using a graphics editor. Similar to the other software tools mentioned, a great
variety of graphics editors is available, starting with simple and lightweight utilities and going
all the way to full-featured applications.
You will want your graphics editor to have several features. First, it should be able to handle
GIF, PNG, and JPG image formats. The program should also be able to save transparent GIF
and PNG files and give you some control over the saved format parameters, such as the number of colors in the GIF image palette. For creating more elaborate graphics, you might want
an application that supports special effects such as drop shadow, emboss, and so on.
Some of the most popular graphics editor applications are as follows:
GIMP: An advanced, free, multiplatform image editor
Adobe Photoshop: A professional image editing application
Adobe Photoshop Elements: A simplified (and much more affordable) version of
Photoshop with most of the needed functionality
Corel Paint Shop Pro: An advanced image editing application for Windows
Building Your First Extension
This section is a tutorial for creating, packaging, and deploying Firefox extensions. You will
learn how to create a fully functional extension, how to test and troubleshoot it, and finally,
how to deploy it by publishing it on your web page and on other extension-related sites.
The extension we will create in this section is called SiteLeds. The idea is to have an icon, or a
status led, on your Firefox status bar that displays the state of a given web page — whether it is
available or not and, if so, whether it was recently changed. Such an extension has several uses:
You can monitor your own website and make sure the server is up and running, or alternatively,
you can watch some web page for changes and get a notification whenever the page is updated.
Introduction to Chrome
As you should know by now, the process of building applications with Mozilla is very similar to
building dynamic web pages. Typically, a web page is an HTML document that defines the
page contents, a JavaScript program that adds some functionality to the page, and a CSS style
sheet that determines its appearance. Building applications with Mozilla is very similar. The
XUL document defines the user interface, the JavaScript makes it dynamic, and a style sheet
specifies its presentation. Figure 17-1 demonstrates this concept.
323
324
Part VI — Creating Extensions and Themes
Dynamic Web Page
XUL Application
HTML
XUL
JavaScript
CSS
JavaScript
CSS
FIGURE 17-1: A comparison between a web page and a XUL application
To specify the address of any specific web resource, an HTML page, a CSS file, and so on, you
use a Uniform Resource Locator (URL). For example, if you want to specify the official Firefox
page, you can use its URL, http://www.mozilla.org/products/firefox/index.
html. The http part means that the page should be fetched from the Web using the HTTP
protocol. Similarly, when specifying application resources (XUL documents, CSS style sheets,
and many other components of our application), you will use a chrome URL. The Web is the
place where all the web pages are found. Similarly, the chrome contains all the elements of our
application user interface. Figure 17-2 further clarifies this concept. One example of a chrome
resource is the Firefox Options dialog, which can be found at the following address:
chrome://browser/content/pref/pref.xul.
Web
Browser
Chrome URL
HTTP URL
Chrome
FIGURE 17-2: Web versus chrome resources
As mentioned in the previous chapter, XUL applications can be run directly from the Web. We
will be using the regular HTTP URLs to reference such applications. Chrome addresses are used
to reference the components of the locally installed application, such as the browser itself or any
of its extensions. Because XUL applications that are run directly from the Web can be potentially
malicious, they have some security restrictions imposed on them by the browser. Installed components accessed through the chrome URL do not have such restrictions.
Chapter 17 — Creating Extensions
Let’s use our web address analogy to introduce some additional concepts. For the browser to be
able to find a web page using its URL, the page domain (mozilla.org in our previous example) must be first registered in a domain registry. Similarly, for Mozilla to locate the files that
define a XUL application, the packages that contain these files must be first registered in the
chrome registry. When a browser receives a chrome URL, it looks into the chrome registry,
obtains the installation locations of the needed files, and then loads the documents from these
locations. Using this method for accessing our application resources allows us to be independent of the physical location of the files, as long as the chrome registry contains the correct
information.
A typical chrome URL looks like this:
chrome:////
Take a closer look at the different parts of the chrome URL:
chrome:: This means that we want a chrome resource.
package name: The package of the wanted file. Typically, every Mozilla application is
split into several packages. For example, Firefox consists of browser, mozapps, help, and
so on. An extension is usually a separate package with its own package name.
type: There are three chrome resource types:
■
content: XUL and JavaScript documents are located in the content part of the
package. This part contains the definition of the user interface and its behavior.
■
skin: This part contains the files that determine the appearance of the user interface, typically CSS style sheets and images. Separating this part from the content
allows having several skins in the same package. The browser can determine which
skin to display based on the user’s current browser theme.
■
locale: All user interface strings and messages should be located in this part of the
chrome package. Separating them from the content part allows the package to contain several sets of strings, each translated into a different language. The browser
determines which translation to use based on its current user interface language.
The string resources are usually specified in XML Document Type Definition
(DTD) and JavaScript property files (more on this later in this chapter).
Figure 17-3 demonstrates the structure of a typical chrome package.
You don’t have to keep the content, locale, and skin parts packaged in the same physical file.
While this is often the case, a logical package specified in the chrome URL can actually be composed of several physical chrome package files. For example, you can add a translation to an
existing logical package by installing and registering a separate locale package file.
Some examples of chrome URLs follow:
chrome://browser/content/browser.xul: The browser.xul file defines the user
interface of the main browser window. As you can see, it is a part of the browser package
and located in the content part of this package, as expected.
325
326
Part VI — Creating Extensions and Themes
chrome://inspector/content/inspector.xul: The main window of the
DOM inspector extension is defined in the inspector.xul file. This file is a part of the
inspector package.
chrome://browser/skin/browser.css: This is the main browser style sheet.
If there are several sets of skins in the browser package, each containing a different
browser.css file, the correct one will be used, according to the current browser theme.
chrome://browser/locale/browser.dtd: This is the address of the main
browser string table. Note that nothing specifies the specific language or locale in the
URL. If the locale part of the browser package contains several browser.dtd files (each in
a different language), the URL is resolved to the string table file that contains the strings
in the user’s language.
Chrome Package
content
skin
locale
modern
classic
- XUL
- JavaScript
- CSS
- Images
Spanish
English
- DTD
- Properties
FIGURE 17-3: The structure of a typical chrome package
Creating the SiteLeds Extension
In this section, we create the first fully functioning version of our extension. We create the various components of the extension: its XUL user interface definition, its functionality written in
JavaScript, and its CSS style sheet. Then you see how to package these components and create
the final installable extension file.
Building the User Interface
The user interface of our first extension is extremely simple. Because we want to add an icon to
the status bar, our user interface consists of a single statusbarpanel element:
tooltiptext=”SiteLeds Status Icon”
sitestate=”unknown”/>
Chapter 17 — Creating Extensions
We are using the statusbarpanel-iconic class to specify that the status panel will display
an icon. Also, we gave our status bar panel a unique id so we can find it later, using the DOM
methods from JavaScript, and apply different styles to it, using CSS.
The tooltiptext attribute specifies the text that will appear on the panel tooltip.
We have added a new private attribute called sitestate to our panel. This attribute specifies
the current site status and is useful in applying different styles to our panel widget.
Integrating the New Element into Firefox
Now that we have created our user interface element, how do we let the browser know that we
want this new element to appear on the status bar? Mozilla has a mechanism called dynamic
overlays that allows the user interface to be modified dynamically without changing the actual
document that defines it. In our example, we add an element to the Firefox status bar without
modifying the browser XUL definition file.
The procedure for creating a dynamic overlay is straightforward:
1. Create a separate XUL document that specifies all the needed additions and modifications to the original user interface.
2. The root element of the new XUL document is overlay. This specifies that the children of this element are inserted into a target document (or are overlaid on top of it).
Define any number of child elements under the overlay. The id attribute of these elements will be matched against the ids of the elements in the document being overlaid,
and if there is a match, the two elements will be merged.
3. You need to perform one additional step to let Mozilla know which XUL documents are
overlays and what their target documents are. The following sections explore how this is
done.
Let’s modify our XUL definition to specify that we want our status bar panel to appear on the
main browser status bar:
Let’s see exactly what we have done:
We have defined an overlay element. Its id attribute uniquely identifies it, and the
value of the xmlns attribute specifies that the default name space of our XML document is XUL, meaning that all its children are XUL elements.
327
328
Part VI — Creating Extensions and Themes
We have created a child statusbar element in the overlay node. Its id is statusbar, which is identical to the id of the main Firefox statusbar element. This means
that our status bar is overlaid on top of the Firefox status bar.
Because our status bar element is merged into the Firefox status bar, all the child elements of our status bar — in this case, a single statusbarpanel element — are added
to the child elements of the Firefox status bar. We can use the insertbefore attribute
to specify the exact position at which the new status bar panel is inserted. In this case, we
want it to be inserted just before the element with the statusbar-display id, which
is the element that displays the current browser status. Figure 17-4 shows the position of
the new status bar panel.
The SiteLeds status panel
FIGURE 17-4: The position of the new status bar panel
Dynamic overlays can be used not only to add new elements to existing ones but also to modify
the attributes of the existing elements and to add new top-level elements to the target window.
Defining the Appearance
We want our status bar panel to display different icons, depending on the current state of the
monitored site. Table 17-1 specifies the various icons that are displayed according to the
sitestate attribute of our statusbarpanel element.
Chapter 17 — Creating Extensions
Table 17-1 Displayed Icons
State
Icon
Icon Filename
Meaning
unknown
state-unknown.png
The site state is unknown.
ok
state-ok.png
The page is reachable and hasn’t been
modified.
error
state-error.png
There was an error connecting to the site.
modified
state-modified.png
The monitored page has been modified.
Our style sheet document, siteledsOverlay.css, will therefore look like this:
#siteleds-statusbar-panel[sitestate=”unknown”] {
list-style-image: url(“chrome://siteleds/skin/state-unknown.png”);
}
#siteleds-statusbar-panel[sitestate=”ok”] {
list-style-image: url(“chrome://siteleds/skin/state-ok.png”);
}
#siteleds-statusbar-panel[sitestate=”error”] {
list-style-image: url(“chrome://siteleds/skin/state-error.png”);
}
#siteleds-statusbar-panel[sitestate=”modified”] {
list-style-image: url(“chrome://siteleds/skin/state-modified.png”);
}
As you can see, we are selecting our status bar panel by its id attribute (siteleds-statusbarpanel), and specifying which icon to display by defining different rules for different values of
the sitestate attribute. The icon we want to appear on the status bar is specified using the
CSS list-style-image attribute.
Adding the Functionality
We want our extension to try to load a given web page. If this load operation succeeds, the
extension checks whether the page was modified by comparing its content with the one saved
during the previous request. Our JavaScript code then sets the value of the sitestate
attribute of our status panel according to the result of this operation.
We will create the JavaScript implementation file named siteledsOverlay.js.
First, let’s define some global variables:
var gSiteLedsLastRequest = null;
var gSiteLedsLastContent = null;
329
330
Part VI — Creating Extensions and Themes
These variables contain the last HTTP request object and the contents of the last loaded page.
The main function that initiates the page load request is called siteLedsCheckPage:
function siteLedsCheckPage() {
var pageURL = ‘http://www.iosart.com/firefox/siteleds/index.html’;
gSiteLedsLastRequest = new XMLHttpRequest();
gSiteLedsLastRequest.onload = siteLedsPageLoaded;
gSiteLedsLastRequest.onerror = siteLedsPageError;
gSiteLedsLastRequest.open(‘GET’, pageURL);
gSiteLedsLastRequest.send(null);
}
This function does the following:
1. Creates a new XMLHttpRequest object. This object is used to perform HTTP
requests.
2. Adds two event handlers. The siteLedsPageLoaded function is called when the
server responds to our query, and the siteLedsPageError function is executed in
case an error occurs during the HTTP request.
3. Initializes and sends an HTTP Get request for the specified web page.
Our error handling function looks like this:
function siteLedsPageError() {
var ledElement = document.getElementById(‘siteleds-statusbar-panel’);
ledElement.setAttribute(‘sitestate’, ‘error’);
setTimeout(siteLedsCheckPage, 900000);
}
This function is called if there is an error during the attempt to load the monitored web page
(for example, if the network connection is down). We first find our status panel element using
the DOM getElementById method. Then we set the value of the sitestate attribute to
error. This, along with our style sheet definitions, will make the error icon appear on the status bar. Finally, we call the setTimeout function to try to perform the same test again after
900,000 milliseconds (15 minutes).
If the server responds to our HTTP request, the siteLedsPageLoaded function is called:
function siteLedsPageLoaded() {
var ledElement = document.getElementById(‘siteleds-statusbar-panel’);
if (gSiteLedsLastRequest.status == 200) {
var prevContent = gSiteLedsLastContent;
gSiteLedsLastContent = gSiteLedsLastRequest.responseText;
if ((prevContent != null) && (prevContent != gSiteLedsLastContent)) {
ledElement.setAttribute(‘sitestate’, ‘modified’);
} else {
ledElement.setAttribute(‘sitestate’, ‘ok’);
setTimeout(siteLedsCheckPage, 900000);
}
Chapter 17 — Creating Extensions
} else {
ledElement.setAttribute(‘sitestate’, ‘error’);
setTimeout(siteLedsCheckPage, 900000);
}
}
First, we check whether the server returned HTTP status code 200 (OK). If so, the page was
loaded correctly, and we can check whether it has changed since the last time we successfully
loaded it. We compare the contents of the current and the previously loaded pages and,
depending on the outcome of this comparison, set the current state to either ok or modified.
If the server returned a status code other than 200, we set the state of our panel to error.
Also, if the page wasn’t modified, we schedule a new test to 15 minutes from now by calling the
setTimeout function.
After defining all the needed functions, we must tell Firefox to start the site checking cycle
when it loads by adding the following line at the top level of our JavaScript file:
window.addEventListener(“load”, siteLedsCheckPage, false);
This adds an event handler for the window load event, meaning that our
siteLedsCheckPage function will be called after the main Firefox window is first opened
and initialized.
The JavaScript functions and global variables we have defined are evaluated in the global namespace along with the other Firefox JavaScript code. This means that if one of our functions or variables has the same name as an existing identifier, we will have a name collision. To avoid this
situation, you should always create a unique string (“siteLeds” in our case) and use it as a
prefix for all your global identifiers. The following sections describe another technique for avoiding such conflicts.
The final siteledsOverlay.js file is as follows:
var gSiteLedsLastRequest = null;
var gSiteLedsLastContent = null;
function siteLedsCheckPage() {
var pageURL = ‘http://www.iosart.com/firefox/siteleds/index.html’;
gSiteLedsLastRequest = new XMLHttpRequest();
gSiteLedsLastRequest.onload = siteLedsPageLoaded;
gSiteLedsLastRequest.onerror = siteLedsPageError;
gSiteLedsLastRequest.open(‘GET’, pageURL);
gSiteLedsLastRequest.send(null);
}
function siteLedsPageError() {
var ledElement = document.getElementById(‘siteleds-statusbar-panel’);
ledElement.setAttribute(‘sitestate’, ‘error’);
setTimeout(siteLedsCheckPage, 900000);
}
331