new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_cross_addons_messaging.html
@@ -0,0 +1,325 @@
+<!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 type="text/javascript">
+"use strict";
+
+/**
+ * This test file contains test cases related to cross addon messages exchange
+ * based on `runtime.onMessageExternal` and `runtime.onConnectExternal`,
+ * each test coordinates a message exchange between two extensions,
+ * a sender and receiver, and makes assertions on the collected data
+ * (messages, replies, ports and sender info).
+ *
+ * - `test_onMessageExternal`: this test case coordinates a message
+ * exchange based on `runtime.sendMessage` and `runtime.onMessageExternal`,
+ * implemented in the following background scripts:
+ *
+ * - `backgroundExternalMessageSender`
+ * - `backgroundExternalMessageReceiver`
+ *
+ * - `test_onConnectExternal`: this test case coordinates a message
+ * exchange based on `runtime.connect` and `runtime.onConnectExternal`,
+ * implemented in the following background scripts:
+ *
+ * - `backgroundExternalConnectSender`
+ * - `backgroundExternalConnectReceiver`
+ *
+ */
+function backgroundExternalMessageSender({type, targetAddonId, msgToSend}) {
+ browser.test.onMessage.addListener(msg => {
+ if (msg == "send-internal-message") {
+ browser.runtime.sendMessage("internal-send-message");
+ } else if (msg == "send-external-message") {
+ browser.runtime.sendMessage(targetAddonId, msgToSend, {}, response => {
+ browser.test.sendMessage(`${type}.got-reply`, {
+ msg_reply: response,
+ });
+ });
+ browser.test.log(`${browser.runtime.id} sent a message to ${targetAddonId}`);
+ }
+ });
+
+ browser.runtime.onMessage.addListener(msg => {
+ if (msg == "internal-send-message") {
+ browser.test.log(`Internal send message received from the internal listener as expected`);
+ browser.test.notifyPass("send-internal-message.done");
+ } else {
+ browser.test.fail(`runtime.onMessage received an unexpected internal message: "${msg}"`);
+ browser.test.notifyFail("send-internal-message.done");
+ }
+ });
+
+ // This `runtime.onMessageExternal` should not receive any message, if it does
+ // it means that the `MessageChannel.getHandlers` method is not filtering
+ // the `isExternal == false` handlers correctly.
+ browser.runtime.onMessageExternal.addListener(msg => {
+ browser.test.fail(`runtime.onMessageExternal received an unexpected message: "${msg}"`);
+ browser.test.notifyFail("send-internal-message.done");
+ });
+
+ browser.test.sendMessage(`${type}.ready`, {
+ id: browser.runtime.id,
+ uuid: String(window.location).match("://(.*)/")[1],
+ bgURL: String(window.location),
+ });
+}
+
+function backgroundExternalMessageReceiver({type}) {
+ // This `runtime.onMessage` should not receive any message, if it does
+ // it means that the `MessageChannel.getHandlers` method is not filtering
+ // the `isExternal == false` handlers correctly.
+ browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
+ browser.test.fail(`runtime.onMessage received an unexpected msg: ${msg}`);
+ browser.test.sendMessage(`${type}.got-message`, {
+ msg, sender,
+ });
+ });
+
+ browser.runtime.onMessageExternal.addListener((msg, sender, sendResponse) => {
+ browser.test.log(`${browser.runtime.id} received a message from ${sender.extensionId}`);
+ let msg_reply = `${type}.reply`;
+
+ sendResponse(msg_reply);
+
+ browser.test.sendMessage(`${type}.got-message`, {
+ msg, sender, msg_reply,
+ });
+ });
+
+ browser.test.sendMessage(`${type}.ready`, {
+ id: browser.runtime.id,
+ uuid: String(window.location).match("://(.*)/")[1],
+ bgURL: String(window.location),
+ });
+}
+
+function backgroundExternalConnectSender({type, targetAddonId}) {
+ browser.test.onMessage.addListener(msg => {
+ if (msg == "send-internal-connect") {
+ browser.runtime.onConnect.addListener(port => {
+ port.postMessage("internal-port-message");
+ });
+
+ let port = browser.runtime.connect();
+ port.onMessage.addListener(msg => {
+ if (msg == "internal-port-message") {
+ browser.test.log(`Got the expected internal message on the the internal listener`);
+ browser.test.notifyPass("send-internal-connect.done");
+ } else {
+ browser.test.fail(`port.onMessage received an unexpected internal message: "${msg}"`);
+ browser.test.notifyFail("send-internal-message.done");
+ }
+ port.disconnect();
+ });
+ } else if (msg == "send-external-connect") {
+ let port = browser.runtime.connect(targetAddonId);
+ browser.test.log(`${browser.runtime.id} connected to ${targetAddonId}`);
+
+ port.onMessage.addListener(msg => {
+ browser.test.log(`${browser.runtime.id} received a message from ${targetAddonId}`);
+
+ browser.test.sendMessage(`${type}.got-message`, {msg});
+ });
+ }
+ });
+
+ // This `runtime.onConnectExternal` should not receive any port, if it does
+ // it means that the `MessageChannel.getHandlers` method is not filtering
+ // the `isExternal == true` handlers correctly.
+ browser.runtime.onConnectExternal.addListener(port => {
+ browser.test.fail(`runtime.onConnectExternal received an unexpected port from: "${JSON.stringify(port.sender)}"`);
+ browser.test.notifyFail("send-internal-connect.done");
+ });
+
+ browser.test.sendMessage(`${type}.ready`, {
+ id: browser.runtime.id,
+ uuid: String(window.location).match("://(.*)/")[1],
+ bgURL: String(window.location),
+ });
+}
+
+function backgroundExternalConnectReceiver({type, msgToSend}) {
+ // This `runtime.onConnect` should not receive any port, if it does
+ // it means that the `MessageChannel.getHandlers` method is not filtering
+ // the `isExternal == false` handlers correctly.
+ browser.runtime.onConnect.addListener(port => {
+ let sender = port.sender || {};
+ browser.test.fail(`runtime.onConnect received an unexpected port from: ${JSON.stringify(sender)}`);
+ browser.test.sendMessage(`${type}.got-port`, {sender});
+ });
+
+ browser.runtime.onConnectExternal.addListener(port => {
+ let sender = port.sender || {};
+ browser.test.log(`${browser.runtime.id} received a connection from ${sender.extensionId}`);
+ browser.test.sendMessage(`${type}.got-port`, {sender});
+
+ port.postMessage(msgToSend);
+
+ browser.test.log(`${browser.runtime.id} sent a message to ${sender.extensionId}`);
+ });
+
+ browser.test.sendMessage(`${type}.ready`, {
+ id: browser.runtime.id,
+ uuid: String(window.location).match("://(.*)/")[1],
+ bgURL: String(window.location),
+ });
+}
+
+function makeExtension(params) {
+ let {type} = params;
+ let backgroundScript;
+ switch (type) {
+ case "external-message-sender":
+ backgroundScript = backgroundExternalMessageSender;
+ break;
+ case "external-message-receiver":
+ backgroundScript = backgroundExternalMessageReceiver;
+ break;
+ case "external-connect-sender":
+ backgroundScript = backgroundExternalConnectSender;
+ break;
+ case "external-connect-receiver":
+ backgroundScript = backgroundExternalConnectReceiver;
+ break;
+ default:
+ throw new Error("makeExtension - unknown extension of type '${type}'");
+ }
+
+ let extensionData = {
+ background: `(${backgroundScript})(${JSON.stringify(params)});`,
+ };
+ return extensionData;
+}
+
+add_task(function* test_onMessageExternal() {
+ let receiverExtension = ExtensionTestUtils.loadExtension(
+ makeExtension({
+ type: "external-message-receiver",
+ })
+ );
+
+ let senderExtension = ExtensionTestUtils.loadExtension(
+ makeExtension({
+ type: "external-message-sender",
+ targetAddonId: receiverExtension.id,
+ msgToSend: "cross-addons-msg",
+ })
+ );
+
+ yield Promise.all([senderExtension.startup(), receiverExtension.startup()]);
+
+ let [
+ senderInfo,
+ // NOTE: receiverInfo is currently unused
+ ] = yield Promise.all([
+ senderExtension.awaitMessage("external-message-sender.ready"),
+ receiverExtension.awaitMessage("external-message-receiver.ready"),
+ ]);
+
+ senderExtension.sendMessage("send-internal-message");
+ yield senderExtension.awaitFinish("send-internal-message.done");
+
+ senderExtension.sendMessage("send-external-message");
+
+ let [
+ finalReceiverInfo,
+ finalSenderInfo,
+ ] = yield Promise.all([
+ receiverExtension.awaitMessage("external-message-receiver.got-message"),
+ senderExtension.awaitMessage("external-message-sender.got-reply"),
+ ]);
+
+ ok(finalSenderInfo, "The sender extension has got a reply");
+ is(finalSenderInfo.msg_reply, finalReceiverInfo.msg_reply,
+ "The receiver has got the expected message");
+
+ ok(finalReceiverInfo, "The receiver extension has got a message");
+ is(finalReceiverInfo.msg, "cross-addons-msg", "The receiver has got the expected message");
+
+ is(finalReceiverInfo.sender && finalReceiverInfo.sender.url,
+ senderInfo.bgURL, "The receiver has got the expected sender.url");
+
+ // NOTE: sender.id should be probably equal to the sender addon id instead of its uuid,
+ // so that the received could be able to decide if he wants to receive messages from that
+ // particular addon. (current the sender addon id is in the sender as sender.extensionId)
+ is(finalReceiverInfo.sender && finalReceiverInfo.sender.id,
+ senderInfo.uuid, "The receiver has got the expected sender.id");
+ is(finalReceiverInfo.sender && finalReceiverInfo.sender.extensionId,
+ senderInfo.id, "The receiver has got the expected sender.extensionId");
+
+ yield senderExtension.unload();
+ yield receiverExtension.unload();
+});
+
+add_task(function* test_onConnectExternal() {
+ let receiverExtension = ExtensionTestUtils.loadExtension(
+ makeExtension({
+ type: "external-connect-receiver",
+ msgToSend: "cross-addons-msg",
+ })
+ );
+
+ let senderExtension = ExtensionTestUtils.loadExtension(
+ makeExtension({
+ type: "external-connect-sender",
+ targetAddonId: receiverExtension.id,
+ })
+ );
+
+ yield Promise.all([senderExtension.startup(), receiverExtension.startup()]);
+
+ let [
+ senderInfo,
+ // NOTE: receiverInfo is currently unused
+ ] = yield Promise.all([
+ senderExtension.awaitMessage("external-connect-sender.ready"),
+ receiverExtension.awaitMessage("external-connect-receiver.ready"),
+ ]);
+
+ senderExtension.sendMessage("send-internal-connect");
+ yield senderExtension.awaitFinish("send-internal-connect.done");
+
+ senderExtension.sendMessage("send-external-connect");
+
+ let [
+ finalSenderInfo,
+ finalReceiverInfo,
+ ] = yield Promise.all([
+ senderExtension.awaitMessage("external-connect-sender.got-message"),
+ receiverExtension.awaitMessage("external-connect-receiver.got-port"),
+ ]);
+
+ ok(finalReceiverInfo, "The receiver extension has got a port");
+ is(finalReceiverInfo.sender && finalReceiverInfo.sender.url,
+ senderInfo.bgURL, "The receiver has got the expected sender.url");
+
+ // NOTE: sender.id should be probably equal to the sender addon id instead of its uuid,
+ // so that the received could be able to decide if he wants to receive messages from that
+ // particular addon. (current the sender addon id is in the sender as sender.extensionId)
+ is(finalReceiverInfo.sender && finalReceiverInfo.sender.id,
+ senderInfo.uuid, "The receiver has got the expected sender.id");
+ is(finalReceiverInfo.sender && finalReceiverInfo.sender.extensionId,
+ senderInfo.id, "The receiver has got the expected sender.extensionId");
+
+ ok(finalSenderInfo, "The sender extension has got a message");
+ is(finalSenderInfo.msg, "cross-addons-msg", "The sender has got the expected message");
+
+ yield senderExtension.unload();
+ yield receiverExtension.unload();
+});
+
+
+</script>
+
+</body>
+</html>