Bug 1286124 - Part 2/2 - Do not deliver messages to the sender's frame r=kmag
authorRob Wu <rob@robwu.nl>
Wed, 13 Jul 2016 21:33:56 -0700
changeset 311755 9a737024c5cc7632fa66a559ba9a4ae09a1a9451
parent 311754 39fd730e7fb96852adaf2e04c03540b60921bf1f
child 311756 08e4eadbb77d5789e41559ced20dc3fef8b93002
push id20417
push userryanvm@gmail.com
push dateTue, 30 Aug 2016 13:55:34 +0000
treeherderfx-team@b18c8bcdc116 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1286124
milestone51.0a1
Bug 1286124 - Part 2/2 - Do not deliver messages to the sender's frame r=kmag MozReview-Commit-ID: 8xZPDIJyMEo
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/MessageChannel.jsm
toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_self.js
toolkit/components/extensions/test/xpcshell/xpcshell.ini
--- 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
@@ -41,16 +41,17 @@ skip-if = release_build
 [test_ext_manifest_incognito.js]
 [test_ext_manifest_minimum_chrome_version.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]