Chapter 5: Let's build a Firefox extension

  • Revision slug: Firefox_addons_developer_guide/Let's_build_a_Firefox_extension
  • Revision title: Chapter 5: Let's build a Firefox extension
  • Revision id: 76739
  • Created:
  • Creator: Goofy
  • Is current revision? No
  • Comment 1 words added, 1 words removed

Revision Content

{{ Fx_minversion_header("3") }} {{ Draft() }}

{{ PreviousNext("En/Firefox addons developer guide/Using XPCOM—Implementing advanced processes", "En/Firefox addons developer guide/Firefox extensions and XUL applications") }}

This document was authored by Taiga (Gomita) Gomibuchi and was originally published in Japanese for the Firefox Developers Conference Summer 2007. Gomita-san won "Most Useful Upgraded Extension" award in Mozilla's 2006 "Extend Firefox" competition for ScrapBook, and was runner-up in the "Extend Firefox 2" contest for FireGestures.

The chapters so far have each focused on technologies in isolation—XUL, JavaScript, CSS, and XPCOM. In this chapter, we’ll discuss how to put them together to actually build an extension.

Setting up your development environment

Change your preferences for more efficient development

Before you get to work developing your extension, you’ll want to change some of your Firefox preferences. This isn’t a requirement for extension development, but I recommend it as a way to work more efficiently.

To make these changes, type about:config into Firefox’s location bar and open the preferences window; find the preferences listed in Table 1 and double-click on them to set them accordingly. Some of these preferences do not exist—to create them, right-click, select “New>Boolean”, and type in the name and set the value accordingly.

Install the DOM Inspector

The DOM Inspector is an extension that lets you examine HTML and XUL DOM tree structures, JavaScript objects and CSS properties, etc. This is not required for developing extensions, but it is handy to have around. Install extensions for Firefox from the Mozilla Add-ons website.

Create a development profile

If you want to partition your everyday browsing environment from your development environment in Firefox, set up a second profile for development1.

Table 1: Preferences to set for developing extensions

{{ TODO("Make the table cleaner") }}

Preference

Description

Value

nglayout.debug.disable_xul_cache

Ordinarily, Firefox will cache XUL documents after they have been read in once, to speed subsequent displays. Disabling this cache forces XUL documents to be reloaded any every time they are displayed

True

browser.dom.window.dump.enabled

Enables use of the dump method for debugging. Refer to the “JavaScript debugging methods” sidebar.

True

javascript.options.showInConsole

Outputs JavaScript errors to the error console

True

javascript.options.strict

Enforces strict error output from JavaScript

True

 

Developing extensions: What you need to know

Let’s delve into chrome, something you’ll need to know about in order to develop extensions.

Chrome

What is chrome?

Chrome”is the word used to describe all the GUI structural elements that go into an XUL application. For example, in the Firefox browser window, everything other than the web page being displayed in the content area is chrome. Extensions also can be considered XUL applications with chrome.

Three kinds of packages make up chrome

The content package

This package is used to contain the main XUL and JavaScript source files. Most extensions consist of a single content package2

The locale package

This package is used to contain language data that can be translated. To make an extension’s GUI support multiple languages, you can include multiple locale packages, one for each language.

The skin package

This is used to include source files used as visual elements in the GUI, including style sheets and images. Most extensions include only one skin package, but you can include multiple skin packages to allow the GUI to change with different themes.

Chrome URL

Use a file called a “chrome manifest” to register chrome packages with Firefox and start using them. To register a package, you use a special URI scheme called a “Chrome URL” to represent the path to the file. Chrome URLs are structured as:

chrome://%package name%/%package type%/%relative path to source file%

For example, the Chrome URL for the Firefox browser window is as follows:

chrome://browser/content/browser.xul

Here, the package name is “browser”, the file “browser.xul”, and the type of the package is “content”. Source files represented through chrome URLs run with UniversalXPConnect privileges; even if they haven’t been granted “use XPConnect as local file” privileges, as discussed in Chapter 4, they will be able to use XPCOM functions (Figure 1).

Extension/
  chrome manifest
  Chrome/ #registered in the chrome
    content package #Run with privileges
    locale package
    skin package

Figure 1: Chrome packages and chrome registration ({{ TODO("Not really explicit and not a Figure") }})

Cross-package overlays

The “overlay” technique introduced in Chapter 3 required the use of the xul-overlay instruction in the XUL document that is the overlay target. It is also possible to coerce an overlay without using the xul-overlay instruction in the XUL document—this is called a “cross-package overlay” (Figure 2). In fact, you need to use cross-package overlays to append buttons or menus to the Firefox browser window. Use the chrome manifest to invoke a cross-package overlay.

Normal overlay: Adding the xul-overlay instruction to the target XUL document overlays it with another XUL document
Cross-package overlay: Adding a cross manifest permits one XUL file to overlay another without any xul-overlay instruction.

Figure 2: Invoking a cross-package overlay ({{ TODO("Not really explicit and not a Figure") }})

Conclusion

This has been a brief introduction to chrome that probably leaves a lot of unanswered questions. If you keep in mind the following three points at the very least, the next section, Developing a Simple Extension, should help flesh out your understanding.

  1. An extension’s GUI, or chrome, can consist of three kinds of package: content, locale, and skin.
  2. Use a cross-package overlay on top of browser.xul to add a button or menu item to the Firefox browser window.
  3. The chrome manifest plays two important roles: it registers the contents of the chrome packages, and invokes the cross-package overlay.

Developing a Simple Extension: Hello World

In this section, we will write an extremely simple “hello world” extension that only displays the time.3

Phase 1: test install

Our first step will be to perform a test installation consisting of the minimum code needed to add a new menu item to the Tools menu (Figure 3).

Source file structure

First set up the work folders you’ll use to organize your extension’s source files. The folder can be wherever you want, but for the purposes of this guide, we’ll assume it’s at C:\extensions\helloworld . Create folders and files in your work folder as shown in Figure 4. The purpose of each file created during Phase 1 is explained in Table 2.

Create install manifest

Fill in the install.rdf file as shown in Listing 1.

Create the chrome manifest

Fill in the chrome.manifest file as shown in Listing 2.

Register the content package (content instruction)

Line 1, beginning content, contains the code used to register the chrome’s content package. Here, helloworld is a package name, and content/ is a relative path to the folder where the source file is stored. Registering the content package makes it so that the overlay.xul file in the content folder can be accessed using the chrome URL, chrome://helloworld/content/overlay.xul.

XUL cross-package overlay (overlay instruction)

Line 2, beginning overlay, contains the code used to invoke the cross-package overlay, overlaying the Firefox browser window, at chrome://browser/content/browser.xul with chrome://helloworld/content/overlay.xul.

{{ TODO("Figure 3: Menu after Phase 1 complete") }}

{{ TODO("Figure 4: Folder structure used in Phase 1") }}

Table 2: How files are used in Phase 1

{{ TODO("Make the table cleaner") }}

File name

Role

install.rdf

Called the install manifest, this gives basic information about the extension, and is required in order for the extension to be installed in Firefox.

chrome.manifest

This is the chrome manifest described in the earlier section. Registers packages and invokes cross-package overlays.

overly.xul

XUL file that will be overlaid on the Firefox browser window, adding buttons, menu items, etc.

clock.xul

clock.js

The XUL to display a clock in the window, and the JavaScript to control its operation (these files will be used in Phase 2).

Finding overlay merge points

The next step is to find the “merge points” where the overlay document will insert its content into the base document. There are a number of ways to go about this; here, we’ll use the DOM Inspector (Figure 5).

  1. Open the DOM Inspector by selecting the Tools > DOM Inspector menu item.
  2. Type chrome://browser/content/browser.xul into the URL input field at the top, then click the Inspect button. A browser pane will appear in the lower part of the window.
  3. Click the spots numbered 1 and 2 in Figure 5, in that order; the menu element (3) will then be selected.
  4. Expand spot 3; this will disclose the area numbered 4, with several menuitem elements. The menupop element 4 contains the merge point where you’re going to be adding a new menu item; you can see that it has the id menu_ToolsPopup.
  5. Look through the menuitem elements within this menupop element and determine where you want to add your new element. For the time being, let’s locate it in the spot numbered 5.

{{ TODO("Figure 5: Finding merge points for overlays using the DOM Inspector") }}

Listing 1: Content for install.rdf

<?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">
    <!-- Unique ID for extension. Can be in e-mail address format or GUID format -->
    <em:id>helloworld@xuldev.org</em:id>
    <!-- Indicates that this add-on is an extension -->
    <em:type>2</em:type>
    <!-- Extension name displayed in Add-on Manager -->
    <em:name>Hello, World!</em:name>
    <!-- Extension version number. There is a version numbering scheme you must follow -->
    <em:version>0.1</em:version>
    <!-- Brief description displayed in Add-on Manager -->
    <em:description>My first extension.</em:description>
    <!-- Name of extension's primary developer. Change to your name -->
    <em:creator>Gomita</em:creator>
    <!-- Web page address through which extension is distributed -->
    <em:homepageURL>http://www.xuldev.org/helloworld/</em:homepageURL>
    <!-- This section gives details of the target application for the extension--in this case, Firefox 2 -->
    <em:targetApplication>
      <Description>
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
        <em:minVersion>2.0</em:minVersion>
        <em:maxVersion>2.0.0.*</em:maxVersion>
      </Description>
    </em:targetApplication>
  </Description>
</RDF>

Listing 2: Content for chrome.manifest

content helloworld content/
overlay chrome://browser/content/browser.xul chrome://helloworld/content/overlay.xul

Putting overlays on the browser window

Now that you know the merge point for your overlay, you can create the overlay.xul file that will insert it (Listing 3). In line 4, we identify the merge point as the menupop element; in lines 5–7, we have the code that adds the new menu item. The insertbefore attribute determines where the element gets added.

Test install and operational check

This brings us to the point where we finally install our Hello World extension. Using the normal XPI-style installer would just be added trouble in this case, so we won’t use it. For source files under development, we do test installs.

Placing pointer files

First, navigate to the profile folder of the currently active profile4, and open the extensions folder within it. Create a new file there with the name you used as the Hello World extension ID in the install manifest, helloworld@xuldev.org. Set the contents of that file to be the full path to the work folder for your extension source files, C:\extensions\helloworld.

Listing 3: Additional content for overlay.xul

<?xml version="1.0"?>
<overlay id="helloworldOverlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <menupopup id="menu_ToolsPopup">
    <menuitem id="helloworldMenuitem" label="Hello, World!" insertbefore="sanitizeSeparator"
     oncommand="window.openDialog('chrome://helloworld/content/clock.xul','Clock','chrome,centerscreen,modal');"/>
  </menupopup>
</overlay>

 

Phase 2: Adding a function to display the time

In Phase 2, we will make it so that selecting the Hello World menu item we created in Phase 1 will display a window with the time (Figure 6).

Adding an event handler

First, we add an event handler to the menu item that will open the window (Listing 4).

Clock handler

Create the window that displays the clock (Listing 5) and its controller (Listing 6). We’ve already covered the dialog element in Chapter 3. In this case, we only want to display an OK button, so we set the buttons attribute to accept. Within the window are a label element and textbox element, wrapped in a hbox element so that they are arranged horizontally.

Operations check

Perform an operations check to make sure that your changes to the source file are correct. At this point, it’s important that you disabled the XUL cache as discussed in the earlier section, “Setting up your development environment”. Assuming you’ve done that, you’ll be able to confirm the changes to overlay.xul and clock.xul without the bother of relaunching Firefox or reinstalling the extension. If the browser.xul file, which is the target of the overlay in overlay.xul is being read in again, the changes will be reflected, and you’ll be able to see the changes by opening a new browser window. The files clock.xul and clock.js will be read in every time you open the clock window, so just re-opening the clock window once will suffice to check them. During development, it’s important to minimize the number of steps between revising a source file and running an operations check against those changes. The part “Operations checks on revised source files” discusses the general procedure to follow from source-file revision to operations check.

{{ TODO("Figure 6: Clock window produced by Phase 2") }}

Listing 5: Content for clock.xul

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/"?>
<dialog id="clockDialog" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    title="Clock"
    buttons="accept"
    onload="initClock();">
  <script type="application/x-javascript" src="chrome://helloworld/content/clock.js"/>
  <hbox align="center">
    <label value="Current Time:" />
    <textbox id="currentTime" />
  </hbox>
