Bug 1287245 - Ensure globally unique internal port IDs draft
authorRob Wu <rob@robwu.nl>
Fri, 15 Jul 2016 21:44:03 -0700
changeset 389828 412ae6d0d297e3c73bc06eb7764970624a708997
parent 386302 214884d507ee369c1cf14edb26527c4f9a97bf48
child 525869 5d9d6fc9e563368c67b02e231a0046fa432458d5
push id23530
push userbmo:rob@robwu.nl
push dateWed, 20 Jul 2016 05:14:08 +0000
bugs1287245
milestone50.0a1
Bug 1287245 - Ensure globally unique internal port IDs MozReview-Commit-ID: FXNP5MadlMx
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/test/mochitest/mochitest.ini
toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -950,17 +950,17 @@ function promiseDocumentReady(doc) {
     }, true);
   });
 }
 
 /*
  * Messaging primitives.
  */
 
-var nextPortId = 1;
+let gNextPortId = 1;
 
 // Abstraction for a Port object in the extension API. Each port has a unique ID.
 function Port(context, messageManager, name, id, sender) {
   this.context = context;
   this.messageManager = messageManager;
   this.name = name;
   this.id = id;
   this.listenerName = `Extension:Port-${this.id}`;
@@ -1151,17 +1151,18 @@ Messenger.prototype = {
       MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
       return () => {
         MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
       };
     }).api();
   },
 
   connect(messageManager, name, recipient) {
-    let portId = nextPortId++;
+    // TODO(robwu): Use a process ID instead of the process type. bugzil.la/1287626
+    let portId = `${gNextPortId++}-${Services.appinfo.processType}`;
     let port = new Port(this.context, messageManager, name, portId, null);
     let msg = {name, portId};
     // TODO: Disconnect the port if no response?
     this._sendMessage(messageManager, "Extension:Connect", msg, recipient);
     return port.api();
   },
 
   onConnect(name) {
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -55,16 +55,18 @@ skip-if = buildapp == 'b2g' # runat != d
 [test_ext_idle.html]
 [test_ext_localStorage.html]
 [test_ext_onmessage_removelistener.html]
 [test_ext_notifications.html]
 [test_ext_permission_xhr.html]
 skip-if = buildapp == 'b2g' # JavaScript error: jar:remoteopenfile:///data/local/tmp/generated-extension.xpi!/content.js, line 46: NS_ERROR_ILLEGAL_VALUE:
 [test_ext_runtime_connect.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
+[test_ext_runtime_connect_twoway.html]
+skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_runtime_connect2.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_runtime_disconnect.html]
 [test_ext_runtime_getPlatformInfo.html]
 [test_ext_runtime_id.html]
 [test_ext_runtime_sendMessage.html]
 [test_ext_sandbox_var.html]
 [test_ext_sendmessage_reply.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+
+<script>
+"use strict";
+
+add_task(function* test_connect_bidirectionally_and_postMessage() {
+  function backgroundScript() {
+    let onConnectCount = 0;
+    browser.runtime.onConnect.addListener(port => {
+      // 3. onConnect by connect() from CS.
+      browser.test.assertEq("from-cs", port.name);
+      browser.test.assertEq(1, ++onConnectCount,
+          "BG onConnect should be called once");
+
+      let tabId = port.sender.tab.id;
+      browser.test.assertTrue(tabId, "content script must have a tab ID");
+
+      let port2;
+      let postMessageCount1 = 0;
+      port.onMessage.addListener(msg => {
+        // 11. port.onMessage by port.postMessage in CS.
+        browser.test.assertEq("from CS to port", msg);
+        browser.test.assertEq(1, ++postMessageCount1,
+          "BG port.onMessage should be called once");
+
+        // 12. should trigger port2.onMessage in CS.
+        port2.postMessage("from BG to port2");
+      });
+
+      // 4. Should trigger onConnect in CS.
+      port2 = browser.tabs.connect(tabId, {name: "from-bg"});
+      let postMessageCount2 = 0;
+      port2.onMessage.addListener(msg => {
+        // 7. onMessage by port2.postMessage in CS.
+        browser.test.assertEq("from CS to port2", msg);
+        browser.test.assertEq(1, ++postMessageCount2,
+          "BG port2.onMessage should be called once");
+
+        // 8. Should trigger port.onMessage in CS.
+        port.postMessage("from BG to port");
+      });
+    });
+
+    // 1. Notify test runner to create a new tab.
+    browser.test.sendMessage("ready");
+  }
+
+  function contentScript() {
+    let onConnectCount = 0;
+    let port;
+    browser.runtime.onConnect.addListener(port2 => {
+      // 5. onConnect by connect() from BG.
+      browser.test.assertEq("from-bg", port2.name);
+      browser.test.assertEq(1, ++onConnectCount,
+        "CS onConnect should be called once");
+
+      let postMessageCount2 = 0;
+      port2.onMessage.addListener(msg => {
+        // 12. port2.onMessage by port2.postMessage in BG.
+        browser.test.assertEq("from BG to port2", msg);
+        browser.test.assertEq(1, ++postMessageCount2,
+          "CS port2.onMessage should be called once");
+
+        // TODO(robwu): Do not explicitly disconnect, it should not be a problem
+        // if we keep the ports open. However, not closing the ports causes the
+        // test to fail with NS_ERROR_NOT_INITIALIZED in ExtensionUtils.jsm, in
+        // Port.prototype.disconnect (nsIMessageSender.sendAsyncMessage).
+        port.disconnect();
+        port2.disconnect();
+        browser.test.notifyPass("ping pong done");
+      });
+      // 6. should trigger port2.onMessage in BG.
+      port2.postMessage("from CS to port2");
+    });
+
+    // 2. should trigger onConnect in BG.
+    port = browser.runtime.connect({name: "from-cs"});
+    let postMessageCount1 = 0;
+    port.onMessage.addListener(msg => {
+      // 9. onMessage by port.postMessage in BG.
+      browser.test.assertEq("from BG to port", msg);
+      browser.test.assertEq(1, ++postMessageCount1,
+        "CS port.onMessage should be called once");
+
+      // 10. should trigger port.onMessage in BG.
+      port.postMessage("from CS to port");
+    });
+  }
+
+  let extensionData = {
+    background: `(${backgroundScript})();`,
+    manifest: {
+      content_scripts: [{
+        js: ["contentscript.js"],
+        matches: ["http://mochi.test/*/file_sample.html"],
+      }],
+    },
+    files: {
+      "contentscript.js": `(${contentScript})();`,
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  info("extension loaded");
+
+  yield extension.awaitMessage("ready");
+
+  let win = window.open("file_sample.html");
+  yield extension.awaitFinish("ping pong done");
+  win.close();
+
+  yield extension.unload();
+  info("extension unloaded");
+});
+</script>
+</body>