summaryrefslogtreecommitdiffstats
path: root/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs')
-rw-r--r--devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs279
1 files changed, 279 insertions, 0 deletions
diff --git a/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs b/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs
new file mode 100644
index 0000000000..3c5af2a724
--- /dev/null
+++ b/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs
@@ -0,0 +1,279 @@
+/* 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 { 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
+ // which also has to be a true singleton.
+ loadInDevToolsLoader: false,
+ }
+);
+
+const lazy = {};
+
+loader.lazyRequireGetter(
+ lazy,
+ "JsWindowActorTransport",
+ "resource://devtools/shared/transport/js-window-actor-transport.js",
+ true
+);
+
+export class DevToolsFrameParent extends JSWindowActorParent {
+ constructor() {
+ super();
+
+ // 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
+ // same frame. For example, a content toolbox and the browser toolbox.
+ //
+ // The map is indexed by the connection prefix.
+ // The values are objects containing the following properties:
+ // - actor: the frame target actor(as a form)
+ // - connection: the DevToolsServerConnection used to communicate with the
+ // frame target actor
+ // - prefix: the forwarding prefix used by the connection to know
+ // how to forward packets to the frame target
+ // - transport: the JsWindowActorTransport
+ //
+ // Reminder about prefixes: all DevToolsServerConnections have a `prefix`
+ // 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);
+ }
+
+ /**
+ * Request the content process to create the Frame Target if there is one
+ * already available that matches the Browsing Context ID
+ */
+ async instantiateTarget({
+ watcherActorID,
+ connectionPrefix,
+ sessionContext,
+ sessionData,
+ }) {
+ await this.sendQuery("DevToolsFrameParent:instantiate-already-available", {
+ watcherActorID,
+ connectionPrefix,
+ sessionContext,
+ sessionData,
+ });
+ }
+
+ /**
+ * @param {object} arg
+ * @param {object} arg.sessionContext
+ * @param {object} arg.options
+ * @param {boolean} arg.options.isModeSwitching
+ * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
+ */
+ destroyTarget({ watcherActorID, sessionContext, options }) {
+ this.sendAsyncMessage("DevToolsFrameParent:destroy", {
+ watcherActorID,
+ sessionContext,
+ options,
+ });
+ }
+
+ /**
+ * Communicate to the content process that some data have been added.
+ */
+ async addOrSetSessionDataEntry({
+ watcherActorID,
+ sessionContext,
+ type,
+ entries,
+ updateType,
+ }) {
+ try {
+ await this.sendQuery("DevToolsFrameParent:addOrSetSessionDataEntry", {
+ watcherActorID,
+ sessionContext,
+ type,
+ entries,
+ updateType,
+ });
+ } catch (e) {
+ console.warn(
+ "Failed to add session data entry for frame targets in browsing context",
+ this.browsingContext.id
+ );
+ console.warn(e);
+ }
+ }
+
+ /**
+ * Communicate to the content process that some data have been removed.
+ */
+ removeSessionDataEntry({ watcherActorID, sessionContext, type, entries }) {
+ this.sendAsyncMessage("DevToolsFrameParent:removeSessionDataEntry", {
+ watcherActorID,
+ sessionContext,
+ type,
+ entries,
+ });
+ }
+
+ connectFromContent({ watcherActorID, forwardingPrefix, actor }) {
+ const watcher = WatcherRegistry.getWatcher(watcherActorID);
+
+ if (!watcher) {
+ throw new Error(
+ `Watcher Actor with ID '${watcherActorID}' can't be found.`
+ );
+ }
+ const connection = watcher.conn;
+
+ connection.on("closed", this._onConnectionClosed);
+
+ // Create a js-window-actor based transport.
+ const transport = new lazy.JsWindowActorTransport(this, forwardingPrefix);
+ transport.hooks = {
+ onPacket: connection.send.bind(connection),
+ onTransportClosed() {},
+ };
+ transport.ready();
+
+ 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,
+ });
+
+ watcher.notifyTargetAvailable(actor);
+ }
+
+ _onConnectionClosed(status, connectionPrefix) {
+ this._unregisterWatcher(connectionPrefix);
+ }
+
+ /**
+ * Given a watcher connection prefix, unregister everything related to the Watcher
+ * in this JSWindowActor.
+ *
+ * @param {String} connectionPrefix
+ * The connection prefix of the watcher to unregister
+ */
+ async _unregisterWatcher(connectionPrefix) {
+ const connectionInfo = this._connections.get(connectionPrefix);
+ if (!connectionInfo) {
+ return;
+ }
+ const { forwardingPrefix, transport, connection } = connectionInfo;
+ this._connections.delete(connectionPrefix);
+
+ 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();
+ }
+
+ connection.cancelForwarding(forwardingPrefix);
+ }
+
+ /**
+ * Destroy everything that we did related to the current WindowGlobal that
+ * this JSWindow Actor represents:
+ * - close all transports that were used as bridge to communicate with the
+ * DevToolsFrameChild, running in the content process
+ * - unregister these transports from DevToolsServer (cancelForwarding)
+ * - notify the client, via the WatcherActor that all related targets,
+ * one per client/connection are all destroyed
+ *
+ * Note that with bfcacheInParent, we may reuse a JSWindowActor pair after closing all connections.
+ * This is can happen outside of the destruction of the actor.
+ * We may reuse a DevToolsFrameParent and DevToolsFrameChild pair.
+ * When navigating away, we will destroy them and call this method.
+ * Then when navigating back, we will reuse the same instances.
+ * So that we should be careful to keep the class fully function and only clear all its state.
+ *
+ * @param {object} options
+ * @param {boolean} options.isModeSwitching
+ * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
+ */
+ _closeAllConnections(options) {
+ for (const { actor, watcher } of this._connections.values()) {
+ watcher.notifyTargetDestroyed(actor, options);
+ this._unregisterWatcher(watcher.conn.prefix);
+ }
+ this._connections.clear();
+ }
+
+ /**
+ * Supported Queries
+ */
+
+ sendPacket(packet, prefix) {
+ this.sendAsyncMessage("DevToolsFrameParent:packet", { packet, prefix });
+ }
+
+ /**
+ * JsWindowActor API
+ */
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "DevToolsFrameChild:connectFromContent":
+ return this.connectFromContent(message.data);
+ case "DevToolsFrameChild:packet":
+ return this.emit("packet-received", message);
+ case "DevToolsFrameChild: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._unregisterWatcher(watcher.conn.prefix);
+ }
+ }
+ return null;
+ case "DevToolsFrameChild:bf-cache-navigation-pageshow":
+ for (const watcherActor of WatcherRegistry.getWatchersForBrowserId(
+ this.browsingContext.browserId
+ )) {
+ watcherActor.emit("bf-cache-navigation-pageshow", {
+ windowGlobal: this.browsingContext.currentWindowGlobal,
+ });
+ }
+ return null;
+ case "DevToolsFrameChild:bf-cache-navigation-pagehide":
+ for (const watcherActor of WatcherRegistry.getWatchersForBrowserId(
+ this.browsingContext.browserId
+ )) {
+ watcherActor.emit("bf-cache-navigation-pagehide", {
+ windowGlobal: this.browsingContext.currentWindowGlobal,
+ });
+ }
+ return null;
+ default:
+ throw new Error(
+ "Unsupported message in DevToolsFrameParent: " + message.name
+ );
+ }
+ }
+
+ didDestroy() {
+ this._closeAllConnections();
+ }
+}