From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../extensions/MessageManagerProxy.sys.mjs | 210 +++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 toolkit/components/extensions/MessageManagerProxy.sys.mjs (limited to 'toolkit/components/extensions/MessageManagerProxy.sys.mjs') diff --git a/toolkit/components/extensions/MessageManagerProxy.sys.mjs b/toolkit/components/extensions/MessageManagerProxy.sys.mjs new file mode 100644 index 0000000000..4df410e80f --- /dev/null +++ b/toolkit/components/extensions/MessageManagerProxy.sys.mjs @@ -0,0 +1,210 @@ +/* -*- 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 . + * + * @param {nsIMessageSender|Element} target + * The target message manager on which to send messages, or the + * 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 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 + * 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 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 } + ); + } + } +} -- cgit v1.2.3