From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- .../js-process-actor/DevToolsProcessParent.sys.mjs | 272 +++++++++++++-------- 1 file changed, 172 insertions(+), 100 deletions(-) (limited to 'devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs') diff --git a/devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs b/devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs index 28e11def68..303c85e68f 100644 --- a/devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs +++ b/devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs @@ -5,9 +5,9 @@ import { loader } from "resource://devtools/shared/loader/Loader.sys.mjs"; import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; -const { WatcherRegistry } = ChromeUtils.importESModule( - "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent +const { ParentProcessWatcherRegistry } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/ParentProcessWatcherRegistry.sys.mjs", + // ParentProcessWatcherRegistry needs to be a true singleton and loads ActorManagerParent // which also has to be a true singleton. { global: "shared" } ); @@ -24,8 +24,6 @@ export class DevToolsProcessParent extends JSProcessActorParent { constructor() { super(); - this._destroyed = false; - // Map of DevToolsServerConnection's used to forward the messages from/to // the client. The connections run in the parent process, as this code. We // may have more than one when there is more than one client debugging the @@ -44,41 +42,38 @@ export class DevToolsProcessParent extends JSProcessActorParent { // which can be considered as a kind of id. On top of this, parent process // DevToolsServerConnections also have forwarding prefixes because they are // responsible for forwarding messages to content process connections. - this._connections = new Map(); - this._onConnectionClosed = this._onConnectionClosed.bind(this); EventEmitter.decorate(this); } + #destroyed = false; + #connections = new Map(); + /** - * Request the content process to create the ContentProcessTarget + * Request the content process to create all the targets currently watched + * and start observing for new ones to be created later. */ - instantiateTarget({ - watcherActorID, - connectionPrefix, - sessionContext, - sessionData, - }) { - return this.sendQuery( - "DevToolsProcessParent:instantiate-already-available", - { - watcherActorID, - connectionPrefix, - sessionContext, - sessionData, - } - ); + watchTargets({ watcherActorID, targetType }) { + return this.sendQuery("DevToolsProcessParent:watchTargets", { + watcherActorID, + targetType, + }); } - destroyTarget({ watcherActorID, isModeSwitching }) { - this.sendAsyncMessage("DevToolsProcessParent:destroy", { + /** + * Request the content process to stop observing for currently watched targets + * and destroy all the currently active ones. + */ + unwatchTargets({ watcherActorID, targetType, options }) { + this.sendAsyncMessage("DevToolsProcessParent:unwatchTargets", { watcherActorID, - isModeSwitching, + targetType, + options, }); } /** - * Communicate to the content process that some data have been added. + * Communicate to the content process that some data have been added or set. */ addOrSetSessionDataEntry({ watcherActorID, type, entries, updateType }) { return this.sendQuery("DevToolsProcessParent:addOrSetSessionDataEntry", { @@ -100,8 +95,17 @@ export class DevToolsProcessParent extends JSProcessActorParent { }); } - connectFromContent({ watcherActorID, forwardingPrefix, actor }) { - const watcher = WatcherRegistry.getWatcher(watcherActorID); + destroyWatcher({ watcherActorID }) { + return this.sendAsyncMessage("DevToolsProcessParent:destroyWatcher", { + watcherActorID, + }); + } + + /** + * Called when the content process notified us about a new target actor + */ + #onTargetAvailable({ watcherActorID, forwardingPrefix, targetActorForm }) { + const watcher = ParentProcessWatcherRegistry.getWatcher(watcherActorID); if (!watcher) { throw new Error( @@ -110,45 +114,86 @@ export class DevToolsProcessParent extends JSProcessActorParent { } const connection = watcher.conn; - connection.on("closed", this._onConnectionClosed); + // If this is the first target actor for this watcher, + // hook up the DevToolsServerConnection which will bridge + // communication between the parent process DevToolsServer + // and the content process. + if (!this.#connections.get(watcher.conn.prefix)) { + connection.on("closed", this.#onConnectionClosed); - // Create a js-window-actor based transport. - const transport = new lazy.JsWindowActorTransport( - this, - forwardingPrefix, - "DevToolsProcessParent:packet" - ); - transport.hooks = { - onPacket: connection.send.bind(connection), - onClosed() {}, - }; - transport.ready(); + // Create a js-window-actor based transport. + const transport = new lazy.JsWindowActorTransport( + this, + forwardingPrefix, + "DevToolsProcessParent:packet" + ); + transport.hooks = { + onPacket: connection.send.bind(connection), + onClosed() {}, + }; + transport.ready(); - connection.setForwarding(forwardingPrefix, transport); + connection.setForwarding(forwardingPrefix, transport); - this._connections.set(watcher.conn.prefix, { - watcher, - connection, - // This prefix is the prefix of the DevToolsServerConnection, running - // in the content process, for which we should forward packets to, based on its prefix. - // While `watcher.connection` is also a DevToolsServerConnection, but from this process, - // the parent process. It is the one receiving Client packets and the one, from which - // we should forward packets from. - forwardingPrefix, - transport, - actor, - }); + this.#connections.set(watcher.conn.prefix, { + watcher, + connection, + // This prefix is the prefix of the DevToolsServerConnection, running + // in the content process, for which we should forward packets to, based on its prefix. + // While `watcher.connection` is also a DevToolsServerConnection, but from this process, + // the parent process. It is the one receiving Client packets and the one, from which + // we should forward packets from. + forwardingPrefix, + transport, + targetActorForms: [], + }); + } - watcher.notifyTargetAvailable(actor); + this.#connections + .get(watcher.conn.prefix) + .targetActorForms.push(targetActorForm); + + watcher.notifyTargetAvailable(targetActorForm); } - _onConnectionClosed(status, prefix) { - if (this._connections.has(prefix)) { - const { connection } = this._connections.get(prefix); - this._cleanupConnection(connection); + /** + * Called when the content process notified us about a target actor that has been destroyed. + */ + #onTargetDestroyed({ actors, options }) { + for (const { watcherActorID, targetActorForm } of actors) { + const watcher = ParentProcessWatcherRegistry.getWatcher(watcherActorID); + // As we instruct to destroy all targets when the watcher is destroyed, + // we may easily receive the target destruction notification *after* + // the watcher has been removed from the registry. + if (!watcher || watcher.isDestroyed()) { + continue; + } + watcher.notifyTargetDestroyed(targetActorForm, options); + const connectionInfo = this.#connections.get(watcher.conn.prefix); + if (connectionInfo) { + const idx = connectionInfo.targetActorForms.findIndex( + form => form.actor == targetActorForm.actor + ); + if (idx != -1) { + connectionInfo.targetActorForms.splice(idx, 1); + } + // Once the last active target is removed, disconnect the DevTools transport + // and cleanup everything bound to this DOM Process. We will re-instantiate + // a new connection/transport on the next reported target actor. + if (!connectionInfo.targetActorForms.length) { + this.#cleanupConnection(connectionInfo.connection); + } + } } } + #onConnectionClosed = (status, prefix) => { + if (this.#connections.has(prefix)) { + const { connection } = this.#connections.get(prefix); + this.#cleanupConnection(connection); + } + }; + /** * Close and unregister a given DevToolsServerConnection. * @@ -157,30 +202,27 @@ export class DevToolsProcessParent extends JSProcessActorParent { * @param {boolean} options.isModeSwitching * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref */ - async _cleanupConnection(connection, options = {}) { - const connectionInfo = this._connections.get(connection.prefix); - if (!connectionInfo) { - return; + async #cleanupConnection(connection, options = {}) { + const watcherConnectionInfo = this.#connections.get(connection.prefix); + if (watcherConnectionInfo) { + const { forwardingPrefix, transport } = watcherConnectionInfo; + if (transport) { + // If we have a child transport, the actor has already + // been created. We need to stop using this transport. + transport.close(options); + } + // When cancelling the forwarding, one RDP event is sent to the client to purge all requests + // and actors related to a given prefix. + // Be careful that any late RDP event would be ignored by the client passed this call. + connection.cancelForwarding(forwardingPrefix); } - const { forwardingPrefix, transport } = connectionInfo; - connection.off("closed", this._onConnectionClosed); - if (transport) { - // If we have a child transport, the actor has already - // been created. We need to stop using this transport. - transport.close(options); - } + connection.off("closed", this.#onConnectionClosed); - this._connections.delete(connection.prefix); - if (!this._connections.size) { - this._destroy(options); + this.#connections.delete(connection.prefix); + if (!this.#connections.size) { + this.#destroy(options); } - - // When cancelling the forwarding, one RDP event is sent to the client to purge all requests - // and actors related to a given prefix. Do this *after* calling _destroy which will emit - // the target-destroyed RDP event. This helps the Watcher Front retrieve the related target front, - // otherwise it would be too eagerly destroyed by the purge event. - connection.cancelForwarding(forwardingPrefix); } /** @@ -190,20 +232,26 @@ export class DevToolsProcessParent extends JSProcessActorParent { * @param {boolean} options.isModeSwitching * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref */ - _destroy(options) { - if (this._destroyed) { + #destroy(options) { + if (this.#destroyed) { return; } - this._destroyed = true; + this.#destroyed = true; - for (const { actor, connection, watcher } of this._connections.values()) { - watcher.notifyTargetDestroyed(actor, options); - this._cleanupConnection(connection, options); + for (const { + targetActorForms, + connection, + watcher, + } of this.#connections.values()) { + for (const actor of targetActorForms) { + watcher.notifyTargetDestroyed(actor, options); + } + this.#cleanupConnection(connection, options); } } /** - * Supported Queries + * Used by DevTools Transport to send packets to the content process. */ sendPacket(packet, prefix) { @@ -211,7 +259,7 @@ export class DevToolsProcessParent extends JSProcessActorParent { } /** - * JsWindowActor API + * JsProcessActor API */ async sendQuery(msg, args) { @@ -225,24 +273,43 @@ export class DevToolsProcessParent extends JSProcessActorParent { } } + /** + * Called by the JSProcessActor API when the content process sent us a message + */ receiveMessage(message) { switch (message.name) { - case "DevToolsProcessChild:connectFromContent": - return this.connectFromContent(message.data); + case "DevToolsProcessChild:targetAvailable": + return this.#onTargetAvailable(message.data); case "DevToolsProcessChild:packet": return this.emit("packet-received", message); - case "DevToolsProcessChild:destroy": - for (const { form, watcherActorID } of message.data.actors) { - const watcher = WatcherRegistry.getWatcher(watcherActorID); - // As we instruct to destroy all targets when the watcher is destroyed, - // we may easily receive the target destruction notification *after* - // the watcher has been removed from the registry. - if (watcher) { - watcher.notifyTargetDestroyed(form, message.data.options); - this._cleanupConnection(watcher.conn, message.data.options); - } + case "DevToolsProcessChild:targetDestroyed": + return this.#onTargetDestroyed(message.data); + case "DevToolsProcessChild:bf-cache-navigation-pageshow": { + const browsingContext = BrowsingContext.get( + message.data.browsingContextId + ); + for (const watcherActor of ParentProcessWatcherRegistry.getWatchersForBrowserId( + browsingContext.browserId + )) { + watcherActor.emit("bf-cache-navigation-pageshow", { + windowGlobal: browsingContext.currentWindowGlobal, + }); } return null; + } + case "DevToolsProcessChild:bf-cache-navigation-pagehide": { + const browsingContext = BrowsingContext.get( + message.data.browsingContextId + ); + for (const watcherActor of ParentProcessWatcherRegistry.getWatchersForBrowserId( + browsingContext.browserId + )) { + watcherActor.emit("bf-cache-navigation-pagehide", { + windowGlobal: browsingContext.currentWindowGlobal, + }); + } + return null; + } default: throw new Error( "Unsupported message in DevToolsProcessParent: " + message.name @@ -250,7 +317,12 @@ export class DevToolsProcessParent extends JSProcessActorParent { } } + /** + * Called by the JSProcessActor API when this content process is destroyed. + */ didDestroy() { - this._destroy(); + this.#destroy(); } } + +export class BrowserToolboxDevToolsProcessParent extends DevToolsProcessParent {} -- cgit v1.2.3