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
3296 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 { Socket } from 'net';
8
import { VSBuffer } from '../../../base/common/buffer.js';
9
import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
10
import { generateUuid } from '../../../base/common/uuid.js';
11
import { ISocket } from '../../../base/parts/ipc/common/ipc.net.js';
12
import { upgradeToISocket } from '../../../base/parts/ipc/node/ipc.net.js';
13
import { OPTIONS, parseArgs } from '../../environment/node/argv.js';
14
import { IWindowsMainService, OpenContext } from '../../windows/electron-main/windows.js';
15
import { IOpenExtensionWindowResult } from '../common/extensionHostDebug.js';
16
import { ExtensionHostDebugBroadcastChannel } from '../common/extensionHostDebugIpc.js';
17
18
export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHostDebugBroadcastChannel<TContext> {
19
20
constructor(
21
private windowsMainService: IWindowsMainService
22
) {
23
super();
24
}
25
26
override call(ctx: TContext, command: string, arg?: any): Promise<any> {
27
if (command === 'openExtensionDevelopmentHostWindow') {
28
return this.openExtensionDevelopmentHostWindow(arg[0], arg[1]);
29
} else if (command === 'attachToCurrentWindowRenderer') {
30
return this.attachToCurrentWindowRenderer(arg[0]);
31
} else {
32
return super.call(ctx, command, arg);
33
}
34
}
35
36
private async attachToCurrentWindowRenderer(windowId: number): Promise<IOpenExtensionWindowResult> {
37
const codeWindow = this.windowsMainService.getWindowById(windowId);
38
if (!codeWindow?.win) {
39
return { success: false };
40
}
41
42
return this.openCdp(codeWindow.win);
43
}
44
45
private async openExtensionDevelopmentHostWindow(args: string[], debugRenderer: boolean): Promise<IOpenExtensionWindowResult> {
46
const pargs = parseArgs(args, OPTIONS);
47
pargs.debugRenderer = debugRenderer;
48
49
const extDevPaths = pargs.extensionDevelopmentPath;
50
if (!extDevPaths) {
51
return { success: false };
52
}
53
54
const [codeWindow] = await this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
55
context: OpenContext.API,
56
cli: pargs,
57
forceProfile: pargs.profile,
58
forceTempProfile: pargs['profile-temp']
59
});
60
61
if (!debugRenderer) {
62
return { success: true };
63
}
64
65
const win = codeWindow.win;
66
if (!win) {
67
return { success: true };
68
}
69
70
return this.openCdp(win);
71
}
72
73
private async openCdpServer(ident: string, onSocket: (socket: ISocket) => void) {
74
const { createServer } = await import('http'); // Lazy due to https://github.com/nodejs/node/issues/59686
75
const server = createServer((req, res) => {
76
res.statusCode = 404;
77
res.end();
78
});
79
80
server.on('upgrade', (req, socket) => {
81
if (!req.url?.includes(ident)) {
82
socket.end();
83
return;
84
}
85
const upgraded = upgradeToISocket(req, socket as Socket, {
86
debugLabel: 'extension-host-cdp-' + generateUuid(),
87
});
88
89
if (upgraded) {
90
onSocket(upgraded);
91
}
92
});
93
94
return server;
95
}
96
97
private async openCdp(win: BrowserWindow): Promise<IOpenExtensionWindowResult> {
98
const debug = win.webContents.debugger;
99
100
let listeners = debug.isAttached() ? Infinity : 0;
101
const ident = generateUuid();
102
const server = await this.openCdpServer(ident, listener => {
103
if (listeners++ === 0) {
104
debug.attach();
105
}
106
107
const store = new DisposableStore();
108
store.add(listener);
109
110
const writeMessage = (message: object) => {
111
if (!store.isDisposed) { // in case sendCommand promises settle after closed
112
listener.write(VSBuffer.fromString(JSON.stringify(message))); // null-delimited, CDP-compatible
113
}
114
};
115
116
const onMessage = (_event: Electron.Event, method: string, params: unknown, sessionId?: string) =>
117
writeMessage({ method, params, sessionId });
118
119
const onWindowClose = () => {
120
listener.end();
121
store.dispose();
122
};
123
124
win.addListener('close', onWindowClose);
125
store.add(toDisposable(() => win.removeListener('close', onWindowClose)));
126
127
debug.addListener('message', onMessage);
128
store.add(toDisposable(() => debug.removeListener('message', onMessage)));
129
130
store.add(listener.onData(rawData => {
131
let data: { id: number; sessionId: string; method: string; params: {} };
132
try {
133
data = JSON.parse(rawData.toString());
134
} catch (e) {
135
console.error('error reading cdp line', e);
136
return;
137
}
138
139
debug.sendCommand(data.method, data.params, data.sessionId)
140
.then((result: object) => writeMessage({ id: data.id, sessionId: data.sessionId, result }))
141
.catch((error: Error) => writeMessage({ id: data.id, sessionId: data.sessionId, error: { code: 0, message: error.message } }));
142
}));
143
144
store.add(listener.onClose(() => {
145
if (--listeners === 0) {
146
debug.detach();
147
}
148
}));
149
});
150
151
await new Promise<void>(r => server.listen(0, '127.0.0.1', r));
152
win.on('close', () => server.close());
153
154
const serverAddr = server.address();
155
const serverAddrBase = typeof serverAddr === 'string' ? serverAddr : `ws://127.0.0.1:${serverAddr?.port}`;
156
return { rendererDebugAddr: `${serverAddrBase}/${ident}`, success: true };
157
}
158
}
159
160