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