Bug 1316396: Part 5 - Move MessageManagerProxy to ExtensionUtils, and add support for proxied listeners. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Thu, 10 Nov 2016 10:50:36 -0800
changeset 352140 e2aa5d3af1089e05650634b8f24e675ae99a902f
parent 352139 d5fa01b12572b777103c15f55ef09281215f917e
child 352141 6469ada96bff9afb7f40a7c2762f253fcff49a49
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1316396
milestone52.0a1
Bug 1316396: Part 5 - Move MessageManagerProxy to ExtensionUtils, and add support for proxied listeners. r=aswan MozReview-Commit-ID: KhinS46k0yW
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/MessageChannel.jsm
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -993,16 +993,185 @@ function findPathInObject(obj, path, pri
   }
 
   if (typeof obj === "function") {
     return obj.bind(parent);
   }
   return obj;
 }
 
+/**
+ * Acts as a proxy for a message manager or message manager owner, and
+ * tracks docShell swaps so that messages are always sent to the same
+ * receiver, even if it is moved to a different <browser>.
+ *
+ * @param {nsIMessageSender|Element} target
+ *        The target message manager on which to send messages, or the
+ *        <browser> element which owns it.
+ */
+class MessageManagerProxy {
+  constructor(target) {
+    this.listeners = new DefaultMap(() => new Map());
+
+    if (target instanceof Ci.nsIMessageSender) {
+      Object.defineProperty(this, "messageManager", {
+        value: target,
+        configurable: true,
+        writable: true,
+      });
+    } else {
+      this.addListeners(target);
+    }
+  }
+
+  /**
+   * Disposes of the proxy object, removes event listeners, and drops
+   * all references to the underlying message manager.
+   *
+   * Must be called before the last reference to the proxy is dropped,
+   * unless the underlying message manager or <browser> is also being
+   * destroyed.
+   */
+  dispose() {
+    if (this.eventTarget) {
+      this.removeListeners(this.eventTarget);
+      this.eventTarget = null;
+    } else {
+      this.messageManager = null;
+    }
+  }
+
+  /**
+   * Returns true if the given target is the same as, or owns, the given
+   * message manager.
+   *
+   * @param {nsIMessageSender|MessageManagerProxy|Element} target
+   *        The message manager, MessageManagerProxy, or <browser>
+   *        element agaisnt which to match.
+   * @param {nsIMessageSender} messageManager
+   *        The message manager against which to match `target`.
+   *
+   * @returns {boolean}
+   *        True if `messageManager` is the same object as `target`, or
+   *        `target` is a MessageManagerProxy or <browser> element that
+   *        is tied to it.
+   */
+  static matches(target, messageManager) {
+    return target === messageManager || target.messageManager === messageManager;
+  }
+
+  /**
+   * @property {nsIMessageSender|null} messageManager
+   *        The message manager that is currently being proxied. This
+   *        may change during the life of the proxy object, so should
+   *        not be stored elsewhere.
+   */
+  get messageManager() {
+    return this.eventTarget && this.eventTarget.messageManager;
+  }
+
+  /**
+   * Sends a message on the proxied message manager.
+   *
+   * @param {array} args
+   *        Arguments to be passed verbatim to the underlying
+   *        sendAsyncMessage method.
+   * @returns {undefined}
+   */
+  sendAsyncMessage(...args) {
+    return this.messageManager.sendAsyncMessage(...args);
+  }
+
+  /**
+   * Adds a message listener to the current message manager, and
+   * transfers it to the new message manager after a docShell swap.
+   *
+   * @param {string} message
+   *        The name of the message to listen for.
+   * @param {nsIMessageListener} listener
+   *        The listener to add.
+   * @param {boolean} [listenWhenClosed = false]
+   *        If true, the listener will receive messages which were sent
+   *        after the remote side of the listener began closing.
+   */
+  addMessageListener(message, listener, listenWhenClosed = false) {
+    this.messageManager.addMessageListener(message, listener, listenWhenClosed);
+    this.listeners.get(message).set(listener, listenWhenClosed);
+  }
+
+  /**
+   * Adds a message listener from the current message manager.
+   *
+   * @param {string} message
+   *        The name of the message to stop listening for.
+   * @param {nsIMessageListener} listener
+   *        The listener to remove.
+   */
+  removeMessageListener(message, listener) {
+    this.messageManager.removeMessageListener(message, listener);
+
+    let listeners = this.listeners.get(message);
+    listeners.delete(listener);
+    if (!listeners.size) {
+      this.listeners.delete(message);
+    }
+  }
+
+  /**
+   * @private
+   * Iterates over all of the currently registered message listeners.
+   */
+  * iterListeners() {
+    for (let [message, listeners] of this.listeners) {
+      for (let [listener, listenWhenClosed] of listeners) {
+        yield {message, listener, listenWhenClosed};
+      }
+    }
+  }
+
+  /**
+   * @private
+   * Adds docShell swap listeners to the message manager owner.
+   *
+   * @param {Element} target
+   *        The target element.
+   */
+  addListeners(target) {
+    target.addEventListener("SwapDocShells", this);
+
+    for (let {message, listener, listenWhenClosed} of this.iterListeners()) {
+      target.addMessageListener(message, listener, listenWhenClosed);
+    }
+
+    this.eventTarget = target;
+  }
+
+  /**
+   * @private
+   * Removes docShell swap listeners to the message manager owner.
+   *
+   * @param {Element} target
+   *        The target element.
+   */
+  removeListeners(target) {
+    target.removeEventListener("SwapDocShells", this);
+
+    for (let {message, listener} of this.iterListeners()) {
+      target.removeMessageListener(message, listener);
+    }
+  }
+
+  handleEvent(event) {
+    if (event.type == "SwapDocShells") {
+      this.removeListeners(this.eventTarget);
+      this.addListeners(event.detail);
+    }
+  }
+}
+
 this.ExtensionUtils = {
   defineLazyGetter,
   detectLanguage,
   extend,
   findPathInObject,
   flushJarCache,
   getConsole,
   getInnerWindowID,
@@ -1022,12 +1191,13 @@ this.ExtensionUtils = {
   stylesheetMap,
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
   EventManager,
   ExtensionError,
   IconDetails,
   LocaleData,
+  MessageManagerProxy,
   PlatformInfo,
   SingletonEventManager,
   SpreadArgs,
 };
--- a/toolkit/components/extensions/MessageChannel.jsm
+++ b/toolkit/components/extensions/MessageChannel.jsm
@@ -103,133 +103,25 @@ this.EXPORTED_SYMBOLS = ["MessageChannel
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
+                                  "resource://gre/modules/ExtensionUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
-/**
- * Acts as a proxy for a message manager or message manager owner, and
- * tracks docShell swaps so that messages are always sent to the same
- * receiver, even if it is moved to a different <browser>.
- *
- * Currently only proxies message sending functions, and does not handle
- * transfering listeners in any way.
- *
- * @param {nsIMessageSender|Element} target
- *        The target message manager on which to send messages, or the
- *        <browser> element which owns it.
- */
-class MessageManagerProxy {
-  constructor(target) {
-    if (target instanceof Ci.nsIMessageSender) {
-      Object.defineProperty(this, "messageManager", {
-        value: target,
-        configurable: true,
-        writable: true,
-      });
-    } else {
-      this.addListeners(target);
-    }
-  }
 
-  /**
-   * Disposes of the proxy object, removes event listeners, and drops
-   * all references to the underlying message manager.
-   *
-   * Must be called before the last reference to the proxy is dropped,
-   * unless the underlying message manager or <browser> is also being
-   * destroyed.
-   */
-  dispose() {
-    if (this.eventTarget) {
-      this.removeListeners(this.eventTarget);
-      this.eventTarget = null;
-    } else {
-      this.messageManager = null;
-    }
-  }
-
-  /**
-   * Returns true if the given target is the same as, or owns, the given
-   * message manager.
-   *
-   * @param {nsIMessageSender|MessageManagerProxy|Element} target
-   *        The message manager, MessageManagerProxy, or <browser>
-   *        element agaisnt which to match.
-   * @param {nsIMessageSender} messageManager
-   *        The message manager against which to match `target`.
-   *
-   * @returns {boolean}
-   *        True if `messageManager` is the same object as `target`, or
-   *        `target` is a MessageManagerProxy or <browser> element that
-   *        is tied to it.
-   */
-  static matches(target, messageManager) {
-    return target === messageManager || target.messageManager === messageManager;
-  }
-
-  /**
-   * @property {nsIMessageSender|null} messageManager
-   *        The message manager that is currently being proxied. This
-   *        may change during the life of the proxy object, so should
-   *        not be stored elsewhere.
-   */
-  get messageManager() {
-    return this.eventTarget && this.eventTarget.messageManager;
-  }
-
-  /**
-   * Sends a message on the proxied message manager.
-   *
-   * @param {array} args
-   *        Arguments to be passed verbatim to the underlying
-   *        sendAsyncMessage method.
-   * @returns {undefined}
-   */
-  sendAsyncMessage(...args) {
-    return this.messageManager.sendAsyncMessage(...args);
-  }
-
-  /**
-   * @private
-   * Adds docShell swap listeners to the message manager owner.
-   *
-   * @param {Element} target
-   *        The target element.
-   */
-  addListeners(target) {
-    target.addEventListener("SwapDocShells", this);
-    this.eventTarget = target;
-  }
-
-  /**
-   * @private
-   * Removes docShell swap listeners to the message manager owner.
-   *
-   * @param {Element} target
-   *        The target element.
-   */
-  removeListeners(target) {
-    target.removeEventListener("SwapDocShells", this);
-  }
-
-  handleEvent(event) {
-    if (event.type == "SwapDocShells") {
-      this.removeListeners(this.eventTarget);
-      this.addListeners(event.detail);
-    }
-  }
-}
+XPCOMUtils.defineLazyGetter(this, "MessageManagerProxy",
+                            () => ExtensionUtils.MessageManagerProxy);
 
 /**
  * Handles the mapping and dispatching of messages to their registered
  * handlers. There is one broker per message manager and class of
  * messages. Each class of messages is mapped to one native message
  * name, e.g., "MessageChannel:Message", and is dispatched to handlers
  * based on an internal message name, e.g., "Extension:ExecuteScript".
  */