Process scripts

Process scripts are new in Firefox 38.

If you're using the addon sdk you can use the remote/parent module's remoteRequire instead.

When you need to run code in the content process in order to access web content, then you should use frame scripts. However, a problem with frame scripts is that they can be loaded multiple times in the content process, in multiple scopes that are insulated from each other. For example, if you call the global frame message manager's loadFrameScript() function, then the script will be loaded separately into all open tabs. This can then cause a problem the frame scripts are interacting with a global service in the content process.

For example, in multiprocess Firefox, if you need to use nsIContentPolicy to register a content policy, you must do this in the content process. But if you register it in a frame script, and the frame script is loaded more than once, you'll register the content policy more than once, which probably isn't what you intend.

Similarly, some observer notifications must be registered in the content process, but if you do this in a frame script, and the frame script is loaded more than once, then you will get multiple notifications for that event.

The solution in these cases is to use a process script instead of a frame script.

You can load a process script by accessing a parent process message manager and calling its loadProcessScript() function. The following code uses the global parent process message manager, which will load the script into the the chrome process and any child processes:

// chrome code
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
  .getService(Ci.nsIProcessScriptLoader);
ppmm.loadProcessScript("chrome://whatever/process-script.js", true);
ppmm.addMessageListener("Hello", function(msg) { ... });

The process script's global is a child process message manager, which enables the process script to receive messages from the chrome side, and to send messages to the chrome side:

// process-script.js
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
  dump("Welcome to the process script in a content process");
} else {
  dump("Welcome to the process script in the main process");
}

// Message is sent using ContentProcessMessageManager
sendAsyncMessage("Hello");

In this example, the dump() statement will run once in each content process as well as in the main process. This example also figures out whether it is running in the chrome or in a content process by looking at the process type in Services.appinfo.

In just about every respect, using process scripts is like using frame scripts:

  • you can pass the allowDelayedLoad to loadProcessScript(). If you do, you must call removeDelayedProcessScript() when your extension is disabled or removed
  • the message-passing APIs are the same: sendAsyncMessage() is available in both directions, while sendSyncMessage() is available from content to chrome only
  • process scripts are system-privileged, and have access to the Components object. However, process scripts are subject to the same kinds of limitations as frame scripts (for example, no file system access).
  • process scripts stay loaded until their host process is closed.
  • the environment for process scripts is similar to that for frame scripts. However, you don't get access to web content or DOM events from a process script.

Retrieving the content frame message manager for a content window

When an observer notification in a process script contains a content document or window it can be useful to not use talk through the child/parent process message managers but through the window's content frame message manager, e.g. if responses should be processed by a frame script instead of the process script.

This can be achieved by traversing the docshell tree up to the top window and then retrieving its content message manager, as follows:

function contentMMFromContentWindow(window) {
  let tree = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShellTreeItem);
  let top = tree.sameTypeRootTreeItem;
  let iface = QueryInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIInterfaceRequestor);
  return iface.getInterface(Ci.nsIContentFrameMessageManager);
}

This is intended for unprivileged pages running in a content process. Chrome-privileged pages or things running in the parent process may require special treatment.

If the above doesn't work try this:

function contentMMFromContentWindow_Method2(aContentWindow) {
    return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDocShell)
                         .QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIContentFrameMessageManager);
}