Bug 1383310 - Extensions Devtools panels should not receive messages or ports from content scripts. draft
authorLuca Greco <lgreco@mozilla.com>
Thu, 14 Sep 2017 16:43:45 +0200
changeset 666910 d4f856e92acf70d0790a741696f9c1eddcea8815
parent 664736 dd6b788f149763c4014c27f2fe1a1d13228bda82
child 732234 8a44f94a0b0c36c8ba4fbd7535b7c9d4f4efcf10
push id80547
push userluca.greco@alcacoop.it
push dateTue, 19 Sep 2017 11:53:09 +0000
bugs1383310
milestone57.0a1
Bug 1383310 - Extensions Devtools panels should not receive messages or ports from content scripts. MozReview-Commit-ID: B5Hq11Tb3Y0
browser/components/extensions/test/browser/browser_ext_devtools_page.js
toolkit/components/extensions/ExtensionChild.jsm
--- a/browser/components/extensions/test/browser/browser_ext_devtools_page.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_page.js
@@ -6,77 +6,276 @@ const {DevToolsShim} = Cu.import("chrome
 const {gDevTools} = DevToolsShim;
 
 /**
  * This test file ensures that:
  *
  * - the devtools_page property creates a new WebExtensions context
  * - the devtools_page can exchange messages with the background page
  */
-
 add_task(async function test_devtools_page_runtime_api_messaging() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
 
   function background() {
+    browser.runtime.onMessage.addListener((msg, sender) => {
+      if (sender.tab) {
+        browser.test.sendMessage("content_script_message_received");
+      }
+    });
+
     browser.runtime.onConnect.addListener((port) => {
+      if (port.sender.tab) {
+        browser.test.sendMessage("content_script_port_received");
+        return;
+      }
+
       let portMessageReceived = false;
 
       port.onDisconnect.addListener(() => {
         browser.test.assertTrue(portMessageReceived,
                                 "Got a port message before the port disconnect event");
-        browser.test.notifyPass("devtools_page_connect.done");
+        browser.test.sendMessage("devtools_page_connect.done");
       });
 
       port.onMessage.addListener((msg) => {
         portMessageReceived = true;
         browser.test.assertEq("devtools -> background port message", msg,
                               "Got the expected message from the devtools page");
         port.postMessage("background -> devtools port message");
       });
     });
   }
 
   function devtools_page() {
+    browser.runtime.onConnect.addListener(port => {
+      // Fail if a content script port has been received by the devtools page (Bug 1383310).
+      if (port.sender.tab) {
+        browser.test.fail(`A DevTools page should not receive ports from content scripts`);
+      }
+    });
+
+    browser.runtime.onMessage.addListener((msg, sender) => {
+      // Fail if a content script message has been received by the devtools page (Bug 1383310).
+      if (sender.tab) {
+        browser.test.fail(`A DevTools page should not receive messages from content scripts`);
+      }
+    });
+
     const port = browser.runtime.connect();
     port.onMessage.addListener((msg) => {
       browser.test.assertEq("background -> devtools port message", msg,
                             "Got the expected message from the background page");
       port.disconnect();
     });
     port.postMessage("devtools -> background port message");
+
+    browser.test.sendMessage("devtools_page_loaded");
+  }
+
+  function content_script() {
+    browser.test.onMessage.addListener(msg => {
+      switch (msg) {
+        case "content_script.send_message":
+          browser.runtime.sendMessage("content_script_message");
+          break;
+        case "content_script.connect_port":
+          const port = browser.runtime.connect();
+          port.disconnect();
+          break;
+        default:
+          browser.test.fail(`Unexpected message ${msg} received by content script`);
+      }
+    });
+
+    browser.test.sendMessage("content_script_loaded");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
       devtools_page: "devtools_page.html",
+      content_scripts: [
+        {
+          js: ["content_script.js"],
+          matches: ["http://mochi.test/*"],
+        },
+      ],
     },
     files: {
+      "content_script.js": content_script,
       "devtools_page.html": `<!DOCTYPE html>
-      <html>
-       <head>
-         <meta charset="utf-8">
-       </head>
-       <body>
-         <script src="devtools_page.js"></script>
-       </body>
-      </html>`,
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            <script src="devtools_page.js"></script>
+          </body>
+        </html>`,
       "devtools_page.js": devtools_page,
     },
   });
 
   await extension.startup();
 
+  info("Wait the content script load");
+  await extension.awaitMessage("content_script_loaded");
+
   let target = gDevTools.getTargetForTab(tab);
 
+  info("Open the developer toolbox");
   await gDevTools.showToolbox(target, "webconsole");
-  info("developer toolbox opened");
+
+  info("Wait the devtools page load");
+  await extension.awaitMessage("devtools_page_loaded");
+
+  info("Wait the connection 'devtools_page -> background' to complete");
+  await extension.awaitMessage("devtools_page_connect.done");
 
-  await extension.awaitFinish("devtools_page_connect.done");
+  // Send a message from the content script and expect it to be received from
+  // the background page (repeated twice to be sure that the devtools_page had
+  // the chance to receive the message and fail as expected).
+  info("Wait for 2 content script messages to be received from the background page");
+  extension.sendMessage("content_script.send_message");
+  await extension.awaitMessage("content_script_message_received");
+  extension.sendMessage("content_script.send_message");
+  await extension.awaitMessage("content_script_message_received");
+
+  // Create a port from the content script and expect a port to be received from
+  // the background page (repeated twice to be sure that the devtools_page had
+  // the chance to receive the message and fail as expected).
+  info("Wait for 2 content script ports to be received from the background page");
+  extension.sendMessage("content_script.connect_port");
+  await extension.awaitMessage("content_script_port_received");
+  extension.sendMessage("content_script.connect_port");
+  await extension.awaitMessage("content_script_port_received");
 
   await gDevTools.closeToolbox(target);
 
   await target.destroy();
 
   await extension.unload();
 
   await BrowserTestUtils.removeTab(tab);
 });
+
+/**
+ * This test file ensures that:
+ *
+ * - the devtools_page can exchange messages with an extension tab page
+ */
+
+add_task(async function test_devtools_page_and_extension_tab_messaging() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+  function background() {
+    browser.runtime.onMessage.addListener((msg, sender) => {
+      if (sender.tab) {
+        browser.test.sendMessage("extension_tab_message_received");
+      }
+    });
+
+    browser.runtime.onConnect.addListener((port) => {
+      if (port.sender.tab) {
+        browser.test.sendMessage("extension_tab_port_received");
+      }
+    });
+
+    browser.tabs.create({url: browser.runtime.getURL("extension_tab.html")});
+  }
+
+  function devtools_page() {
+    browser.runtime.onConnect.addListener(port => {
+      browser.test.sendMessage("devtools_page_onconnect");
+    });
+
+    browser.runtime.onMessage.addListener((msg, sender) => {
+      browser.test.sendMessage("devtools_page_onmessage");
+    });
+
+    browser.test.sendMessage("devtools_page_loaded");
+  }
+
+  function extension_tab() {
+    browser.test.onMessage.addListener(msg => {
+      switch (msg) {
+        case "extension_tab.send_message":
+          browser.runtime.sendMessage("content_script_message");
+          break;
+        case "extension_tab.connect_port":
+          const port = browser.runtime.connect();
+          port.disconnect();
+          break;
+        default:
+          browser.test.fail(`Unexpected message ${msg} received by content script`);
+      }
+    });
+
+    browser.test.sendMessage("extension_tab_loaded");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      devtools_page: "devtools_page.html",
+    },
+    files: {
+      "extension_tab.html": `<!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            <h1>Extension Tab Page</h1>
+            <script src="extension_tab.js"></script>
+          </body>
+        </html>`,
+      "extension_tab.js": extension_tab,
+      "devtools_page.html": `<!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            <script src="devtools_page.js"></script>
+          </body>
+        </html>`,
+      "devtools_page.js": devtools_page,
+    },
+  });
+
+  await extension.startup();
+
+  info("Wait the extension tab page load");
+  await extension.awaitMessage("extension_tab_loaded");
+
+  let target = gDevTools.getTargetForTab(tab);
+
+  info("Open the developer toolbox");
+  await gDevTools.showToolbox(target, "webconsole");
+
+  info("Wait the devtools page load");
+  await extension.awaitMessage("devtools_page_loaded");
+
+  extension.sendMessage("extension_tab.send_message");
+
+  info("Wait for an extension tab message to be received from the devtools page");
+  await extension.awaitMessage("devtools_page_onmessage");
+
+  info("Wait for an extension tab message to be received from the background page");
+  await extension.awaitMessage("extension_tab_message_received");
+
+  extension.sendMessage("extension_tab.connect_port");
+
+  info("Wait for an extension tab port to be received from the devtools page");
+  await extension.awaitMessage("devtools_page_onconnect");
+
+  info("Wait for an extension tab port to be received from the background page");
+  await extension.awaitMessage("extension_tab_port_received");
+
+  await gDevTools.closeToolbox(target);
+
+  await target.destroy();
+
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -346,16 +346,23 @@ class NativePort extends Port {
  */
 class Messenger {
   constructor(context, messageManagers, sender, filter, optionalFilter) {
     this.context = context;
     this.messageManagers = messageManagers;
     this.sender = sender;
     this.filter = filter;
     this.optionalFilter = optionalFilter;
+
+    // Include the context envType in the sender info.
+    this.sender.envType = context.envType;
+
+    // Exclude messages coming from content scripts for the devtools extension contexts
+    // (See Bug 1383310).
+    this.excludeContentScriptSender = (this.context.envType === "devtools_child");
   }
 
   _sendMessage(messageManager, message, data, recipient) {
     let options = {
       recipient,
       sender: this.sender,
       responseType: MessageChannel.RESPONSE_FIRST,
     };
@@ -388,16 +395,22 @@ class Messenger {
     return new EventManager(this.context, name, fire => {
       const [location] = new this.context.cloneScope.Error().stack.split("\n", 1);
 
       let listener = {
         messageFilterPermissive: this.optionalFilter,
         messageFilterStrict: this.filter,
 
         filterMessage: (sender, recipient) => {
+          // Exclude messages coming from content scripts for the devtools extension contexts
+          // (See Bug 1383310).
+          if (this.excludeContentScriptSender && sender.envType === "content_child") {
+            return false;
+          }
+
           // Ignore the message if it was sent by this Messenger.
           return (sender.contextId !== this.context.contextId &&
                   filter(sender, recipient));
         },
 
         receiveMessage: ({target, data: holder, sender, recipient, channelId}) => {
           if (!this.context.active) {
             return;
@@ -483,16 +496,22 @@ class Messenger {
 
   _onConnect(name, filter) {
     return new EventManager(this.context, name, fire => {
       let listener = {
         messageFilterPermissive: this.optionalFilter,
         messageFilterStrict: this.filter,
 
         filterMessage: (sender, recipient) => {
+          // Exclude messages coming from content scripts for the devtools extension contexts
+          // (See Bug 1383310).
+          if (this.excludeContentScriptSender && sender.envType === "content_child") {
+            return false;
+          }
+
           // Ignore the port if it was created by this Messenger.
           return (sender.contextId !== this.context.contextId &&
                   filter(sender, recipient));
         },
 
         receiveMessage: ({target, data: message, sender}) => {
           let {name, portId} = message;
           let mm = getMessageManager(target);