Firefox插件脚本和内容脚本的并发性

3
在使用 Firefox 的 Add-on SDK 编写插件时,我注意到插件代码和内容脚本代码会互相阻塞执行。此外,插件代码似乎还会阻塞与其他 Firefox 窗口(而不仅仅是标签页)的交互。 Firefox 插件的并发/进程模型是什么? 是否可能在没有协作多线程(如定时器)的情况下同时运行插件代码和内容脚本代码? 插件代码被加载了几次?每个窗口一次?每个标签页一次?只有一次? 文档 中说:
“Mozilla 平台正在向一种模型过渡,其中它使用独立进程来显示 UI、处理 Web 内容和执行插件。主要插件代码将在插件进程中运行,并且不会直接访问任何 Web 内容。”
所以我希望未来它们确实是不会干扰彼此的独立进程,但现在似乎不是这样。
更新:
我尝试使用来自插件代码的页面工作者(page-worker),但不幸的是它仍会阻塞内容脚本(以及所有其他 JavaScript)。我还尝试使用页面工作者中的 Web Worker,在调用 Web Worker 的 postMessage 函数时遇到了以下错误:
“TypeError:worker.postMessage 不是一个函数”
我还尝试在页面工作者中创建一个 iframe,然后在 iframe 中创建一个 Web Worker,但不幸的是,我不能从页面工作者中使用 window.addEventListener。我得到了以下错误:
“TypeError:window.addEventMessage 不是一个函数”
最后,我尝试通过将脚本(通过脚本元素)注入到页面工作者页面中来创建一个 Web Worker,这似乎可以工作。不幸的是,我无法与此 Web Worker 通信,因为我只能通过 document.defaultView.postMessage 向其发送消息。
噢,我编织的错综复杂的网络...
内容脚本 -> 插件 -> 页面工作者 -> iframe -> Web Worker -> 我的代码
这里有一个简单的例子: package.json
{
    "name": "test", 
    "author": "me", 
    "version": "0.1", 
    "fullName": "My Test Extension", 
    "homepage": "http://example.com", 
    "id": "jid1-FmgBxScAABzB2g", 
    "description": "My test extension"
}

lib/main.js

var data = require("self").data;
var pageMod = require("page-mod");

pageMod.PageMod({
    include: ["http://*", "https://*"],
    contentScriptWhen: "start",
    contentScriptFile: [data.url("content.js")],
    onAttach: function (worker) {
        worker.port.on("message", function (data) {
            // simulate an expensive operation with a busy loop
            var start = new Date();
            while (new Date() - start < data.time);
            worker.port.emit("message", { text: 'done!' });
        });
    }
});

data/content.js

self.port.on("message", function (response) {
    alert(response.text);
});

// call a very expensive operation in the add-on code
self.port.emit("message", { time: 10000 });
2个回答

6

消息系统的设计考虑到了多进程环境。然而,这种环境并没有出现,看起来在不久的将来也不会发生。所以你真正拥有的是附加组件和内容脚本在主线程(UI线程)上运行的同一进程中。这意味着它们只能一个接一个地运行,正如您已经注意到的那样,没有并发性。

是否可能在没有合作多线程(类似于定时器)的情况下同时运行附加组件代码和内容脚本代码?

是的,您可以使用Web Workers(与page-worker模块无关,尽管名称相似)。这通常适用于消耗性操作 - 当插件执行某些操作时,您不希望它停止响应消息。不幸的是,Add-on SDK没有正确公开Web Workers,所以我不得不使用此处建议的解决方法:

worker.port.on("message", function (message) {
    // Get the worker class from a JavaScript module and unload it immediately
    var {Cu} = require("chrome");
    var {Worker} = Cu.import(data.url("dummy.jsm"));
    Cu.unload(data.url("dummy.jsm"));

    var webWorker = new Worker(data.url("expensiveOperation.js"));
    webWorker.addEventListener("message", function(event)
    {
      if (event.data == "done")
        worker.port.emit("message", { text: 'done!' });
    }, false);
});

JavaScript模块data/dummy.jsm只包含一行代码:
var EXPORTED_SYMBOLS=["Worker"];

如果您询问的是插件代码:它只加载一次,并在插件处于活动状态时保持存在。至于内容脚本,每个文档注入脚本都有一个单独的实例。

似乎无法运行。如果我尝试在插件代码中创建Web Worker,则会显示“_ReferenceError: Worker未定义_”。如果我尝试在内容脚本中创建Web Worker,则会显示“_ReferenceError: webWorker.addEventListener不是函数_”。 - fixedpoint
是的,在查看了一些Add-on SDK的讨论后,似乎Web Workers还没有得到很好的支持。我更新了我的答案,并提供了一个允许你使用它们的hack。 - Wladimir Palant
那似乎可以工作,但有一个奇怪的限制。我无法将对象传递给worker中的postMessage方法。它会抛出异常“无法克隆对象”。当然,我可以使用JSON序列化对象,然后将字符串传输到worker中。谢谢。 - fixedpoint
1
@user967974:只有JSON可序列化的对象才能被postMessage接受,当你试图传递一个没有JSON表示的对象时,就会出现这个消息。但是,可能存在作用域问题(你从哪个JS模块获取了Worker,它有自己的作用域和ObjectArray类),因此你确实需要手动使用JSON序列化。 - Wladimir Palant

0
我发现了一个在扩展的后台页面中获取WebWorkers的技巧:
if(typeof(Worker) == 'undefined')
{
    var chromewin   =   win_util.getMostRecentBrowserWindow();
    var Worker      =   chromewin.Worker;
}
var worker      =   new Worker(data.url('path/to/script.js'));

通过访问主窗口的window对象,您可以将Worker类引入当前作用域。这样可以避免所有烦人的Page.Worker解决方法,并且似乎工作得相当不错。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接