Bug 286746 - Invoke sendMessage callback even if there are no listeners
MozReview-Commit-ID: HLIC3ZRcwRm
--- 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>