worker.sys.mjs (5477B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /* global global, postMessage */ 6 7 /* 8 * Worker debugger script that listens for requests to start a `DevToolsServer` for a 9 * worker in a process. Loaded into a specific worker during worker-connector.js' 10 * `connectToWorker` which is called from the same process as the worker. 11 */ 12 13 // This function is used to do remote procedure calls from the worker to the 14 // main thread. It is exposed as a built-in global to every module by the 15 // worker loader. To make sure the worker loader can access it, it needs to be 16 // defined before loading the worker loader script below. 17 let nextId = 0; 18 const workerGlobal = globalThis; 19 20 workerGlobal.rpc = function (method, ...params) { 21 return new Promise((resolve, reject) => { 22 const id = nextId++; 23 workerGlobal.addEventListener("message", function onMessageForRpc(event) { 24 const packet = JSON.parse(event.data); 25 if (packet.type !== "rpc" || packet.id !== id) { 26 return; 27 } 28 if (packet.error) { 29 reject(packet.error); 30 } else { 31 resolve(packet.result); 32 } 33 workerGlobal.removeEventListener("message", onMessageForRpc); 34 }); 35 36 postMessage( 37 JSON.stringify({ 38 type: "rpc", 39 method, 40 params, 41 id, 42 }) 43 ); 44 }); 45 }; 46 47 const { worker } = ChromeUtils.importESModule( 48 "resource://devtools/shared/loader/worker-loader.sys.mjs", 49 { global: "current" } 50 ); 51 52 const { WorkerTargetActor } = worker.require( 53 "resource://devtools/server/actors/targets/worker.js" 54 ); 55 const { DevToolsServer } = worker.require( 56 "resource://devtools/server/devtools-server.js" 57 ); 58 59 DevToolsServer.createRootActor = function () { 60 throw new Error("Should never get here!"); 61 }; 62 63 // This file is only instanciated once for a given WorkerDebugger, which means that 64 // multiple toolbox could end up using the same instance of this script. In order to handle 65 // that, we handle a Map of the different connections, keyed by forwarding prefix. 66 const connections = new Map(); 67 68 export async function handleDevToolsPacket(packet) { 69 switch (packet.type) { 70 case "connect": { 71 const { forwardingPrefix } = packet; 72 73 // Force initializing the server each time on connect 74 // as it may have been destroyed by a previous, now closed toolbox. 75 // Once the last connection drops, the server auto destroy itself. 76 DevToolsServer.init(); 77 78 // Step 3: Create a connection to the parent. 79 const connection = DevToolsServer.connectToParent( 80 forwardingPrefix, 81 workerGlobal 82 ); 83 84 // Step 4: Create a WorkerTarget actor. 85 const workerTargetActor = new WorkerTargetActor( 86 connection, 87 global, 88 packet.workerDebuggerData, 89 packet.options.sessionContext 90 ); 91 // Make the worker manage itself so it is put in a Pool and assigned an actorID. 92 workerTargetActor.manage(workerTargetActor); 93 94 // Step 5: Send a response packet to the parent to notify 95 // it that a connection has been established. 96 connections.set(forwardingPrefix, { 97 connection, 98 workerTargetActor, 99 }); 100 101 // Immediately notify about the target actor form, 102 // so that we can emit RDP events from the target actor 103 // and have them correctly routed up to the frontend. 104 // The target front has to be first created by receiving its form 105 // before being able to receive RDP events. 106 postMessage( 107 JSON.stringify({ 108 type: "connected", 109 forwardingPrefix, 110 workerTargetForm: workerTargetActor.form(), 111 }) 112 ); 113 114 // We might receive data to watch. 115 if (packet.options.sessionData) { 116 const promises = []; 117 for (const [type, entries] of Object.entries( 118 packet.options.sessionData 119 )) { 120 promises.push( 121 workerTargetActor.addOrSetSessionDataEntry( 122 type, 123 entries, 124 false, 125 "set" 126 ) 127 ); 128 } 129 await Promise.all(promises); 130 } 131 132 // Finally, notify when we are done processing session data 133 // We are processing breakpoints, which means we can release the execution of the worker 134 // from the main thread via `WorkerDebugger.setDebuggerReady(true)` 135 postMessage(JSON.stringify({ type: "session-data-processed" })); 136 137 break; 138 } 139 140 case "add-or-set-session-data-entry": 141 await connections 142 .get(packet.forwardingPrefix) 143 .workerTargetActor.addOrSetSessionDataEntry( 144 packet.dataEntryType, 145 packet.entries, 146 packet.updateType 147 ); 148 postMessage(JSON.stringify({ type: "session-data-entry-added-or-set" })); 149 break; 150 151 case "remove-session-data-entry": 152 await connections 153 .get(packet.forwardingPrefix) 154 .workerTargetActor.removeSessionDataEntry( 155 packet.dataEntryType, 156 packet.entries 157 ); 158 break; 159 160 case "disconnect": 161 // This will destroy the associate WorkerTargetActor (and the actors it manages). 162 if (connections.has(packet.forwardingPrefix)) { 163 connections.get(packet.forwardingPrefix).connection.close(); 164 connections.delete(packet.forwardingPrefix); 165 } 166 break; 167 } 168 }