</dialog>

Listing 6: content for clock.js

function initClock() {
  showCurrentTime();
  window.setInterval(showCurrentTime, 1000);
}

function showCurrentTime() {
  var textbox = document.getElementById("currentTime");
  textbox.value = new Date().toLocaleTimeString();
  textbox.select();
}

What if something’s not working right?

If your extension isn’t working according to plan, first see if there’s an error being displayed on the Errors panel of the Error Console. If you set things up as recommended in the section “Setting up your development environment”, XUL and JavaScript errors will appear here. See the part “JavaScript debugging techniques” for useful tips on that subject5. If Firefox freezes up, you’ll need to terminate the Firefox process from the task manager.

Phase 3: Adding multilingual support

The clock window that we created in Phase 2 displays everything in English. In Phase 3, we’re going to add multilingual support, making it so that it users running Firefox in a French environment will see “Heure actuelle”, and those running it in English will see “Current time” (Figure 7).

{{ TODO("Figure 7: Clock window after Phase 3") }}

Directory structure

To add multilingual support to this extension, we’ll add a locale package to the chrome. Create a new locale folder with subfolders and files within the helloworld folder, as shown in Figure 8. The purpose of each of these files is explained in Table 3.

{{ TODO("Figure 8: Directory structure with locale package added") }}

Table 3: Files used in Phase 3

{{ TODO("Make the table cleaner") }}

name

purpose

locale\en-US\clock.dtd

DTD definining entity references used in clock.xul (for English).

locale\fr-FR\clock.dtd

DTD definining entity references used in clock.xul (for French).

Adding locale packages

Registering locale packages (locale instruction)

The line beginning locale is used to register the chrome’s local package; helloworld is the package name, and locale/en-US/ and locale/fr-FR/ are the relative paths to the folders containing the source files; en-US and fr-FR indicate that those locale packages are for English and French, respectively.

Replacing text with entity references in XUL

To make the extension support multiple languages, you will need to separate out all the hard-coded display text in clock.xul and move it to a DTD file. The DTD file is located inside the locale package, and the correct file is picked automatically to match the user’s language preferences. Edit clock.xul to match Listing 8. Add the DOCTYPE declaration that references the DTD file, and replace “clock” and “current time” with entity references.

Create the DTD that defines the entity references

Now create the DTD file that gets referred to by clock.xul (Listing 9). The clock.dtd file inside the fr-FR folder must be encoded as UTF-8.

Replacing User Interface messages within JavaScript files with properties references


It may happen your extension displays some useful messages such as alerts to the user.  Though they are nested in JavaScript, the strings should be localized too, for the sake of UI consistency.
Suppose you have these strings nested in a .js file:

