Pitfalls for add-on developers

This article needs a technical review. How you can help.

Overview

This page lists patterns that add-on authors might be using that won't work, or will work differently, in multiprocess Firefox.

It's split into two sections: chrome process and content process.

Chrome process

The first section, chrome process, covers things that used to work in the chrome process that will no longer work in multiprocess Firefox. These are the sorts of things that will break an old add-on in multiprocess Firefox. The fix is generally some variant of "do that in a frame script loaded into the content process".

Compatibility shims

For many of the patterns described here we've implemented compatibility shims so the patterns still work. For example: whenever extensions try to access web content from the chrome process, the browser will return a Cross Process Object Wrapper that gives the chrome code synchronous access to the content.

You'll get the shims for your add-on by default, unless you set the multiprocessCompatible flag in your add-on's install manifest.

However, these shims are not a substitute for migrating extensions:

  • they are only a temporary measure, and will be removed eventually
  • they can have a bad effect on responsiveness
  • there are likely to be edge cases in which they don't work properly

For each pattern we've noted:

  • whether a shim exists and what kind of behavior it provides
  • how to update your add-on so you don't need the shim

Content process

However, some things that used to work in a chrome process will not work in the content process. The second section, content process, lists these sorts of things along with their mitigation.

Chrome process pitfalls

gBrowser.contentWindow, window.content...

Without the shim

All APIs in the chrome process that provide direct access to content objects will no longer work. For example:

// chrome code

gBrowser.contentWindow;                    // null

gBrowser.contentDocument;                  // null

gBrowser.selectedBrowser.contentWindow;    // null

window.content;                            // null

content;                                   // null

With the shim

The shim will give you a CPOW for the content object in these situations.

To make the shim unnecessary: factor the code that needs to access content into a separate script, load that script into the content process as a frame script, and communicate between the chrome script and the frame script using the message-passing APIs. See the article on using the message manager.

nsIContentPolicy

Without the shim

Under multiprocess Firefox you can't use nsIContentPolicy in the chrome process, because it needs to touch web content.

With the shim

The shim enables you to add content policies in the chrome process. It transparently registers an nsIContentPolicy in the content process, whose shouldLoad just forwards to the chrome process. The content to check is forwarded as a CPOW. The chrome process then checks the content against the policy supplied by the add-on, and forwards the response back to the child to be enforced.

To make the shim unnecessary: define and register nsIContentPolicy inside a frame script.

Observers in the chrome process

Depending on the topic, you need to register observers in either the chrome process or in a frame script.

For most topics you need to register observers in the chrome process.

However, you must listen to content-document-global-created and document-element-inserted in a frame script. Observers for these topics get content objects as the aSubject argument to observe(), so notifications are not sent to the chrome process.

There is a shim that will forward these two topics to the chrome process, sending CPOWs as the aSubject argument.

HTTP requests

You can't observe HTTP requests in the content process. If you do, you'll get an error.

If you do so in the chrome process, it will mostly work. The subject for the observer notification will be an nsIHttpChannel as you would expect. A common pattern here is to use the notificationCallbacks property of the nsIHttpChannel to get the DOM window that initiated the load, like this:

observe: function (subject, topic, data) {
  if (topic == "http-on-modify-request") {
    var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
    var domWindow = httpChannel.notificationCallbacks.getInterface(Ci.nsIDOMWindow);
  }
}

Or this:

observe: function (subject, topic, data) {
  if (topic == "http-on-modify-request") {
    var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
    var domWindow = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
  }
}

In multiprocess Firefox these patterns will no longer work: the getInterface call will fail.

In multiprocess Firefox, notificationCallbacks is a special object that tries to emulate the single-process notificationsCallbacks object as best it can. It will return a dummy nsILoadContext when asked, but any attempt to get a window out of it will fail.

There is an outstanding bug (bug 1108827) to implement a shim here, that will make notificationCallbacks a CPOW for the objects in the content process.

DOM Events

Without the shim

In multiprocess Firefox, if you want to register an event listener on some content DOM node, that needs to happen in the content process.

It used to be that if you registered a listener on the XUL <browser>  or <tab> element that hosted some DOM content, then events in the content would bubble up to the XUL and you could handle them there. This no longer happens in multiprocess Firefox.

With the shim

The shim intercepts chrome process code that adds listeners to XUL elements and sets up listeners in the content process, relaying the result back to the chrome process. The Event object itself is relayed to the chrome process as a CPOW.

To make the shim unnecessary: register event listeners on the global object inside a frame script. For example:

addEventListener("load", handler, true) // for example
If you need to contact the chrome process when that happens, send it a message.
 

Content process pitfalls

Many privileged APIs will just work in a content process. Anything that just manipulates data structures will just work. XHR and Workers will work.

File I/O

You should not write to or read from the disk from a frame script, in particular the profile directory. Even if this is possible, you should not do it and may expect that it could stop working at any time. File I/O should all be done in the chrome process. For example:

XUL and browser UI

Anything that tries to touch the browser UI or anything to do with XUL is likely to not work in the content process. For example:

Chrome windows

Anything that needs to use chrome windows will not work in the content process. For example:

Places API

The Places API can't be used inside a frame script. For example:

Observers in the content process

As noted in Observers in the chrome process, most observers should be registered in the chrome process and will not work in the content process. The exceptions are content-document-global-created and document-element-inserted , which must be registered in a frame script.

QI from content window to chrome window

There's a particular pattern often used to get from a content window to the associated chrome window. It looks something like this:
 
window.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIWebNavigation)
                         .QueryInterface(Ci.nsIDocShellTreeItem)
                         .rootTreeItem
                         .QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDOMWindow);
This will no longer work. In the content process the root tree item is an nsITabChild, that cannot be converted to an nsIDOMWindow, so the second getInterface call here will fail.
 

If you want a chrome window: send a message from the content process using the message manager. The target property of the object passed into the message handler in the chrome process is the XUL <browser> receiving the message, and you can get the chrome window from that (Note: I'm not really sure how...).

nsIAboutModule

By default, custom about: pages registered using nsIAboutModule are loaded in the chrome process. This means that you can't access their content from the content process (via XHR, for example).

There is a shim for this, that makes the content of about: pages registered by the add-on transparently available in the content process.

To avoid the shim: if you need to access the content of your about page from the content process, you need to register the nsIAboutModule in the content process as well as the chrome process. By default, about: pages (except for a small whitelist) are loaded in the chrome process when browsed to from the AwesomeBar.

Document Tags and Contributors

 Contributors to this page: wbamberg
 Last updated by: wbamberg,