as-test 1.1.5 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -1
- package/README.md +4 -9
- package/assembly/index.ts +10 -15
- package/assembly/src/expectation.ts +11 -11
- package/assembly/src/fuzz.ts +11 -7
- package/assembly/src/log.ts +2 -2
- package/assembly/src/suite.ts +5 -5
- package/assembly/src/tests.ts +8 -8
- package/assembly/util/wipc.ts +5 -1
- package/bin/build-worker-pool.js +146 -142
- package/bin/build-worker.js +37 -34
- package/bin/commands/build-core.js +577 -465
- package/bin/commands/build.js +49 -29
- package/bin/commands/clean-core.js +120 -113
- package/bin/commands/clean.js +14 -8
- package/bin/commands/doctor-core.js +288 -289
- package/bin/commands/doctor.js +1 -1
- package/bin/commands/fuzz-core.js +467 -414
- package/bin/commands/fuzz.js +27 -10
- package/bin/commands/init-core.js +905 -794
- package/bin/commands/init.js +2 -2
- package/bin/commands/run-core.js +2675 -2344
- package/bin/commands/run.js +43 -25
- package/bin/commands/test.js +56 -32
- package/bin/commands/web-runner-source.js +1 -1
- package/bin/commands/web-session.js +516 -525
- package/bin/coverage-points.js +363 -341
- package/bin/crash-store.js +56 -66
- package/bin/index.js +4092 -3150
- package/bin/reporters/default.js +1090 -890
- package/bin/reporters/tap.js +319 -325
- package/bin/types.js +67 -67
- package/bin/util.js +1290 -1239
- package/bin/wipc.js +70 -73
- package/lib/build/index.d.ts +3 -1
- package/lib/build/index.js +1039 -1034
- package/lib/build/web-runner/client.js +1 -1
- package/lib/build/web-runner/html.js +1 -1
- package/lib/build/web-runner/worker.js +1 -1
- package/package.json +6 -3
- package/transform/lib/coverage.js +4 -4
- package/transform/lib/log.js +9 -5
- package/assembly/util/json.ts +0 -112
|
@@ -5,579 +5,570 @@ import http from "http";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
const WEB_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
7
7
|
export class PersistentWebSessionHost {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
8
|
+
constructor(headless) {
|
|
9
|
+
this.headless = headless;
|
|
10
|
+
this.html = buildWebSessionHtml();
|
|
11
|
+
this.client = buildWebSessionClientSource();
|
|
12
|
+
this.worker = buildWebSessionWorkerSource();
|
|
13
|
+
this.server = http.createServer((req, res) => this.onRequest(req, res));
|
|
14
|
+
this.browserProcess = null;
|
|
15
|
+
this.ownsBrowserProcess = false;
|
|
16
|
+
this.serverSockets = new Set();
|
|
17
|
+
this.wsSocket = null;
|
|
18
|
+
this.wsBuffer = Buffer.alloc(0);
|
|
19
|
+
this.ready = false;
|
|
20
|
+
this.closed = false;
|
|
21
|
+
this.currentJob = null;
|
|
22
|
+
this.nextJobId = 1;
|
|
23
|
+
this.closeError = null;
|
|
24
|
+
this.readyResolve = null;
|
|
25
|
+
this.readyReject = null;
|
|
26
|
+
this.terminalHooksEnabled = Boolean(process.stdin.isTTY);
|
|
27
|
+
this.onTerminalClosed = () => {
|
|
28
|
+
void this.exitFromTerminalClosure();
|
|
29
|
+
};
|
|
30
|
+
this.onTerminalHangup = () => {
|
|
31
|
+
void this.exitFromTerminalClosure();
|
|
32
|
+
};
|
|
33
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
34
|
+
this.readyResolve = resolve;
|
|
35
|
+
this.readyReject = reject;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
static async start(headless) {
|
|
39
|
+
const host = new PersistentWebSessionHost(headless);
|
|
40
|
+
await host.listen();
|
|
41
|
+
return host;
|
|
42
|
+
}
|
|
43
|
+
async runJob(env, label, onBinary) {
|
|
44
|
+
await this.readyPromise;
|
|
45
|
+
if (this.closed) {
|
|
46
|
+
throw this.closeError ?? new Error("web session host is already closed");
|
|
37
47
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await host.listen();
|
|
41
|
-
return host;
|
|
48
|
+
if (this.currentJob) {
|
|
49
|
+
throw new Error("web session host already has an active job");
|
|
42
50
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
throw this.closeError ?? new Error("web session host is already closed");
|
|
47
|
-
}
|
|
48
|
-
if (this.currentJob) {
|
|
49
|
-
throw new Error("web session host already has an active job");
|
|
50
|
-
}
|
|
51
|
-
const wasmPath = env.AS_TEST_WASM_PATH || "";
|
|
52
|
-
if (!wasmPath.length) {
|
|
53
|
-
throw new Error("AS_TEST_WASM_PATH is not set for web session job");
|
|
54
|
-
}
|
|
55
|
-
const helperPath = env.AS_TEST_HELPER_PATH?.length
|
|
56
|
-
? env.AS_TEST_HELPER_PATH
|
|
57
|
-
: null;
|
|
58
|
-
const jobId = String(this.nextJobId++);
|
|
59
|
-
const browserEnv = {
|
|
60
|
-
...env,
|
|
61
|
-
AS_TEST_WASM_PATH: `/job/${jobId}/${path.basename(wasmPath)}`,
|
|
62
|
-
};
|
|
63
|
-
if (helperPath) {
|
|
64
|
-
browserEnv.AS_TEST_HELPER_PATH = `/job/${jobId}/${path.basename(helperPath)}`;
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
delete browserEnv.AS_TEST_HELPER_PATH;
|
|
68
|
-
}
|
|
69
|
-
await new Promise((resolve, reject) => {
|
|
70
|
-
this.currentJob = {
|
|
71
|
-
id: jobId,
|
|
72
|
-
env: browserEnv,
|
|
73
|
-
label,
|
|
74
|
-
wasmPath,
|
|
75
|
-
helperPath,
|
|
76
|
-
onBinary,
|
|
77
|
-
resolve: () => {
|
|
78
|
-
this.currentJob = null;
|
|
79
|
-
resolve();
|
|
80
|
-
},
|
|
81
|
-
reject: (error) => {
|
|
82
|
-
this.currentJob = null;
|
|
83
|
-
reject(error);
|
|
84
|
-
},
|
|
85
|
-
started: false,
|
|
86
|
-
};
|
|
87
|
-
this.sendControl({ kind: "load", env: browserEnv, label });
|
|
88
|
-
});
|
|
51
|
+
const wasmPath = env.AS_TEST_WASM_PATH || "";
|
|
52
|
+
if (!wasmPath.length) {
|
|
53
|
+
throw new Error("AS_TEST_WASM_PATH is not set for web session job");
|
|
89
54
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
55
|
+
const helperPath = env.AS_TEST_HELPER_PATH?.length
|
|
56
|
+
? env.AS_TEST_HELPER_PATH
|
|
57
|
+
: null;
|
|
58
|
+
const jobId = String(this.nextJobId++);
|
|
59
|
+
const browserEnv = {
|
|
60
|
+
...env,
|
|
61
|
+
AS_TEST_WASM_PATH: `/job/${jobId}/${path.basename(wasmPath)}`,
|
|
62
|
+
};
|
|
63
|
+
if (helperPath) {
|
|
64
|
+
browserEnv.AS_TEST_HELPER_PATH = `/job/${jobId}/${path.basename(helperPath)}`;
|
|
65
|
+
} else {
|
|
66
|
+
delete browserEnv.AS_TEST_HELPER_PATH;
|
|
94
67
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
this.currentJob = {
|
|
70
|
+
id: jobId,
|
|
71
|
+
env: browserEnv,
|
|
72
|
+
label,
|
|
73
|
+
wasmPath,
|
|
74
|
+
helperPath,
|
|
75
|
+
onBinary,
|
|
76
|
+
resolve: () => {
|
|
77
|
+
this.currentJob = null;
|
|
78
|
+
resolve();
|
|
79
|
+
},
|
|
80
|
+
reject: (error) => {
|
|
81
|
+
this.currentJob = null;
|
|
82
|
+
reject(error);
|
|
83
|
+
},
|
|
84
|
+
started: false,
|
|
85
|
+
};
|
|
86
|
+
this.sendControl({ kind: "load", env: browserEnv, label });
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
sendReply(frame) {
|
|
90
|
+
if (!this.wsSocket || !frame.length) return;
|
|
91
|
+
sendWebSocketFrame(this.wsSocket, 0x2, frame);
|
|
92
|
+
}
|
|
93
|
+
async close(reason) {
|
|
94
|
+
if (this.closed) return;
|
|
95
|
+
this.closed = true;
|
|
96
|
+
this.removeTerminalHooks();
|
|
97
|
+
const closeError = reason ?? new Error("web session host closed");
|
|
98
|
+
this.closeError = closeError;
|
|
99
|
+
if (!this.ready) {
|
|
100
|
+
this.readyReject?.(closeError);
|
|
101
|
+
this.readyResolve = null;
|
|
102
|
+
this.readyReject = null;
|
|
103
|
+
}
|
|
104
|
+
if (this.currentJob) {
|
|
105
|
+
this.currentJob.reject(closeError);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
this.sendControl({
|
|
109
|
+
kind: "shutdown",
|
|
110
|
+
ok: reason == null,
|
|
111
|
+
message: reason?.message ?? "",
|
|
112
|
+
});
|
|
113
|
+
} catch {}
|
|
114
|
+
try {
|
|
115
|
+
this.wsSocket?.end();
|
|
116
|
+
} catch {}
|
|
117
|
+
try {
|
|
118
|
+
this.wsSocket?.destroy();
|
|
119
|
+
} catch {}
|
|
120
|
+
try {
|
|
121
|
+
this.server.closeIdleConnections?.();
|
|
122
|
+
this.server.closeAllConnections?.();
|
|
123
|
+
} catch {}
|
|
124
|
+
for (const socket of this.serverSockets) {
|
|
125
|
+
try {
|
|
126
|
+
socket.destroy();
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
await new Promise((resolve) => {
|
|
130
|
+
try {
|
|
131
|
+
this.server.close(() => resolve());
|
|
132
|
+
} catch {
|
|
133
|
+
resolve();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
if (
|
|
137
|
+
this.browserProcess &&
|
|
138
|
+
this.ownsBrowserProcess &&
|
|
139
|
+
!this.browserProcess.killed
|
|
140
|
+
) {
|
|
141
|
+
killOwnedBrowserProcess(this.browserProcess);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async exitFromTerminalClosure() {
|
|
145
|
+
await this.close(new Error("terminal side closed"));
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
async listen() {
|
|
149
|
+
this.installTerminalHooks();
|
|
150
|
+
this.server.on("connection", (socket) => {
|
|
151
|
+
this.serverSockets.add(socket);
|
|
152
|
+
socket.on("close", () => {
|
|
153
|
+
this.serverSockets.delete(socket);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
this.server.on("upgrade", (req, socket) => {
|
|
157
|
+
if ((req.url ?? "") != "/ws") {
|
|
158
|
+
socket.destroy();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const key = String(req.headers["sec-websocket-key"] ?? "");
|
|
162
|
+
if (!key) {
|
|
163
|
+
socket.destroy();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const accept = createHash("sha1")
|
|
167
|
+
.update(key + WEB_MAGIC)
|
|
168
|
+
.digest("base64");
|
|
169
|
+
socket.write(
|
|
170
|
+
[
|
|
171
|
+
"HTTP/1.1 101 Switching Protocols",
|
|
172
|
+
"Upgrade: websocket",
|
|
173
|
+
"Connection: Upgrade",
|
|
174
|
+
"Sec-WebSocket-Accept: " + accept,
|
|
175
|
+
"",
|
|
176
|
+
"",
|
|
177
|
+
].join("\r\n"),
|
|
178
|
+
);
|
|
179
|
+
this.wsSocket = socket;
|
|
180
|
+
this.wsBuffer = Buffer.alloc(0);
|
|
181
|
+
socket.on("data", (chunk) => this.onWebSocketData(chunk));
|
|
182
|
+
socket.on("end", () => {
|
|
183
|
+
this.wsSocket = null;
|
|
184
|
+
if (!this.closed) {
|
|
185
|
+
void this.close(new Error("web browser disconnected"));
|
|
124
186
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
187
|
+
});
|
|
188
|
+
socket.on("close", () => {
|
|
189
|
+
this.wsSocket = null;
|
|
190
|
+
if (!this.closed) {
|
|
191
|
+
void this.close(new Error("web browser disconnected"));
|
|
129
192
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
catch { }
|
|
193
|
+
});
|
|
194
|
+
socket.on("error", (error) => {
|
|
195
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
196
|
+
if (!this.closed) {
|
|
197
|
+
void this.close(err);
|
|
136
198
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
await new Promise((resolve, reject) => {
|
|
202
|
+
this.server.once("error", (error) =>
|
|
203
|
+
reject(error instanceof Error ? error : new Error(String(error))),
|
|
204
|
+
);
|
|
205
|
+
this.server.listen(0, "127.0.0.1", () => resolve());
|
|
206
|
+
});
|
|
207
|
+
const address = this.server.address();
|
|
208
|
+
if (!address || typeof address == "string") {
|
|
209
|
+
throw new Error("failed to determine web session host address");
|
|
210
|
+
}
|
|
211
|
+
const url = `http://127.0.0.1:${address.port}/`;
|
|
212
|
+
if (!this.headless && !process.env.BROWSER?.trim().length) {
|
|
213
|
+
process.stdout.write(`Open web session: ${url}\n`);
|
|
214
|
+
} else {
|
|
215
|
+
const launched = launchBrowser(url, this.headless);
|
|
216
|
+
this.browserProcess = launched.process;
|
|
217
|
+
this.ownsBrowserProcess = launched.ownsProcess;
|
|
218
|
+
this.browserProcess.on("close", (code) => {
|
|
219
|
+
if (this.closed) return;
|
|
220
|
+
const error = new Error(
|
|
221
|
+
`web browser process exited with code ${code ?? 0}`,
|
|
222
|
+
);
|
|
223
|
+
if (!this.ready && this.readyReject) {
|
|
224
|
+
this.readyReject(error);
|
|
225
|
+
return;
|
|
149
226
|
}
|
|
227
|
+
void this.close(error);
|
|
228
|
+
});
|
|
150
229
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
230
|
+
await this.readyPromise;
|
|
231
|
+
}
|
|
232
|
+
installTerminalHooks() {
|
|
233
|
+
if (!this.terminalHooksEnabled) return;
|
|
234
|
+
process.stdin.on("close", this.onTerminalClosed);
|
|
235
|
+
process.stdin.on("end", this.onTerminalClosed);
|
|
236
|
+
process.on("SIGHUP", this.onTerminalHangup);
|
|
237
|
+
}
|
|
238
|
+
removeTerminalHooks() {
|
|
239
|
+
if (!this.terminalHooksEnabled) return;
|
|
240
|
+
process.stdin.off("close", this.onTerminalClosed);
|
|
241
|
+
process.stdin.off("end", this.onTerminalClosed);
|
|
242
|
+
process.off("SIGHUP", this.onTerminalHangup);
|
|
243
|
+
}
|
|
244
|
+
onRequest(req, res) {
|
|
245
|
+
const headers = {
|
|
246
|
+
"Cross-Origin-Embedder-Policy": "require-corp",
|
|
247
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
248
|
+
"Cache-Control": "no-store",
|
|
249
|
+
};
|
|
250
|
+
const url = req.url ?? "/";
|
|
251
|
+
if (url == "/" || url.startsWith("/?")) {
|
|
252
|
+
res.writeHead(200, {
|
|
253
|
+
...headers,
|
|
254
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
255
|
+
});
|
|
256
|
+
res.end(this.html);
|
|
257
|
+
return;
|
|
154
258
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
"Upgrade: websocket",
|
|
179
|
-
"Connection: Upgrade",
|
|
180
|
-
"Sec-WebSocket-Accept: " + accept,
|
|
181
|
-
"",
|
|
182
|
-
"",
|
|
183
|
-
].join("\r\n"));
|
|
184
|
-
this.wsSocket = socket;
|
|
185
|
-
this.wsBuffer = Buffer.alloc(0);
|
|
186
|
-
socket.on("data", (chunk) => this.onWebSocketData(chunk));
|
|
187
|
-
socket.on("end", () => {
|
|
188
|
-
this.wsSocket = null;
|
|
189
|
-
if (!this.closed) {
|
|
190
|
-
void this.close(new Error("web browser disconnected"));
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
socket.on("close", () => {
|
|
194
|
-
this.wsSocket = null;
|
|
195
|
-
if (!this.closed) {
|
|
196
|
-
void this.close(new Error("web browser disconnected"));
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
socket.on("error", (error) => {
|
|
200
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
201
|
-
if (!this.closed) {
|
|
202
|
-
void this.close(err);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
259
|
+
if (url == "/client.js") {
|
|
260
|
+
res.writeHead(200, {
|
|
261
|
+
...headers,
|
|
262
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
263
|
+
});
|
|
264
|
+
res.end(this.client);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (url == "/worker.js") {
|
|
268
|
+
res.writeHead(200, {
|
|
269
|
+
...headers,
|
|
270
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
271
|
+
});
|
|
272
|
+
res.end(this.worker);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (this.currentJob) {
|
|
276
|
+
const wasmUrl = this.currentJob.env.AS_TEST_WASM_PATH;
|
|
277
|
+
const helperUrl = this.currentJob.env.AS_TEST_HELPER_PATH ?? "";
|
|
278
|
+
if (url == wasmUrl) {
|
|
279
|
+
res.writeHead(200, {
|
|
280
|
+
...headers,
|
|
281
|
+
"Content-Type": "application/wasm",
|
|
205
282
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
283
|
+
res.end(fs.readFileSync(this.currentJob.wasmPath));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (helperUrl.length && url == helperUrl && this.currentJob.helperPath) {
|
|
287
|
+
res.writeHead(200, {
|
|
288
|
+
...headers,
|
|
289
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
209
290
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
291
|
+
res.end(fs.readFileSync(this.currentJob.helperPath, "utf8"));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
res.writeHead(404, headers);
|
|
296
|
+
res.end("not found");
|
|
297
|
+
}
|
|
298
|
+
onWebSocketData(chunk) {
|
|
299
|
+
this.wsBuffer = Buffer.concat([this.wsBuffer, chunk]);
|
|
300
|
+
while (this.wsBuffer.length >= 2) {
|
|
301
|
+
const first = this.wsBuffer[0];
|
|
302
|
+
const second = this.wsBuffer[1];
|
|
303
|
+
const opcode = first & 0x0f;
|
|
304
|
+
const masked = (second & 0x80) !== 0;
|
|
305
|
+
let length = second & 0x7f;
|
|
306
|
+
let offset = 2;
|
|
307
|
+
if (length == 126) {
|
|
308
|
+
if (this.wsBuffer.length < offset + 2) return;
|
|
309
|
+
length = this.wsBuffer.readUInt16BE(offset);
|
|
310
|
+
offset += 2;
|
|
311
|
+
} else if (length == 127) {
|
|
312
|
+
if (this.wsBuffer.length < offset + 8) return;
|
|
313
|
+
length = Number(this.wsBuffer.readBigUInt64BE(offset));
|
|
314
|
+
offset += 8;
|
|
315
|
+
}
|
|
316
|
+
const maskLength = masked ? 4 : 0;
|
|
317
|
+
if (this.wsBuffer.length < offset + maskLength + length) return;
|
|
318
|
+
let payload = this.wsBuffer.subarray(
|
|
319
|
+
offset + maskLength,
|
|
320
|
+
offset + maskLength + length,
|
|
321
|
+
);
|
|
322
|
+
if (masked) {
|
|
323
|
+
const mask = this.wsBuffer.subarray(offset, offset + 4);
|
|
324
|
+
const unmasked = Buffer.alloc(length);
|
|
325
|
+
for (let i = 0; i < length; i++) {
|
|
326
|
+
unmasked[i] = payload[i] ^ mask[i % 4];
|
|
232
327
|
}
|
|
233
|
-
|
|
328
|
+
payload = unmasked;
|
|
329
|
+
} else {
|
|
330
|
+
payload = Buffer.from(payload);
|
|
331
|
+
}
|
|
332
|
+
this.wsBuffer = this.wsBuffer.subarray(offset + maskLength + length);
|
|
333
|
+
if (opcode == 0x8) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (opcode == 0x1) {
|
|
337
|
+
this.onControl(payload.toString("utf8"));
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (opcode == 0x2 && this.currentJob) {
|
|
341
|
+
this.currentJob.onBinary(Buffer.from(payload));
|
|
342
|
+
}
|
|
234
343
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
344
|
+
}
|
|
345
|
+
onControl(raw) {
|
|
346
|
+
let message = null;
|
|
347
|
+
try {
|
|
348
|
+
message = JSON.parse(raw);
|
|
349
|
+
} catch {
|
|
350
|
+
return;
|
|
241
351
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
352
|
+
if (message?.kind == "ready") {
|
|
353
|
+
this.ready = true;
|
|
354
|
+
this.readyResolve?.();
|
|
355
|
+
this.readyResolve = null;
|
|
356
|
+
this.readyReject = null;
|
|
357
|
+
return;
|
|
248
358
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const url = req.url ?? "/";
|
|
256
|
-
if (url == "/" || url.startsWith("/?")) {
|
|
257
|
-
res.writeHead(200, {
|
|
258
|
-
...headers,
|
|
259
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
260
|
-
});
|
|
261
|
-
res.end(this.html);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (url == "/client.js") {
|
|
265
|
-
res.writeHead(200, {
|
|
266
|
-
...headers,
|
|
267
|
-
"Content-Type": "text/javascript; charset=utf-8",
|
|
268
|
-
});
|
|
269
|
-
res.end(this.client);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
if (url == "/worker.js") {
|
|
273
|
-
res.writeHead(200, {
|
|
274
|
-
...headers,
|
|
275
|
-
"Content-Type": "text/javascript; charset=utf-8",
|
|
276
|
-
});
|
|
277
|
-
res.end(this.worker);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (this.currentJob) {
|
|
281
|
-
const wasmUrl = this.currentJob.env.AS_TEST_WASM_PATH;
|
|
282
|
-
const helperUrl = this.currentJob.env.AS_TEST_HELPER_PATH ?? "";
|
|
283
|
-
if (url == wasmUrl) {
|
|
284
|
-
res.writeHead(200, {
|
|
285
|
-
...headers,
|
|
286
|
-
"Content-Type": "application/wasm",
|
|
287
|
-
});
|
|
288
|
-
res.end(fs.readFileSync(this.currentJob.wasmPath));
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (helperUrl.length && url == helperUrl && this.currentJob.helperPath) {
|
|
292
|
-
res.writeHead(200, {
|
|
293
|
-
...headers,
|
|
294
|
-
"Content-Type": "text/javascript; charset=utf-8",
|
|
295
|
-
});
|
|
296
|
-
res.end(fs.readFileSync(this.currentJob.helperPath, "utf8"));
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
res.writeHead(404, headers);
|
|
301
|
-
res.end("not found");
|
|
359
|
+
if (message?.kind == "instantiated") {
|
|
360
|
+
if (this.currentJob && !this.currentJob.started) {
|
|
361
|
+
this.currentJob.started = true;
|
|
362
|
+
this.sendControl({ kind: "start" });
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
302
365
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const first = this.wsBuffer[0];
|
|
307
|
-
const second = this.wsBuffer[1];
|
|
308
|
-
const opcode = first & 0x0f;
|
|
309
|
-
const masked = (second & 0x80) !== 0;
|
|
310
|
-
let length = second & 0x7f;
|
|
311
|
-
let offset = 2;
|
|
312
|
-
if (length == 126) {
|
|
313
|
-
if (this.wsBuffer.length < offset + 2)
|
|
314
|
-
return;
|
|
315
|
-
length = this.wsBuffer.readUInt16BE(offset);
|
|
316
|
-
offset += 2;
|
|
317
|
-
}
|
|
318
|
-
else if (length == 127) {
|
|
319
|
-
if (this.wsBuffer.length < offset + 8)
|
|
320
|
-
return;
|
|
321
|
-
length = Number(this.wsBuffer.readBigUInt64BE(offset));
|
|
322
|
-
offset += 8;
|
|
323
|
-
}
|
|
324
|
-
const maskLength = masked ? 4 : 0;
|
|
325
|
-
if (this.wsBuffer.length < offset + maskLength + length)
|
|
326
|
-
return;
|
|
327
|
-
let payload = this.wsBuffer.subarray(offset + maskLength, offset + maskLength + length);
|
|
328
|
-
if (masked) {
|
|
329
|
-
const mask = this.wsBuffer.subarray(offset, offset + 4);
|
|
330
|
-
const unmasked = Buffer.alloc(length);
|
|
331
|
-
for (let i = 0; i < length; i++) {
|
|
332
|
-
unmasked[i] = payload[i] ^ mask[i % 4];
|
|
333
|
-
}
|
|
334
|
-
payload = unmasked;
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
payload = Buffer.from(payload);
|
|
338
|
-
}
|
|
339
|
-
this.wsBuffer = this.wsBuffer.subarray(offset + maskLength + length);
|
|
340
|
-
if (opcode == 0x8) {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
if (opcode == 0x1) {
|
|
344
|
-
this.onControl(payload.toString("utf8"));
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
if (opcode == 0x2 && this.currentJob) {
|
|
348
|
-
this.currentJob.onBinary(Buffer.from(payload));
|
|
349
|
-
}
|
|
350
|
-
}
|
|
366
|
+
if (message?.kind == "done") {
|
|
367
|
+
this.currentJob?.resolve();
|
|
368
|
+
return;
|
|
351
369
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (message?.kind == "ready") {
|
|
361
|
-
this.ready = true;
|
|
362
|
-
this.readyResolve?.();
|
|
363
|
-
this.readyResolve = null;
|
|
364
|
-
this.readyReject = null;
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
if (message?.kind == "instantiated") {
|
|
368
|
-
if (this.currentJob && !this.currentJob.started) {
|
|
369
|
-
this.currentJob.started = true;
|
|
370
|
-
this.sendControl({ kind: "start" });
|
|
371
|
-
}
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
if (message?.kind == "done") {
|
|
375
|
-
this.currentJob?.resolve();
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
if (message?.kind == "error") {
|
|
379
|
-
this.currentJob?.reject(new Error(String(message.message ?? "browser runtime failed")));
|
|
380
|
-
}
|
|
370
|
+
if (message?.kind == "error") {
|
|
371
|
+
this.currentJob?.reject(
|
|
372
|
+
new Error(String(message.message ?? "browser runtime failed")),
|
|
373
|
+
);
|
|
381
374
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
sendWebSocketFrame(this.wsSocket, 0x1, Buffer.from(JSON.stringify(message), "utf8"));
|
|
375
|
+
}
|
|
376
|
+
sendControl(message) {
|
|
377
|
+
if (!this.wsSocket) {
|
|
378
|
+
throw new Error("web session host is not connected to a browser");
|
|
387
379
|
}
|
|
380
|
+
sendWebSocketFrame(
|
|
381
|
+
this.wsSocket,
|
|
382
|
+
0x1,
|
|
383
|
+
Buffer.from(JSON.stringify(message), "utf8"),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
388
386
|
}
|
|
389
387
|
function sendWebSocketFrame(socket, opcode, payload) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
socket.write(Buffer.concat([header, payload]));
|
|
388
|
+
let header;
|
|
389
|
+
if (payload.length < 126) {
|
|
390
|
+
header = Buffer.from([0x80 | opcode, payload.length]);
|
|
391
|
+
} else if (payload.length < 65536) {
|
|
392
|
+
header = Buffer.alloc(4);
|
|
393
|
+
header[0] = 0x80 | opcode;
|
|
394
|
+
header[1] = 126;
|
|
395
|
+
header.writeUInt16BE(payload.length, 2);
|
|
396
|
+
} else {
|
|
397
|
+
header = Buffer.alloc(10);
|
|
398
|
+
header[0] = 0x80 | opcode;
|
|
399
|
+
header[1] = 127;
|
|
400
|
+
header.writeBigUInt64BE(BigInt(payload.length), 2);
|
|
401
|
+
}
|
|
402
|
+
socket.write(Buffer.concat([header, payload]));
|
|
407
403
|
}
|
|
408
404
|
function launchBrowser(url, headless) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
: "could not open a browser automatically");
|
|
405
|
+
if (process.env.BROWSER?.trim()) {
|
|
406
|
+
const child = spawnBrowserCommand(process.env.BROWSER, url, headless);
|
|
407
|
+
if (child) return { process: child, ownsProcess: true };
|
|
408
|
+
}
|
|
409
|
+
if (!headless) {
|
|
410
|
+
const opened = openWithSystemBrowser(url);
|
|
411
|
+
if (opened) return { process: opened, ownsProcess: false };
|
|
412
|
+
}
|
|
413
|
+
const direct = openWithInstalledBrowser(url, headless);
|
|
414
|
+
if (direct) return { process: direct, ownsProcess: true };
|
|
415
|
+
throw new Error(
|
|
416
|
+
headless
|
|
417
|
+
? "could not find a headless-capable browser"
|
|
418
|
+
: "could not open a browser automatically",
|
|
419
|
+
);
|
|
425
420
|
}
|
|
426
421
|
function openWithSystemBrowser(url) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (!hasExecutable("xdg-open"))
|
|
441
|
-
return null;
|
|
442
|
-
return spawn("xdg-open", [url], { stdio: "ignore", detached: true });
|
|
422
|
+
if (process.platform == "darwin") {
|
|
423
|
+
if (!hasExecutable("open")) return null;
|
|
424
|
+
return spawn("open", [url], { stdio: "ignore", detached: true });
|
|
425
|
+
}
|
|
426
|
+
if (process.platform == "win32") {
|
|
427
|
+
if (!hasExecutable("cmd")) return null;
|
|
428
|
+
return spawn("cmd", ["/c", "start", "", url], {
|
|
429
|
+
stdio: "ignore",
|
|
430
|
+
detached: true,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
if (!hasExecutable("xdg-open")) return null;
|
|
434
|
+
return spawn("xdg-open", [url], { stdio: "ignore", detached: true });
|
|
443
435
|
}
|
|
444
436
|
function openWithInstalledBrowser(url, headless) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
437
|
+
const candidates = [
|
|
438
|
+
{ command: "chromium", headless: ["--headless=new"] },
|
|
439
|
+
{ command: "chromium-browser", headless: ["--headless=new"] },
|
|
440
|
+
{ command: "google-chrome", headless: ["--headless=new"] },
|
|
441
|
+
{ command: "google-chrome-stable", headless: ["--headless=new"] },
|
|
442
|
+
{ command: "chrome", headless: ["--headless=new"] },
|
|
443
|
+
{ command: "msedge", headless: ["--headless=new"] },
|
|
444
|
+
{ command: "firefox", headless: ["-headless"] },
|
|
445
|
+
];
|
|
446
|
+
for (const candidate of candidates) {
|
|
447
|
+
if (!hasExecutable(candidate.command)) continue;
|
|
448
|
+
return spawn(
|
|
449
|
+
candidate.command,
|
|
450
|
+
[...(headless ? candidate.headless : []), url],
|
|
451
|
+
{ stdio: "ignore", detached: true },
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
460
455
|
}
|
|
461
456
|
function spawnBrowserCommand(commandValue, url, headless) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
args.push(url);
|
|
466
|
-
return spawn(direct, args, {
|
|
467
|
-
stdio: "ignore",
|
|
468
|
-
detached: true,
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
const parts = splitCommand(String(commandValue));
|
|
472
|
-
if (!parts.length)
|
|
473
|
-
return null;
|
|
474
|
-
const command = parts[0];
|
|
475
|
-
if (!hasExecutable(command))
|
|
476
|
-
return null;
|
|
477
|
-
const args = parts.slice(1);
|
|
478
|
-
if (headless) {
|
|
479
|
-
args.push(...resolveHeadlessFlags(commandValue));
|
|
480
|
-
}
|
|
457
|
+
const direct = unwrapQuotedPath(String(commandValue).trim());
|
|
458
|
+
if (hasExecutable(direct)) {
|
|
459
|
+
const args = headless ? resolveHeadlessFlags(direct) : [];
|
|
481
460
|
args.push(url);
|
|
482
|
-
return spawn(
|
|
483
|
-
|
|
484
|
-
|
|
461
|
+
return spawn(direct, args, {
|
|
462
|
+
stdio: "ignore",
|
|
463
|
+
detached: true,
|
|
485
464
|
});
|
|
465
|
+
}
|
|
466
|
+
const parts = splitCommand(String(commandValue));
|
|
467
|
+
if (!parts.length) return null;
|
|
468
|
+
const command = parts[0];
|
|
469
|
+
if (!hasExecutable(command)) return null;
|
|
470
|
+
const args = parts.slice(1);
|
|
471
|
+
if (headless) {
|
|
472
|
+
args.push(...resolveHeadlessFlags(commandValue));
|
|
473
|
+
}
|
|
474
|
+
args.push(url);
|
|
475
|
+
return spawn(command, args, {
|
|
476
|
+
stdio: "ignore",
|
|
477
|
+
detached: true,
|
|
478
|
+
});
|
|
486
479
|
}
|
|
487
480
|
function resolveHeadlessFlags(commandValue) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
];
|
|
481
|
+
const lower = commandValue.toLowerCase();
|
|
482
|
+
if (lower.includes("firefox")) return ["-headless"];
|
|
483
|
+
return [
|
|
484
|
+
"--headless=new",
|
|
485
|
+
"--disable-gpu",
|
|
486
|
+
"--no-first-run",
|
|
487
|
+
"--no-default-browser-check",
|
|
488
|
+
];
|
|
497
489
|
}
|
|
498
490
|
function hasExecutable(command) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
}
|
|
491
|
+
if (!command.length) return false;
|
|
492
|
+
if (command.includes("/") || command.includes("\\")) {
|
|
493
|
+
return fs.existsSync(command);
|
|
494
|
+
}
|
|
495
|
+
const pathValue = process.env.PATH ?? "";
|
|
496
|
+
const suffixes =
|
|
497
|
+
process.platform == "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
|
|
498
|
+
for (const base of pathValue.split(path.delimiter)) {
|
|
499
|
+
if (!base) continue;
|
|
500
|
+
for (const suffix of suffixes) {
|
|
501
|
+
if (fs.existsSync(path.join(base, command + suffix))) {
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
514
504
|
}
|
|
515
|
-
|
|
505
|
+
}
|
|
506
|
+
return false;
|
|
516
507
|
}
|
|
517
508
|
function splitCommand(commandValue) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
else {
|
|
531
|
-
current += char;
|
|
532
|
-
}
|
|
533
|
-
continue;
|
|
534
|
-
}
|
|
535
|
-
if (char == "'" || char == '"') {
|
|
536
|
-
quote = char;
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
if (/\s/.test(char)) {
|
|
540
|
-
if (current.length) {
|
|
541
|
-
parts.push(current);
|
|
542
|
-
current = "";
|
|
543
|
-
}
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
if (char == "\\" && i + 1 < commandValue.length) {
|
|
547
|
-
current += commandValue[++i];
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
509
|
+
const parts = [];
|
|
510
|
+
let current = "";
|
|
511
|
+
let quote = "";
|
|
512
|
+
for (let i = 0; i < commandValue.length; i++) {
|
|
513
|
+
const char = commandValue[i];
|
|
514
|
+
if (quote) {
|
|
515
|
+
if (char == quote) {
|
|
516
|
+
quote = "";
|
|
517
|
+
} else if (char == "\\" && i + 1 < commandValue.length) {
|
|
518
|
+
current += commandValue[++i];
|
|
519
|
+
} else {
|
|
550
520
|
current += char;
|
|
521
|
+
}
|
|
522
|
+
continue;
|
|
551
523
|
}
|
|
552
|
-
if (
|
|
524
|
+
if (char == "'" || char == '"') {
|
|
525
|
+
quote = char;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (/\s/.test(char)) {
|
|
529
|
+
if (current.length) {
|
|
553
530
|
parts.push(current);
|
|
531
|
+
current = "";
|
|
532
|
+
}
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (char == "\\" && i + 1 < commandValue.length) {
|
|
536
|
+
current += commandValue[++i];
|
|
537
|
+
continue;
|
|
554
538
|
}
|
|
555
|
-
|
|
539
|
+
current += char;
|
|
540
|
+
}
|
|
541
|
+
if (current.length) {
|
|
542
|
+
parts.push(current);
|
|
543
|
+
}
|
|
544
|
+
return parts;
|
|
556
545
|
}
|
|
557
546
|
function unwrapQuotedPath(value) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
return value;
|
|
547
|
+
if (
|
|
548
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
549
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
550
|
+
) {
|
|
551
|
+
return value.slice(1, -1);
|
|
552
|
+
}
|
|
553
|
+
return value;
|
|
563
554
|
}
|
|
564
555
|
function killOwnedBrowserProcess(browserProcess) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
catch { }
|
|
574
|
-
try {
|
|
575
|
-
browserProcess.kill("SIGTERM");
|
|
556
|
+
try {
|
|
557
|
+
if (
|
|
558
|
+
process.platform != "win32" &&
|
|
559
|
+
typeof browserProcess.pid == "number" &&
|
|
560
|
+
browserProcess.pid > 0
|
|
561
|
+
) {
|
|
562
|
+
process.kill(-browserProcess.pid, "SIGTERM");
|
|
563
|
+
return;
|
|
576
564
|
}
|
|
577
|
-
|
|
565
|
+
} catch {}
|
|
566
|
+
try {
|
|
567
|
+
browserProcess.kill("SIGTERM");
|
|
568
|
+
} catch {}
|
|
578
569
|
}
|
|
579
570
|
function buildWebSessionHtml() {
|
|
580
|
-
|
|
571
|
+
return `<!doctype html>
|
|
581
572
|
<html lang="en">
|
|
582
573
|
<head>
|
|
583
574
|
<meta charset="utf-8" />
|
|
@@ -761,7 +752,7 @@ function buildWebSessionHtml() {
|
|
|
761
752
|
</html>`;
|
|
762
753
|
}
|
|
763
754
|
function buildWebSessionClientSource() {
|
|
764
|
-
|
|
755
|
+
return String.raw`const runnerOrigin = location.origin;
|
|
765
756
|
const worker = new Worker(new URL("/worker.js", runnerOrigin), { type: "module" });
|
|
766
757
|
const wsUrl = new URL("/ws", runnerOrigin);
|
|
767
758
|
wsUrl.protocol = location.protocol == "https:" ? "wss:" : "ws:";
|
|
@@ -918,7 +909,7 @@ exitButton.addEventListener("click", () => {
|
|
|
918
909
|
`;
|
|
919
910
|
}
|
|
920
911
|
function buildWebSessionWorkerSource() {
|
|
921
|
-
|
|
912
|
+
return String.raw`let replyState = null;
|
|
922
913
|
let replyBytes = null;
|
|
923
914
|
const WIPC_MAGIC = [0x57, 0x49, 0x50, 0x43];
|
|
924
915
|
let runtimeEnv = {};
|