MDN may have intermittent access issues April 18 13:00 - April 19 01:00 UTC. See whistlepig.mozilla.org for all notifications.

mozilla
Your Search Results

    Chapter 5: Let's build a Firefox extension

    In This Article
    1. Setting up your development environment
      1. Create a development profile
      2. Change your preferences for more efficient development
      3. Install the DOM Inspector
      4. Optional - Install Firebug and Chromebug
    2. Developing extensions: What you need to know
      1. Chrome
        1. What is chrome?
        2. Three kinds of packages make up chrome
        3. Chrome URL
      2. Cross-package overlays
      3. Conclusion
    3. Developing a Simple Extension: Hello World
      1. Phase 1: test install
        1. Source file structure
        2. Create install manifest
        3. Create the chrome manifest
        4. Test install and operational check
      2. Phase 2: Adding a function to display the time
        1. Adding an event handler
        2. Clock handler
        3. Operations check
      3. Phase 3: Adding multilingual support
        1. Directory structure
        2.  
        3. Adding locale packages
        4. Replacing text with entity references in XUL
        5. Create the DTD that defines the entity references
        6. Replacing User Interface messages within JavaScript files with properties references
        7. Operations check
      4. Phase 4: Adding a toolbar button
        1. Directory structure
        2. Add the skin package
        3. Add the toolbar button
        4. Create the style sheet
        5. Operations check
      5. Phase 5: XPI packaging
        1. XPI file directory structure
        2. Packaging procedure
        3. XPI operations check
    4. Developing practical extensions: A session-management extension
      1. Phase 1: test install
        1. Source file structure
        2. Creating install manifest
        3. Creating chrome manifest
        4. Finding overlay merge points
        5. Browser window overlay
        6. Test install
      2. Phase 2: Implement functionality
        1. Figure out JavaScript skeleton
        2. Adding event handlers
        3. Implementing methods
      3. Phase 3: Creating a preference panel
        1. Directory structure
        2. Deciding the format for your preferences
        3. Defaults definition file
        4. Creating the preference panel
        5. Changing behavior through preference values
        6. Operations check
      4. Phase 4: XPI packaging
    5. Conclusion
      1. Operations checks on revised source files
        1. XUL that opens new windows
        2. XUL overlaying other windows
        3. properties file
        4. XPCOM components inside the components directory
        5. Default preferences file inside the defaults\preferences directory
        6. chrome.manifest
        7. install.rdf
      2. The session store API
        1. getBrowserState()
        2. setWindowState(aWindow, aState, aOverwrite)
      3. JavaScript debugging methods
        1. alert
        2. dump
        3. error console
      4. Where can I learn more about the XPCOM interface?

    Obsolete
    This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.

    Draft
    This page is not complete.

    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.

    An easier method of building a Firefox/Thunderbird Addon for developers who are well-acquainted with IDEs like Netbeans, Eclipse, etc. is to use Netbeans (IDE from Sun Microsystems) and Foxbeans (Plugin for Netbeans, by TeeSoft).

    Setting up your development environment

    In order to develop (and test) extensions in a most convenient way it is recommended to apply some changes to Firefox. In the following you will find a brief description how to do so; a more detailed one can be found under Setting up an extension development environment.

    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 development. To create this dev profile start Firefox with

    firefox.exe -no-remote -P dev

    On the first start the Profile Manager will appear, where you can create the dev profile and configure its home-path.

    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.

    Preference Description Value

    nglayout.debug.disable_xul_cache (not present in Firefox 3.5+)

    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 (not present in Firefox 3.5+)

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

    javascript.options.showInConsole (present in Firefox 3.5+)

    Outputs JavaScript errors to the error console. True
    javascript.options.strict (present in Firefox 3.5+) Enforces strict error output from JavaScript True

    Table 1: Preferences to set for developing extensions

    To make these changes, start your development profile, 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

    Optional - Install Firebug and Chromebug

    Chromebug is used by the Firebug development team to develop Firebug.  You can use it to help debug Firefox extensions as well as to inspect and learn how the Firefox UI (Chrome) is constructed. You can learn more about Chromebug and download it at   http://getfirebug.com/wiki/index.php/Chromebug_User_Guide

    You may also find this extension to be valuable: Extension Developer https://addons.mozilla.org/en-US/firefox/addon/7434  

    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 (FIXME: 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 (FIXME: 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).

    FIXME: Figure 3: Menu after Phase 1 complete

    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.

    Figure 4: Folder structure used in Phase 1

    C:/
    └───extensions
        └───helloworld
            │   chrome.manifest
            │   install.rdf
            │
            └───content
                    clock.js
                    clock.xul
                    overlay.xul

    Table 2: How files are used in Phase 1

    FIXME: 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.

    content/overlay.xul

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

    content/clock.xul

    content/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).

    Create install manifest

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

    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>4.0.0.*</em:maxVersion>
          </Description>
        </em:targetApplication>
      </Description>
    </RDF>
    

    Create the chrome manifest

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

    Listing 2: Content for chrome.manifest

    content helloworld content/
    overlay chrome://browser/content/browser.xul chrome://helloworld/content/overlay.xul
    
    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.

    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.  Firefox 31 Open the DOM Inspector by open menu>Developer>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

    FIXME: Figure 5: Finding merge points for overlays using the DOM Inspector

    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).

    FIXME: Figure 6: Clock window produced by Phase 2

    Adding an event handler

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

    FIXME: Listing 4: Event handler

    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.

    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/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();
    }
    

    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.

    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).

    FIXME: 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.

    FIXME: Figure 8: Directory structure with locale package added

    Table 3: Files used in Phase 3

    FIXME: Make the table cleaner

    File name Role

    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.

    Listing 7: Additional content for chrome.manifest

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

    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.

    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/javascript" src="chrome://helloworld/content/clock.js" />
      <hbox align="center">
        <label value="&helloworld.currentTime;:" />
        <textbox id="currentTime" />
      </hbox>
    </dialog>
    

    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.

    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">

    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.

     

    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).

    FIXME: Figure 9: Toolbar after Phase 4

    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.

    FIXME: Figure 10: Directory structure with skin package added

    Add the skin package

    Update chrome.manifest with the contents of Listing 10.

    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
    
    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.

    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>
    

    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.

    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);
    }
    

    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

    FIXME: 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.

    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).

    FIXME: Figure 11: Directory structure inside an xpi file

    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.

    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.

    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
    

    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.

    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.

    FIXME: Figure 12: Session-management extension interface: the menu

    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).

    FIXME: Figure 13: Schematic of session saving and restorin

    Source file structure

    FIXME: 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.

    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/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>
    

    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.

     

    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.

    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);
    
    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.

    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.

    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>
    

     

    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: FIXME: 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.

    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);
    },
    
    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.

    Listing 18: Content for uninit method

    uninit: function() {
      this._dir = null;
    },
    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.

    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);
    },
    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.

    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);
    },
    
    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.

    Listing 21: Content for clear method

    clear: function(event)
      {
        event.preventBubble();
        var fileEnum = this._dir.directoryEntries;
        while (fileEnum.hasMoreElements()) {
          var file = fileEnum.getNext().QueryInterface(Components.interfaces.nsIFile);
          file.remove(false);
          // debug
          dump("SessionStore> clear: " + file.leafName + "\n");
        }
      },
    

     

    _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.

    Listing 22: Content for _readFile method

    _readFile: function(aFile)
      {
        try {
          var stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
                       createInstance(Components.interfaces.nsIFileInputStream);
          stream.init(aFile, 0x01, 0, 0);
          var cvstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
                         createInstance(Components.interfaces.nsIConverterInputStream);
          cvstream.init(stream, "UTF-8", 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
          var content = "";
          var data = {};
          while (cvstream.readString(4096, data)) {
            content += data.value;
          }
          cvstream.close();
          return content.replace(/\r\n?/g, "\n");
        }
        catch (ex) { }
        return null;
      },
    
    _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.

    Listing 23: Content for _writeFile method

    _writeFile: function(aFile, aData)
      {
        // init stream
        var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"].
                     createInstance(Components.interfaces.nsIFileOutputStream);
        stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0);
        // convert to UTF-8
        var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
                        createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
        converter.charset = "UTF-8";
        var convertedData = converter.ConvertFromUnicode(aData);
        convertedData += converter.Finish();
        // write and close stream
        stream.write(convertedData, convertedData.length);
        if (stream instanceof Components.interfaces.nsISafeOutputStream) {
          stream.finish();
        } else {
          stream.close();
        }
      },
    
    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.

    Listing 24: Content for createMenu method

     createMenu: function(event)
      {
        var menupopup = event.target;
        for (var i = menupopup.childNodes.length - 1; i >= 0; i--) {
          var node = menupopup.childNodes[i];
          if (node.hasAttribute("fileName"))
            menupopup.removeChild(node);
        }
        var fileEnum = this._dir.directoryEntries;
        while (fileEnum.hasMoreElements()) {
          var file = fileEnum.getNext().QueryInterface(Components.interfaces.nsIFile);
          var re = new RegExp("^session_(\\d+)\.js$");
          if (!re.test(file.leafName))
            continue;
          var dateTime = new Date(parseInt(RegExp.$1, 10)).toLocaleString();
          var menuitem = document.createElement("menuitem");
          menuitem.setAttribute("label", dateTime);
          menuitem.setAttribute("fileName", file.leafName);
          menupopup.insertBefore(menuitem, menupopup.firstChild.nextSibling.nextSibling);
        }
      },
    
    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.

     

    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).

    FIXME: 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

    FIXME: 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

     FIXME: 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

    FIXME: 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 25 gives the contents for the default preferences, as described in Table 6. Save this in defaults\preferences\sessionstoreprefs.js.

    Listing 25: Content for sessionstore-prefs.js

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

    Creating the preference panel

    Create prefs.xul with the contents from Listing 26. 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 27 to your install manifest to make it so that the Add-on Manager can open your preference panel.

    Listing 26: 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 27: Additional content for install.rdf

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

    Changing behavior through preference values

    In Listing 28, 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 28, 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.

    FIXME: Listing 28: nslPrefBranch object demonstration

    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 29). 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.

    Listing 29: Check the value of variable foo

    alert(foo);

    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 30). 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.

    Listing 30: List the properties of obj

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

    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 31.

    Listing 31: 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);
    }
    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.

     

     

    Where can I learn more about the XPCOM interface?

    FIXME: 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 the Mozilla Cross-Reference7, 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)
    7 - Mozilla Cross-Reference (http://mxr.mozilla.org/)

    Document Tags and Contributors

    Tags: 
    Last updated by: javier1nc,
    Hide Sidebar