165 lines
5.3 KiB
JavaScript
165 lines
5.3 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/. */
|
|
|
|
"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;
|
|
}
|
|
});
|