This page describes patterns 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".
This is one of a pair of articles: the other one lists limitations of frame scripts.
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
You can see all the places where your add-on uses compatibility shims by setting the dom.ipc.shims.enabledWarnings
preference and watching the browser console as you use the add-on.
The rest of this page documents patterns that don't work in multiprocess Firefox as well as how are shims try to paper over the problem. 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
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
As a special note, docshells live in the content process, so they are also inaccessible:
gBrowser.docShell; // null gBrowser.selectedBrowser.docShell; // null
With the shim
The shim will give you a CPOW for the content object in these situations.
In some situations, the content process may not be initialized when an add-on asks for access to its content. In this case, the shim will return a JavaScript object that looks somewhat like a window or a document for about:blank. However, this "dummy" object is completely static and only exposes a few of the normal properties that windows and documents have. For this reason, add-ons that try to access content objects in fresh <browser> elements may run into trouble.
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.
Limitations of CPOWs
Cross process object wrappers (CPOWs) are a migration aid giving chrome code synchronous access to content objects. However, there are various limitations on the kinds of things you can do with a CPOW.
Note that from Firefox 47 onwards, unsafe CPOW usage is no longer permitted in browser code. If browser code tries an unsafe CPOW operation, the browser will throw an exception and you'll see an "unsafe CPOW usage forbidden” error in the Browser Console.
Add-on code is still allowed to use CPOWs "unsafely". However, if an add-on passes a CPOW into a platform API, and that platform API then attempts an unsafe operation on it, this will throw an exception. So as a general rule, add-ons should avoid passing CPOWs into non-addon code.
nsIContentPolicy
Without the shim
In multiprocess Firefox, if you register the nsIContentPolicy
in the chrome process then it will never see any attempts to load web content, because they happen in the content process.
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
in the content process. If you need to ensure that the policy is only registered once, use a process script to register the policy.
nsIWebProgressListener
This API will work in the chrome process. There is a shim that gives you access to the DOMWindow
property of the nsIWebProgress
object passed into onStateChange
. However, the DOMWindow
is passed asynchronously, so by the time the chrome process receives it, the DOM might have changed (for example, because code running in the content process modified it or we navigated to a different page). We're working on fixing this issue in bug 1118880.
Also note that unlike other shims, this shim is always active.
Alternatively, you can use nsIWebProgressListener
in the content process.
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.
The correct way to access the DOM window is through a message manager. In an HTTP observer, you can get the browser message manager for the window using code like this:
observe: function (subject, topic, data) { if (topic == "http-on-modify-request") { var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel); var loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext); // topFrameElement is the <browser> element var topFrameElement = loadContext.topFrameElement; var browserMM = topFrameElement.messageManager; console.log("browserMM: " + browserMM); } }
However, before Firefox 38, this technique will not work if multiprocess Firefox is disabled: specifically, topFrameElement
will be null. This means that if you need to write code that works before Firefox 38 and on both multiprocess and non-multiprocess variants, you need to implement both paths:
- test whether
topFrameElement
is null - if it is, you're running in single-process Firefox, and should use the old way
- if it isn't, you're running in multiprocess Firefox and should use the new way
From Firefox 38 onwards, the topFrameElement approach always works.
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
Sandboxes
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).
You can change this default in the code you use to register the about: URI. See about: and chrome: URIs.
JavaScript code modules (JSMs)
nsIProtocolHandler
nsIProtocolHandler
must register the custom protocol in the child content process.remoteRequire()
in the "remote/parent" module.