Please note, this is a STATIC archive of website developer.mozilla.org from November 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

跨进程对象包装器

本文档介绍了 Cross Process Object Wrappers (CPOWs),这使 chrome 代码能够同步访问多进程 Firefox 中的内容。

在多进程 Firefox 中,chrome 代码运行在与 Web 内容不同的另一个进程中。因此 chrome 代码不能直接与 Web 内容交互;相反,它必须考虑将与 Web 内容交互的脚本放在单独的脚本中,这被称为框架脚本(frame scripts),也称帧脚本。

Chrome 代码可以使用消息管理器加载框架脚本到内容进程,然后可以使用消息传递 API 与它们通信。有关于此的更多信息,详见 消息管理器 的使用文档。

Chrome 到内容的通信必须是异步的。这是因为 chrome 进程运行着 Firefox UI,因此如果被内容进程所影响,缓慢的内容进程可能致使 Firefox 对用户无响应。

将同步代码转换成异步可能是困难并且耗时的。作为一个迁移的辅助,消息框架使框架脚本变成了内容对象,通过一个被称为 Cross Process Object Wrapper(简称 CPOW)的包装器,使其在 chrome 中可用。但是,尽管 CPOWs 很方便,它们存在严重的局限性并且可能导致响应性问题,因此只应在必要时使用,并仅作为迁移的辅助。

从框架脚本传递 CPOWs

框架脚本可以发送消息到 chrome,使用两个全局函数之一:sendAsyncMessage() 或者 sendSyncMessage()。这些函数的第三个可选参数是被包装的属性对象。举例来说,框架脚本在用户点击它时发送一个 DOM 节点到 chrome,并将 clicked 属性作为第三个参数:

// frame script
addEventListener("click", function (event) {
  sendAsyncMessage("my-e10s-extension-message", {}, { clicked : event.target });
}, false);

在 chrome 脚本中,DOM 节点现在是通过 Cross Process Object Wrapper 访问,作为该消息的 objects  属性的个属性。chrome 脚本可以获得和设置包装的对象属性,以及调用它的函数:

// chrome script
windowMM.addMessageListener("my-e10s-extension-message", handleMessage);

function handleMessage(message) {
  let wrapper = message.objects.clicked;
  console.log(wrapper.innerHTML);
  wrapper.innerHTML = "<h2>已被 chrome 修改!</h2>"
  wrapper.setAttribute("align", "center");
}

自动生成的 CPOWs

没有自我声明多进程兼容的附加组件会加载一些兼容性垫片。其中一个垫片提供了以下行为:每当 chrome 代码尝试直接访问内容(例如通过 window.content 或者 browser.contentDocument),提供一个包装了内容的 CPOW。这意味着下面这样的例子在多进程 Firefox 中也能正常工作。

gBrowser.selectedBrowser.contentDocument.body.innerHTML = "被 chrome 代码替换";

但仍然要记住,这是通过 CPOW 访问,并不是直接访问内容。

双向 CPOWs

一个常见的模式是 chrome 代码访问内容对象并添加事件监听器到那里。为了解决这个问题,CPOWs 是双向的。这意味着如果内容传递了一个 CPOW 到 chrome 进程,chrome 进程可以同步传递对象(如事件监听器函数)到 CPOW 中定义的函数。

这意味着你可以写这样的代码:

// frame script

/*
在 mouseover,发送 button 到 chrome 脚本,以一个CPOW形式。
*/

var button = content.document.getElementById("click-me");

button.addEventListener("mouseover", function (event) {
  sendAsyncMessage("my-addon-message", {}, { element : event.target });
}, false);
// chrome script

/*
载入框架脚本,然后监听消息。
在我们得到消息时,提取 CPOW 并添加一个函数作为监听器到按钮的 "click" 事件。
*/

  browserMM.loadFrameScript("chrome://my-addon/content/frame-script.js", false);
  browserMM.addMessageListener("my-addon-message", function(message) {
    let wrapper = message.objects.element;
    wrapper.addEventListener("click", function() {
      console.log("被点击了");
    });
  });

映射内容文档到 XUL 浏览器

一个常见的模式是获取 XUL <browser>,它对应一个内容文档。要做到这点, gBrowser.getBrowserForDocument  和 gBrowser.getBrowserForContentWindow 分别可以传递一个内容文档和内容窗口的 CPOW,并且返回这些文档 / 窗口所属的 XUL  <browser>。如果没有找到这样的浏览器,两者都是返回 null。

CPOWs 的限制

尽管 CPOWs 可以方便的使用,但它有几个主要的局限性,在下面列出。

CPOWs 与平台 API

你不能传递 CPOWs 到预期会收到 DOM 对象的平台 API。举例来说,你不能传递一个 CPOW  nsIFocusManager.setFocus()

Chrome 响应性

在 chrome 这边缺少同步 API 是有意的:因为 chrome 进程运行着 Firefox UI,任何响应性问题都将影响整个浏览器。在制成 chrome 进程与内容进程的过程中,CPOWs 打破了这个原则,并且致使内容进程可能使整个浏览器陷入无响应状态。

性能

尽管包装器看起来像是一个完全在 chrome 脚本范围下管控的对象,但它实际上只是一个到内容进程中一个对象的引用。在你访问一个包装器的属性时,它发送一个同步消息到内容进程及返回结果。这意味着它比使用一个对象慢很多倍。

消息顺序

CPOWs 可能违反你做出的有关消息排序的假设。考虑以下代码:

mm.addMessageListener("GotLoadEvent", function (msg) {
  mm.sendAsyncMessage("ChangeDocumentURI", {newURI: "hello.com"});
  let uri = msg.objects.document.documentURI;
  dump("收到加载事件: " + uri + "\n");
});

这发送了一个消息,要求框架脚本更改当前文档的 URI,然后通过一个 CPOW 访问当前的文档 URI。你可能预期 uri 的值得到设置的 "hello.com"。但这不一定:为了避免死锁,CPOW 消息可以绕过正常的消息并且被优先处理。对 documentURI 属性的请求有可能在 "ChangeDocumentURI" 的消息之前被处理,并因而 uri 持有它在更改之前的值。

出于这个原因,最好不要混用 CPOWs 和正常的消息管理器消息。还有一个坏主意是将 CPOWs 用于任何安全相关,因为你可能获得不一致的结果,与使用消息管理器的相关代码。

文档标签和贡献者

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