worker-connector.js (7255B)
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 "use strict"; 6 7 var DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); 8 9 loader.lazyRequireGetter( 10 this, 11 "MainThreadWorkerDebuggerTransport", 12 "resource://devtools/shared/transport/worker-transport.js", 13 true 14 ); 15 16 /** 17 * Start a DevTools server in a worker and add it as a child server for a given active connection. 18 * 19 * @param {DevToolsConnection} connection 20 * @param {WorkerDebugger} dbg: The WorkerDebugger we want to create a target actor for. 21 * @param {string} forwardingPrefix: The prefix that will be used to forward messages 22 * to the DevToolsServer on the worker thread. 23 * @param {object} options: An option object that will be passed with the "connect" packet. 24 * @param {object} options.sessionData: The sessionData object that will be passed to the 25 * worker target actor. 26 */ 27 function connectToWorker(connection, dbg, forwardingPrefix, options) { 28 return new Promise((resolve, reject) => { 29 if (!DevToolsUtils.isWorkerDebuggerAlive(dbg)) { 30 reject("closed"); 31 return; 32 } 33 34 // Step 1: Ensure the worker debugger is initialized. 35 if (!dbg.isInitialized) { 36 dbg.initialize( 37 "resource://devtools/server/startup/shared-worker-initializer.js" 38 ); 39 40 // Create a listener for rpc requests from the worker debugger. Only do 41 // this once, when the worker debugger is first initialized, rather than 42 // for each connection. 43 const listener = { 44 onClose: () => { 45 dbg.removeListener(listener); 46 }, 47 48 onMessage: message => { 49 message = JSON.parse(message); 50 if (message.type !== "rpc") { 51 if (message.type == "session-data-processed") { 52 // The thread actor has finished processing session data, including breakpoints. 53 // Allow content to begin executing in the worker and possibly hit early breakpoints. 54 dbg.setDebuggerReady(true); 55 } 56 return; 57 } 58 59 Promise.resolve() 60 .then(() => { 61 const method = { 62 fetch: DevToolsUtils.fetch, 63 }[message.method]; 64 if (!method) { 65 throw Error("Unknown method: " + message.method); 66 } 67 68 return method.apply(undefined, message.params); 69 }) 70 .then( 71 value => { 72 dbg.postMessage( 73 JSON.stringify({ 74 type: "rpc", 75 result: value, 76 error: null, 77 id: message.id, 78 }) 79 ); 80 }, 81 reason => { 82 dbg.postMessage( 83 JSON.stringify({ 84 type: "rpc", 85 result: null, 86 error: reason, 87 id: message.id, 88 }) 89 ); 90 } 91 ); 92 }, 93 }; 94 95 dbg.addListener(listener); 96 } 97 98 if (!DevToolsUtils.isWorkerDebuggerAlive(dbg)) { 99 reject("closed"); 100 return; 101 } 102 103 // WorkerDebugger.url isn't always an absolute URL. 104 // Use the related document URL in order to make it absolute. 105 const absoluteURL = dbg.window?.location?.href 106 ? new URL(dbg.url, dbg.window.location.href).href 107 : dbg.url; 108 109 // Step 2: Send a connect request to the worker debugger. 110 dbg.postMessage( 111 JSON.stringify({ 112 type: "connect", 113 forwardingPrefix, 114 options, 115 workerDebuggerData: { 116 id: dbg.id, 117 name: dbg.name, 118 type: dbg.type, 119 relatedDocumentInnerWindowId: 120 dbg.window?.windowGlobalChild?.innerWindowId, 121 url: absoluteURL, 122 // We don't have access to Services.prefs in Worker thread, so pass its value 123 // from here. 124 workerConsoleApiMessagesDispatchedToMainThread: 125 Services.prefs.getBoolPref( 126 "dom.worker.console.dispatch_events_to_main_thread" 127 ), 128 }, 129 }) 130 ); 131 132 // Steps 3-5 are performed on the worker thread (see worker.js). 133 134 // Step 6: Wait for a connection response from the worker debugger. 135 const listener = { 136 onClose: () => { 137 dbg.removeListener(listener); 138 139 reject("closed"); 140 }, 141 142 onMessage: message => { 143 message = JSON.parse(message); 144 if ( 145 message.type !== "connected" || 146 message.forwardingPrefix !== forwardingPrefix 147 ) { 148 return; 149 } 150 151 // The initial connection message has been received, don't 152 // need to listen any longer 153 dbg.removeListener(listener); 154 155 // Step 7: Create a transport for the connection to the worker. 156 const transport = new MainThreadWorkerDebuggerTransport( 157 dbg, 158 forwardingPrefix 159 ); 160 transport.ready(); 161 transport.hooks = { 162 onTransportClosed: () => { 163 if (DevToolsUtils.isWorkerDebuggerAlive(dbg)) { 164 // If the worker happens to be shutting down while we are trying 165 // to close the connection, there is a small interval during 166 // which no more runnables can be dispatched to the worker, but 167 // the worker debugger has not yet been closed. In that case, 168 // the call to postMessage below will fail. The onTransportClosed hook on 169 // DebuggerTransport is not supposed to throw exceptions, so we 170 // need to make sure to catch these early. 171 try { 172 dbg.postMessage( 173 JSON.stringify({ 174 type: "disconnect", 175 forwardingPrefix, 176 }) 177 ); 178 } catch (e) { 179 // We can safely ignore these exceptions. The only time the 180 // call to postMessage can fail is if the worker is either 181 // shutting down, or has finished shutting down. In both 182 // cases, there is nothing to clean up, so we don't care 183 // whether this message arrives or not. 184 } 185 } 186 187 connection.cancelForwarding(forwardingPrefix); 188 }, 189 190 onPacket: packet => { 191 // Ensure that any packets received from the server on the worker 192 // thread are forwarded to the client on the main thread, as if 193 // they had been sent by the server on the main thread. 194 connection.send(packet); 195 }, 196 }; 197 198 // Ensure that any packets received from the client on the main thread 199 // to actors on the worker thread are forwarded to the server on the 200 // worker thread. 201 connection.setForwarding(forwardingPrefix, transport); 202 203 resolve({ 204 workerTargetForm: message.workerTargetForm, 205 transport, 206 }); 207 }, 208 }; 209 dbg.addListener(listener); 210 }); 211 } 212 213 exports.connectToWorker = connectToWorker;