多进程 Firefox 与 SDK

这篇翻译不完整。请帮忙从英语翻译这篇文章

我们目前正在使 Firefox 变为多进程,它为浏览器界面使用一个操作系统进程,为运行的网页使用另一个进程来执行代码,这个项目被称为 "electrolysis" 或者 "e10s"。更多信息请参考多进程 Firefox 相关页面

本文章介绍了开发者如何测试基于 SDK 的附加组件是否与多进程的 Firefox 兼容,以及如何解决出现的问题。

SDK 的合约

SDK 为附加组件的开发者承诺了:

  • 顶层 API 对多进程 Firefox 完全工作。如果并没有,请报告为 SDK 中的 bug。
  • 底层 API 也许不工作。如果你在使用底层 API,考虑检查、测试和重构某些代码。

在实践中,大多数底层 API 将正常工作,但可以直接访问网页内容的底层 API 无法正常工作。

兼容性垫片

举例来说,你可能认为这是行不通的:

var contentDocument = require("sdk/window/utils")
  .getMostRecentBrowserWindow().content.document;

但是,Firefox 为附加组件提供了许多 API 的兼容性垫片。这意味着许多 API,包括上述例子的那个,仍然工作。虽然有两条警示需注意:

  • 这些垫片不完美:某些附加组件在有垫片的情况下仍然不工作。举例来说,许多垫片通过为内容对象返回跨进称对象包装器来工作,并且这并非始终以同样的方式作为内容对象。
  • 这些垫片可能对性能有不良影响,我们希望人们最终去除它们。

We don't yet have a complete list of low-level APIs that are not multiprocess compatible: that is, will not work without the shims. Where we know that a low-level API is not multiprocess compatible, we've indicated that in the documentation page for it.

测试

To test whether an add-on works without the shims, use the "multiprocess" permission.

Note that you can only do this if you are using jpm. You can't use the "multiprocess" permission if you are using cfx.

Setting this permission will disable the shims for your add-on, so you can test to see if it's really multiprocess compatible or not.

However, there's a catch: some of the SDK's APIs themselves still depend on the shims. So by setting the "multiprocess" permission, your add-on might not work, even if you are only using high-level APIs. For example:

var selection = require("sdk/selection");

function myListener() {
  console.log(selection.text);
}

selection.on('select', myListener);

This add-on will not work if you've set the "multiprocess" permission, because sdk/selection depends on the shims. We're working on fixing these problems: see bug 1004745 and its dependencies.

使用框架脚本

If the shims don't enable your add-on to work properly, or you're trying to remove your dependency on the shims, then the solution is the same as it is for all add-ons:

  • factor the code that needs access to content objects into frame scripts
  • use the message manager to load the frame scripts into the content process
  • use the message manager to exchange messages with the frame scripts

In the rest of this section we'll outline some SDK-specific details of using the message manager. However, you'll also need to read the main message manager documentation for the details on working with frame scripts.

If you're used to working with content scripts in the SDK, then frame scripts might feel similar, but they're actually very different. Frame scripts are more like a part of the main add-on code that just happens to be running in the content process.

Differences between frame scripts and content scripts include:

  • the environment for frame scripts and the environment for content scripts are totally different:
    • content scripts have a very similar environment to scripts loaded by web pages, plus the self global.
    • frame scripts also have access to web content, but have a different set of APIs for communicating with the chrome process, and access to the Components object, which enables them to use privileged XPCOM APIs and load JSMs.
  • content scripts are reloaded when a new document is loaded. Frame scripts aren't: once a frame script is loaded into a tab it remains loaded until the tab is closed. For more details, see the article on frame script lifetime.

访问消息管理器

全局消息管理器

To access the global message manager, you load the nsIMessageListenerManager service. Before you can do this in an SDK add-on, you have to require("chrome") to get access to the Components object:

const {Cc, Ci} = require("chrome");

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

窗口消息管理器

