From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../working_with_multiprocess_firefox/index.html | 296 +++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 files/zh-cn/mozilla/add-ons/working_with_multiprocess_firefox/index.html (limited to 'files/zh-cn/mozilla/add-ons/working_with_multiprocess_firefox/index.html') diff --git a/files/zh-cn/mozilla/add-ons/working_with_multiprocess_firefox/index.html b/files/zh-cn/mozilla/add-ons/working_with_multiprocess_firefox/index.html new file mode 100644 index 0000000000..152a15de20 --- /dev/null +++ b/files/zh-cn/mozilla/add-ons/working_with_multiprocess_firefox/index.html @@ -0,0 +1,296 @@ +--- +title: 编写适合多进程 Firefox 的扩展 +slug: Mozilla/Add-ons/Working_with_multiprocess_Firefox +translation_of: Archive/Add-ons/Working_with_multiprocess_Firefox +--- +
+

致 Firefox 开发者:本文描述如何使你开发的扩展能在多进程 Firefox 中运行。

+
+ +

目前,在桌面版 Firefox 中,Chrome 代码(Chrome Code)和内容(Content)运行在同一个进程里。因此扩展可以直接访问网页内容:

+ +
gBrowser.selectedBrowser.contentDocument.body.innerHTML = "replaced by chrome code";
+ +

不过呢,在多进程 Firefox(又称 Electrolysis 或 E10S)中,扩展代码(Add-on Code)和内容则在不同的进程中运行,所以上述直接访问就不一定可行了。

+ +

在多进程 Firefox 中,扩展需要将触及内容的代码分解成单独分离的脚本,也就是所谓的框架脚本(Frame Scripts)。框架脚本在内容进程中运行,可以直接访问内容。框架脚本通过消息传递接口(Message-passing API)与扩展的剩余部分进行通讯。

+ +

运行在 Chrome 进程中的扩展代码必须向框架脚本发送异步消息。这样做可以确保 Firefox 的用户界面不会被内容进程卡死。

+ +

内容进程则可以向 Chrome 进程发送同步或异步消息,不过使用异步消息是再好不过了。

+ +

关于使用消息管理器的更多细节,请参阅:消息管理器指南。接下来,本文将告诉你如何判断你开发的扩展是否会受到这方面的影响,同时简要说明需要如何进行修改。最后,通过对一些简单的样板扩展进行修改,让它们能在多进程 Firefox 中运行。

+ +

检查你的扩展是否受到影响

+ +

总体规则如下:

+ + + +

为了证实是否受到影响,你还需要进一步测试,这个测试过程由两步过程构成:

+ + + +

现在,你可以在多进程 Firefox 中禁用兼容性管理工具来测试你的扩展的兼容性了。可惜你还不能在开启多进程功能的时候安装新扩展,因此你必须先安装好扩展再开启多进程功能。关于这个问题,详见 bug 1055808

+ +

更新你的代码

+ +

更新代码的一般方法是:

+ + + +

更多细节,请参见 message manager 文档。

+ +

新应用程序接口的向前兼容能力

+ +

新的多进程 Firefox 的消息接口在多进程模式没有开启的情况下仍然可用。实际上它们从 Firefox 4开始就在某种程度上可用了。不过呢,最初的应用程序接口和现在的并不相同。一些已知的差异如下:

+ + + +

你不仅应该在开启了多进程支持的每夜版Firefox上测试你的扩展的变化,而且应该在你打算支持的没有开启多进程支持的发行版(Release Build)中测试。

+ +

举几个例子

+ +

这部分将演示修改几种不同的扩展的过程。这些扩展都是很简易的,意在展示基础的扩展模式在多进程Firefox中需要的不同处理方式。

+ +

你可以在 e10s-example-addons GitHub repository 中找到这些例子的所有源代码。

+ +

在所有页面运行一个脚本

+ +
+

查看这个例子的源代码

+
+ +

