RemotePageManager

One of the challenges of converting Firefox features and add-ons to multiprocess Firefox involves in-content pages. These pages (like about:addons) display in a tab but for now still render in the main process. There can be many reasons for wanting to move these into the content process, particularly if the page interacts with web content in any way but more generally just to keep the main UI as responsive as possible.

This raises the problem of how the page communicates back to the main process to get information and trigger actions. Commonly this is done with a frame script listening for DOM events from inside the page, performing the necessary message passing and then passing results back to the page. However, this middleman approach has problems. The frame code is maintained separately from the page code leading to three different pieces of code handling behavior for the page. Add-ons also have to be careful to choose unique names for messages to avoid conflicting with other add-ons and Firefox code.

RemotePageManager is an API designed to simplify this. It acts as the middleman between code in the main process and code in the page without needing to write custom code in a frame script. Code in the main process registers URLs that it is interested in. Whenever a frame loads a page with that URL, a pseudo message manager is created to allow message passing between just that page and the code in the main process. Code running in the page itself can access the message manager directly without needing to use a frame script at all.

RemotePageManager

RemotePageManager is the low-level way to interact with in-content pages. It includes the following methods:

addRemotePageListener(url, callback)
Registers interest in a URL. Whenever a new page loads at that URL callback is called and passed the message channel to use to communicate with the page. Only one callback can be registered per URL.
removeRemotePageListener(url)
Unregisters interest in a URL. The callback passed above will not be called again.

RemotePages

RemotePages is a higher-level option. As well as the methods listed here it also has the message channel methods which will send messages to, and receive messages from, every page currently loaded at the URL.

RemotePages(url)
Creates a new RemotePages instance for a URL.
destroy()
Destroys the instance.

Message channel methods

All of these methods are available directly in the page, on RemotePages instances, or on the channel passed to callbacks registered with RemotePageManager.addRemotePageListener.

sendAsyncMessage(name, data)
Sends a named message to the other side of the channel. data is copied as a structured clone.
addMessageListener(name, listener)
Adds a listener for a named message. listener will be called when the other side of the channel sends a message for name. listener will be passed an object with the properties target, name, and data.
removeMessageListener(name, listener)
Removed a listener for a named message.

Special messages

As well as any messages that Firefox or add-on code sends through the message channels, some special messages will be sent. Generally any message name starting with "RemotePage:" should be considered reserved for future use.

RemotePage:Init
Sent to a RemotePages instance when a new page is loaded with the matching URL. The target property of the object passed to any listener is a message channel for just that page.
RemotePage:Load
Sent when the load event for a matching page in the content process is fired.
RemotePage.Unload
Sent when a matching page in the content process is unloaded.

Low level example

This example waits for a page to load and then passes it some information.

// code running in the main process somewhere
Components.utils.import("resource://gre/modules/RemotePageManager.jsm");
RemotePageManager.addRemotePageListener("about:foo", (channel) => {
  // Wait for page load here to be sure the page has had chance to register for this message
  channel.addMessageListener("RemotePage:Load", function() {
    channel.sendAsyncMessage("MyMessage", { somedata: 42 });
  });
});

Here is the code that runs in the page. Note that it has direct access to addMessageListener (as well as the other message channel methods).

// code running in the content webpage
addMessageListener("MyMessage", function(msg) {
  alert(msg.data.somedata);
});

High level example

The code above uses a callback that is called every time a page is loaded. In reality it is going to be more common for code in the main process to simply respond to requests from the page and want to update all pages at once. RemotePages is more suited to this:

// code running in the main process somewhere
Components.utils.import("resource://gre/modules/Preferences.jsm");
Components.utils.import("resource://gre/modules/RemotePageManager.jsm");
let listener = new RemotePages("about:bar");
// Listens to messages from all current and future pages
listener.addMessageListener("GetLabel", ({ target }) => {
  // target is the channel for just the page that sent this message
  target.sendAsyncMessage("SetLabel", Preferences.get("extensions.label.text"));
});
Preferences.observe("extensions.label.text", (newLabel) => {
  // Sends messages to all currently open pages
  listener.sendAsyncMessage("SetLabel", newLabel);
});
listener.addMessageListener("ButtonClicked", () => {
  // Do something here
});
// code running in the content webpage
var button = document.getElementById("button");
var label = document.getElementById("label");
sendAsyncMessage("GetLabel");
addMessageListener("SetLabel", function(msg) {
  label.textContent = msg.data;
});
function buttonClicked() {
  sendAsyncMessage("ButtonClicked");
}
button.addEventListener("click", buttonClicked, false);

Document Tags and Contributors

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