XUL Migration Guide

This guide aims to help you migrate a XUL-based add-on to the SDK.

First we'll outline how to decide whether your add-on is a good candidate for migration via a comparison of the benefits and limitations of the SDK versus XUL development.

Next, we'll look at some of the main tasks involved in migrating:

Finally, we'll walk through a simple example.

Should You Migrate?

See this comparison of the benefits and limitations of SDK development and XUL development.

Whether you should migrate a particular add-on is largely a matter of how well the SDK's supported APIs meet its needs.

  • If your add-on can accomplish everything it needs using only the supported APIs, it's a good candidate for migration.

  • If your add-on needs a lot of help from third party packages, low-level APIs, or XPCOM, then the cost of migrating is high, and may not be worth it at this point.

  • If your add-on only needs a little help from those techniques, and can accomplish most of what it needs using the supported APIs, then it might still be worth migrating: we'll add more supported APIs in future releases to meet important use cases.

Content Scripts

In a XUL-based add-on, code that uses XPCOM objects, code that manipulates the browser chrome, and code that interacts with web pages all runs in the same context. But the SDK makes a distinction between:

  • add-on scripts, which can use the SDK APIs, but are not able to interact with web pages
  • content scripts, which can access web pages, but do not have access to the SDK's APIs

Content scripts and add-on scripts communicate by sending each other JSON messages: in fact, the ability to communicate with the add-on scripts is the only extra privilege a content script is granted over a normal remote web page script.

A XUL-based add-on will need to be reorganized to respect this distinction.

Suppose an add-on wants to make a cross-domain XMLHttpRequest based on some data extracted from a web page. In a XUL-based extension you would implement all this in a single script. An SDK-based equivalent would need to be structured like this:

  • the main add-on code (1) attaches a content script to the page, and (2) registers a listener function for messages from the content script
  • the content script (3) extracts the data from the page and (4) sends it to the main add-on code in a message
  • the main add-on code (5) receives the message and (6) sends the request, using the SDK's request API
Content script organization

There are two related reasons for this design. The first is security: it reduces the risk that a malicious web page will be able to access privileged APIs. The second is the need to be compatible with the multi-process architecture planned for Firefox: after this is implemented in Firefox, all add-ons will need to use a similar pattern, so it's likely that a XUL-based add-on will need to be rewritten anyway.

There's much more information on content scripts in the Working With Content Scripts guide.

Using the Supported APIs

The SDK provides a set of high level APIs providing some basic user interface components and functionality commonly required by add-ons. These are collected together in the addon-kit package. Because we expect to keep these APIs compatible as new versions of Firefox are released, we call them the "supported" APIs.

See the tutorials and the "High-Level API" reference in the "Developer Guide" sidebar. If the supported APIs do what you need, they're the best option: you get the benefits of compatibility across Firefox releases and of the SDK's security model.

APIs like widget and panel are very generic and with the right content can be used to replace many specific XUL elements. But there are some notable limitations in the SDK APIs and even a fairly simple UI may need some degree of redesign to work with them.

Some limitations are the result of intentional design choices. For example, widgets always appear by default in the add-on bar (although users may relocate them by toolbar customization) because it makes for a better user experience for add-ons to expose their interfaces in a consistent way. In such cases it's worth considering changing your user interface to align with the SDK APIs.

Some limitations only exist because we haven't yet implemented the relevant APIs: for example, there's currently no way to add items to the browser's main menus using the SDK's supported APIs.

Many add-ons will need to make some changes to their user interfaces if they are to use only the SDK's supported APIs, and add-ons which make drastic changes to the browser chrome will very probably need more than the SDK's supported APIs can offer.

Similarly, the supported APIs expose only a small fraction of the full range of XPCOM functionality.

Using Third Party Packages

The SDK is extensible by design: developers can create new modules filling gaps in the SDK, and package them for distribution and reuse. Add-on developers can install these packages and use the new modules.

If you can find a third party package that does what you want, this is a great way to use features not supported in the SDK without having to use the low-level APIs.

See the guide to adding Firefox menu items. Some useful third party packages are collected in the Jetpack Wiki.

Note, though, that by using third party packages you're likely to lose the security and compatibility benefits of using the SDK.

Using the Low-level APIs

But note that unlike the supported APIs, low-level APIs do not come with a compatibility guarantee, so we do not expect code using them will necessarily continue to work as new versions of Firefox are released.

