Bug 1357486: Part 0e - Support legacy extensions in OOP mode. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 07 Jul 2017 15:12:45 -0700
changeset 605565 a9cc5633536e159aa178ac8105a72cb97bbe58b7
parent 605564 00dc5151a4b0f8dc6015cdb5cd392e2b968f7cf4
child 605566 f49082b932105695dd496e53e7bad52fb807e42b
push id67457
push usermaglione.k@gmail.com
push dateSat, 08 Jul 2017 01:21:07 +0000
reviewersaswan
bugs1357486
milestone56.0a1
Bug 1357486: Part 0e - Support legacy extensions in OOP mode. r?aswan MozReview-Commit-ID: 4JHxX78HNRV
toolkit/components/extensions/ExtensionParent.jsm
toolkit/components/extensions/LegacyExtensionsUtils.jsm
toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
toolkit/components/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -172,53 +172,92 @@ ProxyMessenger = {
     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}) {
+  async receiveMessage({target, messageName, channelId, sender, recipient, data, responseType}) {
     if (recipient.toNativeApp) {
       let {childId, toNativeApp} = recipient;
       if (messageName == "Extension:Message") {
         let context = ParentAPIManager.getContextById(childId);
         return new NativeApp(context, toNativeApp).sendMessage(data);
       }
       if (messageName == "Extension:Connect") {
         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;
     }
 
+    const noHandlerError = {
+      result: MessageChannel.RESULT_NO_HANDLER,
+      message: "No matching message handler for the given recipient.",
+    };
+
     let extension = GlobalManager.extensionMap.get(sender.extensionId);
     let receiverMM = this.getMessageManagerForRecipient(recipient);
     if (!extension || !receiverMM) {
-      return Promise.reject({
-        result: MessageChannel.RESULT_NO_HANDLER,
-        message: "No matching message handler for the given recipient.",
-      });
+      return Promise.reject(noHandlerError);
     }
 
     if ((messageName == "Extension:Message" ||
          messageName == "Extension:Connect") &&
         apiManager.global.tabGetSender) {
       // From ext-tabs.js, undefined on Android.
       apiManager.global.tabGetSender(extension, target, sender);
     }
-    return MessageChannel.sendMessage(receiverMM, messageName, data, {
+
+    let promise1 = MessageChannel.sendMessage(receiverMM, messageName, data, {
       sender,
       recipient,
       responseType,
     });
+
+    // If we have a remote, embedded extension, the legacy side is
+    // running in a different process than the WebExtension side.
+    // As a result, we need to dispatch the message to both the parent
+    // and extension processes, and manually merge the results.
+    if (extension.isEmbedded && extension.remote) {
+      let promise2 = MessageChannel.sendMessage(Services.ppmm.getChildAt(0), messageName, data, {
+        sender,
+        recipient,
+        responseType,
+      });
+
+      let result = undefined;
+      let failures = 0;
+      let tryPromise = async promise => {
+        try {
+          let res = await promise;
+          if (result === undefined) {
+            result = res;
+          }
+        } catch (e) {
+          if (e.result != MessageChannel.RESULT_NO_HANDLER) {
+            throw e;
+          }
+          failures++;
+        }
+      };
+
+      await Promise.all([tryPromise(promise1), tryPromise(promise2)]);
+      if (failures == 2) {
+        return Promise.reject(noHandlerError);
+      }
+      return result;
+    }
+
+    return promise1;
   },
 
   /**
    * @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.
    */
--- a/toolkit/components/extensions/LegacyExtensionsUtils.jsm
+++ b/toolkit/components/extensions/LegacyExtensionsUtils.jsm
@@ -140,16 +140,18 @@ class EmbeddedExtension {
 
       // This is the instance of the WebExtension embedded in the hybrid add-on.
       this.extension = new Extension({
         id: this.addonId,
         resourceURI: embeddedExtensionURI,
         version: this.version,
       });
 
+      this.extension.isEmbedded = true;
+
       // This callback is register to the "startup" event, emitted by the Extension instance
       // after the extension manifest.json has been loaded without any errors, but before
       // starting any of the defined contexts (which give the legacy part a chance to subscribe
       // runtime.onMessage/onConnect listener before the background page has been loaded).
       const onBeforeStarted = () => {
         this.extension.off("startup", onBeforeStarted);
 
         // Resolve the startup promise and reset the startupError.
--- a/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js
@@ -52,16 +52,17 @@ add_task(async function test_legacy_exte
     });
   }
 
   let extensionData = {
     background,
   };
 
   let extension = Extension.generate(extensionData);
+  extension.isEmbedded = true;
 
   let waitForExtensionInfo = new Promise((resolve, reject) => {
     extension.on("test-message", function testMessageListener(kind, msg, ...args) {
       if (msg != "webextension-ready") {
         reject(new Error(`Got an unexpected test-message: ${msg}`));
       } else {
         extension.off("test-message", testMessageListener);
         resolve(args[0]);
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -24,16 +24,18 @@ skip-if = os == "android" || (os=='linux
 [test_ext_downloads_search.js]
 skip-if = os == "android"
 [test_ext_experiments.js]
 [test_ext_extension.js]
 [test_ext_extensionPreferencesManager.js]
 [test_ext_extensionSettingsStore.js]
 [test_ext_extension_startup_telemetry.js]
 [test_ext_idle.js]
+[test_ext_legacy_extension_context.js]
+[test_ext_legacy_extension_embedding.js]
 [test_ext_localStorage.js]
 [test_ext_management.js]
 [test_ext_management_uninstall_self.js]
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
 [test_ext_privacy.js]
 [test_ext_privacy_disable.js]
 [test_ext_privacy_update.js]
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -46,14 +46,12 @@ tags = webextensions in-process-webexten
 [test_ext_unknown_permissions.js]
 [test_locale_converter.js]
 [test_locale_data.js]
 
 [test_ext_permissions.js]
 skip-if = os == "android" # Bug 1350559
 [test_ext_runtime_sendMessage_args.js]
 
-[test_ext_legacy_extension_context.js]
-[test_ext_legacy_extension_embedding.js]
 [test_proxy_scripts.js]
 
 [include:xpcshell-common.ini]
 [include:xpcshell-content.ini]