Bug 1394841 - Test WebExt messaging with RDM. r=kmag,ochameau
authorJ. Ryan Stinnett <jryans@gmail.com>
Wed, 01 Nov 2017 17:40:16 -0500
changeset 444240 bbf3fb8a107c69674b02f187ad53908864407a37
parent 444239 134b46ce0aa580d9b181cc7d1ad3752fd7cc6460
child 444241 d253ab30bac2ac8ab92e3cdd5bf89ba8073a4613
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag, ochameau
bugs1394841
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1394841 - Test WebExt messaging with RDM. r=kmag,ochameau This test verifies that WebExt messaging works as expected for both the background page scripts and content scripts when RDM is used. MozReview-Commit-ID: 3fODg3nYLr7
devtools/client/responsive.html/browser/tunnel.js
devtools/client/responsive.html/test/browser/browser.ini
devtools/client/responsive.html/test/browser/browser_ext_messaging.js
--- a/devtools/client/responsive.html/browser/tunnel.js
+++ b/devtools/client/responsive.html/browser/tunnel.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { Ci } = require("chrome");
+const { Ci, Cu } = require("chrome");
 const Services = require("Services");
 const { Task } = require("devtools/shared/task");
 const { BrowserElementWebNavigation } = require("./web-navigation");
 const { getStack } = require("devtools/shared/platform/stack");
 
 // A symbol used to hold onto the frame loader from the outer browser while tunneling.
 const FRAME_LOADER = Symbol("devtools/responsive/frame-loader");
 // Export for use in tests.
