forked from mirrors/gecko-dev
328 lines
11 KiB
JavaScript
328 lines
11 KiB
JavaScript
/* 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 { 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" }
|
|
);
|
|
|
|
const lazy = {};
|
|
loader.lazyRequireGetter(
|
|
lazy,
|
|
"JsWindowActorTransport",
|
|
"devtools/shared/transport/js-window-actor-transport",
|
|
true
|
|
);
|
|
|
|
export class DevToolsProcessParent extends JSProcessActorParent {
|
|
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.
|
|
|
|
EventEmitter.decorate(this);
|
|
}
|
|
|
|
#destroyed = false;
|
|
#connections = new Map();
|
|
|
|
/**
|
|
* Request the content process to create all the targets currently watched
|
|
* and start observing for new ones to be created later.
|
|
*/
|
|
watchTargets({ watcherActorID, targetType }) {
|
|
return this.sendQuery("DevToolsProcessParent:watchTargets", {
|
|
watcherActorID,
|
|
targetType,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
targetType,
|
|
options,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Communicate to the content process that some data have been added or set.
|
|
*/
|
|
addOrSetSessionDataEntry({ watcherActorID, type, entries, updateType }) {
|
|
return this.sendQuery("DevToolsProcessParent:addOrSetSessionDataEntry", {
|
|
watcherActorID,
|
|
type,
|
|
entries,
|
|
updateType,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Communicate to the content process that some data have been removed.
|
|
*/
|
|
removeSessionDataEntry({ watcherActorID, type, entries }) {
|
|
this.sendAsyncMessage("DevToolsProcessParent:removeSessionDataEntry", {
|
|
watcherActorID,
|
|
type,
|
|
entries,
|
|
});
|
|
}
|
|
|
|
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(
|
|
`Watcher Actor with ID '${watcherActorID}' can't be found.`
|
|
);
|
|
}
|
|
const connection = watcher.conn;
|
|
|
|
// 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();
|
|
|
|
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,
|
|
targetActorForms: [],
|
|
});
|
|
}
|
|
|
|
this.#connections
|
|
.get(watcher.conn.prefix)
|
|
.targetActorForms.push(targetActorForm);
|
|
|
|
watcher.notifyTargetAvailable(targetActorForm);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param {DevToolsServerConnection} connection
|
|
* @param {object} options
|
|
* @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 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);
|
|
}
|
|
|
|
connection.off("closed", this.#onConnectionClosed);
|
|
|
|
this.#connections.delete(connection.prefix);
|
|
if (!this.#connections.size) {
|
|
this.#destroy(options);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroy and cleanup everything for this DOM Process.
|
|
*
|
|
* @param {object} options
|
|
* @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) {
|
|
return;
|
|
}
|
|
this.#destroyed = true;
|
|
|
|
for (const {
|
|
targetActorForms,
|
|
connection,
|
|
watcher,
|
|
} of this.#connections.values()) {
|
|
for (const actor of targetActorForms) {
|
|
watcher.notifyTargetDestroyed(actor, options);
|
|
}
|
|
this.#cleanupConnection(connection, options);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used by DevTools Transport to send packets to the content process.
|
|
*/
|
|
|
|
sendPacket(packet, prefix) {
|
|
this.sendAsyncMessage("DevToolsProcessParent:packet", { packet, prefix });
|
|
}
|
|
|
|
/**
|
|
* JsProcessActor API
|
|
*/
|
|
|
|
async sendQuery(msg, args) {
|
|
try {
|
|
const res = await super.sendQuery(msg, args);
|
|
return res;
|
|
} catch (e) {
|
|
console.error("Failed to sendQuery in DevToolsProcessParent", msg);
|
|
console.error(e.toString());
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the JSProcessActor API when the content process sent us a message
|
|
*/
|
|
receiveMessage(message) {
|
|
switch (message.name) {
|
|
case "DevToolsProcessChild:targetAvailable":
|
|
return this.#onTargetAvailable(message.data);
|
|
case "DevToolsProcessChild:packet":
|
|
return this.emit("packet-received", message);
|
|
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
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the JSProcessActor API when this content process is destroyed.
|
|
*/
|
|
didDestroy() {
|
|
this.#destroy();
|
|
}
|
|
}
|
|
|
|
export class BrowserToolboxDevToolsProcessParent extends DevToolsProcessParent {}
|