第一个扩展在每一个页面加载的时候运行一些代码。这些代码不和扩展的其他部分交互,它们只是对页面进行一些预设的修改。在这个例子中,扩展向文档的主体(body)添加了一个边界。

+ +

这个扩展通过将一种“页面加载中”代码碎片("On page load" code snippet)附加于可扩展用户界面语言层来实现此修改。

+ +
var myExtension = {
+    init: function() {
+        // The event can be DOMContentLoaded, pageshow, pagehide, load or unload.
+        if(gBrowser) gBrowser.addEventListener("DOMContentLoaded", this.onPageLoad, false);
+    },
+    onPageLoad: function(aEvent) {
+        var doc = aEvent.originalTarget; // doc is document that triggered the event
+        if (doc.nodeName != "#document") return; // only documents
+        // make whatever modifications you want to doc
+        doc.body.style.border = "5px solid blue";
+    }
+}
+
+window.addEventListener("load", function load(event){
+    window.removeEventListener("load", load, false); //remove listener, no longer needed
+    myExtension.init();
+},false);
+ +

因为这段代码直接访问网络内容,所以它不能在多进程 Firefox 中运行。
+

+ +

移植到消息管理器

+ +

为了使用消息管理器移植这个例子,我们可将这个扩展全部的主体部分放入一个框架脚本:

+ +
// frame-script.js
+// will run in the content process
+
+addEventListener("DOMContentLoaded", function(event) {
+  var doc = event.originalTarget;
+  if (doc.nodeName != "#document") return; // only documents
+  doc.body.style.border = "5px solid red";
+});
+
+ +

我们将为这个框架脚本注册一个 chrome:// URL :

+ +
// chrome.manifest
+
+content    modify-all-pages    chrome/content/
+
+ +

我们附加到XUL overlay的主体脚本,只是一个使用全局消息管理器来在每个标签页中加载框架脚本的 stub。

+ +
// chrome script
+// will run in the chrome process
+
+var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+  .getService(Ci.nsIMessageListenerManager);
+
+globalMM.loadFrameScript("chrome://modify-all-pages/content/frame-script.js", true);
+ +

+ +

移植到 Add-on SDK

+ +

一个好的替代这样的一个扩展的思路是将其移植到 Add-on SDK。Add-on SDK 包括一个名为 page-mod 的设计为在网页中加载脚本的模块。Add-on SDK 称这些脚本为内容脚本。

+ +

这种情况下扩展的主要代码创建一个 page-mod 来加载内容脚本到用户载入的每个页面:

+ +
// main.js
+
+var pageMod = require("sdk/page-mod");
+var self = require("sdk/self");
+
+pageMod.PageMod({
+  include: "*",
+  contentScriptFile: self.data.url("modify-all-pages.js")
+});
+ +

内容脚本可以直接修改页面:

+ +
// modify-all-pages.js - content script
+
+document.body.style.border = "5px solid green";
+ +

在活动标签中运行一个脚本

+ +
+

查看这个例子的代码。

+
+ +

这个例子说明了一个扩展如何:

+ + + +

这个例子是一个无需重启的扩展,它使用 CustomizableUI 模块添加了一个按钮。当用户点击这个按钮,这个扩展运行一些代码来改变当前标签。其基础构造取自 Jorge Villalobos 的 Australis "Hello World" 扩展 .
+
+ 代码实际做的事是:找到任意 <img> 元素并将其 src 替换为从硬编码于扩展中的列表中随机抽取的无意义的 GIF 图像。 无意义的 gifs 从Whimsy extension 获取。

+ +

第一个版本直接访问页面,因此其不是多进程兼容的:

