Bug 1316396: Part 5 - Move MessageManagerProxy to ExtensionUtils, and add support for proxied listeners. r?aswan
MozReview-Commit-ID: KhinS46k0yW
--- 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".
*/