chrome 脚本的限制

This page describes patterns that used to work in the chrome process that will no longer work in multiprocess Firefox. These are the sorts of things that will break an old add-on in multiprocess Firefox. The fix is generally some variant of "do that in a frame script loaded into the content process".

This is one of a pair of articles: the other one lists limitations of frame scripts.


For many of the patterns described here we've implemented compatibility shims so the patterns still work. For example: whenever extensions try to access web content from the chrome process, the browser will return a Cross Process Object Wrapper that gives the chrome code synchronous access to the content.

You'll get the shims for your add-on by default, unless you set the multiprocessCompatible flag in your add-on's install manifest.

However, these shims are not a substitute for migrating extensions:

  • they are only a temporary measure, and will be removed eventually
  • they can have a bad effect on responsiveness
  • there are likely to be edge cases in which they don't work properly

For each pattern we've noted:

  • whether a shim exists and what kind of behavior it provides
  • how to update your add-on so you don't need the shim

gBrowser.contentWindow, window.content...


所有在 chrome 进程中的 API 提供的直接访问内容对象将不再工作。例如:

// chrome code

gBrowser.contentWindow;                    // null

gBrowser.contentDocument;                  // null

gBrowser.selectedBrowser.contentWindow;    // null

window.content;                            // null

content;                                   // null

特别说明,docshells 存在于内容进程,因此它们也无法访问:

gBrowser.docShell;                         // null

gBrowser.selectedBrowser.docShell;         // null


在这些情况下,垫片为你通过一个 CPOW 提供内容对象。

In some situations, the content process may not be initialized when an add-on asks for access to its content. In this case, the shim will return a JavaScript object that looks somewhat like a window or a document for about:blank. However, this "dummy" object is completely static and only exposes a few of the normal properties that windows and documents have. For this reason, add-ons that try to access content objects in fresh <browser> elements may run into trouble.

To make the shim unnecessary: factor the code that needs to access content into a separate script, load that script into the content process as a frame script, and communicate between the chrome script and the frame script using the message-passing APIs. See the article on using the message manager.

CPOW 的限制

跨进程对象包装器 (CPOWs) 是一个迁移辅助,给 chrome 代码带来同步访问内容对象的能力。但是,在使用它时也有各种限制



在多进程的 Firefox 上,你无法在 chrome  进程中使用 nsIContentPolicy,因为它需要接触网络内容。


The shim enables you to add content policies in the chrome process. It transparently registers an nsIContentPolicy in the content process, whose shouldLoad just forwards to the chrome process. The content to check is forwarded as a CPOW. The chrome process then checks the content against the policy supplied by the add-on, and forwards the response back to the child to be enforced.

为了使垫片不再必要,在内容进程中定义和注册 nsIContentPolicy。如果你需要确保该政策只注册一次,使用一个 process 脚本 来注册该政策。


这个 API 在 chrome 进程中工作。有一个垫片让你可以访问传递到 onStateChange nsIWebProgress  对象的 DOMWindow 属性。但是,该 DOMWindow 是异步传递,因此在 chrome 进程收到时,DOM 可能已经改变了(例如,因为代码运行的内容进程已经修改它,或者我们已经导航到另一个页面)。


我们正在努力修复此问题,见 bug 1118880

另外,你可以在内容进程中使用 nsIWebProgressListener

chrome 进程中的 Observers

根据不同的主题,你需要在 chrome 进程或者一个框架脚本中注册 observers。

对于大多数主题,你需要在 chrome 进程中注册 observers。

但是,你必须在一个框架脚本中监听 content-document-global-created and document-element-inserted。这些主题的 Observers 获取内容对象并作为 aSubject 参数到 observe(),因此通知不会发送到 chrome 进程。

有一个垫片会将两个主题转发到 chrome 进程,将 CPOWs 发送为 aSubject 参数。


你不能观测(observe)内容进程中的 HTTP 请求。如果这样做,你将得到一个错误。

如果你在 chrome 进程中这样做,它一般会工作。observer 通知的主题将是一个 nsIHttpChannel,正如你所期望的。

A common pattern here is to use the notificationCallbacks property of the nsIHttpChannel to get the DOM window that initiated the load, like this:

observe: function (subject, topic, data) {
  if (topic == "http-on-modify-request") {
    var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
    var domWindow = httpChannel.notificationCallbacks.getInterface(Ci.nsIDOMWindow);


observe: function (subject, topic, data) {
  if (topic == "http-on-modify-request") {
    var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
    var domWindow = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;

In multiprocess Firefox these patterns will no longer work: the getInterface call will fail.

In multiprocess Firefox, notificationCallbacks is a special object that tries to emulate the single-process notificationsCallbacks object as best it can. It will return a dummy nsILoadContext when asked, but any attempt to get a window out of it will fail.

There is an outstanding bug (bug 1108827) to implement a shim here that will make notificationCallbacks a CPOW for the objects in the content process.

The correct way to access the DOM window is through a message manager. In an HTTP observer, you can get the browser message manager for the window using code like this:

observe: function (subject, topic, data) {
  if (topic == "http-on-modify-request") {
    var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
    var loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext);
    // topFrameElement is the <browser> element
    var topFrameElement = loadContext.topFrameElement;
    var browserMM = topFrameElement.messageManager;
    console.log("browserMM: " + browserMM);

However, before Firefox 38, this technique will not work if multiprocess Firefox is disabled: specifically, topFrameElement will be null. This means that if you need to write code that works before Firefox 38 and on both multiprocess and non-multiprocess variants, you need to implement both paths:

  • test whether topFrameElement is null
  • if it is, you're running in single-process Firefox, and should use the old way
  • if it isn't, you're running in multiprocess Firefox and should use the new way

From Firefox 38 onwards, the topFrameElement approach always works.

DOM 事件


In multiprocess Firefox, if you want to register an event listener on some content DOM node, that needs to happen in the content process.

It used to be that if you registered a listener on the XUL <browser>  or <tab> element that hosted some DOM content, then events in the content would bubble up to the XUL and you could handle them there. This no longer happens in multiprocess Firefox.


The shim intercepts chrome process code that adds listeners to XUL elements and sets up listeners in the content process, relaying the result back to the chrome process. The Event object itself is relayed to the chrome process as a CPOW.

To make the shim unnecessary: register event listeners on the global object inside a frame script. For example:

addEventListener("load", handler, true) // for example
如果你需要在这时联系 chrome 进程,发送一个消息。


You can create sandboxes in the chrome or the content process. Sandboxes are often used as a safe way to manipulate web content, and if that's your goal, create the sandbox in the content process.
There is a shim for sandboxes: if you make a sandbox in the chrome process and give it content principals (by passing a CPOW as the first argument to Components.utils.Sandbox) then we'll actually make it in the content process.


By default, custom about: pages registered using nsIAboutModule are loaded in the chrome process. This means that you can't access their content from the content process (via XHR, for example).

你可以在你注册 about: URI 的代码中改变这个默认值。见 about: 和 chrome: URI

JavaScript 代码模块 (JSM)

在单进程的 Firefox 中,你可以使用 JavaScript 代码模块 (JSM) 来维持全局状态。在多进程的 Firefox 中,一个加载到某个进程的 JSM 不与加载到另一个进程的同样 JSM 共享状态:因此你不能使用一个 JSM 在 chrome 和内容进程之间共享状态。
If an add-on wants to use a JSM to share state in this way, it's best to load the JSM in the chrome process, and have frame scripts store and access the JSM's state by sending messages to the chrome process using the message manager.