This document describes Cross Process Object Wrappers (CPOWs), which enable chrome code to synchronously access content in multiprocess Firefox.
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.
In multiprocess Firefox, chrome code runs in a different process from web content. So chrome code can't directly interact with web content: instead, it must factor out the code that interacts with web content into separate scripts that are called frame scripts.
Chrome code can load frame scripts into the content process using the message manager, and can then communicate with them using a message-passing API. To learn more about this, see the documentation on using the message manager.
Chrome to content messaging must be asynchronous. This is because the chrome process runs the Firefox UI, so if it were blocked by the content process, then a slow content process could cause Firefox to become unresponsive to users.
Converting synchronous code to be asynchronous can be difficult and time-consuming. As a migration aid, the messaging framework enables frame scripts to make content objects available to chrome through a wrapper called a Cross Process Object Wrapper, also known as a CPOW. However, although CPOWs are convenient, they have serious limitations and can cause responsiveness problems, so should only be used when necessary and only as a migration aid.
Passing CPOWs from frame scripts
Frame scripts can send messages to chrome using one of two global functions: sendAsyncMessage()
or sendSyncMessage()
. The optional third parameter to each of these functions is an object whose properties are objects to wrap. For example, this frame script sends a DOM node to chrome when the user clicks it, as the clicked
property of the third argument:
// frame script addEventListener("click", function (event) { sendAsyncMessage("my-e10s-extension-message", {}, { clicked : event.target }); }, false);
In the chrome script, the DOM node is now accessible through a Cross Process Object Wrapper, as a property of the objects
property of the message. The chrome script can get and set the wrapped object's properties and call its functions:
// chrome script windowMM.addMessageListener("my-e10s-extension-message", handleMessage); function handleMessage(message) { let wrapper = message.objects.clicked; console.log(wrapper.innerHTML); wrapper.innerHTML = "<h2>Modified by chrome!</h2>" wrapper.setAttribute("align", "center"); }
Auto-generated CPOWs
Add-ons that have not declared themselves multiprocess compatible are set up with a number of compatibility shims. One of these shims provides the following behavior: whenever chrome code tries to access content directly (for example, through window.content
or browser.contentDocument
), it is given back a CPOW that wraps the content object. This means that examples like this will actually work, even in multiprocess Firefox:
gBrowser.selectedBrowser.contentDocument.body.innerHTML = "replaced by chrome code";
It's still important to keep in mind, though, that this is access through a CPOW and not direct access to content.
Bidirectional CPOWs
A common pattern is for chrome code to access content objects and add event listeners to them. To deal with this, CPOWs are bidirectional. This means that if content passes a CPOW to the chrome process, the chrome process can synchronously pass objects (such as event listener functions) into functions defined in the CPOW.
This means you can write code like this:
// frame script /* On mouseover, send the button to the chrome script as a CPOW. */ var button = content.document.getElementById("click-me"); button.addEventListener("mouseover", function (event) { sendAsyncMessage("my-addon-message", {}, { element : event.target }); }, false);
// chrome script /* Load the frame script, then listen for the message. When we get the message, extract the CPOW and add a function as a listener to the button's "click" event. */ browserMM.loadFrameScript("chrome://my-addon/content/frame-script.js", false); browserMM.addMessageListener("my-addon-message", function(message) { let wrapper = message.objects.element; wrapper.addEventListener("click", function() { console.log("they clicked"); }); });
Mapping content documents to XUL browsers
A common pattern is to get the XUL <browser>
that corresponds to a content document. To enable this, gBrowser.getBrowserForDocument
and gBrowser.getBrowserForContentWindow
can be passed a CPOW for the content document and content window, respectively, and return the XUL <browser>
that these documents / windows belong to. Both return null
if no such browser can be found.
Limitations of CPOWs
Although CPOWs can be convenient they have several major limitations, which are listed below.
CPOWs and platform APIs
You can't pass CPOWs into platform APIs that expect to receive DOM objects. For example, you can't pass a CPOW into nsIFocusManager.setFocus()
.
Also, from Firefox 47 onwards, browser code can no longer perform unsafe CPOW operations. This means that as an add-on developer, you should not pass CPOWs into browser APIs.
Chrome responsiveness
The lack of a synchronous API on the chrome side is intentional: because the chrome process runs the Firefox UI, any responsiveness problems affect the whole browser. By making the chrome process block on the content process, CPOWs break this principle and allow unresponsive content process to make the whole browser unresponsive.
See safe and unsafe CPOWs for more on this.
Performance
Although the wrapper looks just like an object completely contained in the chrome script scope, it's really just a reference to an object in the content process. When you access a property of the wrapper, it sends a synchronous message to the content process and returns the result. This means it is many times slower to use than an object.
Message ordering
CPOWs can violate assumptions you might make about message ordering. Consider this code:
mm.addMessageListener("GotLoadEvent", function (msg) { mm.sendAsyncMessage("ChangeDocumentURI", {newURI: "hello.com"}); let uri = msg.objects.document.documentURI; dump("Received load event: " + uri + "\n"); });
This sends a message asking the frame script to change the current document URI, then accesses the current document URI via a CPOW. You might expect the value of uri
to come back as "hello.com". But it might not: in order to avoid deadlocks, CPOW messages can bypass normal messages and be processed first. It’s possible that the request for the documentURI property will be processed before the "ChangeDocumentURI" message, in which case uri
will have its previous value.
For this reason, it’s best not to mix CPOWs with normal message manager messages. It’s also a bad idea to use CPOWs for anything security-related, since you may get results that are not consistent with surrounding code that might use the message manager.
Safe and unsafe CPOWs
The main reason CPOWs can be bad for responsiveness is that they make the chrome process block on the content process.
Essentially, the problem is this: suppose the content process is busy with some long-running operation (for example, garbage collection). In multiprocess Firefox, the browser UI is still responsive, because it's running in a different process. But if the chrome process then tries to use a CPOW, the chrome process will block until the content process is ready to respond. Suddenly, what looked like a simple property access could block the UI for a noticeable amount of time:
There is one scenario, though, in which this is not a problem: when the content process is already blocking on a synchronous message it sent to the chrome process.
The content process is blocked anyway waiting for the chrome process, so there's no chance, in this situation, that the CPOW call will block on some long-running activity in the content process. The content process can respond immediately to the CPOW call:
There is still a performance cost to the CPOW in this situation, because the CPOW call is an IPC call instead of a simple property access in the same process. However, the potential impact on responsiveness is much smaller.
Also, in this situation, the content process is in a known state. We know it's blocked waiting for the synchronous message to return, and we know that won't change during the CPOW call. This makes the CPOW call much safer than it would otherwise be.
This situation is thus called "safe CPOW usage". All other uses of CPOWs are called "unsafe CPOW usage".
See also:
- https://mikeconley.ca/blog/2015/02/17/on-unsafe-cpow-usage-in-firefox-desktop-and-why-is-my-nightly-so-sluggish-with-e10s-enabled/
- https://blog.lassey.us/2015/01/10/unsafe-cpow-usage/