@@ -323,18 +323,18 @@ exports.tunnelToInnerBrowser = tunnelToI
  * This module allows specific messages of interest to be directed from the
  * outer browser to the inner browser (and vice versa) in a targetted fashion
  * without having to touch the original code paths that use them.
  */
 function MessageManagerTunnel(outer, inner) {
   if (outer.isRemoteBrowser) {
     throw new Error("The outer browser must be non-remote.");
   }
-  this.outer = outer;
-  this.inner = inner;
+  this.outerRef = Cu.getWeakReference(outer);
+  this.innerRef = Cu.getWeakReference(inner);
   this.tunneledMessageNames = new Set();
   this.init();
 }
 
 MessageManagerTunnel.prototype = {
 
   /**
    * Most message manager methods are left alone and are just passed along to
@@ -446,32 +446,40 @@ MessageManagerTunnel.prototype = {
     "ViewSource:",
   ],
 
   OUTER_TO_INNER_FRAME_SCRIPTS: [
     // DevTools server for OOP frames
     "resource://devtools/server/child.js"
   ],
 
+  get outer() {
+    return this.outerRef.get();
+  },
+
   get outerParentMM() {
     if (!this.outer[FRAME_LOADER]) {
       return null;
     }
     return this.outer[FRAME_LOADER].messageManager;
   },
 
   get outerChildMM() {
     // This is only possible because we require the outer browser to be
     // non-remote, so we're able to reach into its window and use the child
     // side message manager there.
     let docShell = this.outer[FRAME_LOADER].docShell;
     return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIContentFrameMessageManager);
   },
 
+  get inner() {
+    return this.innerRef.get();
+  },
+
   get innerParentMM() {
     if (!this.inner.frameLoader) {
       return null;
     }
     return this.inner.frameLoader.messageManager;
   },
 
   init() {
@@ -619,9 +627,13 @@ MessageManagerTunnel.prototype = {
            this.OUTER_TO_INNER_MESSAGE_PREFIXES.some(prefix => name.startsWith(prefix));
   },
 
   _shouldTunnelInnerToOuter(name) {
     return this.INNER_TO_OUTER_MESSAGES.includes(name) ||
            this.INNER_TO_OUTER_MESSAGE_PREFIXES.some(prefix => name.startsWith(prefix));
   },
 
+  toString() {
+    return "[object MessageManagerTunnel]";
+  },
+
 };
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -22,27 +22,29 @@ support-files =
 [browser_device_custom_remove.js]
 [browser_device_custom.js]
 [browser_device_modal_error.js]
 [browser_device_modal_exit.js]
 [browser_device_modal_submit.js]
 [browser_device_width.js]
 [browser_dpr_change.js]
 [browser_exit_button.js]
+[browser_ext_messaging.js]
+tags = devtools webextensions
 [browser_frame_script_active.js]
 [browser_hide_container.js]
 [browser_menu_item_01.js]
 [browser_menu_item_02.js]
 [browser_mouse_resize.js]
 [browser_navigation.js]
 skip-if = true # Bug 1413765
 [browser_network_throttling.js]
 [browser_page_state.js]
 [browser_permission_doorhanger.js]
-tags = geolocation
+tags = devtools geolocation
 skip-if = true # Bug 1413765
 [browser_resize_cmd.js]
 [browser_screenshot_button.js]
 [browser_tab_close.js]
 [browser_tab_remoteness_change.js]
 [browser_target_blank.js]
 [browser_toolbox_computed_view.js]
 [browser_toolbox_rule_view.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_ext_messaging.js
@@ -0,0 +1,129 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env webextensions */
+
+"use strict";
+
+const TEST_URL = "http://example.com/";
+
+// These allowed rejections are copied from
+// browser/components/extensions/test/browser/head.js.
+PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
+PromiseTestUtils.whitelistRejectionsGlobally(/Receiving end does not exist/);
+
+add_task(async function () {
+  let tab = await addTab(TEST_URL);
+  await openRDM(tab);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+
+      "content_scripts": [{
+        "matches": [TEST_URL],
+        "js": ["content-script.js"],
+        "run_at": "document_start",
+      }],
+    },
+
+    async background() {
+      const TEST_URL = "http://example.com/"; // eslint-disable-line no-shadow
+
+      browser.test.log("Background script init");
+
+      let extTab;
+      let contentMessage = new Promise(resolve => {
+        browser.test.log("Listen to content");
+        let listener = async (msg, sender, respond) => {
+          browser.test.assertEq(msg, "hello-from-content",
+            "Background script got hello-from-content message");
+
+          let tabs = await browser.tabs.query({
+            currentWindow: true,
+            active: true,
+          });
+          browser.test.assertEq(tabs.length, 1,
+            "One tab is active in the current window");
+          extTab = tabs[0];
+          browser.test.log(`Tab: id ${extTab.id}, url ${extTab.url}`);
+          browser.test.assertEq(extTab.url, TEST_URL, "Tab has the test URL");
+
+          browser.test.assertTrue(!!sender, "Message has a sender");
+          browser.test.assertTrue(!!sender.tab, "Message has a sender.tab");
+          browser.test.assertEq(sender.tab.id, extTab.id,
+            "Sender's tab ID matches the RDM tab ID");
+          browser.test.assertEq(sender.tab.url, extTab.url,
+            "Sender's tab URL matches the RDM tab URL");
+
+          browser.runtime.onMessage.removeListener(listener);
+          resolve();
+        };
+        browser.runtime.onMessage.addListener(listener);
+      });
+
+      // Wait for "resume" message so we know the content script is also ready.
+      await new Promise(resolve => {
+        browser.test.onMessage.addListener(resolve);
+        browser.test.sendMessage("background-script-ready");
+      });
+
+      await contentMessage;
+
+      browser.test.log("Send message from background to content");
+      let contentSender = await browser.tabs.sendMessage(
+        extTab.id,
+        "hello-from-background"
+      );
+      browser.test.assertEq(contentSender.id, browser.runtime.id,
+        "The sender ID in content matches this extension");
+
+      browser.test.notifyPass("rdm-messaging");
+    },
+
+    files: {
+      "content-script.js": async function () {
+        browser.test.log("Content script init");
+
+        browser.test.log("Listen to background");
+        browser.runtime.onMessage.addListener((msg, sender, respond) => {
+          browser.test.assertEq(msg, "hello-from-background",
+            "Content script got hello-from-background message");
+
+          browser.test.assertTrue(!!sender, "Message has a sender");
+          browser.test.assertTrue(!!sender.id, "Message has a sender.id");
+
+          let { id } = sender;
+          respond({ id });
+        });
+
+        // Wait for "resume" message so we know the background script is also ready.
+        await new Promise(resolve => {
+          browser.test.onMessage.addListener(resolve);
+          browser.test.sendMessage("content-script-ready");
+        });
+
+        browser.test.log("Send message from content to background");
+        browser.runtime.sendMessage("hello-from-content");
+      },
+    },
+  });
+
+  let contentScriptReady = extension.awaitMessage("content-script-ready");
+  let backgroundScriptReady = extension.awaitMessage("background-script-ready");
+  let finish = extension.awaitFinish("rdm-messaging");
+
+  await extension.startup();
+
+  // It appears the background script and content script can loaded in either order, so
+  // we'll wait for the both to listen before proceeding.
+  await backgroundScriptReady;
+  await contentScriptReady;
+  extension.sendMessage("resume");
+
+  await finish;
+  await extension.unload();
+
+  await closeRDM(tab);
+  await removeTab(tab);
+});