Path: blob/main/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { BrowserWindow } from 'electron';6import { Socket } from 'net';7import { VSBuffer } from '../../../base/common/buffer.js';8import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';9import { generateUuid } from '../../../base/common/uuid.js';10import { ISocket } from '../../../base/parts/ipc/common/ipc.net.js';11import { upgradeToISocket } from '../../../base/parts/ipc/node/ipc.net.js';12import { OPTIONS, parseArgs } from '../../environment/node/argv.js';13import { IWindowsMainService, OpenContext } from '../../windows/electron-main/windows.js';14import { IOpenExtensionWindowResult } from '../common/extensionHostDebug.js';15import { ExtensionHostDebugBroadcastChannel } from '../common/extensionHostDebugIpc.js';1617export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHostDebugBroadcastChannel<TContext> {1819constructor(20private windowsMainService: IWindowsMainService21) {22super();23}2425override call(ctx: TContext, command: string, arg?: any): Promise<any> {26if (command === 'openExtensionDevelopmentHostWindow') {27return this.openExtensionDevelopmentHostWindow(arg[0], arg[1]);28} else if (command === 'attachToCurrentWindowRenderer') {29return this.attachToCurrentWindowRenderer(arg[0]);30} else {31return super.call(ctx, command, arg);32}33}3435private async attachToCurrentWindowRenderer(windowId: number): Promise<IOpenExtensionWindowResult> {36const codeWindow = this.windowsMainService.getWindowById(windowId);37if (!codeWindow?.win) {38return { success: false };39}4041return this.openCdp(codeWindow.win);42}4344private async openExtensionDevelopmentHostWindow(args: string[], debugRenderer: boolean): Promise<IOpenExtensionWindowResult> {45const pargs = parseArgs(args, OPTIONS);46pargs.debugRenderer = debugRenderer;4748const extDevPaths = pargs.extensionDevelopmentPath;49if (!extDevPaths) {50return { success: false };51}5253const [codeWindow] = await this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {54context: OpenContext.API,55cli: pargs,56forceProfile: pargs.profile,57forceTempProfile: pargs['profile-temp']58});5960if (!debugRenderer) {61return { success: true };62}6364const win = codeWindow.win;65if (!win) {66return { success: true };67}6869return this.openCdp(win);70}7172private async openCdpServer(ident: string, onSocket: (socket: ISocket) => void) {73const { createServer } = await import('http'); // Lazy due to https://github.com/nodejs/node/issues/5968674const server = createServer((req, res) => {75res.statusCode = 404;76res.end();77});7879server.on('upgrade', (req, socket) => {80if (!req.url?.includes(ident)) {81socket.end();82return;83}84const upgraded = upgradeToISocket(req, socket as Socket, {85debugLabel: 'extension-host-cdp-' + generateUuid(),86});8788if (upgraded) {89onSocket(upgraded);90}91});9293return server;94}9596private async openCdp(win: BrowserWindow): Promise<IOpenExtensionWindowResult> {97const debug = win.webContents.debugger;9899let listeners = debug.isAttached() ? Infinity : 0;100const ident = generateUuid();101const server = await this.openCdpServer(ident, listener => {102if (listeners++ === 0) {103debug.attach();104}105106const store = new DisposableStore();107store.add(listener);108109const writeMessage = (message: object) => {110if (!store.isDisposed) { // in case sendCommand promises settle after closed111listener.write(VSBuffer.fromString(JSON.stringify(message))); // null-delimited, CDP-compatible112}113};114115const onMessage = (_event: Electron.Event, method: string, params: unknown, sessionId?: string) =>116writeMessage({ method, params, sessionId });117118const onWindowClose = () => {119listener.end();120store.dispose();121};122123win.addListener('close', onWindowClose);124store.add(toDisposable(() => win.removeListener('close', onWindowClose)));125126debug.addListener('message', onMessage);127store.add(toDisposable(() => debug.removeListener('message', onMessage)));128129store.add(listener.onData(rawData => {130let data: { id: number; sessionId: string; method: string; params: {} };131try {132data = JSON.parse(rawData.toString());133} catch (e) {134console.error('error reading cdp line', e);135return;136}137138debug.sendCommand(data.method, data.params, data.sessionId)139.then((result: object) => writeMessage({ id: data.id, sessionId: data.sessionId, result }))140.catch((error: Error) => writeMessage({ id: data.id, sessionId: data.sessionId, error: { code: 0, message: error.message } }));141}));142143store.add(listener.onClose(() => {144if (--listeners === 0) {145debug.detach();146}147}));148});149150await new Promise<void>(r => server.listen(0, '127.0.0.1', r));151win.on('close', () => server.close());152153const serverAddr = server.address();154const serverAddrBase = typeof serverAddr === 'string' ? serverAddr : `ws://127.0.0.1:${serverAddr?.port}`;155return { rendererDebugAddr: `${serverAddrBase}/${ident}`, success: true };156}157}158159160