forked from mirrors/gecko-dev
		
	 9691ab4a5f
			
		
	
	
		9691ab4a5f
		
	
	
	
	
		
			
			Depends on D175553 Differential Revision: https://phabricator.services.mozilla.com/D176005
		
			
				
	
	
		
			210 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			210 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | |
| /* vim: set sts=2 sw=2 et tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
 | |
| 
 | |
| const { DefaultMap } = ExtensionUtils;
 | |
| 
 | |
| /**
 | |
|  * 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.
 | |
|  */
 | |
| export class MessageManagerProxy {
 | |
|   constructor(target) {
 | |
|     this.listeners = new DefaultMap(() => new Map());
 | |
|     this.closed = false;
 | |
| 
 | |
|     if (target instanceof Ci.nsIMessageSender) {
 | |
|       this.messageManager = target;
 | |
|     } else {
 | |
|       this.addListeners(target);
 | |
|     }
 | |
| 
 | |
|     Services.obs.addObserver(this, "message-manager-close");
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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;
 | |
|     }
 | |
|     this.messageManager = null;
 | |
| 
 | |
|     Services.obs.removeObserver(this, "message-manager-close");
 | |
|   }
 | |
| 
 | |
|   observe(subject, topic, data) {
 | |
|     if (topic === "message-manager-close") {
 | |
|       if (subject === this.messageManager) {
 | |
|         this.closed = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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 against 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.
 | |
|    */
 | |
| 
 | |
|   /**
 | |
|    * 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) {
 | |
|     if (this.messageManager) {
 | |
|       return this.messageManager.sendAsyncMessage(...args);
 | |
|     }
 | |
| 
 | |
|     Cu.reportError(
 | |
|       `Cannot send message: Other side disconnected: ${uneval(args)}`
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   get isDisconnected() {
 | |
|     return this.closed || !this.messageManager;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Iterates over all of the currently registered message listeners.
 | |
|    *
 | |
|    * @private
 | |
|    */
 | |
|   *iterListeners() {
 | |
|     for (let [message, listeners] of this.listeners) {
 | |
|       for (let [listener, listenWhenClosed] of listeners) {
 | |
|         yield { message, listener, listenWhenClosed };
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Adds docShell swap listeners to the message manager owner.
 | |
|    *
 | |
|    * @param {Element} target
 | |
|    *        The target element.
 | |
|    * @private
 | |
|    */
 | |
|   addListeners(target) {
 | |
|     target.addEventListener("SwapDocShells", this);
 | |
| 
 | |
|     this.eventTarget = target;
 | |
|     this.messageManager = target.messageManager;
 | |
| 
 | |
|     for (let { message, listener, listenWhenClosed } of this.iterListeners()) {
 | |
|       this.messageManager.addMessageListener(
 | |
|         message,
 | |
|         listener,
 | |
|         listenWhenClosed
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Removes docShell swap listeners to the message manager owner.
 | |
|    *
 | |
|    * @param {Element} target
 | |
|    *        The target element.
 | |
|    * @private
 | |
|    */
 | |
|   removeListeners(target) {
 | |
|     target.removeEventListener("SwapDocShells", this);
 | |
| 
 | |
|     for (let { message, listener } of this.iterListeners()) {
 | |
|       this.messageManager.removeMessageListener(message, listener);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   handleEvent(event) {
 | |
|     if (event.type == "SwapDocShells") {
 | |
|       this.removeListeners(this.eventTarget);
 | |
|       // The SwapDocShells event is dispatched for both browsers that are being
 | |
|       // swapped. To avoid double-swapping, register the event handler after
 | |
|       // both SwapDocShells events have fired.
 | |
|       this.eventTarget.addEventListener(
 | |
|         "EndSwapDocShells",
 | |
|         () => {
 | |
|           this.addListeners(event.detail);
 | |
|         },
 | |
|         { once: true }
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| }
 |