Bug 1287245 - Ensure globally unique internal port IDs
MozReview-Commit-ID: FXNP5MadlMx
--- 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>