1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim: set sts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // @ts-nocheck TODO: Many references to old types which don't exist anymore.
9 import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
11 const { DefaultMap } = ExtensionUtils;
14 * Acts as a proxy for a message manager or message manager owner, and
15 * tracks docShell swaps so that messages are always sent to the same
16 * receiver, even if it is moved to a different <browser>.
18 * @param {nsIMessageSender|Element} target
19 * The target message manager on which to send messages, or the
20 * <browser> element which owns it.
22 export class MessageManagerProxy {
24 this.listeners = new DefaultMap(() => new Map());
27 if (target instanceof Ci.nsIMessageSender) {
28 this.messageManager = target;
30 this.addListeners(target);
33 Services.obs.addObserver(this, "message-manager-close");
37 * Disposes of the proxy object, removes event listeners, and drops
38 * all references to the underlying message manager.
40 * Must be called before the last reference to the proxy is dropped,
41 * unless the underlying message manager or <browser> is also being
45 if (this.eventTarget) {
46 this.removeListeners(this.eventTarget);
47 this.eventTarget = null;
49 this.messageManager = null;
51 Services.obs.removeObserver(this, "message-manager-close");
54 observe(subject, topic, data) {
55 if (topic === "message-manager-close") {
56 if (subject === this.messageManager) {
63 * Returns true if the given target is the same as, or owns, the given
66 * @param {nsIMessageSender|MessageManagerProxy|Element} target
67 * The message manager, MessageManagerProxy, or <browser>
68 * element against which to match.
69 * @param {nsIMessageSender} messageManager
70 * The message manager against which to match `target`.
73 * True if `messageManager` is the same object as `target`, or
74 * `target` is a MessageManagerProxy or <browser> element that
77 static matches(target, messageManager) {
79 target === messageManager || target.messageManager === messageManager
84 * @property {nsIMessageSender|null} messageManager
85 * The message manager that is currently being proxied. This
86 * may change during the life of the proxy object, so should
87 * not be stored elsewhere.
91 * Sends a message on the proxied message manager.
94 * Arguments to be passed verbatim to the underlying
95 * sendAsyncMessage method.
96 * @returns {undefined}
98 sendAsyncMessage(...args) {
99 if (this.messageManager) {
100 return this.messageManager.sendAsyncMessage(...args);
104 `Cannot send message: Other side disconnected: ${uneval(args)}`
108 get isDisconnected() {
109 return this.closed || !this.messageManager;
113 * Adds a message listener to the current message manager, and
114 * transfers it to the new message manager after a docShell swap.
116 * @param {string} message
117 * The name of the message to listen for.
118 * @param {nsIMessageListener} listener
119 * The listener to add.
120 * @param {boolean} [listenWhenClosed = false]
121 * If true, the listener will receive messages which were sent
122 * after the remote side of the listener began closing.
124 addMessageListener(message, listener, listenWhenClosed = false) {
125 this.messageManager.addMessageListener(message, listener, listenWhenClosed);
126 this.listeners.get(message).set(listener, listenWhenClosed);
130 * Adds a message listener from the current message manager.
132 * @param {string} message
133 * The name of the message to stop listening for.
134 * @param {nsIMessageListener} listener
135 * The listener to remove.
137 removeMessageListener(message, listener) {
138 this.messageManager.removeMessageListener(message, listener);
140 let listeners = this.listeners.get(message);
141 listeners.delete(listener);
142 if (!listeners.size) {
143 this.listeners.delete(message);
148 * Iterates over all of the currently registered message listeners.
153 for (let [message, listeners] of this.listeners) {
154 for (let [listener, listenWhenClosed] of listeners) {
155 yield { message, listener, listenWhenClosed };
161 * Adds docShell swap listeners to the message manager owner.
163 * @param {Browser} target
164 * The target element.
167 addListeners(target) {
168 target.addEventListener("SwapDocShells", this);
170 this.eventTarget = target;
171 this.messageManager = target.messageManager;
173 for (let { message, listener, listenWhenClosed } of this.iterListeners()) {
174 this.messageManager.addMessageListener(
183 * Removes docShell swap listeners to the message manager owner.
185 * @param {Element} target
186 * The target element.
189 removeListeners(target) {
190 target.removeEventListener("SwapDocShells", this);
192 for (let { message, listener } of this.iterListeners()) {
193 this.messageManager.removeMessageListener(message, listener);
198 if (event.type == "SwapDocShells") {
199 this.removeListeners(this.eventTarget);
200 // The SwapDocShells event is dispatched for both browsers that are being
201 // swapped. To avoid double-swapping, register the event handler after
202 // both SwapDocShells events have fired.
203 this.eventTarget.addEventListener(
206 this.addListeners(event.detail);