Bug 1258360 - [webext] Implement runtime.onMessageExternal and runtime.onConnectExternal. r?kmag draft
authorLuca Greco <lgreco@mozilla.com>
Mon, 09 May 2016 15:54:29 +0200
changeset 364834 c3b9caddf860947e80b6bbedc07e0f53971d9013
parent 364830 da2b27e1aabd9e23870c09072753faba6ad0a42d
child 364835 99488d71bc27c97591b43c469782da6159722427
push id17573
push userluca.greco@alcacoop.it
push dateMon, 09 May 2016 13:55:29 +0000
reviewerskmag
bugs1258360
milestone49.0a1
Bug 1258360 - [webext] Implement runtime.onMessageExternal and runtime.onConnectExternal. r?kmag MozReview-Commit-ID: DpQStoV6s8P
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/MessageChannel.jsm
toolkit/components/extensions/ext-runtime.js
toolkit/components/extensions/schemas/runtime.json
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -208,17 +208,18 @@ class BaseContext {
    * A wrapper around MessageChannel.sendMessage which adds the extension ID
    * to the recipient object, and ensures replies are not processed after the
    * context has been unloaded.
    */
   sendMessage(target, messageName, data, options = {}) {
     options.recipient = options.recipient || {};
     options.sender = options.sender || {};
 
-    options.recipient.extensionId = this.extension.id;
+    // Use the options.recipient if any.
+    options.recipient.extensionId = options.recipient.extensionId || this.extension.id;
     options.sender.extensionId = this.extension.id;
     options.sender.contextId = this.contextId;
 
     return MessageChannel.sendMessage(target, messageName, data, options);
   }
 
   get lastError() {
     this.checkedLastError = true;
@@ -926,19 +927,20 @@ Messenger.prototype = {
         } else {
           return Promise.reject({message: error.message});
         }
       });
 
     return this.context.wrapPromise(promise, responseCallback);
   },
 
-  onMessage(name) {
+  onMessage(name, isExternal = false) {
     return new SingletonEventManager(this.context, name, callback => {
       let listener = {
+        isExternal,
         messageFilterPermissive: this.filter,
 
         receiveMessage: ({target, data: message, sender, recipient}) => {
           if (this.delegate) {
             this.delegate.getSender(this.context, target, sender);
           }
 
           let sendResponse;
@@ -977,19 +979,20 @@ Messenger.prototype = {
     let portId = nextPortId++;
     let port = new Port(this.context, messageManager, name, portId, null);
     let msg = {name, portId};
     // TODO: Disconnect the port if no response?
     this._sendMessage(messageManager, "Extension:Connect", msg, recipient);
     return port.api();
   },
 
-  onConnect(name) {
+  onConnect(name, isExternal = false) {
     return new SingletonEventManager(this.context, name, callback => {
       let listener = {
+        isExternal,
         messageFilterPermissive: this.filter,
 
         receiveMessage: ({target, data: message, sender, recipient}) => {
           let {name, portId} = message;
           let mm = getMessageManager(target);
           if (this.delegate) {
             this.delegate.getSender(this.context, target, sender);
           }
--- a/toolkit/components/extensions/MessageChannel.jsm
+++ b/toolkit/components/extensions/MessageChannel.jsm
@@ -149,36 +149,58 @@ 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));
 
     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} 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)) {
+      let matchStrict = MessageChannel.matchesFilter(handler.messageFilterStrict || {},
+                                                     recipient);
+      let matchPermissive = MessageChannel.matchesFilter(handler.messageFilterPermissive || {},
+                                                         recipient, false);
+
+      if (matchStrict && matchPermissive) {
+        // Special filtering related to cross-addons messages and port connection:
+        if (messageName == "Extension:Message" || messageName == "Extension:Connect") {
+          if (handler.isExternal &&
+              (sender.extensionId === recipient.extensionId)) {
+            // Skip the message if is not a cross-addons message and
+            // the handler want to receive only external messages.
+            continue;
+          }
+
+          if (!handler.isExternal &&
+              (sender.extensionId !== recipient.extensionId)) {
+            // Skip the message if is a cross-addons message and
+            // the handler do not want to receive any external message.
+            continue;
+          }
+        }
+
         yield handler;
       }
     }
   }
 
   /**
    * Registers a handler for the given message.
    *
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -16,18 +16,20 @@ extensions.registerSchemaAPI("runtime", 
         return () => {
           extension.onStartup = null;
         };
       }).api(),
 
       onInstalled: ignoreEvent(context, "runtime.onInstalled"),
 
       onMessage: context.messenger.onMessage("runtime.onMessage"),
+      onConnect: context.messenger.onConnect("runtime.onConnect"),
 
-      onConnect: context.messenger.onConnect("runtime.onConnect"),
+      onMessageExternal: context.messenger.onMessage("runtime.onMessageExternal", true),
+      onConnectExternal: context.messenger.onConnect("runtime.onConnectExternal", true),
 
       connect: function(extensionId, connectInfo) {
         let name = connectInfo !== null && connectInfo.name || "";
         let recipient = extensionId !== null ? {extensionId} : {extensionId: extension.id};
 
         return context.messenger.connect(Services.cpmm, name, recipient);
       },
 
--- a/toolkit/components/extensions/schemas/runtime.json
+++ b/toolkit/components/extensions/schemas/runtime.json
@@ -461,17 +461,16 @@
         "type": "function",
         "description": "Fired when a connection is made from either an extension process or a content script.",
         "parameters": [
           {"$ref": "Port", "name": "port"}
         ]
       },
       {
         "name": "onConnectExternal",
-        "unsupported": true,
         "type": "function",
         "description": "Fired when a connection is made from another extension.",
         "parameters": [
           {"$ref": "Port", "name": "port"}
         ]
       },
       {
         "name": "onMessage",
@@ -485,17 +484,16 @@
         "returns": {
           "type": "boolean",
           "optional": true,
           "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
         }
       },
       {
         "name": "onMessageExternal",
-        "unsupported": true,
         "type": "function",
         "description": "Fired when a message is sent from another extension/app. Cannot be used in a content script.",
         "parameters": [
           {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."},
           {"name": "sender", "$ref": "MessageSender" },
           {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." }
         ],
         "returns": {