+ +
// bootstrap.js
+
+let Gifinate = {
+  init : function() {
+    let io =
+      Cc["@mozilla.org/network/io-service;1"].
+        getService(Ci.nsIIOService);
+
+    // the 'style' directive isn't supported in chrome.manifest for bootstrapped
+    // extensions, so this is the manual way of doing the same.
+    this._ss =
+      Cc["@mozilla.org/content/style-sheet-service;1"].
+        getService(Ci.nsIStyleSheetService);
+    this._uri = io.newURI("chrome://gifinate/skin/toolbar.css", null, null);
+    this._ss.loadAndRegisterSheet(this._uri, this._ss.USER_SHEET);
+
+    // create widget and add it to the main toolbar.
+    CustomizableUI.createWidget(
+      { id : "gifinate-button",
+        defaultArea : CustomizableUI.AREA_NAVBAR,
+        label : "Gifinate",
+        tooltiptext : "Gifinate!",
+        onCommand : function(aEvent) {
+          Gifinate.replaceImages(aEvent.target.ownerDocument.defaultView.content.document);
+        }
+      });
+  },
+
+  replaceImages : function(contentDocument) {
+      let images = contentDocument.getElementsByTagName("img");
+      for (var i = 0; i < images.length; ++i) {
+        let gif = this.gifs[Math.floor(Math.random() * this.gifs.length)];
+        images[i].src = gif;
+      }
+    },
+ +

+ +

移植到消息管理器

+ +

为了移植这个例子到消息管理器,我们将使 onCommand 加载一个框架脚本到当前的 <browser>,然后监听来自框架脚本的 "request-gifs" 信息。这些 "request-gifs" 信息应当包含我们在此页面上需要的 GIFs :信息监听器取回并返回这个数量的 GIFs。

+ +
// bootstrap.js
+// will run in the chrome process
+
+let Gifinate = {
+  init : function() {
+    let io =
+      Cc["@mozilla.org/network/io-service;1"].
+        getService(Ci.nsIIOService);
+
+    // the 'style' directive isn't supported in chrome.manifest for bootstrapped
+    // extensions, so this is the manual way of doing the same.
+    this._ss =
+      Cc["@mozilla.org/content/style-sheet-service;1"].
+        getService(Ci.nsIStyleSheetService);
+    this._uri = io.newURI("chrome://gifinate/skin/toolbar.css", null, null);
+    this._ss.loadAndRegisterSheet(this._uri, this._ss.USER_SHEET);
+
+    // create widget and add it to the main toolbar.
+    CustomizableUI.createWidget(
+      { id : "gifinate-button",
+        defaultArea : CustomizableUI.AREA_NAVBAR,
+        label : "Gifinate Button",
+        tooltiptext : "Gifinate!",
+        onCommand : function(aEvent) {
+          Gifinate.replaceImages(aEvent.target.ownerDocument);
+        }
+      });
+  },
+
+  replaceImages : function(xulDocument) {
+    var browserMM = xulDocument.defaultView.gBrowser.selectedBrowser.messageManager;
+    browserMM.loadFrameScript("chrome://gifinate/content/frame-script.js", false);
+    browserMM.addMessageListener("request-gifs", Gifinate.getGifs);
+  },
+
+  getGifs : function(message) {
+    var gifsToReturn = new Array(message.data);
+    for (var i = 0; i < gifsToReturn.length; i++) {
+      let gif = this.gifs[Math.floor(Math.random() * this.gifs.length)];
+      gifsToReturn[i] = gif;
+    }
+    return gifsToReturn;
+  },
+
+ +

再次地,我们需要为这个框架脚本注册一个 chrome:// URL:

+ +
// chrome.manifest
+
+content gifinate frame-script.js
+ +

在框架脚本中,我们获取所有的 <img> 元素并发送 "request-gifs" 信息给扩展主体代码。 由于这是框架脚本我们可以将其变为一个同步信息,并使用其返回值更新 src 属性:

+ +
// frame-script.js
+// will run in the content process
+
+var images = content.document.getElementsByTagName("img");
+var response = sendSyncMessage("request-gifs", images.length);
+var gifs = response[0];
+
+for (var i = 0; i < images.length; ++i) {
+  images[i].src = gifs[i];
+}
+ +

整个扩展的流程现在像这样:
+

+ +

已知问题

+ +

这里是可能影响扩展开发者移植到多进程firefox的开放的bug列表:

+ + -- cgit v1.2.3-54-g00ecf