The window message manager is available as the messageManager property of a chrome window. To understand what a chrome window is, you need to distinguish three sorts of windows:

  • a content window is the Window object familiar to Web developers as the global object for JavaScript loaded from an HTML document. This is the same window that content scripts in the SDK access.
  • a chrome window, also called a browser window. This is a Firefox application window, hosting the Firefox UI as well as a collection of tabs, each of which can in turn host a DOM window to display web content. This is the global for traditional overlay-based add-ons. In the SDK, the low-level window/utils module works with these sorts of windows.
  • an SDK window, as made available by the high level windows module. Each SDK window maps to a chrome window, but omits most of the chrome window's properties, including messageManager. If you have an SDK window you can convert it to a chrome window using viewFor.

So to get a window message manager from the SDK, you have a couple of options:

(1) Use the window/utils module:

// Note that although this code uses window/utils,
// it's safe to run in the chrome process because
// it only accesses chrome objects.

// get the active chrome window
var browserWindow = require("sdk/window/utils").getMostRecentBrowserWindow();

var windowMM = browserWindow.messageManager;

(2) Use viewFor to convert an SDK window to a chrome window:

// get the active SDK window
var windows = require("sdk/windows").browserWindows;
var activeWindow = windows.activeWindow;

// convert it to a chrome window
var browserWindow = require("sdk/view/core").viewFor(activeWindow);

var windowMM = browserWindow.messageManager;

浏览器消息管理器

The browser message manager is available as the messageManager property of an XUL browser.

In the SDK, to get a browser for a given tab, you can use the tabs/utils module's getBrowserForTab function. getBrowserForTab expects an XUL tab as an argument, so if you have an SDK tab object, you'll need to convert it to an XUL tab using viewFor:

// get the active SDK tab
var tab = require("sdk/tabs").activeTab;
// convert it to an XUL tab
var xulTab = require("sdk/view/core").viewFor(tab);
// get the XUL browser for this tab
var xulBrowser = require("sdk/tabs/utils").getBrowserForTab(xulTab);

var browserMM = xulBrowser.messageManager;

Again, although this code uses tabs/utils, it's safe to run in the chrome process because it only accesses chrome objects.

载入框架脚本

You can load frame scripts using the message manager, passing in a chrome:// or resource:// URL pointing to the script. With the SDK, the simplest approach is to keep frame scripts under your add-on's data directory, and pass in a resource:// URL created using self.data.url:

const self = require("sdk/self");  

messageManager.loadFrameScript(self.data.url("frame-script.js"), false);

Note that unlike the APIs to load content scripts, you can only load a single frame script here.

例子

For example, this add-on trivially accesses content directly using a low-level API:

function logContent() {
  var contentDocument = require("sdk/window/utils")
    .getMostRecentBrowserWindow().content.document;
  console.log(contentDocument.body.innerHTML);
}

require("sdk/ui/button/action").ActionButton({
  id: "log-content",
  label: "Log Content",
  icon: "./icon-16.png",
  onClick: logContent
});

This add-on will work by default due to the shims, but will break if you set multiprocessCompatible. So you could rewrite the add-on to use frame scripts:

/*
frame-script.js is in the "data" directory, and has this content:

sendAsyncMessage("sdk-low-level-apis-e10s@jetpack:got-content",
                 content.document.body.innerHTML);

*/

const self = require("sdk/self");

function logContentAsync() {
  var tab = require("sdk/tabs").activeTab;
  var xulTab = require("sdk/view/core").viewFor(tab);
  var xulBrowser = require("sdk/tabs/utils").getBrowserForTab(xulTab);

  var browserMM = xulBrowser.messageManager;
  browserMM.loadFrameScript(self.data.url("frame-script.js"), false);
  browserMM.addMessageListener("sdk-low-level-apis-e10s@jetpack:got-content",
                               logContent);
}

function logContent(message) {
  console.log(message.data);
}

require("sdk/ui/button/action").ActionButton({
  id: "log-content",
  label: "Log Content",
  icon: "./icon-16.png",
  onClick: logContentAsync
});

现在附加组件能正常工作了,即使你设置了 multiprocessCompatible。

文档标签和贡献者

 此页面的贡献者: yfdyh000
 最后编辑者: yfdyh000,