Message manager overview

In multiprocess Firefox there are (at least) two processes:

  • the chrome process, also called the parent process, runs the browser UI (chrome) code and code inserted by extensions
  • one or more content processes, also called child processes. These processes run web content.

Message managers are designed to enable chrome-privileged JavaScript code in one process to communicate with chrome-privileged JavaScript code in a different process.

This article describes the different types of message manager, how to access them, and at a high level, what sorts of things you can use them for.

At the top level, there are two different sorts of message managers:

  • Frame message managers: these enable chrome process code to load a script into a browser frame (essentially, a single browser tab) in the content process. These scripts are called frame scripts, and as the name suggests, they are scoped to a specific browser frame. If chrome code wants to run code in the content process so it can access web content, this is usually the sort of message manager to use.
  • Process message managers: these correspond to process boundaries, and enable code running in the parent (chrome) process to communicate with code running in the child (content) process. From Firefox 38 onwards, they also enable code running in the parent process to load process scripts into the child process. These are like frame scripts, except they are global to the child process. Process scripts are most likely to be useful when an extension wants to run some code only once in the content process, to access some global service: for example, to register an observer or a content policy.

Frame message managers

In multiprocess Firefox, when chrome code needs to interact with web content, it needs to:

  • factor the code that needs direct access to content into separate scripts, which are called "frame scripts"
  • use a frame message manager to load these frame scripts into the content process
  • use the frame message manager API to communicate with the frame script

Some older articles on multiprocess Firefox and the message manager might refer to "content scripts" instead of "frame scripts". This usage is deprecated because the Add-on SDK uses "content script" to refer to a similar but different kind of script.

So fundamentally, frame message managers enable chrome code to:

  • load a script into a frame (essentially, a single browser tab) in the content process. These scripts are called "frame scripts".
  • communicate with frame scripts using message-passing APIs

There are various types of frame message managers, as depicted in this diagram:

This diagram shows the setup when there are 2 browser windows open, one with 2 tabs open and one with 1 tab open.

Chrome process

In the chrome process, there's a hierarchy of frame message managers: the global frame message manager, window message managers, and browser message managers.

Global frame message manager

Description

There's a single global frame message manager in the chrome process.

This operates on all frames, in all content tabs. If you load a frame script using the global frame message manager, the script gets loaded separately into every open tab: three times, in the diagram above. Similarly, if you send a message using the global frame message manager, it's received by all content tabs, and is then delivered to any frame scripts that are listening for it.

Its most important functions and attributes are:

childCount : contains the number of children (typically, browser windows)

getChildAt() : get the child at the given index

loadFrameScript() : load a frame script into every tab in the browser

broadcastAsyncMessage() : send a message to frame scripts

addMessageListener() : start listening to a specific message from all frame scripts

removeMessageListener() : stop listening to a specific message

Interfaces

nsIFrameScriptLoader

nsIMessageListenerManager

nsIMessageBroadcaster

How to access

Access it using Components.classes:

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

You can also access it as the mm property of Services.jsm, if you are in the parent process.

Window message manager

Description

There's a window message manager for every browser window: two, in the diagram above.

It operates on all content tabs in a given window. If you load a frame script using the window message manager it gets loaded separately into each tab open in that particular window. If you send a message using the window message manager, it gets sent to all content tabs in that window.

Its most important functions and attributes are:

childCount : contains the number of children (typically, browser tabs)

getChildAt() : get the child at the given index

loadFrameScript() : load a frame script into every tab in this window

broadcastAsyncMessage() : send a message to all frame scripts in this window

addMessageListener() : start listening to a specific message from frame scripts

removeMessageListener() : stop listening to a specific message

Interfaces

nsIFrameScriptLoader

nsIMessageListenerManager

nsIMessageBroadcaster

How to access

You can access it as a property of the browser window:

// chrome script
let windowMM = window.messageManager;

Browser message manager

Note that in this context, "browser" refers to the XUL <browser> object, which is a frame that hosts a single Web document. It does not refer to the more general sense of a Web browser.

Description

Finally, there's a browser message manager for every open content tab: three, in the diagram above.

This corresponds one-to-one with a content tab. Scripts you load using a browser message manager are loaded only into that content tab, and messages you send are delivered only to that content tab.

You can mix and match: so for example, you could load a script into every tab using the global message manager, but then send a message to the script instance loaded into a specific tab by using the browser message manager.

Its most important functions are:

loadFrameScript() : load a frame script into this browser frame (tab)

sendAsyncMessage() : send a message to all frame scripts in this browser frame

addMessageListener() : start listening to a specific message from frame scripts

removeMessageListener() : stop listening to a specific message

Interfaces

nsIProcessChecker

nsIFrameScriptLoader

nsIMessageListenerManager

