Chrome code and frame scripts communicate back and forth using a messaging API which can include JSON-serializable objects as arguments.
The API is mostly symmetrical, with one major exception: frame scripts can send asynchronous or synchronous messages to chrome, but chrome can only send asynchronous messages to content. This is an intentional design decision made to prevent content code from making chrome code unresponsive.
Where absolutely necessary, frame scripts can pass wrappers called Cross Process Object Wrappers (also known as CPOWs) to chrome, and chrome can use these wrappers to get synchronous access to content objects.
Content to chrome
The frame script can choose to send synchronous or asynchronous messages to chrome code.
Asynchronous messaging
To send an asynchronous message the frame script uses the global sendAsyncMessage()
function:
// frame script sendAsyncMessage("[email protected]:my-e10s-extension-message");
sendAsyncMessage()
takes one mandatory parameter, which is the name of the message. All messages share the same namespace, so to avoid conflicts with other code, you'll need to ensure that the names you use are unique. If you're using the message manager in an add-on, a good way to do that is to prefix messages with your add-on's ID.
After the name, you can pass detailed data as a string or a JSON-serializable object, and after that you can pass any objects it wants to pass to content as CPOWs.
The example below sends a message named "my-e10s-extension-message", with a data
payload containing details
and tag
properties, and exposes the event.target
object as a CPOW:
// frame script addEventListener("click", function (event) { sendAsyncMessage("[email protected]:my-e10s-extension-message", { details : "they clicked", tag : event.target.tagName }, { target : event.target }); }, false);
To receive messages from content, a chrome script needs to add a message listener using the message manager's addMessageListener()
API.
The message passed to the listener is an object containing the following properties:
name |
String containing the message name. |
sync |
Boolean declaring whether the message was send synchronously or aynchronously. |
data |
The JSON object passed as the second parameter to sendAsyncMessage() . |
target |
The XUL <browser> element from which this message was sent. |
objects |
An object whose properties are any CPOWs exposed by the sender as the third argument to sendAsyncMessage() |
In the example below the listener just logs all the messages details:
// chrome script messageManager.addMessageListener("[email protected]:my-e10s-extension-message", listener); function listener(message) { console.log(message.name); console.log(message.sync); console.log(message.data); console.log(message.target); console.log(message.objects); }
So combining this message listener with the message above will give console output somewhat like this, when the user clicks a <div>
:
"[email protected]:my-e10s-extension-message" false Object { details: "they clicked", tag: "div" } <xul:browser anonid="initialBrowser" ... > { target: <div#searchContainer> }
If your code requires access to a window
(for example to run window.openDialog
), and your message listener is run from somewhere without access to a window
(e.g. an XPCOM component), you can access the window of the browser
that sent the message with message.target.ownerDocument.defaultView
.
Synchronous messaging
To send a synchronous message, the frame script uses the global sendSyncMessage()
function:
// frame script sendSyncMessage("[email protected]:my-e10s-extension-message");
When a chrome script receives a synchronous message, it should return a value from its message listener:
// chrome script messageManager.addMessageListener("[email protected]:my-e10s-extension-message", listener); function listener(message) { return "value from chrome"; }
This value is then presented to the frame script in the return value of sendSyncMessage()
. Because a single message can be received by more than one listener, the return value of sendSyncMessage()
is an array of all the values returned from every listener, even if it only contains a single value:
// frame script addEventListener("click", function (event) { var results = sendSyncMessage("[email protected]:my-e10s-extension-message", { details : "they clicked", tag : event.target.tagName }); content.console.log(results[0]); // "value from chrome" }, false);
Like arguments, return values from sendSyncMessage()
must be JSON-serializable, so chrome can't return functions.
removeMessageListener()
To stop listening for messages from content, use the message manager's removeMessageListener()
method:
// chrome script messageManager.removeMessageListener("[email protected]:my-e10s-extension-message", listener);
Chrome to content
To send a message from chrome to content, you need to know what kind of message manager you're using. If it's a browser message manager, you can use the message manager's sendAsyncMessage
method:
// chrome script browser.messageManager.sendAsyncMessage("[email protected]:message-from-chrome");
If you have a window or a global message manager, you need to use the broadcastAsyncMessage
method:
// chrome script window.messageManager.broadcastAsyncMessage("[email protected]:message-from-chrome");
These methods takes one mandatory parameter, which is the message name. All messages share the same namespace, so to avoid conflicts with other code, you'll need to ensure that the names you use are unique. If you're using the message manager in an add-on, a good way to do that is to prefix messages with your add-on's ID.
After the message name, you can pass detailed data as a string or a JSON-serializable object:
// chrome script messageManager.sendAsyncMessage("[email protected]:message-from-chrome", { details : "some more details" });
To receive a message from chrome, a frame script uses the global addMessageListener()
function. This takes two parameters: the name of the message and a listener function. The listener will be passed a message
object whose data
property is the message payload:
// frame script function handleMessageFromChrome(message) { var payload = message.data.details; // "some more details" } addMessageListener("[email protected]:message-from-chrome", handleMessageFromChrome);
message-manager-disconnect
If you're using a message manager to communicate with a script that may be running in a different process, you can listen for the message-manager-disconnect observer notification to know when the message manager has disconnected from the other end of the conversation, so you can stop sending it messages or expecting to receive messages.
For example, suppose we load a script into the current <browser>
on some event, and keep the browser message manager in an array, so we can send it messages:
var messageManagers = []; ... // on some event var browserMM = gBrowser.selectedBrowser.messageManager; browserMM.loadFrameScript("chrome://[email protected]/content/frame-script.js", false); messageManagers.push(browserMM); console.log(messageManagers.length);
We can listen for message-manager-disconnect
to update the array when the message managers disconnect (for example because the user closed the tab):
function myObserver() { } myObserver.prototype = { observe: function(subject, topic, data) { var index = messageManagers.indexOf(subject); if (index != -1) { console.log("one of our message managers disconnected"); mms.splice(index, 1); } }, register: function() { var observerService = Cc["@mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService); observerService.addObserver(this, "message-manager-disconnect", false); console.log("listening"); }, unregister: function() { var observerService = Cc["@mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService); observerService.removeObserver(this, "message-manager-disconnect"); } } var observer = new myObserver(); observer.register();