Personal tools

Content Process Event Handlers

From MozillaWiki

Jump to: navigation, search

Bug 542242 added support for content process event listeners. The basic idea is that chrome asks content process to load script files which add listeners to the content process' root event handling scope. The event listeners can then send messages to chrome process.

nsIFrameMessageManager file has the new interfaces related to content process event listeners and message handling.
Note! Currently the API is for JS only.


ALL THE COMMENTS ABOUT THE API ARE VERY WELCOME! (Ping smaug on IRC)

Contents

Content Process

For each tab the child process has a JS context/scope, which implements nsIContentFrameMessageManager and nsIDOMEventTarget. That scope is above the current top level content window, so capturing event listeners will be called first using event listeners added in that scope and event listeners added to bubble phase will be called after listeners in the content page. The current top level window can be accessed using content property, and docShell points to the nsIDocShell object. The scope has also the methods from nsIDOMEventTarget and the new methods from nsIContentFrameMessageManager:

- addMessageListener adds a listeners for messages coming from chrome. First parameter is the name of the message, second one is the listener (a JS Function).

- removeMessageListener removes a message listener. First parameter is the name of the message, second one is the listener (a JS Function).

- sendAsyncMessage sends asynchronously a message to chrome. It takes 1 or 2 parameters; first one is the name of the message and second one is a JS object which will be JSON'ed.

- sendSyncMessage sends synchronously a message to chrome. Currently it takes 1 or more parameters; first one is the name of the message, second one is a JS object which will be JSON'ed and the rest are JS objects, which chrome process can access from message.objects list. If chrome process message listener returns some value, it will be JSON'ed and sendSyncMessage() returns that in an array. (In an array because chrome process may have several listeners for a message and each one may return a result.)
NOTE! In the near future sendSyncMessage() will be able to take more parameters. The parameters after 2nd one will be send as CPOW objects to the chrome process.

- dump prints a string to stdout.

Chrome process

On chrome side the chrome window and each nsIFrameLoader object have messageManager property, which implements nsIChromeFrameMessageManager. When the one from window is used, it forwards sendAsyncMessage and loadFrameScript calls to each remote frame in the document. When a message is received from content process, listeners in nsIFrameLoader's message manager will be called first, and then the ones in the window's message manager.

messageManager has the following methods:

- addMessageListener adds a listeners for messages coming from content. First parameter is the name of the message, second one is the listener (a JS Function).

- removeMessageListener removes a message listener. First parameter is the name of the message, second one is the listener (a JS Function).

- sendAsyncMessage sends asynchronously a message to content. It takes 1 or 2 parameters; first one is the name of the message and second one is a JS object which will be JSON'ed.

- loadFrameScript asks content process root scope to load a script. The first parameter is the absolute URL of the script and the second one is a boolean, which indicates whether messageManager should try to load the url once the content process root scope becomes available. If false, and the root scope isn't there yet, the method does nothing.
NOTE! Using data: urls does work. Sending the following URL should print out "Hello world!": data:,dump("Hello world!");
ISSUE! While using chrome URLs does work, there is currently an open bug 542907 to support all the same chrome URLs in the content process what chrome process sees.

Messages and message listeners

Message listeners get a JS object as the parameter. The object looks current like the following:

 {
   name:    %message name%,
   target:  %the target of the message%
   sync:    %true or false%,
   json:    %json object or null%
   objects: %an array of objects sent using sendSyncMessage% (NOTE, DO NOT USE THIS. .objects will change!)
 }

target is in chrome the xul:browser element which got the message and on content process side it is the global scope object (aka TabChildGlobal).

When the CPOW support is added to the synchronous messages, the object will have also property objects.

Message listeners can be either Javascript functions, which get message as the only parameter, or listeners can be objects which have receiveMessage function as a property.

Examples

Forward all the clicks on HTML A elements to chrome

// remote.js
// Note, this is just an example. Doesn't work if click happens in a
// descendant element of the A.

addEventListener("click",
  function(e) {
    if (e.target instanceof Components.interfaces.nsIDOMHTMLAnchorElement &&
        sendSyncMessage("linkclick", { href : e.target.href })[0].cancel) {
      e.preventDefault();
    }
  },
  false);
// some-script-in-chrome.js
messageManager.loadFrameScript("path://to/remote.js", true);
messageManager.addMessageListener("linkclick",
  function(m) {
    return { cancel: !confirm("Do you want to load " + m.json.href) };
  }
);

Forward a message from chrome to content

This will broadcast a message to *all* content tabs.

// contentscript.js
addMessageListener("MyMessenger.MyMessage", function(obj) {
  content.wrappedJSObject.console.log("OH HAI");
});
// some-script-in-chrome.js
messageManager.sendAsyncMessage('MyMessenger.MyMessage', {});