Bug 1871127 - Add tsconfig, basic types, and fix or ignore remaining type errors...
[gecko.git] / toolkit / components / extensions / MessageManagerProxy.sys.mjs
blob387b5876e1e79db7f7f0d1e08a26ba827e31fa02
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;
13 /**
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>.
17  *
18  * @param {nsIMessageSender|Element} target
19  *        The target message manager on which to send messages, or the
20  *        <browser> element which owns it.
21  */
22 export class MessageManagerProxy {
23   constructor(target) {
24     this.listeners = new DefaultMap(() => new Map());
25     this.closed = false;
27     if (target instanceof Ci.nsIMessageSender) {
28       this.messageManager = target;
29     } else {
30       this.addListeners(target);
31     }
33     Services.obs.addObserver(this, "message-manager-close");
34   }
36   /**
37    * Disposes of the proxy object, removes event listeners, and drops
38    * all references to the underlying message manager.
39    *
40    * Must be called before the last reference to the proxy is dropped,
41    * unless the underlying message manager or <browser> is also being
42    * destroyed.
43    */
44   dispose() {
45     if (this.eventTarget) {
46       this.removeListeners(this.eventTarget);
47       this.eventTarget = null;
48     }
49     this.messageManager = null;
51     Services.obs.removeObserver(this, "message-manager-close");
52   }
54   observe(subject, topic, data) {
55     if (topic === "message-manager-close") {
56       if (subject === this.messageManager) {
57         this.closed = true;
58       }
59     }
60   }
62   /**
63    * Returns true if the given target is the same as, or owns, the given
64    * message manager.
65    *
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`.
71    *
72    * @returns {boolean}
73    *        True if `messageManager` is the same object as `target`, or
74    *        `target` is a MessageManagerProxy or <browser> element that
75    *        is tied to it.
76    */
77   static matches(target, messageManager) {
78     return (
79       target === messageManager || target.messageManager === messageManager
80     );
81   }
83   /**
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.
88    */
90   /**
91    * Sends a message on the proxied message manager.
92    *
93    * @param {Array} args
94    *        Arguments to be passed verbatim to the underlying
95    *        sendAsyncMessage method.
96    * @returns {undefined}
97    */
98   sendAsyncMessage(...args) {
99     if (this.messageManager) {
100       return this.messageManager.sendAsyncMessage(...args);
101     }
103     Cu.reportError(
104       `Cannot send message: Other side disconnected: ${uneval(args)}`
105     );
106   }
108   get isDisconnected() {
109     return this.closed || !this.messageManager;
110   }
112   /**
113    * Adds a message listener to the current message manager, and
114    * transfers it to the new message manager after a docShell swap.
115    *
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.
123    */
124   addMessageListener(message, listener, listenWhenClosed = false) {
125     this.messageManager.addMessageListener(message, listener, listenWhenClosed);
126     this.listeners.get(message).set(listener, listenWhenClosed);
127   }
129   /**
130    * Adds a message listener from the current message manager.
131    *
132    * @param {string} message
133    *        The name of the message to stop listening for.
134    * @param {nsIMessageListener} listener
135    *        The listener to remove.
136    */
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);
144     }
145   }
147   /**
148    * Iterates over all of the currently registered message listeners.
149    *
150    * @private
151    */
152   *iterListeners() {
153     for (let [message, listeners] of this.listeners) {
154       for (let [listener, listenWhenClosed] of listeners) {
155         yield { message, listener, listenWhenClosed };
156       }
157     }
158   }
160   /**
161    * Adds docShell swap listeners to the message manager owner.
162    *
163    * @param {Browser} target
164    *        The target element.
165    * @private
166    */
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(
175         message,
176         listener,
177         listenWhenClosed
178       );
179     }
180   }
182   /**
183    * Removes docShell swap listeners to the message manager owner.
184    *
185    * @param {Element} target
186    *        The target element.
187    * @private
188    */
189   removeListeners(target) {
190     target.removeEventListener("SwapDocShells", this);
192     for (let { message, listener } of this.iterListeners()) {
193       this.messageManager.removeMessageListener(message, listener);
194     }
195   }
197   handleEvent(event) {
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(
204         "EndSwapDocShells",
205         () => {
206           this.addListeners(event.detail);
207         },
208         { once: true }
209       );
210     }
211   }