nsIMessageSender

How to access

The browser message manager can be accessed as a property of the XUL <browser> element:

// chrome script
let browserMM = gBrowser.selectedBrowser.messageManager;

Content process

Content frame message manager

Description

There's a content frame message manager for every open tab. It's the content-side end of frame message manager conversations.

Frame scripts are loaded into the content frame message manager scope, and messages from chrome message managers end up here.

The content frame message manager provides the global object for frame scripts (but note that there is trickery to ensure that top-level variables defined by frame scripts are not shared).

Frame scripts can use this object to send messages to the chrome process, and to receive messages from the chrome process.

Its most important attributes and functions are:

content : access the DOM window hosted by the tab

docShell : access the top-level docshell

Components : access privileged objects and APIs

addEventListener() : listen to DOM events

addMessageListener() : receive messages from the chrome process

sendAsyncMessage() : send asynchronous messages to the chrome process

sendSyncMessage() : send synchronous messages to the chrome process

Interfaces

nsIDOMEventTarget

nsIMessageListenerManager

nsIMessageSender

nsISyncMessageSender

nsIContentFrameMessageManager

How to access The content frame message manager is the global object in frame scripts.

Process message managers

Process message managers correspond to process boundaries, and enable code running in different processes to communicate. Multiprocess Firefox has the concept of:

  • a "parent process"
  • "child processes" which are processes spawned by the parent process.

For practical purposes, in multiprocess Firefox the parent process is the chrome process, and child processes are content processes.

In each child process, there's a single child process message manager (CPMM). There's also an additional child-in-process message manager (CIPMM) in the parent process.

For each child process message manager, there's a parent process message manager (PPMM) in the parent process.

There's also a single global parent process message manager (GPPMM) in the parent process, that provides access to all the parent process message managers. The diagram below shows the setup that would result from having two child processes:

With the GPPMM, you can broadcast messages to the CIPMM and all CPMMs. With a PPMM, you can send a message to its corresponding CPMM. With a CPMM, you can send messages to the parent process: these messages are received first by the corresponding PPMM, then by the GPPMM.

From Firefox 38 onwards, you can also use a parent process message manager to load a script into a child process. This is the recommended way to load a script that executes just once per child process, which is something you might want to do if you are interacting with some global service (for example, adding listeners to observer notifications or registering a content policy).

Parent process

Global parent process message manager

Description

The global parent process message manager (GPPMM) is global to the parent process.

  • Messages sent using the GPPMM get sent to all CPMMs in all child processes.
  • Process scripts loaded using the GPPMM get loaded in all child processes.

Its most important functions and attributes are:

childCount : contains the number of children (child processes, plus the in-content child)

getChildAt() : get the child at the given index

loadProcessScript() : load a process script into every content process

broadcastAsyncMessage() : send a message to all process scripts

addMessageListener() : start listening to a specific message from process scripts

removeMessageListener() : stop listening to a specific message

Interfaces

nsIProcessScriptLoader

nsIMessageListenerManager

nsIMessageBroadcaster

How to access

You can access the GPPMM with code like this:

// parent process
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
           .getService(Ci.nsIMessageBroadcaster);

You can also access it as the ppmm property of Services.jsm, if you are in the parent process.

Parent process message manager

Description

There's one parent process message manager (PPMM) in the parent process for every child process, and its API is oriented to that one child process.

  • Messages sent using the PPMM are received only by the corresponding CPMM
  • Scripts loaded using the PPMM are loaded only into the corresponding child process.

Its most important functions are:

loadProcessScript() : load a process script into the content process

broadcastAsyncMessage() : send a message to process scripts

addMessageListener() : start listening to a specific message from process scripts

removeMessageListener() : stop listening to a specific message

Interfaces

nsIProcessChecker

nsIProcessScriptLoader

nsIMessageListenerManager

nsIMessageSender

How to access

You can access a PPMM using the getChildAt() function in the GPPMM:

// parent process
let ppmm = Services.ppmm.getChildAt(1);

Child process

Child process message manager

Description

There's one child process message manager (CPMM) in each child process. Messages sent using the CPMM are sent to the corresponding PPMM and are also relayed to the GPPMM.

Its most important attributes and functions are:

Components : access privileged objects and APIs

addMessageListener() : receive messages from the parent process

sendAsyncMessage() : send asynchronous messages to the parent process

sendSyncMessage() : send synchronous messages to the parent process

Interfaces

nsIMessageListenerManager

nsIMessageSender

nsISyncMessageSender

nsIContentProcessMessageManager

How to access

Code running in a child process can access the CPMM with code like this:

// child process script
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
           .getService(Ci.nsISyncMessageSender);

You can also access it as the cpmm property of Services.jsm, if you are in the child process.