Bug 1317101 - Part 7a: Add a `remote` flag to run an extension out-of-process based on a preference. r?billm draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 12 Nov 2016 16:45:36 -0800
changeset 438073 14201484d276bef8b0a741218b5ed47dd9963320
parent 438072 0de312c933cf2e929b8c4c8208dd517570a522a4
child 438074 bbe0af7c3c5e12f08314d309ac51b089c0201e68
push id35614
push usermaglione.k@gmail.com
push dateSun, 13 Nov 2016 03:28:59 +0000
reviewersbillm
bugs1317101
milestone52.0a1
Bug 1317101 - Part 7a: Add a `remote` flag to run an extension out-of-process based on a preference. r?billm MozReview-Commit-ID: ChinmbLjnQA
modules/libpref/init/all.js
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/extensions/ExtensionParent.jsm
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4708,16 +4708,17 @@ pref("xpinstall.whitelist.required", tru
 pref("xpinstall.signatures.required", false);
 pref("extensions.alwaysUnpack", false);
 pref("extensions.minCompatiblePlatformVersion", "2.0");
 pref("extensions.webExtensionsMinPlatformVersion", "42.0a1");
 
 // Other webextensions prefs
 pref("extensions.webextensions.keepStorageOnUninstall", false);
 pref("extensions.webextensions.keepUuidOnUninstall", false);
+pref("extensions.webextensions.outOfProcess", false);
 
 pref("network.buffer.cache.count", 24);
 pref("network.buffer.cache.size",  32768);
 
 // Desktop Notification
 pref("notification.feature.enabled", false);
 
 // Web Notification
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -21,16 +21,20 @@ const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.importGlobalProperties(["TextEncoder"]);
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+/* globals processCount */
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "processCount", "dom.ipc.processCount");
+
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionAPIs",
                                   "resource://gre/modules/ExtensionAPI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                   "resource://gre/modules/ExtensionStorage.jsm");
@@ -583,16 +587,30 @@ this.Extension = class extends Extension
       Services.obs.addObserver(this, "xpcom-shutdown", false);
       this.cleanupFile = addonData.cleanupFile || null;
       delete addonData.cleanupFile;
     }
 
     this.addonData = addonData;
     this.startupReason = startupReason;
 
