The message manager

This article is in need of a technical review.

The message manager is a message-passing framework for chrome script to touch content.

In Firefox 4, chrome script can access content and content docshells directly through properties on a XUL browser element. But in Mobile Firefox 4 and in a future version of Firefox, content will run in separate processes from chrome, and direct access will not be possible. Instead, chrome script must use separate scripts (called "content scripts", or "frame scripts") for code which must synchronously touch content, and run these scripts asynchronously using the message manager.

The message manager is available in Firefox 4 so that Firefox code and extensions can start using the new API immediately, even before Firefox switches to use content processes.

Note: The message manager is a JavaScript-only API and will not be made available to binary components.

Messages

Messages passed through the message manager have a name and optional data. Sending a message is simple:

manager.sendAsyncMessage("message-name", {"foo": 2});

The data is serialized in the message. This means that it is not possible to send object references via messages. In other words, you can send over JSON data, which is serialized into a string (just like JSON.stringify()) and parsed on the other side (just like JSON.parse()).

When a message is sent from chrome to content, it must be dispatched asynchronously, because chrome is not allowed to block on content. However, content scripts may synchronously send a message to chrome and wait for a response:

var response = sendSyncMessage("message-name", {"foo": 1});

The response is an array of returned values from each listener.

Message listeners

Script can add message listeners to a message manager:

function listenerFunction(msg)
{
  ...
}

manager.addMessageListener("message name", listenerFunction);
manager.removeMessageListener("message name", listenerFunction);

Message properties

The listener function is called back with an object as its first parameter which contains the following properties:

  • name: [string] the name of the message
  • sync: [boolean] indicates if this message was sent as a synchronous or asynchronous message by the sender (only content can send synchronous messages to the parent)
  • data: [object] the JSON object with the data included by the sender
  • target: [XULElement] the browser element associated with the content that this message came from
  • objects: [object] the Cross Process Object Wrappers passed by the sender

Note: Cross Process Object Wrappers should not be used by normal code, especially not addons! The feature is just meant to solve compatibility problems on multiprocess desktop Firefox, that are otherwise impossible to address.

/* in the content script */
sendAsyncMessage("example", {title: document.title});

--------------------------

/* in the parent */
function listenerFunction(msg)
{
  var title = msg.data.title; // the title of the page
  var browser = msg.target;   // the browser associated with the page
  var name = msg.name;        // "example"
  var sync = msg.sync;        // false - sendAsyncMessage was used
}

Available message managers

Message managers are structured in trees. The most commonly used tree is structured as follows:

  • Global message manager
    • Per-window message managers
      • Per-tab message managers (actually, per-<browser> element)

There is one message manager per XUL browser element. To get the message manager for the browser, simply use browser.messageManager.

For chrome windows, there is also a special window.messageManager which allows a chrome window to receive messages from any browser loaded in the window. Messages are sent to the browser.messageManager first, and then to the window.messageManager. Note that you cannot send messages to the window messageManager; you can only register to receive messages.

When a message is received from a content process, listeners in the browser.messageManager are called first, followed by the ones in the window's message manager. If multiple message managers are listening for a synchronous message, all return values are sent back to the content script as an array (for example, ["rv1 from browser.messageManager", "rv2 from browser.messageManager", "rv1 from window.messageManager", "rv2 from window.messageManager"]).

Finally, there is a global message manager for all Firefox windows. Message listeners are called for the global message manager after they have been called for the per-browser and per-window message managers. The global message manager is obtained via

let gmm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);

Content scripts

In order for messages to be useful, a content script must be installed which either sends or receives messages and interacts with the DOM loaded into the browser. Before loading any pages into a browser, code running in the chrome process uses browser.messageManager.loadFrameScript() to inject a script into the content process. That script then can add listeners into the content process and send result messages back to the chrome process.

Every browser element has its own JavaScript global for running content scripts. The same global is shared by all content scripts that are loaded into that browser element. This global has the following properties:

  • content - The DOM window of the content loaded in the browser.
  • docShell - The nsIDocShell associated with the browser.
  • addMessageListener()
  • removeMessageListener()
  • sendAsyncMessage()
  • sendSyncMessage()
  • dump()
  • atob()
  • btoa()

Content scripts access to everything that goes with content (docshells). They don't have access to the chrome UI directly.

It's also possible to call loadFrameScript() on the message manager of a chrome window, or on the global message manager. In these cases, the script is loaded into every browser element controlled by the message manager. For windows, this will be all the browser elements in the window.

When loading a script into the frame, you can choose to have it loaded when the remote frame becomes available by setting the "allow delayed load" flag when calling the nsIChromeFrameMessageManager.loadFrameScript() method. This will cause your script to be loaded into every frame that's created from that time on. Note that this does not include iframes, as they do not have message managers.

Note: This can be a problem! Prior to Gecko 9.0, there was no way to remove these delayed-load scripts. Starting in Gecko 9.0 (Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6), you should call nsIChromeFrameMessageManager.removeDelayedFrameScript() when your add-on doesn't need the script anymore, such as in the shutdown() handler on bootstrapped add-ons.

A simple example

This simple example forwards all clicks on HTML <a> elements to chrome. This is a simplified example, and won't work for child elements, but it should help understand how the messaging system works.

The content script

This code runs in the content process to set up an event listener that forwards the click events to the chrome process.

addEventListener("click",
  function(e) {
    if (e.target instanceof Components.interfaces.nsIDOMHTMLAnchorElement &&
        sendSyncMessage("linkclick", { href : e.target.href })[0].cancel) {
      e.preventDefault();
    }
  },
  false);

The chrome script

This code runs in chrome to receive the click events and load the content script.

browser.messageManager.addMessageListener("linkclick",
  function(m) {
    return { cancel: !confirm("Do you want to load " + m.json.href) };
  }
);
browser.messageManager.loadFrameScript("chrome://myextension/content/checklinks.js", true);

See also

Document Tags and Contributors

Last updated by: kscarfone,