if ( password == userPassword ) {
oPrefs.setBoolPref("access.authenticated", true);
}
else {
alert ("Invalid password");
......
function clear()
{
sure = confirm("Are you sure?");

First, you have to find or create a myextension.properties file in your locale folder (Notice: always stick to UTF-8). Then you simply write variable names and the sentences or words that should appear to the final user. Something like :

wrongPassMessage=Invalid password
areYouSureMessage=Are you sure?

(where Invalid password and Are you sure? are the strings to be displayed to the final user) Notice : a simple = sign is enough


Now you go back to the .js file where the strings to be localized are nested.
At the very top of the file you create one string bundle with the address of the properties file where to find strings.

var gmyextensionBundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);

var _bundle = gmyextensionBundle.createBundle("chrome://myextension/locale/myextension.properties")

 

You can now use your string substitutes in the .js where they are needed: see examples below

 if ( password == userPassword ) { oPrefs.setBoolPref("access.authenticated", true); } 
else { alert (_bundle.GetStringFromName("wrongPassMessage"));  function clear()
 { sure = confirm(_bundle.GetStringFromName("areYouSureMessage"));

 

 

Operations check

This brings us to the operations check. Because we’ve changed the chrome manifest, we need to relaunch Firefox once, as discussed in the part “Operations checks on revised source files”. So relaunch Firefox and display the clock window to make sure it is working correctly. Then type about:config into the location bar, and change the value of general.useragent.locale from en to fr, and re-display the clock window to confirm that it appears in French now.

Listing 7: Additional content for chrome.manifest

locale helloworld en-US locale/en-US/
locale helloworld fr-FR locale/fr-FR/

Listing 8: Revisions to clock.xul

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/"?>
<!DOCTYPE dialog SYSTEM "chrome://helloworld/locale/clock.dtd">
<dialog id="clockDialog" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    title="&helloworld.clock;" buttons="accept"
    onload="initClock();">
  <script type="application/x-javascript" src="chrome://helloworld/content/clock.js" />
  <hbox align="center">
    <label value="&helloworld.currentTime;:" />
    <textbox id="currentTimeTextbox" />
  </hbox>
</dialog>

Listing 9: Content for clock.dtd

locale\en-US\clock.dtd:

<!ENTITY helloworld.clock "Clock">
<!ENTITY helloworld.currentTime "Current Time">

locale\fr-FR\clock.dtd:

<!ENTITY helloworld.clock "Horloge">
<!ENTITY helloworld.currentTime "Heure courante">

Phase 4: Adding a toolbar button

In Phase 4, we’ll use graphics and style sheets to create a toolbar button that will open the clock window (Figure 9).

Directory structure

The style sheet and graphics files will be located inside the chrome’s skin package. The extension will actually work fine even if these are located in the content package, but putting them in the skin package has the benefit of making it possible to design GUIs that match specific Firefox themes, and allowing theme developers to create special visual effects for extensions.

First, add the skin directory and its subordinate files to your helloworld directory as shown in Figure 10. The purpose of each of these files is explained in Table 4. Download the files icon.png and icon-small.png from the resources website and place them appropriately.

{{ TODO("Figure 9: Toolbar after Phase 4") }}

{{ TODO("Figure 10: Directory structure with skin package added") }}

Add the skin package

Update chrome.manifest with the contents of Listing 10.

Register skin package (skin instruction)

Line 1 beginning skin is used to register the skin package; helloworld is the package name; skin/classic/ is the relative path to the folder containing the source files; classic/1.0 indicates that this skin package is meant for the Firefox standard theme.

Cross-package overlays with style sheets (style instruction)

In Phase 1, we used the overlay instruction to invoke a cross-package overlay with XUL; to do this with a style sheet, we use the style instruction. Lines 2-3 create overlays on the browser window and the “customize toolbar” window.

Add the toolbar button

To add the toolbar button to the browser window, update overlay.xul as shown in Listing 11. In order to make it so that users can relocate the button where they like, you will add a new toolbarbutton element, using the special element toolbarpalette with the id BrowserToolbarPalette as the merge point. You’ll also add a new command element, so that a click on the toolbar button will share its process with the menu item.

Create the style sheet

Update the file overlay.css with the contents of Listing 12. Lines 1–3 define the icon image to use with full-sized icons, and lines 4–6 define the icon image to use when small toolbar icons have been selected.

Operations check

Since we have edited the chrome manifest, as we did in Phase 3, we need to relaunch Firefox. Check to make sure that, when you click on the “customize toolbar” button at the top-right of the window that the new toolbar icon appears in the resulting window. Drag it onto the toolbar, and click it to make sure it works correctly.

Table 4: Files used in Phase 4

{{ TODO("Make the table cleaner") }}

name

purpose

icon.png

The full-size toolbar button icon file

icon-small.png

the small toolbar button icon file

overlay.css

Style sheet that defines the toolbar buttons to use. Overlays both the browser window and the “customize toolbar” window.

Listing 10: Additional content for chrome.manifest

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

Listing 11: Revisions to overlay.xul

<?xml version="1.0"?>
<overlay id="helloworldOverlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <commandset id="mainCommandSet">
    <command id="helloworldCommand" oncommand="window.openDialog(
      'chrome://helloworld/content/clock.xul',
      'Clock','chrome,centerscreen,modal');" />
  </commandset>
  <toolbarpalette id="BrowserToolbarPalette">
     <toolbarbutton id="helloworldButton" label="Hello, World!" class="toolbarbutton-1" command="helloworldCommand" />
  </toolbarpalette>
  <menupopup id="menu_ToolsPopup">
    <menuitem id="helloworldMenuitem" label="Hello, World!" insertbefore="sanitizeSeparator"
       command="helloworldCommand" />
  </menupopup>
</overlay>

Listing 12: Content for overlay.css

#helloworldButton {
  list-style-image:
  url(chrome://helloworld/skin/icon.png);
}

toolbar[iconsize="small"] #helloworldButton {
  list-style-image: url(chrome://helloworld/skin/icon-small.png);
}

 

Phase 5: XPI packaging

We actually completed our Hello World extension in Phase 4, but you can’t distribute the source files in this state to other users. So we need to create an xpi-formatted installer.

XPI file directory structure

XPI is basically just a zip archive, but the directory structure inside an xpi file is generally different from your development directory structure (Figure 11).

Packaging procedure

To package your extension as an xpi while preserving the directory structure of the source files you used during development, make the changes shown in Figure 11, as described below.

{{ TODO("Do we really need to explain the jar part ? That's a bit confusing and not really needed") }}

  1. Create a new chrome directory inside the helloworld directory.
  2. Zip the content, locale, and skin directories1, rename the resulting archive helloworld.jar, and place it inside the chrome directory you created in Step 1.
  3. Copy chrome.manifest and rename the backup chrome.manifest.bak.
  4. Change the content of chrome.manifest to be in line with Listing 13.
  5. Zip install.rdf, chrome.manifest, and the chrome folder together, and rename the resulting archive helloworld.xpi.

That completes the process of XPI packaging. One point you need to take special note of is that in Step 4, you updated chrome.manifest so that the files it refers to have paths pointing inside the zip archive.

In order to go back into development mode after you’ve finished packaging the extension as an xpi, you need to revert the chrome.manifest you edited in Step 4 to point to the backup files in Step 3. If you forget to do this, any subsequent changes you make to your source files during development will not be reflected. You may want to write a batch file to automate this task.

There’s a simpler way to package an extensions as an xpi. Create a zip archive containing install.rdf, chrome.manifest, content, locale, and skin. Name this helloworld.xpi.

This omits creating the archive of the chrome packages called helloworld.jar. This kind of xpi file will run fine, but will slow down Firefox’s launch time.

{{ TODO("Figure 11: Directory structure inside an xpi file") }}

Listing 13: Revisions to chrome.manifest

content helloworld jar:chrome/helloworld.jar!/content/
overlay chrome://browser/content/browser.xul chrome://helloworld/content/overlay.xul
locale helloworld en-US jar:chrome/helloworld.jar!/locale/en-US/
locale helloworld fr-FR jar:chrome/helloworld.jar!/locale/fr-FR/
skin helloworld classic/1.0 jar:chrome/helloworld.jar!/skin/classic/
style chrome://browser/content/browser.xul chrome://helloworld/skin/overlay.css
style chrome://global/content/customizeToolbar.xul chrome://helloworld/skin/overlay.css

 

XPI operations check

After you create the xpi file, make sure it works. Since you’ve already got the development version of the Hello World extension running as a test install on your current profile, restart Firefox under a different profile (see footnote 1). Drag and drop the xpi file onto your browser window to start the installation.

This completes the explanation of how to create and package a simple “hello world” extension.

Developing practical extensions:
A session-management extension

In this section, we will create an extension that uses a new feature: the session store API. This will allow the user to save and restore session snapshots (browser window states) at any time.

Phase 1: test install

Figure 12 shows what the interface for the session-management extension will look like. Under the Tool menu, the Session Store submenu contains two items—Save Session and Clear Sessions; in between them is a list of previously saved sessions that you can restore, most recent first.

As discussed in the part “The Session Store API”, sessions are represented as JSON character strings. Sessions are stored in a subdirectory of the profile directory (called “sessionstore”), with each session stored as a text file in it (Figure 13).

Source file structure

{{ TODO("Figure 14 shows the directory structure for this project") }}

Creating install manifest

Create your work folder, and create an install manifest with the contents of Listing 1, but in this case, change em:id to sessionstore@example.org and em:name to Session Store.

Creating chrome manifest

Create a chrome manifest with the contents of Listing 2.

Finding overlay merge points

Use the same overlay merge point and element to add on to as you did for the Hello World extension.

Browser window overlay

Create a file that will overlay the browser window, overlay.xul, with the contents from Listing 14.

Test install

Perform a test install using pointer files as you did in the previous section. Afterwards, you should see a Session Store menu item under the Tools menu.

{{ TODO("Figure 12: Session-management extension interface: the menu") }}

{{ TODO("Figure 13: Schematic of session saving and restorin") }}

{{ TODO("Figure 14: Source file directory structure") }}

Listing 14: Content for overlay.xul

<?xml version="1.0"?>
<overlay id="sessionstoreOverlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <script type="application/x-javascript"
      src="chrome://sessionstore/content/overlay.js" />
    <menupopup id="menu_ToolsPopup">
      <menu label="Session Store" insertbefore="sanitizeSeparator">
        <menupopup>
          <menuitem label="Save Session" />
          <menuseparator />
          <!-- Dynamically generated menu items go here -->
          <menuseparator />
        <menuitem label="Clear Sessions" />
      </menupopup>
    </menu>
  </menupopup>
</overlay>

 

Phase 2: Implement functionality

In Phase 2, we’ll use JavaScript to implement the session save and restore functions using the session store API.

Figure out JavaScript skeleton

Decide on method names and variable names for each of the processes you’ll need to implement these features, and put them into an overlay.js file. Follow the code in Listing 15.

Wrap methods and variables as properties of objects

You’ll note that in Listing 15, I’ve defined a lot of different methods and variables as properties of a single object, gSessionStore. Take note that when using an extension to create an overlay on the browser window, any global variables and functions defined within JavaScript that get opened as overlays on top of browser.xul all become properties of the browser.xul window object. This means that all those variables and functions get mixed up with those defined by Firefox itself and any other extensions that are running. Wrapping everything in one object is a good way to isolate extensions from each other and keep them from trampling each others’ toes. Defining a class would be another good way.

Initializing when opening browser window

With extensions, there are a lot of situations where you want to perform some kind of initialization when the browser window opens. To do that, add an event listener for the load event that targets the window object. In Listing 15, an init method runs when the window opens, and an uninit method runs when the window closes.

Listing 15: Content for overlay.js

var gSessionStore = {
  // Directory to save sessions (nsILocalFile)
  _dir: null,
  // Initialization
  init: function() { },
  // uninitialization
  uninit: function() { },
  // Save session (event handler)
  save: function(event) { },
  // Restore session (event handler)
  restore: function(event) { },
  // Delete session (event handler)
  clear: function(event) { },
  // Dynamically generate menu items (event handler)
  createMenu: function(event) { },
  // Read file
  _readFile: function(aFile) { },
  // Write file
  _writeFile: function(aFile, aData) { },
};
window.addEventListener("load", function(){
gSessionStore.init(); }, false);
window.addEventListener("unload", function(){
gSessionStore.uninit(); }, false);

Listing 16: Revisions to overlay.xul

<menupopup onpopupshowing="gSessionStore.createMenu(event);"
    oncommand="gSessionStore.restore(event);">
  <menuitem label="Save Session" oncommand="gSessionStore.save(event);" />
  <menuseparator />
  <!-- Dynamically generated menu items go here -->
  <menuseparator />
  <menuitem label="Clear Sessions" oncommand="gSessionStore.clear(event);" />
</menupopup>

 

Adding event handlers

Add the four event handlers you defined in overlay.js to overlay.xul (Listing 16). This takes advantage of event bubbling2, appending the restore event handler to the menupopup element one layer above the menuitem element. In the code for the save and restore event handlers, we will actually need blocks to prevent event bubbling.

Implementing methods

Now we’re going to actually implement the methods we defined in the gSessionStore object, one at a time. In the interest of space, I’m not including all the code here, but you can download it from the following URL: {{ TODO("include code from xuldev: translate it and attach it to the chapter document") }}

init method

The init method is as shown in Listing 17. The init method first gets the sessionstore directory inside the profile directory using nslLocalFile, which we’ll refer to through the variable _dir in subsequent lines (lines 3–6); if the directory doesn’t exist, we’ll create it (lines 7–8)3.

uninit method

Listing 18 gives the code for the uninit method. This just discards the variable _dir that was created by the init method. This process actually isn’t important.

save method

Listing 19 gives the code for the save method. In line 3, you see the block that prevents the bubbling that would result in this being called by the oncommand handler in the menupop element, which is the parent of the menuitem element. In lines 4–6, we use the nslSessionStore interface’s getBrowserState method to get a JSON text-string representation of the states of all currently open browser windows. In lines 7–9, we create the target file’s nslFile object by duplicating the _dir property. In line 10, we use the _writefile method (which we’ll get to shortly) to write the JSON string representing the session to the file.

restore method

Listing 20 shows the event handler for the dynamically generated menu items, which were created using the createMenu method (which we’ll get to shortly). Lines 3–6 get the file name from the special attribute fileName that was added by the createMenu method, and read in the JSON text string from that file using the _readFile method (which we’ll get to shortly). Lines 7–9 use the nslSessionStore interface’s setWindowState method to restore the session, using the current window as an origin point. Session restoration overwrites any currently open tabs.

clear method

This uses the directoryEntries property of the nslFile interface to delete all files within the directory you got earlier. Refer to the section “Traversing folders” in Chapter 4.

_readFile method

Reads the nslFile object passed as a parameter, and returns its contents as a text string. When the file is read in, its contents are converted from UTF-8 to Unicode.

_writeFile method

Creates a new file for the nslFile object passed as a parameter, and writes the text string, also passed as a parameter. Upon writing, the text is converted from Unicode to UTF-8.

createMenu method

This event handler is called when the Session Store submenu opens. First, it deletes the menuitem elements that were dynamically generated the last time the submenu opened, and which are still left over. Next, it dynamically generates menuitem elements based on the names of all the files in the session-storage directory, and inserts them into the menu.

Operations check

This completes the functional implementation. Open a new window and make sure that all three functions—saving a session, restoring a session, and deleting sessions—all work correctly.

Listing 17: Content for init method

init: function() {
  var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
               .getService(Components.interfaces.nsIProperties);
  this._dir = dirSvc.get("ProfD", Components.interfaces.nsILocalFile);
  this._dir.append("sessionstore");
  if (!this._dir.exists())
    this._dir.create(this._dir.DIRECTORY_TYPE, 0700);
},

Listing 18: Content for uninit method

uninit: function() {
  this._dir = null;
},

Listing 19: Content for save method

save: function(event) {
  event.stopPropagation();
  var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
           .getService(Components.interfaces.nsISessionStore);
  var state = ss.getBrowserState();
  var fileName = "session_" + Date.now() + ".js";
  var file = this._dir.clone();
  file.append(fileName);
  this._writeFile(file, state);
},

Listing 20: Content for restore method

restore: function(event) {
  var fileName = event.target.getAttribute("fileName");
  var file = this._dir.clone();
  file.append(fileName);
  var state = this._readFile(file);
  var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
           .getService(Components.interfaces.nsISessionStore);
  ss.setWindowState(window, state, false);
},

Phase 3: Creating a preference panel

You don’t need to use ini files, registries, or anything like that to give users options they can set through a preference panel. Firefox lets you read and write preference values using handy XPCOM services and create preference panels with interface widgets. In Phase 3, we’ll try this out (Figure 15).

{{ TODO("Figure 15: Finished preference panel") }}

Directory structure

Figure 16 shows the directory structure we’ll be using in Phase 3. Table 5 explains the new files being used.

Table 5: Files used in Phase 3

{{ TODO("Make the table cleaner") }}

name

purpose

sessionstore-prefs.js

File sets defaults for preferences. This should always be located inside defaults\preferences

prefs.xul

The preference panel

 {{ TODO("Figure 16: Source file structure") }}

Deciding the format for your preferences

Next you need to determine the names, types, and values for your preferences (Table 6). Each preference should be prefixed with something that uniquely identifies your extension—in this case, we’ll use extensions.sessionstore.

Table 6: Preference formats

{{ TODO("Make the table cleaner") }}

Preference name

Type

Value

extensions.sessionstore.warnOnClear

Boolean

Activated when deleting sessions. If true, causes a confirmation dialog to appear; if false, deletes immediately.

extensions.sessionstore.replaceTabs

Integer

Activated when restoring sessions.

0: Leave current tabs

1: Overwrite current tabs

2: Ask each time

Defaults definition file

Listing 21 gives the contents for the default preferences, as described in Table 6. Save this in defaults\preferences\sessionstoreprefs.js.

Creating the preference panel

Create prefs.xul with the contents from Listing 22. The prefwindow element is a special root element for creating preference panels, with the prefpane element subordinate to it. You can create an interface similar to Firefox’s options window by inserting multiple prefpane elements, which allows users to switch between panels by clicking buttons. The preference element creates a preference value that can be written and read by the preference panel, with the preference name indicated by the name attribute, and the type by the type attribute. Furthermore, by matching the preference element’s id to the preference attribute of a checkbox or radiogroup, you can specify the interface widget that will be used to set your preferences. Add the contents of Listing 23 to your install manifest to make it so that the Add-on Manager can open your preference panel.

Listing 21: Content for sessionstore-prefs.js

pref("extensions.sessionstore.warnOnClear", true);
pref("extensions.sessionstore.replaceTabs", 2);

Listing 22: Content for prefs.xul

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<prefwindow id="sessionstorePrefs" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
     title="Session Store Preferences">
  <prefpane>
    <preferences>
      <preference id="extensions.sessionstore.warnOnClear"
           name="extensions.sessionstore.warnOnClear"
           type="bool" />
      <preference id="extensions.sessionstore.replaceTabs"
           name="extensions.sessionstore.replaceTabs"
           type="int" />
    </preferences>
    <checkbox label="Confirm before clearing sessions"
         preference="extensions.sessionstore.warnOnClear" />
    <groupbox>
      <caption label="When restoring session:" />
      <radiogroup preference="extensions.sessionstore.replaceTabs">
        <radio value="0" label="Keep current tabs" />
        <radio value="1" label="Replace current tabs"/>
        <radio value="2" label="Ask me every time" />
      </radiogroup>
    </groupbox>
  </prefpane>
</prefwindow>

Listing 23: Additional content for install.rdf

<em:optionsURL>chrome://sessionstore/content/prefs.xul</em:optionsURL>

Changing behavior through preference values

In Listing 24, we add a preference that allows a confirmation dialog to appear when we delete sessions. When we get the preference value from JavaScript, we take advantage the XPCOM nslPrefService interface, using the getBoolPref and getIntPref methods as appropriate to the data type. In Listing 24, we get the nslPrefBranch object that lets us read and write all preferences beginning with the prefix extensions.sessionstore., which is what lets us use the getBoolPref method to get the value.

Operations check

We’ve revised the install manifest, so we need to reinstall the extension (see the part “Operations checks on revised source files”). Once you’ve reinstalled it, first open about:config, and confirm that the previous preference values are set. Next, select Session Store from the Add-on Manager and click the “Preferences” button to bring up the preference panel. Try changing the preferences, and see if those changes are reflected when you restore or delete sessions.

Phase 4: XPI packaging

This procedure is unchanged from Phase 5 in the “Developing a Simple Extension” section, but take note of everything within the defaults folder.

Conclusion

We’ve looked at how to build extensions at an extremely basic level in this chapter, and there are a lot of other interesting features to explore in extensions and XUL. But I hope that having gotten your feet wet with extensions development, you’ll want to dive in now.

Operations checks on revised source files

In the interest of developing extensions efficiently, you want to minimize the number of steps between revising a source file and running an operations check on it. For each type of source file, there’s a different procedure to follow. The following all assumes that you’ve disabled the XUL cache as discussed in the section “Setting up your development environment”.

XUL that opens new windows

Confirm changes by closing and reopening window. If the XUL is displaying as a sidebar, reopen the sidebar. Likewise with any JavaScript, DTDs, or style sheets referenced by the XUL.

XUL overlaying other windows

You need to force the overlay-target XUL to reload. If it’s the browser window that’s the target, you can simply open a new browser window to confirm the changes.

properties file

If you make changes to a properties file in the locale package, those changes will not be reflected in Firefox until you relaunch it.

XPCOM components inside the components directory

You will need to delete the files compreg.dat and xpti.dat inside the profile folder and relaunch Firefox.

Default preferences file inside the defaults\preferences directory

You will need to relaunch Firefox.

chrome.manifest

You will need to relaunch Firefox.

install.rdf

You will need to temporarily uninstall the extension and then reinstall it. In practice, what you’ll do is remove the pointer file, relaunch Firefox, return the pointer file, and relaunch Firefox again. On a Linux system, use the touch command on the directory pointed at by the pointer file to change its “last changed” date, and avoid the relaunching.

The session store API

The session store API4 is one of the new developer-oriented features in Firefox 2. Using the various methods in the nslSessionStore interface, which is an XPCOM service, you can easily save and restore session states. Functions in Firefox that let you reopen the last closed tab, or restore to your previous state after a crash, are all implemented through this API.

We used two methods in the nslSessionStore interface in this project. Let’s look at how they work.

getBrowserState()

Returns a JSON-formatted5 text string representing the state of every currently open browser window. This string includes extensive information regarding every open window and tab, the forward/back history for each tab, page scroll position, text zoom, contents of form elements, etc.

setWindowState(aWindow, aState, aOverwrite)

Restores the browser window indicated by the parameter aWindow to the state indicated by aState. If aOverwrite is true, the currently open tab is overwritten; otherwise, a new tab is created for the restored content.

JavaScript debugging methods

alert

The easiest way to debug javascvript is using the window.alert method to display variables in a dialog (Listing A). Except for asynchronous processes, all processing stops while the dialog is up, so this technique is useful when you want to pin down a value that can vary during a process.

dump

In a Windows environment, you can launch Firefox with the -console argument, which opens a dump console. Using the dump method, you can send text to the dump console (Listing B). The text output being sent to the console does not suspend any other processes, and this is most useful when there’s a high volume of messages being output. In order to use the dump method most effectively, you’ll need to follow the recommendations in “Setting up your development environment”. Note that in a Windows environment, non-latin text sent to the dump console will get corrupted.

error console

The nslConsoleService interface

Using the logStringMessage method of the nslConsoleService interface, which is an XPCOM service, you can output text to the “message” panel of the error console. Non-latn text will appear correctly here. If you are going to be issuing messages to the error console frequently, it’s handy to define a function like that in Listing C.

Components.utils.reportError method

Another way to output text to the same error console is with the Components.utils.reportError method. This will send its output to the “error” panel of the error console.

Listing A: Check the value of variable foo

alert(foo);

Listing B: List the properties of obj

for (var i in obj) {
  dump(i + " : " + obj[i] + "\n");
}

Listing C: Function to output messages to the error console

function log(aText) {
  var console = Components.classes["@mozilla.org/consoleservice;1"]
      .getService(Components.interfaces.nsIConsoleService);
  console.logStringMessage(aText);
}

 

Where can I learn more about the XPCOM interface?

{{ TODO("add links:") }}

The Mozilla Development Center has extensive documentation on the nsISessionStore interface we’ve discussed in this section6. In general, details (IDL) on the XPCOM interface are available from XULPlanet7 and the Mozilla Cross-Reference8, which includes source code with full-text searching available.

1 - In Windows XP, you can use the “compress folder” command to create a zip archive, but I recommend installing a tool like 7-Zip that lets you specify the compression ratio. It’s OK to have a low compression ratio when you are creating the jar file, and a high compression ratio when creating the xpi file.
2 - An event generated by an element “bubbles up” to the root.
3 - See Code Snippets: File I/O-MDC (http://developer.mozilla.org/en/docs..._special_files) for techniques on getting special folders, like the profile folder.
5 - JSON is short for JavaScript Object Notation. It is a data format that can easily be read from and written to in JavaScript.
6 - nslSessionStore-MDC (http://developer.mozilla.org)
8 - Mozilla Cross-Reference (http://mxr.mozilla.org/)

 

 

Revision Source

<p>{{ Fx_minversion_header("3") }} {{ Draft() }}</p>
<p>{{ PreviousNext("En/Firefox addons developer guide/Using XPCOM—Implementing advanced processes", "En/Firefox addons developer guide/Firefox extensions and XUL applications") }}</p>
<p><em>This document was authored by </em><a class="external" href="http://www.xuldev.org/blog/" title="http://www.xuldev.org/blog/"><span class="external"><em>Taiga (Gomita) Gomibuchi</em></span></a><em> and was originally published in Japanese for the </em><a class="link-https" href="https://wiki.mozilla.org/Japan/FxDevCon/Summer2007/English" rel="external nofollow" target="_blank" title="https://wiki.mozilla.org/Japan/FxDevCon/Summer2007/English"><em>Firefox Developers Conference Summer 2007</em></a><em>. Gomita-san won "Most Useful Upgraded Extension" award in Mozilla's 2006 "Extend Firefox" competition for <a class="external" href="http://amb.vis.ne.jp/mozilla/scrapbook/" title="http://amb.vis.ne.jp/mozilla/scrapbook/">ScrapBook</a>, and was runner-up in the "Extend Firefox 2" contest for <a class="external" href="http://www.xuldev.org/firegestures/" title="http://www.xuldev.org/firegestures/">FireGestures</a>.</em></p>
<p>The chapters so far have each focused on technologies in isolation—XUL, JavaScript, CSS, and XPCOM. In this chapter, we’ll discuss how to put them together to actually build an extension.</p>
<h2>Setting up your development environment</h2>
<h3>Change your preferences for more efficient development</h3>
<p>Before you get to work developing your extension, you’ll want to change some of your Firefox preferences. This isn’t a requirement for extension development, but I recommend it as a way to work more efficiently.</p>
<p>To make these changes, type <em>about:config</em> into Firefox’s location bar and open the preferences window; find the preferences listed in Table 1 and double-click on them to set them accordingly. Some of these preferences do not exist—to create them, right-click, select “New&gt;Boolean”, and type in the name and set the value accordingly.</p>
<h3>Install the DOM Inspector</h3>
<p>The DOM Inspector is an extension that lets you examine HTML and XUL DOM tree structures, JavaScript objects and CSS properties, etc. This is not required for developing extensions, but it is handy to have around. Install extensions for Firefox from the <a class="link-https" href="https://addons.mozilla.org/en-US/firefox/addon/6622" title="https://addons.mozilla.org/en-US/firefox/addon/6622">Mozilla Add-ons website</a>.</p>
<h3>Create a development profile</h3>
<p>If you want to partition your everyday browsing environment from your development environment in Firefox, set up a second profile for development<sup><a href="#footnote1" id="from_footnote1">1</a></sup>.</p>
<p>Table 1: Preferences to set for developing extensions</p>
<p>{{ TODO("Make the table cleaner") }}</p>
<table border="1" cellpadding="8" cellspacing="0" width="615"> <tbody> <tr> <td> <p>Preference</p> </td> <td> <p>Description</p> </td> <td> <p>Value</p> </td> </tr> <tr> <td> <p>nglayout.debug.disable_xul_cache</p> </td> <td> <p>Ordinarily, Firefox will cache XUL documents after they have been read in once, to speed subsequent displays. Disabling this cache forces XUL documents to be reloaded any every time they are displayed</p> </td> <td> <p>True</p> </td> </tr> <tr> <td> <p>browser.dom.window.dump.enabled</p> </td> <td> <p>Enables use of the dump method for debugging. Refer to the “JavaScript debugging methods” sidebar.</p> </td> <td> <p>True</p> </td> </tr> <tr> <td> <p>javascript.options.showInConsole</p> </td> <td> <p>Outputs JavaScript errors to the error console</p> </td> <td> <p>True</p> </td> </tr> <tr> <td> <p>javascript.options.strict</p> </td> <td> <p>Enforces strict error output from JavaScript</p> </td> <td> <p>True</p> </td> </tr> </tbody>
</table>
<p> </p>
<h2>Developing extensions: What you need to know</h2>
<p>Let’s delve into <strong>chrome</strong>, something you’ll need to know about in order to develop extensions.</p>
<h3>Chrome</h3>
<h4>What is chrome?</h4>
<p>“<em>Chrome</em>”is the word used to describe all the GUI structural elements that go into an XUL application. For example, in the Firefox browser window, everything other than the web page being displayed in the content area is chrome. Extensions also can be considered XUL applications with chrome.</p>
<h4>Three kinds of packages make up chrome</h4>
<h5>The content package</h5>
<p>This package is used to contain the main XUL and JavaScript source files. Most extensions consist of a single content package<sup><a href="#footnote2" id="from_footnote2">2</a></sup></p>
<h5>The locale package</h5>
<p>This package is used to contain language data that can be translated. To make an extension’s GUI support multiple languages, you can include multiple locale packages, one for each language.</p>
<h5>The skin package</h5>
<p>This is used to include source files used as visual elements in the GUI, including style sheets and images. Most extensions include only one skin package, but you can include multiple skin packages to allow the GUI to change with different themes.</p>
<h4>Chrome URL</h4>
<p>Use a file called a “chrome manifest” to register chrome packages with Firefox and start using them. To register a package, you use a special URI scheme called a “Chrome URL” to represent the path to the file. Chrome URLs are structured as:</p>
<pre>chrome://<em>%package name%</em>/<em>%package type%</em>/%<em>relative path to source file</em>%</pre>
<p>For example, the Chrome URL for the Firefox browser window is as follows:</p>
<pre>chrome://browser/content/browser.xul</pre>
<p>Here, the package name is “<em>browser</em>”, the file “<em>browser.xul</em>”, and the type of the package is “<em>content</em>”. Source files represented through chrome URLs run with <em>UniversalXPConnect</em> privileges; even if they haven’t been granted “<em>use XPConnect as local file</em>” privileges, as discussed in <a class="internal" href="/En/Firefox_addons_developer_guide/Using_XPCOM%E2%80%94Implementing_advanced_processes" title="En/Firefox addons developer guide/Using XPCOM—Implementing advanced processes">Chapter 4</a>, they will be able to use XPCOM functions (Figure 1).</p>
<pre>Extension/
  chrome manifest
  Chrome/ #registered in the chrome
    content package #Run with privileges
    locale package
    skin package</pre>
<p>Figure 1: Chrome packages and chrome registration ({{ TODO("Not really explicit and not a Figure") }})</p>
<h3>Cross-package overlays</h3>
<p>The “overlay” technique introduced in <a class="internal" href="/En/Firefox_addons_developer_guide/Introduction_to_XUL%E2%80%94How_to_build_a_more_intuitive_UI" title="En/Firefox addons developer guide/Introduction to XUL—How to build a more intuitive UI">Chapter 3</a> required the use of the xul-overlay instruction in the XUL document that is the overlay target. It is also possible to coerce an overlay without using the xul-overlay instruction in the XUL document—this is called a “<em>cross-package overlay</em>” (Figure 2). In fact, you need to use cross-package overlays to append buttons or menus to the Firefox browser window. Use the chrome manifest to invoke a cross-package overlay.</p>
<pre>Normal overlay: Adding the xul-overlay instruction to the target XUL document overlays it with another XUL document
Cross-package overlay: Adding a cross manifest permits one XUL file to overlay another without any xul-overlay instruction.</pre>
<p>Figure 2: Invoking a cross-package overlay ({{ TODO("Not really explicit and not a Figure") }})</p>
<h3>Conclusion</h3>
<p>This has been a brief introduction to chrome that probably leaves a lot of unanswered questions. If you keep in mind the following three points at the very least, the next section, Developing a Simple Extension, should help flesh out your understanding.</p>
<ol> <li>An extension’s GUI, or chrome, can consist of three kinds of package: <em>content</em>, <em>locale</em>, and <em>skin</em>.</li> <li>Use a cross-package overlay on top of <em>browser.xul</em> to add a button or menu item to the Firefox browser window.</li> <li>The chrome manifest plays two important roles: it registers the contents of the chrome packages, and invokes the cross-package overlay.</li>
</ol>
<h2>Developing a Simple Extension: Hello World</h2>
<p>In this section, we will write an extremely simple “<em>hello world</em>” extension that only displays the time.<sup><a href="#footnote3" id="from_footnote3">3</a></sup></p>
<h3>Phase 1: test install</h3>
<p>Our first step will be to perform a test installation consisting of the minimum code needed to add a new menu item to the Tools menu (Figure 3).</p>
<h4>Source file structure</h4>
<p>First set up the work folders you’ll use to organize your extension’s source files. The folder can be wherever you want, but for the purposes of this guide, we’ll assume it’s at <em>C:\extensions\helloworld</em> . Create folders and files in your work folder as shown in Figure 4. The purpose of each file created during Phase 1 is explained in Table 2.</p>
<h4>Create install manifest</h4>
<p>Fill in the <em>install.rdf</em> file as shown in Listing 1.</p>
<h4>Create the chrome manifest</h4>
<p>Fill in the <em>chrome.manifest</em> file as shown in Listing 2.</p>
<h5>Register the content package (content instruction)</h5>
<p>Line 1, beginning content, contains the code used to register the chrome’s content package. Here, <em>helloworld</em> is a package name, and <em>content/</em> is a relative path to the folder where the source file is stored. Registering the content package makes it so that the <em>overlay.xul</em> file in the content folder can be accessed using the chrome URL, <em><a class=" external" href="chrome://helloworld/content/overlay.xul" rel="freelink">chrome://helloworld/content/overlay.xul</a></em>.</p>
<h5>XUL cross-package overlay (overlay instruction)</h5>
<p>Line 2, beginning overlay, contains the code used to invoke the cross-package overlay, overlaying the Firefox browser window, at <em><a class=" external" href="chrome://browser/content/browser.xul" rel="freelink">chrome://browser/content/browser.xul</a></em> with <em><a class=" external" href="chrome://helloworld/content/overlay.xul" rel="freelink">chrome://helloworld/content/overlay.xul</a></em>.</p>
<p>{{ TODO("Figure 3: Menu after Phase 1 complete") }}</p>
<p>{{ TODO("Figure 4: Folder structure used in Phase 1") }}</p>
<p>Table 2: How files are used in Phase 1</p>
<p>{{ TODO("Make the table cleaner") }}</p>
<table border="1" cellpadding="8" cellspacing="0" width="615"> <tbody> <tr> <td> <p>File name</p> </td> <td> <p>Role</p> </td> </tr> <tr> <td> <p>install.rdf</p> </td> <td> <p>Called the install manifest, this gives basic information about the extension, and is required in order for the extension to be installed in Firefox.</p> </td> </tr> <tr> <td> <p>chrome.manifest</p> </td> <td> <p>This is the chrome manifest described in the earlier section. Registers packages and invokes cross-package overlays.</p> </td> </tr> <tr> <td> <p>overly.xul</p> </td> <td> <p>XUL file that will be overlaid on the Firefox browser window, adding buttons, menu items, etc.</p> </td> </tr> <tr> <td> <p>clock.xul</p> <p>clock.js</p> </td> <td> <p>The XUL to display a clock in the window, and the JavaScript to control its operation (these files will be used in Phase 2).</p> </td> </tr> </tbody>
</table>
<h4>Finding overlay merge points</h4>
<p>The next step is to find the “merge points” where the overlay document will insert its content into the base document. There are a number of ways to go about this; here, we’ll use the DOM Inspector (Figure 5).</p>
<ol> <li>Open the DOM Inspector by selecting the Tools &gt; DOM Inspector menu item.</li> <li>Type <a class=" external" href="chrome://browser/content/browser.xul" rel="freelink">chrome://browser/content/browser.xul</a> into the URL input field at the top, then click the Inspect button. A browser pane will appear in the lower part of the window.</li> <li>Click the spots numbered 1 and 2 in Figure 5, in that order; the menu element (3) will then be selected.</li> <li>Expand spot 3; this will disclose the area numbered 4, with several menuitem elements. The menupop element 4 contains the merge point where you’re going to be adding a new menu item; you can see that it has the id menu_ToolsPopup.</li> <li>Look through the menuitem elements within this menupop element and determine where you want to add your new element. For the time being, let’s locate it in the spot numbered 5.</li>
</ol>
<p>{{ TODO("Figure 5: Finding merge points for overlays using the DOM Inspector") }}</p>
<p>Listing 1: Content for install.rdf</p>
<pre class="brush: xml">&lt;?xml version="1.0"?&gt;
&lt;RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"&gt;
  &lt;Description about="urn:mozilla:install-manifest"&gt;
    &lt;!-- Unique ID for extension. Can be in e-mail address format or GUID format --&gt;
    &lt;em:id&gt;helloworld@xuldev.org&lt;/em:id&gt;
    &lt;!-- Indicates that this add-on is an extension --&gt;
    &lt;em:type&gt;2&lt;/em:type&gt;
    &lt;!-- Extension name displayed in Add-on Manager --&gt;
    &lt;em:name&gt;Hello, World!&lt;/em:name&gt;
    &lt;!-- Extension version number. There is a version numbering scheme you must follow --&gt;
    &lt;em:version&gt;0.1&lt;/em:version&gt;
    &lt;!-- Brief description displayed in Add-on Manager --&gt;
    &lt;em:description&gt;My first extension.&lt;/em:description&gt;
    &lt;!-- Name of extension's primary developer. Change to your name --&gt;
    &lt;em:creator&gt;Gomita&lt;/em:creator&gt;
    &lt;!-- Web page address through which extension is distributed --&gt;
    &lt;em:homepageURL&gt;http://www.xuldev.org/helloworld/&lt;/em:homepageURL&gt;
    &lt;!-- This section gives details of the target application for the extension--in this case, Firefox 2 --&gt;
    &lt;em:targetApplication&gt;
      &lt;Description&gt;
        &lt;em:id&gt;{ec8030f7-c20a-464f-9b0e-13a3a9e97384}&lt;/em:id&gt;
        &lt;em:minVersion&gt;2.0&lt;/em:minVersion&gt;
        &lt;em:maxVersion&gt;2.0.0.*&lt;/em:maxVersion&gt;
      &lt;/Description&gt;
    &lt;/em:targetApplication&gt;
  &lt;/Description&gt;
&lt;/RDF&gt;
</pre>
<p>Listing 2: Content for chrome.manifest</p>
<pre>content helloworld content/
overlay chrome://browser/content/browser.xul chrome://helloworld/content/overlay.xul
</pre>
<h4>Putting overlays on the browser window</h4>
<p>Now that you know the merge point for your overlay, you can create the <em>overlay.xul</em> file that will insert it (Listing 3). In line 4, we identify the merge point as the menupop element; in lines 5–7, we have the code that adds the new menu item. The insertbefore attribute determines where the element gets added.</p>
<h4>Test install and operational check</h4>
<p>This brings us to the point where we finally install our Hello World extension. Using the normal XPI-style installer would just be added trouble in this case, so we won’t use it. For source files under development, we do test installs.</p>
<h5>Placing pointer files</h5>
<p>First, navigate to the profile folder of the currently active profile<sup><a href="#footnote4" id="from_footnote4">4</a></sup>, and open the extensions folder within it. Create a new file there with the name you used as the Hello World extension ID in the install manifest, <em><a class=" link-mailto" href="mailto:helloworld@xuldev.org" rel="freelink">helloworld@xuldev.org</a></em>. Set the contents of that file to be the full path to the work folder for your extension source files,<em> C:\extensions\helloworld</em>.</p>
<p>Listing 3: Additional content for overlay.xul</p>
<pre class="brush: xml">&lt;?xml version="1.0"?&gt;
&lt;overlay id="helloworldOverlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"&gt;
  &lt;menupopup id="menu_ToolsPopup"&gt;
    &lt;menuitem id="helloworldMenuitem" label="Hello, World!" insertbefore="sanitizeSeparator"
     oncommand="window.openDialog('chrome://helloworld/content/clock.xul','Clock','chrome,centerscreen,modal');"/&gt;
  &lt;/menupopup&gt;
&lt;/overlay&gt;
</pre>
<p> </p>
<h3>Phase 2: Adding a function to display the time</h3>
<p>In Phase 2, we will make it so that selecting the Hello World menu item we created in Phase 1 will display a window with the time (Figure 6).</p>
<h4>Adding an event handler</h4>
<p>First, we add an event handler to the menu item that will open the window (Listing 4).</p>
<h4>Clock handler</h4>
<p>Create the window that displays the clock (Listing 5) and its controller (Listing 6). We’ve already covered the dialog element in Chapter 3. In this case, we only want to display an OK button, so we set the buttons attribute to accept. Within the window are a label element and textbox element, wrapped in a hbox element so that they are arranged horizontally.</p>
<h4>Operations check</h4>
<p>Perform an operations check to make sure that your changes to the source file are correct. At this point, it’s important that you disabled the XUL cache as discussed in the earlier section, “<em>Setting up your development environment</em>”. Assuming you’ve done that, you’ll be able to confirm the changes to <em>overlay.xul</em> and <em>clock.xul</em> without the bother of relaunching Firefox or reinstalling the extension. If the <em>browser.xul</em> file, which is the target of the overlay in <em>overlay.xul</em> is being read in again, the changes will be reflected, and you’ll be able to see the changes by opening a new browser window. The files <em>clock.xul</em> and <em>clock.js</em> will be read in every time you open the clock window, so just re-opening the clock window once will suffice to check them. During development, it’s important to minimize the number of steps between revising a source file and running an operations check against those changes. The part “<em>Operations checks on revised source files</em>” discusses the general procedure to follow from source-file revision to operations check.</p>
<p>{{ TODO("Figure 6: Clock window produced by Phase 2") }}</p>
<p>Listing 5: Content for clock.xul</p>
<pre class="brush: xml">&lt;?xml version="1.0"?&gt;
&lt;?xml-stylesheet href="chrome://global/skin/"?&gt;
&lt;dialog id="clockDialog" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    title="Clock"
    buttons="accept"
    onload="initClock();"&gt;
  &lt;script type="application/x-javascript" src="chrome://helloworld/content/clock.js"/&gt;
  &lt;hbox align="center"&gt;
    &lt;label value="Current Time:" /&gt;
    &lt;textbox id="currentTime" /&gt;
  &lt;/hbox&gt;
&lt;/dialog&gt;
</pre>
<p>Listing 6: content for clock.js</p>
<pre class="brush: js">function initClock() {
  showCurrentTime();
  window.setInterval(showCurrentTime, 1000);
}

function showCurrentTime() {
  var textbox = document.getElementById("currentTime");
  textbox.value = new Date().toLocaleTimeString();
  textbox.select();
}
</pre>
<h4>What if something’s not working right?</h4>
<p>If your extension isn’t working according to plan, first see if there’s an error being displayed on the <em>Errors panel</em> of the <em>Error Console</em>. If you set things up as recommended in the section “<em>Setting up your development environment</em>”, XUL and JavaScript errors will appear here. See the part “<em>JavaScript debugging techniques</em>” for useful tips on that subject<sup><a href="#footnote5" id="from_footnote5">5</a></sup>. If Firefox freezes up, you’ll need to terminate the Firefox process from the task manager.</p>
<h3>Phase 3: Adding multilingual support</h3>
<p>The clock window that we created in Phase 2 displays everything in English. In Phase 3, we’re going to add multilingual support, making it so that it users running Firefox in a French environment will see “Heure actuelle”, and those running it in English will see “Current time” (Figure 7).</p>
<p>{{ TODO("Figure 7: Clock window after Phase 3") }}</p>
<h4>Directory structure</h4>
<p>To add multilingual support to this extension, we’ll add a locale package to the chrome. Create a new locale folder with subfolders and files within the <em>helloworld</em> folder, as shown in Figure 8. The purpose of each of these files is explained in Table 3.</p>
<p>{{ TODO("Figure 8: Directory structure with locale package added") }}</p>
<p>Table 3: Files used in Phase 3</p>
<p>{{ TODO("Make the table cleaner") }}</p>
<table border="1" cellpadding="8" cellspacing="0" width="590"> <tbody> <tr> <td> <p>name</p> </td> <td> <p>purpose</p> </td> </tr> <tr> <td> <p>locale\en-US\clock.dtd</p> </td> <td> <p>DTD definining entity references used in clock.xul (for English).</p> </td> </tr> <tr> <td> <p>locale\fr-FR\clock.dtd</p> </td> <td> <p>DTD definining entity references used in clock.xul (for French).</p> </td> </tr> </tbody>
</table>
<h4>Adding locale packages</h4>
<h5>Registering locale packages (locale instruction)</h5>
<p>The line beginning locale is used to register the chrome’s local package; helloworld is the package name, and locale/en-US/ and locale/fr-FR/ are the relative paths to the folders containing the source files; en-US and fr-FR indicate that those locale packages are for English and French, respectively.</p>
<h4>Replacing text with entity references in XUL</h4>
<p>To make the extension support multiple languages, you will need to separate out all the hard-coded display text in clock.xul and move it to a DTD file. The DTD file is located inside the locale package, and the correct file is picked automatically to match the user’s language preferences. Edit clock.xul to match Listing 8. Add the DOCTYPE declaration that references the DTD file, and replace “clock” and “current time” with entity references.</p>
<h4>Create the DTD that defines the entity references</h4>
<p>Now create the DTD file that gets referred to by clock.xul (Listing 9). The clock.dtd file inside the fr-FR folder must be encoded as UTF-8.</p>
<h4>Replacing User Interface messages within JavaScript files with properties references</h4>
<p><br>
It may happen your extension displays some useful messages such as alerts to the user.  Though they are nested in JavaScript, the strings should be localized too, for the sake of UI consistency.<br>
Suppose you have these strings nested in a .js file:</p>
<pre class="brush: js">if ( password == userPassword ) {
oPrefs.setBoolPref("access.authenticated", true);
}
else {
alert ("Invalid password");
......
function clear()
{
sure = confirm("Are you sure?");
</pre>
<p>First, you have to find or create a myextension.properties file in your locale folder (Notice: always stick to UTF-8). Then you simply write variable names and the sentences or words that should appear to the final user. Something like :</p>
<pre class="brush: js">wrongPassMessage=Invalid password
areYouSureMessage=Are you sure?
</pre>
<p>(where Invalid password and Are you sure? are the strings to be displayed to the final user) Notice : a simple = sign is enough</p>
<p><br>
Now you go back to the .js file where the strings to be localized are nested.<br>
At the very top of the file you create one string bundle with the address of the properties file where to find strings.</p>
<pre class="brush: js">var gmyextensionBundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);

var _bundle = gmyextensionBundle.createBundle("chrome://myextension/locale/myextension.properties")
<p> </p>
</pre>
<p>You can now use your string substitutes in the .js where they are needed: see examples below</p>
<pre class="brush: js"> if ( password == userPassword ) { oPrefs.setBoolPref("access.authenticated", true); } 
else { alert (_bundle.GetStringFromName("wrongPassMessage"));  function clear()
 { sure = confirm(_bundle.GetStringFromName("areYouSureMessage"));<p> </p></pre>
<p> </p><h4>Operations check</h4>
<p>This brings us to the operations check. Because we’ve changed the chrome manifest, we need to relaunch Firefox once, as discussed in the part “<em>Operations checks on revised source files</em>”. So relaunch Firefox and display the clock window to make sure it is working correctly. Then type about:config into the location bar, and change the value of <em>general.useragent.locale</em> from en to fr, and re-display the clock window to confirm that it appears in French now.</p>
<p>Listing 7: Additional content for chrome.manifest</p>
<pre>locale helloworld en-US locale/en-US/
locale helloworld fr-FR locale/fr-FR/
</pre>
<p>Listing 8: Revisions to clock.xul</p>
<pre class="brush: xml">&lt;?xml version="1.0"?&gt;
&lt;?xml-stylesheet href="chrome://global/skin/"?&gt;
&lt;!DOCTYPE dialog SYSTEM "chrome://helloworld/locale/clock.dtd"&gt;
&lt;dialog id="clockDialog" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    title="&amp;helloworld.clock;" buttons="accept"
    onload="initClock();"&gt;
  &lt;script type="application/x-javascript" src="chrome://helloworld/content/clock.js" /&gt;
  &lt;hbox align="center"&gt;
    &lt;label value="&amp;helloworld.currentTime;:" /&gt;
    &lt;textbox id="currentTimeTextbox" /&gt;
  &lt;/hbox&gt;
&lt;/dialog&gt;
</pre>
<p>Listing 9: Content for clock.dtd</p>
<p>locale\en-US\clock.dtd:</p>
<pre class="brush: xml">&lt;!ENTITY helloworld.clock "Clock"&gt;
&lt;!ENTITY helloworld.currentTime "Current Time"&gt;
</pre>
<p>locale\fr-FR\clock.dtd:</p>
<pre>&lt;!ENTITY helloworld.clock "Horloge"&gt;
&lt;!ENTITY helloworld.currentTime "Heure courante"&gt;
</pre><h3>Phase 4: Adding a toolbar button</h3>
<p>In Phase 4, we’ll use graphics and style sheets to create a toolbar button that will open the clock window (Figure 9).</p>
<h4>Directory structure</h4>
<p>The style sheet and graphics files will be located inside the chrome’s skin package. The extension will actually work fine even if these are located in the content package, but putting them in the skin package has the benefit of making it possible to design GUIs that match specific Firefox themes, and allowing theme developers to create special visual effects for extensions.</p>
<p>First, add the skin directory and its subordinate files to your helloworld directory as shown in Figure 10. The purpose of each of these files is explained in Table 4. Download the files icon.png and <em>icon-small.png</em> from the resources website and place them appropriately.</p>
<p>{{ TODO("Figure 9: Toolbar after Phase 4") }}</p>
<p>{{ TODO("Figure 10: Directory structure with skin package added") }}</p>
<h4>Add the skin package</h4>
<p>Update <em>chrome.manifest</em> with the contents of Listing 10.</p>
<h5>Register skin package (skin instruction)</h5>
<p>Line 1 beginning skin is used to register the skin package; helloworld is the package name; skin/classic/ is the relative path to the folder containing the source files; classic/1.0 indicates that this skin package is meant for the Firefox standard theme.</p>
<h5>Cross-package overlays with style sheets (style instruction)</h5>
<p>In Phase 1, we used the overlay instruction to invoke a cross-package overlay with XUL; to do this with a style sheet, we use the style instruction. Lines 2-3 create overlays on the browser window and the “customize toolbar” window.</p>
<h4>Add the toolbar button</h4>
<p>To add the toolbar button to the browser window, update <em>overlay.xul</em> as shown in Listing 11. In order to make it so that users can relocate the button where they like, you will add a new <code>toolbarbutton</code> element, using the special element <code>toolbarpalette</code> with the id <code>BrowserToolbarPalette</code> as the merge point. You’ll also add a new command element, so that a click on the toolbar button will share its process with the menu item.</p>
<h4>Create the style sheet</h4>
<p>Update the file <code>overlay.css</code> with the contents of Listing 12. Lines 1–3 define the icon image to use with full-sized icons, and lines 4–6 define the icon image to use when small toolbar icons have been selected.</p>
<h4>Operations check</h4>
<p>Since we have edited the chrome manifest, as we did in Phase 3, we need to relaunch Firefox. Check to make sure that, when you click on the “<em>customize toolbar</em>” button at the top-right of the window that the new toolbar icon appears in the resulting window. Drag it onto the toolbar, and click it to make sure it works correctly.</p>
<p>Table 4: Files used in Phase 4</p>
<p>{{ TODO("Make the table cleaner") }}</p>
<table border="1" cellpadding="8" cellspacing="0" width="590"> <tbody> <tr> <td> <p>name</p> </td> <td> <p>purpose</p> </td> </tr> <tr> <td> <p>icon.png</p> </td> <td> <p>The full-size toolbar button icon file</p> </td> </tr> <tr> <td> <p>icon-small.png</p> </td> <td> <p>the small toolbar button icon file</p> </td> </tr> <tr> <td> <p>overlay.css</p> </td> <td> <p>Style sheet that defines the toolbar buttons to use. Overlays both the browser window and the “customize toolbar” window.</p> </td> </tr> </tbody>
</table>
<p>Listing 10: Additional content for chrome.manifest</p>
<pre>skin helloworld classic/1.0 skin/classic/
style chrome://browser/content/browser.xul chrome://helloworld/skin/overlay.css
style chrome://global/content/customizeToolbar.xul chrome://helloworld/skin/overlay.css
</pre>
<p>Listing 11: Revisions to overlay.xul</p>
<pre class="brush: xml">&lt;?xml version="1.0"?&gt;
&lt;overlay id="helloworldOverlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"&gt;
  &lt;commandset id="mainCommandSet"&gt;
    &lt;command id="helloworldCommand" oncommand="window.openDialog(
      'chrome://helloworld/content/clock.xul',
      'Clock','chrome,centerscreen,modal');" /&gt;
  &lt;/commandset&gt;
  &lt;toolbarpalette id="BrowserToolbarPalette"&gt;
     &lt;toolbarbutton id="helloworldButton" label="Hello, World!" class="toolbarbutton-1" command="helloworldCommand" /&gt;
  &lt;/toolbarpalette&gt;
  &lt;menupopup id="menu_ToolsPopup"&gt;
    &lt;menuitem id="helloworldMenuitem" label="Hello, World!" insertbefore="sanitizeSeparator"
       command="helloworldCommand" /&gt;
  &lt;/menupopup&gt;
&lt;/overlay&gt;
</pre>
<p>Listing 12: Content for overlay.css</p>
<pre class="brush: css">#helloworldButton {
  list-style-image:
  url(chrome://helloworld/skin/icon.png);
}

toolbar[iconsize="small"] #helloworldButton {
  list-style-image: url(chrome://helloworld/skin/icon-small.png);
}
</pre>
<p> </p>
<h3>Phase 5: XPI packaging</h3>
<p>We actually completed our Hello World extension in Phase 4, but you can’t distribute the source files in this state to other users. So we need to create an xpi-formatted installer.</p>
<h4>XPI file directory structure</h4>
<p>XPI is basically just a zip archive, but the directory structure inside an xpi file is generally different from your development directory structure (Figure 11).</p>
<h4>Packaging procedure</h4>
<p>To package your extension as an xpi while preserving the directory structure of the source files you used during development, make the changes shown in Figure 11, as described below.</p>
<p>{{ TODO("Do we really need to explain the jar part ? That's a bit confusing and not really needed") }}</p>
<ol> <li>Create a new chrome directory inside the helloworld directory.</li> <li>Zip the content, locale, and skin directories<sup><a href="#footnote1" id="from_footnote1">1</a></sup>, rename the resulting archive helloworld.jar, and place it inside the chrome directory you created in Step 1.</li> <li>Copy chrome.manifest and rename the backup chrome.manifest.bak.</li> <li>Change the content of chrome.manifest to be in line with Listing 13.</li> <li>Zip install.rdf, chrome.manifest, and the chrome folder together, and rename the resulting archive helloworld.xpi.</li>
</ol>
<p>That completes the process of XPI packaging. One point you need to take special note of is that in Step 4, you updated chrome.manifest so that the files it refers to have paths pointing inside the zip archive.</p>
<p>In order to go back into development mode after you’ve finished packaging the extension as an xpi, you need to revert the chrome.manifest you edited in Step 4 to point to the backup files in Step 3. If you forget to do this, any subsequent changes you make to your source files during development will not be reflected. You may want to write a batch file to automate this task.</p>
<p>There’s a simpler way to package an extensions as an xpi. Create a zip archive containing install.rdf, chrome.manifest, content, locale, and skin. Name this helloworld.xpi.</p>
<p>This omits creating the archive of the chrome packages called helloworld.jar. This kind of xpi file will run fine, but will slow down Firefox’s launch time.</p>
<p>{{ TODO("Figure 11: Directory structure inside an xpi file") }}</p>
<p>Listing 13: Revisions to chrome.manifest</p>
<pre>content helloworld jar:chrome/helloworld.jar!/content/
overlay chrome://browser/content/browser.xul chrome://helloworld/content/overlay.xul
locale helloworld en-US jar:chrome/helloworld.jar!/locale/en-US/
locale helloworld fr-FR jar:chrome/helloworld.jar!/locale/fr-FR/
skin helloworld classic/1.0 jar:chrome/helloworld.jar!/skin/classic/
style chrome://browser/content/browser.xul chrome://helloworld/skin/overlay.css
style chrome://global/content/customizeToolbar.xul chrome://helloworld/skin/overlay.css
</pre>
<p> </p>
<h4>XPI operations check</h4>
<p>After you create the xpi file, make sure it works. Since you’ve already got the development version of the Hello World extension running as a test install on your current profile, restart Firefox under a different profile (see footnote 1). Drag and drop the xpi file onto your browser window to start the installation.</p>
<p>This completes the explanation of how to create and package a simple “hello world” extension.</p>
<h2>Developing practical extensions:<br>
A session-management extension</h2>
<p>In this section, we will create an extension that uses a new feature: the session store API. This will allow the user to save and restore session snapshots (browser window states) at any time.</p>
<h3>Phase 1: test install</h3>
<p>Figure 12 shows what the interface for the session-management extension will look like. Under the Tool menu, the Session Store submenu contains two items—<em>Save Session</em> and <em>Clear Sessions</em>; in between them is a list of previously saved sessions that you can restore, most recent first.</p>
<p>As discussed in the part “<em>The Session Store API</em>”, sessions are represented as JSON character strings. Sessions are stored in a subdirectory of the profile directory (called “<em>sessionstore</em>”), with each session stored as a text file in it (Figure 13).</p>
<h4>Source file structure</h4>
<p>{{ TODO("Figure 14 shows the directory structure for this project") }}</p>
<h4>Creating install manifest</h4>
<p>Create your work folder, and create an install manifest with the contents of Listing 1, but in this case, change <em>em:id</em> to <em><a class=" link-mailto" href="mailto:sessionstore@example.org" rel="freelink">sessionstore@example.org</a></em> and <em>em:name</em> to <em>Session Store</em>.</p>
<h4>Creating chrome manifest</h4>
<p>Create a chrome manifest with the contents of Listing 2.</p>
<h4>Finding overlay merge points</h4>
<p>Use the same overlay merge point and element to add on to as you did for the Hello World extension.</p>
<h4>Browser window overlay</h4>
<p>Create a file that will overlay the browser window, <em>overlay.xul</em>, with the contents from Listing 14.</p>
<h4>Test install</h4>
<p>Perform a test install using pointer files as you did in the previous section. Afterwards, you should see a <em>Session Store</em> menu item under the Tools menu.</p>
<p>{{ TODO("Figure 12: Session-management extension interface: the menu") }}</p>
<p>{{ TODO("Figure 13: Schematic of session saving and restorin") }}</p>
<p>{{ TODO("Figure 14: Source file directory structure") }}</p>
<p>Listing 14: Content for overlay.xul</p>
<pre class="brush: xml">&lt;?xml version="1.0"?&gt;
&lt;overlay id="sessionstoreOverlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"&gt;
  &lt;script type="application/x-javascript"
      src="chrome://sessionstore/content/overlay.js" /&gt;
    &lt;menupopup id="menu_ToolsPopup"&gt;
      &lt;menu label="Session Store" insertbefore="sanitizeSeparator"&gt;
        &lt;menupopup&gt;
          &lt;menuitem label="Save Session" /&gt;
          &lt;menuseparator /&gt;
          &lt;!-- Dynamically generated menu items go here --&gt;
          &lt;menuseparator /&gt;
        &lt;menuitem label="Clear Sessions" /&gt;
      &lt;/menupopup&gt;
    &lt;/menu&gt;
  &lt;/menupopup&gt;
&lt;/overlay&gt;
</pre>
<p> </p>
<h3>Phase 2: Implement functionality</h3>
<p>In Phase 2, we’ll use JavaScript to implement the session save and restore functions using the session store API.</p>
<h4>Figure out JavaScript skeleton</h4>
<p>Decide on method names and variable names for each of the processes you’ll need to implement these features, and put them into an <em>overlay.js</em> file. Follow the code in Listing 15.</p>
<h5>Wrap methods and variables as properties of objects</h5>
<p>You’ll note that in Listing 15, I’ve defined a lot of different methods and variables as properties of a single object, <code>gSessionStore</code>. Take note that when using an extension to create an overlay on the browser window, any global variables and functions defined within JavaScript that get opened as overlays on top of <em>browser.xul</em> all become properties of the <em>browser.xul</em> window object. This means that all those variables and functions get mixed up with those defined by Firefox itself and any other extensions that are running. Wrapping everything in one object is a good way to isolate extensions from each other and keep them from trampling each others’ toes. Defining a class would be another good way.</p>
<h5>Initializing when opening browser window</h5>
<p>With extensions, there are a lot of situations where you want to perform some kind of initialization when the browser window opens. To do that, add an event listener for the load event that targets the window object. In Listing 15, an <code>init</code> method runs when the window opens, and an uninit method runs when the window closes.</p>
<p>Listing 15: Content for overlay.js</p>
<pre class="brush: js">var gSessionStore = {
  // Directory to save sessions (nsILocalFile)
  _dir: null,
  // Initialization
  init: function() { },
  // uninitialization
  uninit: function() { },
  // Save session (event handler)
  save: function(event) { },
  // Restore session (event handler)
  restore: function(event) { },
  // Delete session (event handler)
  clear: function(event) { },
  // Dynamically generate menu items (event handler)
  createMenu: function(event) { },
  // Read file
  _readFile: function(aFile) { },
  // Write file
  _writeFile: function(aFile, aData) { },
};
window.addEventListener("load", function(){
gSessionStore.init(); }, false);
window.addEventListener("unload", function(){
gSessionStore.uninit(); }, false);
</pre>
<p>Listing 16: Revisions to overlay.xul</p>
<pre class="brush: xml">&lt;menupopup onpopupshowing="gSessionStore.createMenu(event);"
    oncommand="gSessionStore.restore(event);"&gt;
  &lt;menuitem label="Save Session" oncommand="gSessionStore.save(event);" /&gt;
  &lt;menuseparator /&gt;
  &lt;!-- Dynamically generated menu items go here --&gt;
  &lt;menuseparator /&gt;
  &lt;menuitem label="Clear Sessions" oncommand="gSessionStore.clear(event);" /&gt;
&lt;/menupopup&gt;
</pre>
<p> </p>
<h4>Adding event handlers</h4>
<p>Add the four event handlers you defined in <em><code>overlay.js</code></em> to <em>overlay.xul</em> (Listing 16). This takes advantage of event bubbling<sup><a href="#footnote2" id="from_footnote2">2</a></sup>, appending the restore event handler to the <code>menupopup</code> element one layer above the <code>menuitem</code> element. In the code for the save and restore event handlers, we will actually need blocks to prevent event bubbling.</p>
<h4>Implementing methods</h4>
<p>Now we’re going to actually implement the methods we defined in the <code>gSessionStore</code> object, one at a time. In the interest of space, I’m not including all the code here, but you can download it from the following URL: {{ TODO("include code from xuldev: translate it and attach it to the chapter document") }}</p>
<h5>init method</h5>
<p>The init method is as shown in Listing 17. The init method first gets the <em>sessionstore</em> directory inside the profile directory using <code>nslLocalFile</code>, which we’ll refer to through the variable <code>_dir</code> in subsequent lines (lines 3–6); if the directory doesn’t exist, we’ll create it (lines 7–8)<sup><a href="#footnote3" id="from_footnote3">3</a></sup>.</p>
<h5>uninit method</h5>
<p>Listing 18 gives the code for the uninit method. This just discards the variable <code>_dir</code> that was created by the init method. This process actually isn’t important.</p>
<h5>save method</h5>
<p>Listing 19 gives the code for the save method. In line 3, you see the block that prevents the bubbling that would result in this being called by the <code>oncommand</code> handler in the menupop element, which is the parent of the <code>menuitem</code> element. In lines 4–6, we use the <code>nslSessionStore</code> interface’s <code>getBrowserState</code> method to get a JSON text-string representation of the states of all currently open browser windows. In lines 7–9, we create the target file’s <code>nslFile</code> object by duplicating the <code>_dir</code> property. In line 10, we use the <code>_writefile</code> method (which we’ll get to shortly) to write the JSON string representing the session to the file.</p>
<h5>restore method</h5>
<p>Listing 20 shows the event handler for the dynamically generated menu items, which were created using the <code>createMenu</code> method (which we’ll get to shortly). Lines 3–6 get the file name from the special attribute <code>fileName</code> that was added by the <code>createMenu</code> method, and read in the JSON text string from that file using the <code>_readFile</code> method (which we’ll get to shortly). Lines 7–9 use the <code>nslSessionStore</code> interface’s <code>setWindowState</code> method to restore the session, using the current window as an origin point. Session restoration overwrites any currently open tabs.</p>
<h5>clear method</h5>
<p>This uses the <code>directoryEntries</code> property of the <code>nslFile</code> interface to delete all files within the directory you got earlier. Refer to the section “<em>Traversing folders</em>” in <a class="internal" href="/En/Firefox_addons_developer_guide/Using_XPCOM%E2%80%94Implementing_advanced_processes" title="En/Firefox addons developer guide/Using XPCOM—Implementing advanced processes">Chapter 4</a>.</p>
<h5>_readFile method</h5>
<p>Reads the <code>nslFile</code> object passed as a parameter, and returns its contents as a text string. When the file is read in, its contents are converted from UTF-8 to Unicode.</p>
<h5>_writeFile method</h5>
<p>Creates a new file for the <code>nslFile</code> object passed as a parameter, and writes the text string, also passed as a parameter. Upon writing, the text is converted from Unicode to UTF-8.</p>
<h5>createMenu method</h5>
<p>This event handler is called when the Session Store submenu opens. First, it deletes the menuitem elements that were dynamically generated the last time the submenu opened, and which are still left over. Next, it dynamically generates <code>menuitem</code> elements based on the names of all the files in the session-storage directory, and inserts them into the menu.</p>
<h5>Operations check</h5>
<p>This completes the functional implementation. Open a new window and make sure that all three functions—saving a session, restoring a session, and deleting sessions—all work correctly.</p>
<p>Listing 17: Content for <code>init</code> method</p>
<pre class="brush: js">init: function() {
  var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
               .getService(Components.interfaces.nsIProperties);
  this._dir = dirSvc.get("ProfD", Components.interfaces.nsILocalFile);
  this._dir.append("sessionstore");
  if (!this._dir.exists())
    this._dir.create(this._dir.DIRECTORY_TYPE, 0700);
},
</pre>
<p>Listing 18: Content for uninit method</p>
<pre class="brush: js">uninit: function() {
  this._dir = null;
},
</pre>
<p>Listing 19: Content for save method</p>
<pre class="brush: js">save: function(event) {
  event.stopPropagation();
  var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
           .getService(Components.interfaces.nsISessionStore);
  var state = ss.getBrowserState();
  var fileName = "session_" + Date.now() + ".js";
  var file = this._dir.clone();
  file.append(fileName);
  this._writeFile(file, state);
},
</pre>
<p>Listing 20: Content for restore method</p>
<pre class="brush: js">restore: function(event) {
  var fileName = event.target.getAttribute("fileName");
  var file = this._dir.clone();
  file.append(fileName);
  var state = this._readFile(file);
  var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
           .getService(Components.interfaces.nsISessionStore);
  ss.setWindowState(window, state, false);
},
</pre>
<h3>Phase 3: Creating a preference panel</h3>
<p>You don’t need to use ini files, registries, or anything like that to give users options they can set through a preference panel. Firefox lets you read and write preference values using handy XPCOM services and create preference panels with interface widgets. In Phase 3, we’ll try this out (Figure 15).</p>
<p>{{ TODO("Figure 15: Finished preference panel") }}</p>
<h4>Directory structure</h4>
<p>Figure 16 shows the directory structure we’ll be using in Phase 3. Table 5 explains the new files being used.</p>
<p>Table 5: Files used in Phase 3</p>
<p>{{ TODO("Make the table cleaner") }}</p>
<table border="1" cellpadding="8" cellspacing="0" width="590"> <tbody> <tr> <td> <p>name</p> </td> <td> <p>purpose</p> </td> </tr> <tr> <td> <p>sessionstore-prefs.js</p> </td> <td> <p>File sets defaults for preferences. This should always be located inside defaults\preferences</p> </td> </tr> <tr> <td> <p>prefs.xul</p> </td> <td> <p>The preference panel</p> </td> </tr> </tbody>
</table>
<p> {{ TODO("Figure 16: Source file structure") }}</p>
<h4>Deciding the format for your preferences</h4>
<p>Next you need to determine the names, types, and values for your preferences (Table 6). Each preference should be prefixed with something that uniquely identifies your extension—in this case, we’ll use <em>extensions.sessionstore</em>.</p>
<p>Table 6: Preference formats</p>
<p>{{ TODO("Make the table cleaner") }}</p>
<table border="1" cellpadding="8" cellspacing="0" width="590"> <tbody> <tr> <td> <p>Preference name</p> </td> <td> <p>Type</p> </td> <td> <p>Value</p> </td> </tr> <tr> <td> <p>extensions.sessionstore.warnOnClear</p> </td> <td> <p>Boolean</p> </td> <td> <p>Activated when deleting sessions. If true, causes a confirmation dialog to appear; if false, deletes immediately.</p> </td> </tr> <tr> <td> <p>extensions.sessionstore.replaceTabs</p> </td> <td> <p>Integer</p> </td> <td> <p>Activated when restoring sessions.</p> <p>0: Leave current tabs</p> <p>1: Overwrite current tabs</p> <p>2: Ask each time</p> </td> </tr> </tbody>
</table>
<h4>Defaults definition file</h4>
<p>Listing 21 gives the contents for the default preferences, as described in Table 6. Save this in <em>defaults\preferences\sessionstoreprefs.js</em>.</p>
<h4>Creating the preference panel</h4>
<p>Create <em>prefs.xul</em> with the contents from Listing 22. The <code>prefwindow</code> element is a special root element for creating preference panels, with the prefpane element subordinate to it. You can create an interface similar to Firefox’s options window by inserting multiple prefpane elements, which allows users to switch between panels by clicking buttons. The preference element creates a preference value that can be written and read by the preference panel, with the preference name indicated by the name attribute, and the type by the type attribute. Furthermore, by matching the preference element’s id to the <code>preference</code> attribute of a checkbox or radiogroup, you can specify the interface widget that will be used to set your preferences. Add the contents of Listing 23 to your install manifest to make it so that the Add-on Manager can open your preference panel.</p>
<p>Listing 21: Content for sessionstore-prefs.js</p>
<pre class="brush: js">pref("extensions.sessionstore.warnOnClear", true);
pref("extensions.sessionstore.replaceTabs", 2);
</pre>
<p>Listing 22: Content for prefs.xul</p>
<pre class="brush: xml">&lt;?xml version="1.0"?&gt;
&lt;?xml-stylesheet href="chrome://global/skin/" type="text/css"?&gt;
&lt;prefwindow id="sessionstorePrefs" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
     title="Session Store Preferences"&gt;
  &lt;prefpane&gt;
    &lt;preferences&gt;
      &lt;preference id="extensions.sessionstore.warnOnClear"
           name="extensions.sessionstore.warnOnClear"
           type="bool" /&gt;
      &lt;preference id="extensions.sessionstore.replaceTabs"
           name="extensions.sessionstore.replaceTabs"
           type="int" /&gt;
    &lt;/preferences&gt;
    &lt;checkbox label="Confirm before clearing sessions"
         preference="extensions.sessionstore.warnOnClear" /&gt;
    &lt;groupbox&gt;
      &lt;caption label="When restoring session:" /&gt;
      &lt;radiogroup preference="extensions.sessionstore.replaceTabs"&gt;
        &lt;radio value="0" label="Keep current tabs" /&gt;
        &lt;radio value="1" label="Replace current tabs"/&gt;
        &lt;radio value="2" label="Ask me every time" /&gt;
      &lt;/radiogroup&gt;
    &lt;/groupbox&gt;
  &lt;/prefpane&gt;
&lt;/prefwindow&gt;

</pre>
<p>Listing 23: Additional content for install.rdf</p>
<pre class="brush: xml">&lt;em:optionsURL&gt;chrome://sessionstore/content/prefs.xul&lt;/em:optionsURL&gt;
</pre>
<h4>Changing behavior through preference values</h4>
<p>In Listing 24, we add a preference that allows a confirmation dialog to appear when we delete sessions. When we get the preference value from JavaScript, we take advantage the XPCOM <code>nslPrefService</code> interface, using the <code>getBoolPref</code> and <code>getIntPref</code> methods as appropriate to the data type. In Listing 24, we get the <code>nslPrefBranch</code> object that lets us read and write all preferences beginning with the prefix <code>extensions.sessionstore.</code>, which is what lets us use the <code>getBoolPref</code> method to get the value.</p>
<h4>Operations check</h4>
<p>We’ve revised the install manifest, so we need to reinstall the extension (see the part “<em>Operations checks on revised source files</em>”). Once you’ve reinstalled it, first open <em>about:config</em>, and confirm that the previous preference values are set. Next, select Session Store from the Add-on Manager and click the “<em>Preferences</em>” button to bring up the preference panel. Try changing the preferences, and see if those changes are reflected when you restore or delete sessions.</p>
<h3>Phase 4: XPI packaging</h3>
<p>This procedure is unchanged from Phase 5 in the “<em>Developing a Simple Extension</em>” section, but take note of everything within the defaults folder.</p>
<h2>Conclusion</h2>
<p>We’ve looked at how to build extensions at an extremely basic level in this chapter, and there are a lot of other interesting features to explore in extensions and XUL. But I hope that having gotten your feet wet with extensions development, you’ll want to dive in now.</p>
<h3>Operations checks on revised source files</h3>
<p>In the interest of developing extensions efficiently, you want to minimize the number of steps between revising a source file and running an operations check on it. For each type of source file, there’s a different procedure to follow. The following all assumes that you’ve disabled the XUL cache as discussed in the section “<em>Setting up your development environment</em>”.</p>
<h4>XUL that opens new windows</h4>
<p>Confirm changes by closing and reopening window. If the XUL is displaying as a sidebar, reopen the sidebar. Likewise with any JavaScript, DTDs, or style sheets referenced by the XUL.</p>
<h4>XUL overlaying other windows</h4>
<p>You need to force the overlay-target XUL to reload. If it’s the browser window that’s the target, you can simply open a new browser window to confirm the changes.</p>
<h4>properties file</h4>
<p>If you make changes to a properties file in the locale package, those changes will not be reflected in Firefox until you relaunch it.</p>
<h4>XPCOM components inside the components directory</h4>
<p>You will need to delete the files <em>compreg.dat</em> and<em> xpti.dat</em> inside the profile folder and relaunch Firefox.</p>
<h4>Default preferences file inside the defaults\preferences directory</h4>
<p>You will need to relaunch Firefox.</p>
<h4>chrome.manifest</h4>
<p>You will need to relaunch Firefox.</p>
<h4>install.rdf</h4>
<p>You will need to temporarily uninstall the extension and then reinstall it. In practice, what you’ll do is remove the pointer file, relaunch Firefox, return the pointer file, and relaunch Firefox again. On a Linux system, use the touch command on the directory pointed at by the pointer file to change its “last changed” date, and avoid the relaunching.</p>
<h3>The session store API</h3>
<p>The session store API<sup><a href="#footnote4" id="from_footnote4">4</a></sup> is one of the new developer-oriented features in Firefox 2. Using the various methods in the <code>nslSessionStore</code> interface, which is an XPCOM service, you can easily save and restore session states. Functions in Firefox that let you reopen the last closed tab, or restore to your previous state after a crash, are all implemented through this API.</p>
<p>We used two methods in the <code>nslSessionStore</code> interface in this project. Let’s look at how they work.</p>
<h4>getBrowserState()</h4>
<p>Returns a JSON-formatted<sup><a href="#footnote5" id="from_footnote5">5</a></sup> text string representing the state of every currently open browser window. This string includes extensive information regarding every open window and tab, the forward/back history for each tab, page scroll position, text zoom, contents of form elements, etc.</p>
<h4>setWindowState(aWindow, aState, aOverwrite)</h4>
<p>Restores the browser window indicated by the parameter aWindow to the state indicated by aState. If aOverwrite is true, the currently open tab is overwritten; otherwise, a new tab is created for the restored content.</p>
<h3>JavaScript debugging methods</h3>
<h4>alert</h4>
<p>The easiest way to debug javascvript is using the window.alert method to display variables in a dialog (Listing A). Except for asynchronous processes, all processing stops while the dialog is up, so this technique is useful when you want to pin down a value that can vary during a process.</p>
<h4>dump</h4>
<p>In a Windows environment, you can launch Firefox with the <code>-console</code> argument, which opens a dump console. Using the <code>dump</code> method, you can send text to the dump console (Listing B). The text output being sent to the console does not suspend any other processes, and this is most useful when there’s a high volume of messages being output. In order to use the dump method most effectively, you’ll need to follow the recommendations in “<em>Setting up your development environment</em>”. Note that in a Windows environment, non-latin text sent to the dump console will get corrupted.</p>
<h4>error console</h4>
<h5>The nslConsoleService interface</h5>
<p>Using the <code>logStringMessage</code> method of the <code>nslConsoleService</code> interface, which is an XPCOM service, you can output text to the “message” panel of the error console. Non-latn text will appear correctly here. If you are going to be issuing messages to the error console frequently, it’s handy to define a function like that in Listing C.</p>
<h5>Components.utils.reportError method</h5>
<p>Another way to output text to the same error console is with the <code>Components.utils.reportError</code> method. This will send its output to the “error” panel of the error console.</p>
<p>Listing A: Check the value of variable foo</p>
<pre class="brush: js">alert(foo);
</pre>
<p>Listing B: List the properties of obj</p>
<pre class="brush: js">for (var i in obj) {
  dump(i + " : " + obj[i] + "\n");
}
</pre>
<p>Listing C: Function to output messages to the error console</p>
<pre class="brush: js">function log(aText) {
  var console = Components.classes["@mozilla.org/consoleservice;1"]
      .getService(Components.interfaces.nsIConsoleService);
  console.logStringMessage(aText);
}
</pre>
<p> </p>
<h3>Where can I learn more about the XPCOM interface?</h3>
<p>{{ TODO("add links:") }}</p>
<p>The Mozilla Development Center has extensive documentation on the <code>nsISessionStore</code> interface we’ve discussed in this section<sup><a href="#footnote6" id="from_footnote6">6</a></sup>. In general, details (IDL) on the XPCOM interface are available from XULPlanet<sup><a href="#footnote7" id="from_footnote7">7</a></sup> and the Mozilla Cross-Reference<sup><a href="#footnote8" id="from_footnote8">8</a></sup>, which includes source code with full-text searching available.</p>
<div class="note" id="footnote1"><a href="#from_footnote1">1</a> - In Windows XP, you can use the “compress folder” command to create a zip archive, but I recommend installing a tool like 7-Zip that lets you specify the compression ratio. It’s OK to have a low compression ratio when you are creating the jar file, and a high compression ratio when creating the xpi file.</div>
<div class="note" id="footnote2"><a href="#from_footnote2">2</a> - An event generated by an element “bubbles up” to the root.</div>
<div class="note" id="footnote3"><a href="#from_footnote3">3</a> - See Code Snippets: File I/O-MDC (<a class=" external" href="http://developer.mozilla.org/en/docs/Code_snippets:File_I/O#Getting_special_files" rel="freelink">http://developer.mozilla.org/en/docs..._special_files</a>) for techniques on getting special folders, like the profile folder.</div>
<div class="note" id="footnote4"><a href="#from_footnote4">4</a> - See Session Store API-MDC (<a class=" external" href="http://developer.mozilla.org/en/Session_store_API" rel="freelink">http://developer.mozilla.org/en/Session_store_API</a>)</div>
<div class="note" id="footnote5"><a href="#from_footnote5">5</a> - JSON is short for JavaScript Object Notation. It is a data format that can easily be read from and written to in JavaScript.</div>
<div class="note" id="footnote6"><a href="#from_footnote6">6</a> - nslSessionStore-MDC (<a class=" external" href="http://developer.mozilla.org" rel="freelink">http://developer.mozilla.org</a>)</div>
<div class="note" id="footnote7"><a href="#from_footnote7">7</a> - XULPlanet (<a class=" external" href="http://www.xulplanet.com/" rel="freelink">http://www.xulplanet.com/</a>)</div>
<div class="note" id="footnote8"><a href="#from_footnote8">8</a> - Mozilla Cross-Reference (<a class=" external" href="http://mxr.mozilla.org/" rel="freelink">http://mxr.mozilla.org/</a>)</div>
<p> </p>
<p> </p>
Revert to this revision