+    this.remote = ExtensionManagement.useRemoteWebExtensions;
+
+    if (this.remote && processCount !== 1) {
+      throw new Error("Out-of-process WebExtensions are not supported with multiple child processes");
+    }
+    if (this.remote) {
+      // Since we're currently extensions in the web content child
+      // process for testing, we currently only support single-process
+      // e10s, and the extension child will always run in that process.
+      this.parentMessageManager = Services.ppmm.getChildAt(1);
+    } else {
+      this.parentMessageManager = Services.ppmm.getChildAt(0);
+    }
+
     this.id = addonData.id;
     this.baseURI = NetUtil.newURI(this.getURL("")).QueryInterface(Ci.nsIURL);
     this.principal = this.createPrincipal();
 
     this.onStartup = null;
 
     this.hasShutdown = false;
     this.onShutdown = new Set();
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -300,17 +300,18 @@ function getAPILevelForWindow(window, ad
   }
 
   // WebExtension URLs loaded into top frames UI could have full API level privileges.
   return FULL_PRIVILEGES;
 }
 
 ExtensionManagement = {
   get isExtensionProcess() {
-    return Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
+    return (this.useRemoteWebExtensions ||
+            Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT);
   },
 
   startupExtension: Service.startupExtension.bind(Service),
   shutdownExtension: Service.shutdownExtension.bind(Service),
 
   registerAPI: APIs.register.bind(APIs),
   unregisterAPI: APIs.unregister.bind(APIs),
 
@@ -321,8 +322,11 @@ ExtensionManagement = {
 
   // exported API Level Helpers
   getAddonIdForWindow,
   getAPILevelForWindow,
   API_LEVELS,
 
   APIs,
 };
+
+XPCOMUtils.defineLazyPreferenceGetter(ExtensionManagement, "useRemoteWebExtensions",
+                                      "extensions.webextensions.outOfProcess", false);
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -101,34 +101,31 @@ let apiManager = new class extends Schem
   }
 }();
 
 // Subscribes to messages related to the extension messaging API and forwards it
 // to the relevant message manager. The "sender" field for the `onMessage` and
 // `onConnect` events are updated if needed.
 ProxyMessenger = {
   _initialized: false,
+
   init() {
     if (this._initialized) {
       return;
     }
     this._initialized = true;
 
-    // TODO(robwu): When addons move to a separate process, we should use the
-    // parent process manager(s) of the addon process(es) instead of the
-    // in-process one.
-    let pipmm = Services.ppmm.getChildAt(0);
     // Listen on the global frame message manager because content scripts send
     // and receive extension messages via their frame.
     // Listen on the parent process message manager because `runtime.connect`
     // and `runtime.sendMessage` requests must be delivered to all frames in an
     // addon process (by the API contract).
     // And legacy addons are not associated with a frame, so that is another
     // reason for having a parent process manager here.
-    let messageManagers = [Services.mm, pipmm];
+    let messageManagers = [Services.mm, Services.ppmm];
 
     MessageChannel.addListener(messageManagers, "Extension:Connect", this);
     MessageChannel.addListener(messageManagers, "Extension:Message", this);
     MessageChannel.addListener(messageManagers, "Extension:Port:Disconnect", this);
     MessageChannel.addListener(messageManagers, "Extension:Port:PostMessage", this);
   },
 
   receiveMessage({target, messageName, channelId, sender, recipient, data, responseType}) {
@@ -142,18 +139,19 @@ ProxyMessenger = {
         let context = ParentAPIManager.getContextById(childId);
         NativeApp.onConnectNative(context, target.messageManager, data.portId, sender, toNativeApp);
         return true;
       }
       // "Extension:Port:Disconnect" and "Extension:Port:PostMessage" for
       // native messages are handled by NativeApp.
       return;
     }
+
     let extension = GlobalManager.extensionMap.get(sender.extensionId);
-    let receiverMM = this._getMessageManagerForRecipient(recipient);
+    let receiverMM = this.getMessageManagerForRecipient(recipient, extension);
     if (!extension || !receiverMM) {
       return Promise.reject({
         result: MessageChannel.RESULT_NO_HANDLER,
         message: "No matching message handler for the given recipient.",
       });
     }
 
     if ((messageName == "Extension:Message" ||
@@ -167,33 +165,32 @@ ProxyMessenger = {
       recipient,
       responseType,
     });
   },
 
   /**
    * @param {object} recipient An object that was passed to
    *     `MessageChannel.sendMessage`.
+   * @param {Extension} extension
    * @returns {object|null} The message manager matching the recipient if found.
    */
-  _getMessageManagerForRecipient(recipient) {
-    let {extensionId, tabId} = recipient;
+  getMessageManagerForRecipient(recipient, extension) {
+    let {tabId} = recipient;
     // tabs.sendMessage / tabs.connect
     if (tabId) {
       // `tabId` being set implies that the tabs API is supported, so we don't
       // need to check whether `TabManager` exists.
       let tab = apiManager.global.TabManager.getTab(tabId, null, null);
       return tab && tab.linkedBrowser.messageManager;
     }
 
     // runtime.sendMessage / runtime.connect
-    if (extensionId) {
-      // TODO(robwu): map the extensionId to the addon parent process's message
-      // manager when they run in a separate process.
-      return Services.ppmm.getChildAt(0);
+    if (extension) {
+      return extension.parentMessageManager;
     }
 
     return null;
   },
 };
 
 // Responsible for loading extension APIs into the right globals.
 GlobalManager = {