MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

Working with multiprocess Firefox

翻譯不完整。請協助 翻譯此英文文件

這篇文章提供給 Firefox 擴充套件開發者;文中概述了如何讓擴充套件可以執行於多行程的 Firefox.

在多行程的 Firefox 之前的版本, chrome code (including code inserted by extensions) 和 content 執行於同一作業系統行程中,所以擴充套件可以直接存取 content:

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

然而,在多行程的 Firefox (也稱為 Electrolysis 或 E10S),套件的程式碼與 content 將執行於不同的行程中,因此不再可能直接地存取。

取而代之地, the extension will need to factor code that touches content into separate scripts that are called frame scripts. Frame scripts 執行在 content 行程,並可以直接存取 content. Frame scripts 藉由 message-passing API 和套件其餘的部份通訊。

當套件程式碼(執行於 chrome 行程中)向套件的 frame script(執行於 content 行程中) 傳訊時,必須使用非同步訊息。

content 行程允許向 chrome 行程傳遞同步或非同步訊息,但是,非同步通訊是較好的選擇。

For more details on using the message manager and content scripts, refer to the message manager guide. The rest of this article explains how to work out if you're affected or not, provides an overview of the sorts of changes that are needed, then walks through the process of porting some simple extension patterns so they work properly with multiprocess Firefox.

確認你是否受到相容性影響

As a rule:

  • 如果你只使用 Add-on SDK's high-level APIs,你不會受到相容性困擾 (Add-on SDK 目前並不是完全相容於多行程的 Firefox,但相關問題短期內將會處理完成)
  • 如果你不存取任何網頁內容,你不會受到相容性困擾
  • if you access web content directly using an overlay extension, a bootstrapped extension, or low-level SDK APIs like window/utils or tabs/utils, then you probably will be affected
  • if you load XUL pages in tabs, you will be affected

To know for sure, you need to test it, and setting that up is a two-step process:

  • 開啟 Firefox 的多行程支援: multiprocess support is in Firefox Nightly, but is hiding behind a preference. To enable it visit about:config, find the preference named browser.tabs.remote.autostart, set it to true, and restart the browser. As a visual indicator that you're running multiprocess Firefox, the titles of tabs are underlined.
  • 宣告你的套件是多行程相容的: to make migration to multiprocess Firefox easier, we've implemented shims that help extensions to work even when they're not compatible. To check whether your extension is really compatible you need to disable these shims. To do that, add a new property to your extension's install.rdf named multiprocessCompatible, with a value of true. At the moment, this flag does not disable all shims, so it's not yet a reliable way to test that your add-on is compatible. See bug 1051017 for details on this.

Now you'll be able to test your extension in multiprocess Firefox, with no compatibility shims. At the moment you can't actually install extensions in multiprocess Firefox, so you have to install the extension, then switch on multiprocess support. This is being tracked as bug 1055808.

Updating your code

The general approach to updating your code is:

  • factor the part of your extension that accesses web content into one or more separate scripts. In multiprocess Firefox these are called frame scripts.
  • register chrome:// URLs for your frame scripts
  • use a message manager to load the scripts into browser objects
  • if you need to communicate between the main extension code and a frame script, use message manager APIs to do so
  • if you load XUL in tabs, register these as about: URLs and load them with the about: URL.

There are more details on this in the message manager documentation.

Backwards compatibility of the new APIs

With multiprocess support turned off, the e10s messaging APIs are still available and functional. They have been available in one form or another since Firefox 4; however, the original APIs are different than the current ones. Some known differences:

You should test your changes not only in nightlies with multiprocess support turned on, but also in releases you intend to support with multiprocess support turned off.

Examples

This section walks through the process of porting a few different sorts of extension. The extensions are all extremely simple, and are intended to represent fundamental extension patterns that require different handling in multiprocess Firefox.

You can find all the source code for these examples in the e10s-example-addons GitHub repository.

Run a script in all pages

The first extension runs some code on every page load. The code doesn't need to interact with any other part of the extension: it just makes some predetermined modification to the page. In this case it adds a border to the document's body.

It does this by attaching to a XUL overlay a version of the "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);

Because this code accesses web content directly, it won't work in multiprocess Firefox.

Porting to the message manager

To port this example using the message manager, we can put all the meat of the add-on in a frame script:

// 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";
});

We'll register a chrome:// URL for the frame script:

// chrome.manifest

content    modify-all-pages    chrome/content/

The main script, that we attach to the XUL overlay, is just a stub that uses the global message manager to load the frame script into each tab:

// 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);

Porting to the Add-on SDK

A good alternative for an extension like this is to port to the Add-on SDK. The Add-on SDK includes a module called page-mod which is designed to load scripts into web pages. The Add-on SDK calls these scripts content scripts.

In this case the main extension code creates a page-mod to load a content script into every page loaded by the user:

// main.js

var pageMod = require("sdk/page-mod");
var self = require("sdk/self");

pageMod.PageMod({
  include: "*",
  contentScriptFile: self.data.url("modify-all-pages.js")
});

The content script can modify the page directly:

// modify-all-pages.js - content script

document.body.style.border = "5px solid green";

Run a script in the active tab

The example demonstrates how an extension can:

The example is a restartless extension that adds a button using the CustomizableUI module. When the user clicks the button, the extension runs some code that modifies the current tab. The basic infrastructure is taken from the Australis "Hello World" extension written by Jorge Villalobos.

What the code actually does is: find any <img> elements and replace their src with a link to a silly GIF randomly chosen from a list hardcoded into the extension. The silly gifs are taken from the list in the Whimsy extension.

The first version accesses the page directly, so it's not multiprocess compatible:

// 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;
      }
    },

Porting to the message manager

To port this example to the message manager we'll make onCommand load a frame script into the current <browser>, then listen for "request-gifs" messages from the frame script. The "request-gifs" message is expected to contain the number of GIFs we need for this page: the message listener retrieves and returns that many 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;
  },

Again, we need to register a chrome:// URL for the frame script:

// chrome.manifest

content gifinate frame-script.js

In the frame script, we get all the <img> elements and send the "request-gifs" message to the main add-on code. Because this is a frame script we can make it a synchronous message, and update the src attributes with the value it returns:

// 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];
}

The overall flow of the add-on now looks like this:

Known bugs

This is a list of open bugs likely to affect add-on developers migrating to multiprocess Firefox:

  • Bug 1051238 - frame scripts are cached forever, so an add-on can't properly update without a browser restart
  • Bug 1017320 - tracking bug for implementing compatibility shims
  • Bug 1051017 - not all shims are disabled, even if an add-on is declared to be multiprocess compatible
  • Bug 1055808 - can't install local add-ons in multiprocess Firefox

文件標籤與貢獻者

 此頁面的貢獻者: shyangs
 最近更新: shyangs,