Source code
Revision control
Copy as Markdown
Other Tools
/* 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
"use strict";
/* global global */
/*
* Worker debugger script that listens for requests to start a `DevToolsServer` for a
* worker in a process. Loaded into a specific worker during worker-connector.js'
* `connectToWorker` which is called from the same process as the worker.
*/
// This function is used to do remote procedure calls from the worker to the
// main thread. It is exposed as a built-in global to every module by the
// worker loader. To make sure the worker loader can access it, it needs to be
// defined before loading the worker loader script below.
let nextId = 0;
this.rpc = function (method, ...params) {
return new Promise((resolve, reject) => {
const id = nextId++;
this.addEventListener("message", function onMessageForRpc(event) {
const packet = JSON.parse(event.data);
if (packet.type !== "rpc" || packet.id !== id) {
return;
}
if (packet.error) {
reject(packet.error);
} else {
resolve(packet.result);
}
this.removeEventListener("message", onMessageForRpc);
});
postMessage(
JSON.stringify({
type: "rpc",
method,
params,
id,
})
);
});
}.bind(this);
const { worker } = ChromeUtils.importESModule(
"resource://devtools/shared/loader/worker-loader.sys.mjs",
{ global: "current" }
);
const { WorkerTargetActor } = worker.require(
"resource://devtools/server/actors/targets/worker.js"
);
const { DevToolsServer } = worker.require(
"resource://devtools/server/devtools-server.js"
);
DevToolsServer.createRootActor = function () {
throw new Error("Should never get here!");
};
// This file is only instanciated once for a given WorkerDebugger, which means that
// multiple toolbox could end up using the same instance of this script. In order to handle
// that, we handle a Map of the different connections, keyed by forwarding prefix.
const connections = new Map();
this.addEventListener("message", async function (event) {
const packet = JSON.parse(event.data);
switch (packet.type) {
case "connect":
const { forwardingPrefix } = packet;
// Force initializing the server each time on connect
// as it may have been destroyed by a previous, now closed toolbox.
// Once the last connection drops, the server auto destroy itself.
DevToolsServer.init();
// Step 3: Create a connection to the parent.
const connection = DevToolsServer.connectToParent(forwardingPrefix, this);
// Step 4: Create a WorkerTarget actor.
const workerTargetActor = new WorkerTargetActor(
connection,
global,
packet.workerDebuggerData,
packet.options.sessionContext
);
// Make the worker manage itself so it is put in a Pool and assigned an actorID.
workerTargetActor.manage(workerTargetActor);
// Step 5: Send a response packet to the parent to notify
// it that a connection has been established.
connections.set(forwardingPrefix, {
connection,
workerTargetActor,
});
// Immediately notify about the target actor form,
// so that we can emit RDP events from the target actor
// and have them correctly routed up to the frontend.
// The target front has to be first created by receiving its form
// before being able to receive RDP events.
postMessage(
JSON.stringify({
type: "connected",
forwardingPrefix,
workerTargetForm: workerTargetActor.form(),
})
);
// We might receive data to watch.
if (packet.options.sessionData) {
const promises = [];
for (const [type, entries] of Object.entries(
packet.options.sessionData
)) {
promises.push(
workerTargetActor.addOrSetSessionDataEntry(
type,
entries,
false,
"set"
)
);
}
await Promise.all(promises);
}
// Finally, notify when we are done processing session data
// We are processing breakpoints, which means we can release the execution of the worker
// from the main thread via `WorkerDebugger.setDebuggerReady(true)`
postMessage(JSON.stringify({ type: "session-data-processed" }));
break;
case "add-or-set-session-data-entry":
await connections
.get(packet.forwardingPrefix)
.workerTargetActor.addOrSetSessionDataEntry(
packet.dataEntryType,
packet.entries,
packet.updateType
);
postMessage(JSON.stringify({ type: "session-data-entry-added-or-set" }));
break;
case "remove-session-data-entry":
await connections
.get(packet.forwardingPrefix)
.workerTargetActor.removeSessionDataEntry(
packet.dataEntryType,
packet.entries
);
break;
case "disconnect":
// This will destroy the associate WorkerTargetActor (and the actors it manages).
if (connections.has(packet.forwardingPrefix)) {
connections.get(packet.forwardingPrefix).connection.close();
connections.delete(packet.forwardingPrefix);
}
break;
}
});