Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts
5237 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { BrowserWindow } from 'electron';
7
import { Server } from 'http';
8
import { Socket } from 'net';
9
import { VSBuffer } from '../../../base/common/buffer.js';
10
import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
11
import { generateUuid } from '../../../base/common/uuid.js';
12
import { ISocket } from '../../../base/parts/ipc/common/ipc.net.js';
13
import { upgradeToISocket } from '../../../base/parts/ipc/node/ipc.net.js';
14
import { OPTIONS, parseArgs } from '../../environment/node/argv.js';
15
import { IWindowsMainService, OpenContext } from '../../windows/electron-main/windows.js';
16
import { IOpenExtensionWindowResult } from '../common/extensionHostDebug.js';
17
import { ExtensionHostDebugBroadcastChannel } from '../common/extensionHostDebugIpc.js';
18
19
export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHostDebugBroadcastChannel<TContext> {
20
21
constructor(
22
private windowsMainService: IWindowsMainService
23
) {
24
super();
25
}
26
27
override call(ctx: TContext, command: string, arg?: any): Promise<any> {
28
if (command === 'openExtensionDevelopmentHostWindow') {
29
return this.openExtensionDevelopmentHostWindow(arg[0], arg[1]);
30
} else if (command === 'attachToCurrentWindowRenderer') {
31
return this.attachToCurrentWindowRenderer(arg[0]);
32
} else {
33
return super.call(ctx, command, arg);
34
}
35
}
36
37
private async attachToCurrentWindowRenderer(windowId: number): Promise<IOpenExtensionWindowResult> {
38
const codeWindow = this.windowsMainService.getWindowById(windowId);
39
if (!codeWindow?.win) {
40
return { success: false };
41
}
42
43
return this.openCdp(codeWindow.win, true);
44
}
45
46
private async openExtensionDevelopmentHostWindow(args: string[], debugRenderer: boolean): Promise<IOpenExtensionWindowResult> {
47
const pargs = parseArgs(args, OPTIONS);
48
pargs.debugRenderer = debugRenderer;
49
50
const extDevPaths = pargs.extensionDevelopmentPath;
51
if (!extDevPaths) {
52
return { success: false };
53
}
54
55
const [codeWindow] = await this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
56
context: OpenContext.API,
57
cli: pargs,
58
forceProfile: pargs.profile,
59
forceTempProfile: pargs['profile-temp']
60
});
61
62
if (!debugRenderer) {
63
return { success: true };
64
}
65
66
const win = codeWindow.win;
67
if (!win) {
68
return { success: true };
69
}
70
71
return this.openCdp(win, false);
72
}
73
74
private async openCdpServer(ident: string, onSocket: (socket: ISocket) => void): Promise<{ server: Server; wsUrl: string; port: number }> {
75
const { createServer } = await import('http'); // Lazy due to https://github.com/nodejs/node/issues/59686
76
const server = createServer((req, res) => {
77
if (req.url === '/json/list' || req.url === '/json') {
78
res.setHeader('Content-Type', 'application/json');
79
res.end(JSON.stringify([{
80
description: 'VS Code Renderer',
81
devtoolsFrontendUrl: '',
82
id: ident,
83
title: 'VS Code Renderer',
84
type: 'page',
85
url: 'vscode://renderer',
86
webSocketDebuggerUrl: wsUrl
87
}]));
88
return;
89
} else if (req.url === '/json/version') {
90
res.setHeader('Content-Type', 'application/json');
91
res.end(JSON.stringify({
92
'Browser': 'VS Code Renderer',
93
'Protocol-Version': '1.3',
94
'webSocketDebuggerUrl': wsUrl
95
}));
96
return;
97
}
98
99
res.statusCode = 404;
100
res.end();
101
});
102
103
await new Promise<void>(r => server.listen(0, '127.0.0.1', r));
104
const serverAddr = server.address();
105
const port = typeof serverAddr === 'object' && serverAddr ? serverAddr.port : 0;
106
const serverAddrBase = typeof serverAddr === 'string' ? serverAddr : `ws://127.0.0.1:${serverAddr?.port}`;
107
const wsUrl = `${serverAddrBase}/${ident}`;
108
109
server.on('upgrade', (req, socket) => {
110
if (!req.url?.includes(ident)) {
111
socket.end();
112
return;
113
}
114
const upgraded = upgradeToISocket(req, socket as Socket, {
115
debugLabel: 'extension-host-cdp-' + generateUuid(),
116
enableMessageSplitting: false,
117
});
118
119
if (upgraded) {
120
onSocket(upgraded);
121
}
122
});
123
124
return { server, wsUrl, port };
125
}
126
127
private async openCdp(win: BrowserWindow, debugRenderer: boolean): Promise<IOpenExtensionWindowResult> {
128
const debug = win.webContents.debugger;
129
130
let listeners = debug.isAttached() ? Infinity : 0;
131
const ident = generateUuid();
132
const pageSessionId = debugRenderer ? `page-${ident}` : undefined;
133
const { server, wsUrl, port } = await this.openCdpServer(ident, listener => {
134
if (listeners++ === 0) {
135
debug.attach();
136
}
137
138
const store = new DisposableStore();
139
store.add(listener);
140
141
const writeMessage = (message: object) => {
142
if (!store.isDisposed) { // in case sendCommand promises settle after closed
143
listener.write(VSBuffer.fromString(JSON.stringify(message))); // null-delimited, CDP-compatible
144
}
145
};
146
147
const onMessage = (_event: Electron.Event, method: string, params: unknown, sessionId?: string) =>
148
writeMessage({ method, params, sessionId: sessionId || pageSessionId });
149
150
const onWindowClose = () => {
151
listener.end();
152
store.dispose();
153
};
154
155
win.addListener('close', onWindowClose);
156
store.add(toDisposable(() => win.removeListener('close', onWindowClose)));
157
158
debug.addListener('message', onMessage);
159
store.add(toDisposable(() => debug.removeListener('message', onMessage)));
160
161
store.add(listener.onData(rawData => {
162
let data: { id: number; sessionId?: string; method: string; params: Record<string, unknown> };
163
try {
164
data = JSON.parse(rawData.toString());
165
} catch (e) {
166
console.error('error reading cdp line', e);
167
return;
168
}
169
170
if (debugRenderer) {
171
// Emulate Target.* methods that js-debug expects but Electron's debugger doesn't support
172
const targetInfo = { targetId: ident, type: 'page', title: 'VS Code Renderer', url: 'vscode://renderer' };
173
if (data.method === 'Target.setDiscoverTargets') {
174
writeMessage({ id: data.id, sessionId: data.sessionId, result: {} });
175
writeMessage({ method: 'Target.targetCreated', sessionId: data.sessionId, params: { targetInfo: { ...targetInfo, attached: false, canAccessOpener: false } } });
176
return;
177
}
178
if (data.method === 'Target.attachToTarget') {
179
writeMessage({ id: data.id, sessionId: data.sessionId, result: { sessionId: pageSessionId } });
180
writeMessage({ method: 'Target.attachedToTarget', params: { sessionId: pageSessionId, targetInfo: { ...targetInfo, attached: true, canAccessOpener: false }, waitingForDebugger: false } });
181
return;
182
}
183
if (data.method === 'Target.setAutoAttach' || data.method === 'Target.attachToBrowserTarget') {
184
writeMessage({ id: data.id, sessionId: data.sessionId, result: data.method === 'Target.attachToBrowserTarget' ? { sessionId: 'browser' } : {} });
185
return;
186
}
187
if (data.method === 'Target.getTargets') {
188
writeMessage({ id: data.id, sessionId: data.sessionId, result: { targetInfos: [{ ...targetInfo, attached: true }] } });
189
return;
190
}
191
}
192
193
// Forward to Electron's debugger, stripping our synthetic page sessionId
194
const forwardSessionId = data.sessionId === pageSessionId ? undefined : data.sessionId;
195
196
debug.sendCommand(data.method, data.params, forwardSessionId)
197
.then((result: object) => writeMessage({ id: data.id, sessionId: data.sessionId, result }))
198
.catch((error: Error) => writeMessage({ id: data.id, sessionId: data.sessionId, error: { code: 0, message: error.message } }));
199
}));
200
201
store.add(listener.onClose(() => {
202
if (--listeners === 0) {
203
debug.detach();
204
}
205
}));
206
});
207
208
win.on('close', () => server.close());
209
210
return { rendererDebugAddr: wsUrl, success: true, port: port };
211
}
212
}
213
214