Bug 286746 - Invoke sendMessage callback even if there are no listeners draft
authorRob Wu <rob@robwu.nl>
Thu, 14 Jul 2016 20:34:40 -0700
changeset 388534 c60b97b335486e047178ac0f1964e0ba11c61be8
parent 386302 214884d507ee369c1cf14edb26527c4f9a97bf48
child 388535 f6937a105f5ce1759ab892c2dd15576f4a386ab1
push id23199
push userbmo:rob@robwu.nl
push dateSat, 16 Jul 2016 05:01:30 +0000
bugs286746
milestone50.0a1
Bug 286746 - Invoke sendMessage callback even if there are no listeners MozReview-Commit-ID: HLIC3ZRcwRm
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/MessageChannel.jsm
toolkit/components/extensions/test/mochitest/mochitest.ini
toolkit/components/extensions/test/mochitest/test_ext_runtime_sendMessage_no_receiver.html
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -1075,16 +1075,18 @@ function getMessageManager(target) {
 //    getSender(context, messageManagerTarget, sender): returns a MessageSender
 //      See https://developer.chrome.com/extensions/runtime#type-MessageSender.
 function Messenger(context, messageManagers, sender, filter, delegate) {
   this.context = context;
   this.messageManagers = messageManagers;
   this.sender = sender;
   this.filter = filter;
   this.delegate = delegate;
+
+  MessageChannel.setupMessageManagers(messageManagers);
 }
 
 Messenger.prototype = {
   _sendMessage(messageManager, message, data, recipient) {
     let options = {
       recipient,
       sender: this.sender,
       responseType: MessageChannel.RESPONSE_FIRST,
--- a/toolkit/components/extensions/MessageChannel.jsm
+++ b/toolkit/components/extensions/MessageChannel.jsm
@@ -333,16 +333,41 @@ this.MessageChannel = {
    * If multiple message managers matching the specified recipient tag
    * are listening for a message, all listeners are notified, and all
    * responses are returned as an array, once all listeners have
    * replied.
    */
   RESPONSE_ALL: 2,
 
   /**
+   * Registers placeholder message listeners for the MessageChannel:Message
+   * handler, to make sure that the message always receives a reply, even if
+   * no handlers have been registered yet.
+   *
+   * @param {[nsIMessageSender]} messageManagers
+   */
+  setupMessageManagers(messageManagers) {
+    for (let mm of messageManagers) {
+      let listener = {
+        receiveMessage: (message) => {
+          mm.removeMessageListener(MESSAGE_MESSAGE, listener);
+
+          // The FilteringMessageManager instance is lazily initialized.
+          // If it does not exist yet, create it (implicitly via .get())
+          // and forward the message.
+          if (!this.messageManagers.has(mm)) {
+            this.messageManagers.get(mm).receiveMessage(message);
+          }
+        },
+      };
+      mm.addMessageListener(MESSAGE_MESSAGE, listener);
+    }
+  },
+
+  /**
    * Returns true if the peroperties of the `data` object match those in
    * the `filter` object. Matching is done on a strict equality basis,
    * and the behavior varies depending on the value of the `strict`
    * parameter.
    *
    * @param {object} filter
    *    The filter object to match against.
    * @param {object} data
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -61,16 +61,17 @@ skip-if = buildapp == 'b2g' # JavaScript
 [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_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_runtime_sendMessage_no_receiver.html]
 [test_ext_sandbox_var.html]
 [test_ext_sendmessage_reply.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_sendmessage_reply2.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_sendmessage_doublereply.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_storage.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_sendMessage_no_receiver.html
@@ -0,0 +1,41 @@
+<!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_sendMessage_without_listener() {
+  function backgroundScript() {
+    browser.runtime.sendMessage("msg", reply => {
+      browser.test.assertEq(undefined, reply);
+      let lastError = browser.runtime.lastError;
+      browser.test.assertTrue(lastError, "lastError must be set");
+      browser.test.assertEq("Could not establish connection. Receiving end does not exist.", lastError.message);
+      browser.test.notifyPass("sendMessage callback was invoked");
+    });
+  }
+  let extensionData = {
+    background: `(${backgroundScript})();`,
+    manifest: {},
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  info("extension loaded");
+
+  yield extension.awaitFinish("sendMessage callback was invoked");
+
+  yield extension.unload();
+  info("extension unloaded");
+});
+</script>
+</body>