Bug 1286124 - Part 2/2 - Do not deliver messages to the sender's frame
MozReview-Commit-ID: 8xZPDIJyMEo
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -1300,16 +1300,21 @@ Messenger.prototype = {
return this.context.wrapPromise(promise, responseCallback);
},
onMessage(name) {
return new SingletonEventManager(this.context, name, callback => {
let listener = {
messageFilterPermissive: this.filter,
+ filterMessage: (sender, recipient) => {
+ // Ignore the message if it was sent by this Messenger.
+ return !MessageChannel.matchesFilter(this.sender, sender);
+ },
+
receiveMessage: ({target, data: message, sender, recipient}) => {
if (!this.context.active) {
return;
}
if (this.delegate) {
this.delegate.getSender(this.context, target, sender);
}
@@ -1355,16 +1360,21 @@ Messenger.prototype = {
return port.api();
},
onConnect(name) {
return new SingletonEventManager(this.context, name, callback => {
let listener = {
messageFilterPermissive: this.filter,
+ filterMessage: (sender, recipient) => {
+ // Ignore the port if it was created by this Messenger.
+ return !MessageChannel.matchesFilter(this.sender, sender);
+ },
+
receiveMessage: ({target, data: message, sender, recipient}) => {
let {name, portId} = message;
let mm = getMessageManager(target);
if (this.delegate) {
this.delegate.getSender(this.context, target, sender);
}
let port = new Port(this.context, mm, name, portId, sender);
this.context.runSafeWithoutClone(callback, port.api());
--- a/toolkit/components/extensions/MessageChannel.jsm
+++ b/toolkit/components/extensions/MessageChannel.jsm
@@ -150,50 +150,53 @@ class FilteringMessageManager {
this.handlers = new Map();
}
/**
* Receives a message from our message manager, maps it to a handler, and
* passes the result to our message callback.
*/
receiveMessage({data, target}) {
- let handlers = Array.from(this.getHandlers(data.messageName, data.recipient));
+ let handlers = Array.from(this.getHandlers(data.messageName, data.sender, data.recipient));
data.target = target;
this.callback(handlers, data);
}
/**
* Iterates over all handlers for the given message name. If `recipient`
* is provided, only iterates over handlers whose filters match it.
*
* @param {string|number} messageName
* The message for which to return handlers.
+ * @param {object} sender
+ * The sender data on which to filter handlers.
* @param {object} recipient
* The recipient data on which to filter handlers.
*/
- * getHandlers(messageName, recipient) {
+ * getHandlers(messageName, sender, recipient) {
let handlers = this.handlers.get(messageName) || new Set();
for (let handler of handlers) {
if (MessageChannel.matchesFilter(handler.messageFilterStrict || {}, recipient) &&
- MessageChannel.matchesFilter(handler.messageFilterPermissive || {}, recipient, false)) {
+ MessageChannel.matchesFilter(handler.messageFilterPermissive || {}, recipient, false) &&
+ (!handler.filterMessage || handler.filterMessage(sender, recipient))) {
yield handler;
}
}
}
/**
* Registers a handler for the given message.
*
* @param {string} messageName
* The internal message name for which to register the handler.
* @param {object} handler
* An opaque handler object. The object may have a
* `messageFilterStrict` and/or a `messageFilterPermissive`
- * property on which to filter messages.
+ * property and/or a `filterMessage` method on which to filter messages.
*
* Final dispatching is handled by the message callback passed to
* the constructor.
*/
addHandler(messageName, handler) {
if (!this.handlers.has(messageName)) {
this.handlers.set(messageName, new Set());
}
@@ -431,16 +434,20 @@ this.MessageChannel = {
* `strict=true`.
*
* messageFilterPermissive:
* An object containing arbitrary properties on which to filter
* received messages. Messages will only be dispatched to this
* object if the `recipient` object passed to `sendMessage`
* matches this filter, as determined by `matchesFilter` with
* `strict=false`.
+ *
+ * filterMessage:
+ * An optional function that prevents the handler from handling a
+ * message by returning `false`. See `getHandlers` for the parameters.
*/
addListener(targets, messageName, handler) {
for (let target of [].concat(targets)) {
this.messageManagers.get(target).addHandler(messageName, handler);
}
},
/**
@@ -479,17 +486,18 @@ this.MessageChannel = {
* An object containing any of the following properties:
* @param {object} [options.recipient]
* A structured-clone-compatible object to identify the message
* recipient. The object must match the `messageFilterStrict` and
* `messageFilterPermissive` filters defined by recipients in order
* for the message to be received.
* @param {object} [options.sender]
* A structured-clone-compatible object to identify the message
- * sender. This object may also be used as a filter to prematurely
+ * sender. This object may also be used to avoid delivering the
+ * message to the sender, and as a filter to prematurely
* abort responses when the sender is being destroyed.
* @see `abortResponses`.
* @param {integer} [options.responseType=RESPONSE_SINGLE]
* Specifies the type of response expected. See the `RESPONSE_*`
* contents for details.
* @returns {Promise}
*/
sendMessage(target, messageName, data, options = {}) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_self.js
@@ -0,0 +1,54 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+add_task(function* test_sendMessage_to_self_should_not_trigger_onMessage() {
+ function background() {
+ browser.runtime.onMessage.addListener(msg => {
+ browser.test.assertEq("msg from child", msg);
+ browser.test.notifyPass("sendMessage did not call same-frame onMessage");
+ });
+
+ browser.test.onMessage.addListener(msg => {
+ browser.test.assertEq("sendMessage with a listener in another frame", msg);
+ browser.runtime.sendMessage("should only reach another frame");
+ });
+
+ browser.runtime.sendMessage("should not trigger same-frame onMessage")
+ .then(reply => {
+ browser.test.fail(`Unexpected reply to sendMessage: ${reply}`);
+ }, err => {
+ browser.test.assertEq("Could not establish connection. Receiving end does not exist.", err.message);
+
+ let anotherFrame = document.createElement("iframe");
+ anotherFrame.src = browser.extension.getURL("extensionpage.html");
+ document.body.appendChild(anotherFrame);
+ });
+ }
+
+ function lastScript() {
+ browser.runtime.onMessage.addListener(msg => {
+ browser.test.assertEq("should only reach another frame", msg);
+ browser.runtime.sendMessage("msg from child");
+ });
+ browser.test.sendMessage("sendMessage callback called");
+ }
+
+ let extensionData = {
+ background,
+ files: {
+ "lastScript.js": lastScript,
+ "extensionpage.html": `<!DOCTYPE html><meta charset="utf-8"><script src="lastScript.js"></script>`,
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ yield extension.startup();
+
+ yield extension.awaitMessage("sendMessage callback called");
+ extension.sendMessage("sendMessage with a listener in another frame");
+ yield extension.awaitFinish("sendMessage did not call same-frame onMessage");
+
+ yield extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -40,16 +40,17 @@ skip-if = release_build
[test_ext_manifest_content_security_policy.js]
[test_ext_manifest_incognito.js]
[test_ext_onmessage_removelistener.js]
[test_ext_runtime_connect_no_receiver.js]
[test_ext_runtime_getPlatformInfo.js]
[test_ext_runtime_sendMessage.js]
[test_ext_runtime_sendMessage_errors.js]
[test_ext_runtime_sendMessage_no_receiver.js]
+[test_ext_runtime_sendMessage_self.js]
[test_ext_schemas.js]
[test_ext_schemas_api_injection.js]
[test_ext_schemas_restrictions.js]
[test_ext_simple.js]
[test_ext_storage.js]
[test_getAPILevelForWindow.js]
[test_locale_converter.js]
[test_locale_data.js]