In addition to the High-Level APIs, the SDK includes a number of Low-Level APIs some of which, such as tab-browser, xhr, and window-utils, expose powerful browser capabilities.

In this section we'll use low-level modules how to:

  • modify the browser chrome using dynamic manipulation of the DOM
  • directly access the tabbrowser object

Modifying the Browser Chrome

The window-utils module gives you direct access to chrome windows, including the browser's chrome window. Here's a really simple example add-on that modifies the browser chrome using window-utils:

var windowUtils = require("window-utils");

windowUtils = new windowUtils.WindowTracker({
  onTrack: function (window) {
    if ("chrome://browser/content/browser.xul" != window.location) return;
    var forward = window.document.getElementById('forward-button');
    var parent = window.document.getElementById('unified-back-forward-button');
    parent.removeChild(forward);
  }
});

This example just removes the 'forward' button from the browser. It constructs a WindowTracker object and assigns a function to the constructor's onTrack option. This function will be called whenever a window is opened. The function checks whether the window is the browser's chrome window, and if it is, uses DOM manipulation functions to modify it.

There are more useful examples of this technique in the Jetpack Wiki's collection of third party modules.

Accessing tabbrowser

The tab-browser module gives you direct access to the tabbrowser object. This simple example modifies the selected tab's CSS to enable the user to highlight the selected tab:

var widgets = require("widget");
var tabbrowser = require("tab-browser");
var self = require("self");

function highlightTab(tab) {
  if (tab.style.getPropertyValue('background-color')) {
    tab.style.setProperty('background-color','','important');
  }
  else {
    tab.style.setProperty('background-color','rgb(255,255,100)','important');
  }
}

var widget = widgets.Widget({
  id: "tab highlighter",
  label: "Highlight tabs",
  contentURL: self.data.url("highlight.png"),
  onClick: function() {
    highlightTab(tabbrowser.activeTab);
  }
});

Security Implications

The SDK implements a security model in which an add-on only gets to access the APIs it explicitly imports via require(). This is useful, because it means that if a malicious web page is able to inject code into your add-on's context, it is only able to use the APIs you have imported. For example, if you have only imported the notifications module, then even if a malicious web page manages to inject code into your add-on, it can't use the SDK's file module to access the user's data.

But this means that the more powerful modules you require(), the greater is your exposure if your add-on is compromised. Low-level modules like xhr, tab-browser and window-utils are much more powerful than the modules in addon-kit, so your add-on needs correspondingly more rigorous security design and review.

Using XPCOM

Finally, if none of the above techniques work for you, you can use the require("chrome") statement to get direct access to the Components object, which you can then use to load and access any XPCOM object.

The following complete add-on uses nsIPromptService to display an alert dialog:

var {Cc, Ci} = require("chrome");

var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
                getService(Ci.nsIPromptService);

var widget = require("widget").Widget({
  id: "xpcom example",
  label: "Mozilla website",
  contentURL: "http://www.mozilla.org/favicon.ico",
  onClick: function() {
    promptSvc.alert(null, "My Add-on", "Hello from XPCOM");
  }
});

It's good practice to encapsulate code which uses XPCOM by packaging it in its own module. For example, we could package the alert feature implemented above using a script like:

var {Cc, Ci} = require("chrome");

var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
            getService(Ci.nsIPromptService);

exports.alert = function(title, text) {
    promptSvc.alert(null, title, text);
};

If we save this as "alert.js" in our add-on's lib directory, we can rewrite main.js to use it as follows:

var widget = require("widget").Widget({
  id: "xpcom example",
  label: "Mozilla website",
  contentURL: "http://www.mozilla.org/favicon.ico",
  onClick: function() {
    require("alert").alert("My Add-on", "Hello from XPCOM");
  }
});

One of the benefits of this is that we can control which parts of the add-on are granted chrome privileges, making it easier to review and secure the code.

Security Implications

We saw above that using powerful low-level modules like tab-browser increases the damage that a malicious web page could do if it were able to inject code into your add-ons context. This applies with even greater force to require("chrome"), since this gives full access to the browser's capabilities.

Example: Porting the Library Detector

Porting the Library Detector walks through the process of porting a XUL-based add-on to the SDK. It's a very simple add-on and a good candidate for porting because there are suitable SDK APIs for all its features.

Even so, we have to change its user interface slightly if we are to use only the supported APIs.