Path: blob/main/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts
5267 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 { Codicon } from '../../../../base/common/codicons.js';6import { Disposable } from '../../../../base/common/lifecycle.js';7import { randomPort } from '../../../../base/common/ports.js';8import * as nls from '../../../../nls.js';9import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';10import { Action2, MenuId } from '../../../../platform/actions/common/actions.js';11import { IExtensionHostDebugService } from '../../../../platform/debug/common/extensionHostDebug.js';12import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';13import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';14import { INativeHostService } from '../../../../platform/native/common/native.js';15import { IProductService } from '../../../../platform/product/common/productService.js';16import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';17import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';18import { ActiveEditorContext } from '../../../common/contextkeys.js';19import { IWorkbenchContribution } from '../../../common/contributions.js';20import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';21import { ExtensionHostKind } from '../../../services/extensions/common/extensionHostKind.js';22import { IExtensionService, IExtensionInspectInfo } from '../../../services/extensions/common/extensions.js';23import { IHostService } from '../../../services/host/browser/host.js';24import { IConfig, IDebugService } from '../../debug/common/debug.js';25import { RuntimeExtensionsEditor } from './runtimeExtensionsEditor.js';26import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';2728interface IExtensionHostQuickPickItem extends IQuickPickItem {29portInfo: IExtensionInspectInfo;30}3132// Shared helpers for debug actions33async function getExtensionHostPort(34extensionService: IExtensionService,35nativeHostService: INativeHostService,36dialogService: IDialogService,37productService: IProductService,38): Promise<number | undefined> {39const inspectPorts = await extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, false);40if (inspectPorts.length === 0) {41const res = await dialogService.confirm({42message: nls.localize('restart1', "Debug Extensions"),43detail: nls.localize('restart2', "In order to debug extensions a restart is required. Do you want to restart '{0}' now?", productService.nameLong),44primaryButton: nls.localize({ key: 'restart3', comment: ['&& denotes a mnemonic'] }, "&&Restart")45});46if (res.confirmed) {47await nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });48}49return undefined;50}51if (inspectPorts.length > 1) {52console.warn(`There are multiple extension hosts available for debugging. Picking the first one...`);53}54return inspectPorts[0].port;55}5657async function getRendererDebugPort(58extensionHostDebugService: IExtensionHostDebugService,59windowId: number,60): Promise<number | undefined> {61const result = await extensionHostDebugService.attachToCurrentWindowRenderer(windowId);62return result.success ? result.port : undefined;63}6465export class DebugExtensionHostInDevToolsAction extends Action2 {66constructor() {67super({68id: 'workbench.extensions.action.devtoolsExtensionHost',69title: nls.localize2('openDevToolsForExtensionHost', 'Debug Extension Host In Dev Tools'),70category: Categories.Developer,71f1: true,72icon: Codicon.debugStart,73});74}7576async run(accessor: ServicesAccessor): Promise<void> {77const extensionService = accessor.get(IExtensionService);78const nativeHostService = accessor.get(INativeHostService);79const quickInputService = accessor.get(IQuickInputService);8081const inspectPorts = await extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, true);8283if (inspectPorts.length === 0) {84console.log('[devtoolsExtensionHost] No extension host inspect ports found.');85return;86}8788const items: IExtensionHostQuickPickItem[] = inspectPorts.filter(portInfo => portInfo.devtoolsUrl).map(portInfo => ({89label: portInfo.devtoolsLabel ?? `${portInfo.host}:${portInfo.port}`,90detail: `${portInfo.host}:${portInfo.port}`,91portInfo: portInfo92}));9394if (items.length === 1) {95const portInfo = items[0].portInfo;96nativeHostService.openDevToolsWindow(portInfo.devtoolsUrl!);97return;98}99100const selected = await quickInputService.pick<IExtensionHostQuickPickItem>(items, {101placeHolder: nls.localize('selectExtensionHost', "Pick extension host"),102matchOnDetail: true,103});104105if (selected) {106const portInfo = selected.portInfo;107nativeHostService.openDevToolsWindow(portInfo.devtoolsUrl!);108}109}110}111112export class DebugExtensionHostInNewWindowAction extends Action2 {113constructor() {114super({115id: 'workbench.extensions.action.debugExtensionHost',116title: nls.localize2('debugExtensionHost', "Debug Extension Host In New Window"),117category: Categories.Developer,118f1: true,119icon: Codicon.debugStart,120menu: {121id: MenuId.EditorTitle,122when: ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID),123group: 'navigation',124}125});126}127128async run(accessor: ServicesAccessor): Promise<void> {129const extensionService = accessor.get(IExtensionService);130const nativeHostService = accessor.get(INativeHostService);131const dialogService = accessor.get(IDialogService);132const productService = accessor.get(IProductService);133const instantiationService = accessor.get(IInstantiationService);134const hostService = accessor.get(IHostService);135136const port = await getExtensionHostPort(extensionService, nativeHostService, dialogService, productService);137if (port === undefined) {138return;139}140141const storage = instantiationService.createInstance(Storage);142storage.storeDebugOnNewWindow(port);143hostService.openWindow();144}145}146147export class DebugRendererInNewWindowAction extends Action2 {148constructor() {149super({150id: 'workbench.action.debugRenderer',151title: nls.localize2('debugRenderer', "Debug Renderer In New Window"),152category: Categories.Developer,153f1: true,154});155}156157async run(accessor: ServicesAccessor): Promise<void> {158const extensionHostDebugService = accessor.get(IExtensionHostDebugService);159const environmentService = accessor.get(INativeWorkbenchEnvironmentService);160const instantiationService = accessor.get(IInstantiationService);161const hostService = accessor.get(IHostService);162163const port = await getRendererDebugPort(extensionHostDebugService, environmentService.window.id);164if (port === undefined) {165return;166}167168const storage = instantiationService.createInstance(Storage);169storage.storeRendererDebugOnNewWindow(port);170// Force local window since Chrome debugging only works locally171hostService.openWindow({ remoteAuthority: null });172}173}174175export class DebugExtensionHostAndRendererAction extends Action2 {176constructor() {177super({178id: 'workbench.action.debugExtensionHostAndRenderer',179title: nls.localize2('debugExtensionHostAndRenderer', "Debug Extension Host and Renderer In New Window"),180category: Categories.Developer,181f1: true,182});183}184185async run(accessor: ServicesAccessor): Promise<void> {186const extensionService = accessor.get(IExtensionService);187const nativeHostService = accessor.get(INativeHostService);188const dialogService = accessor.get(IDialogService);189const productService = accessor.get(IProductService);190const extensionHostDebugService = accessor.get(IExtensionHostDebugService);191const environmentService = accessor.get(INativeWorkbenchEnvironmentService);192const instantiationService = accessor.get(IInstantiationService);193const hostService = accessor.get(IHostService);194195const [extHostPort, rendererPort] = await Promise.all([196getExtensionHostPort(extensionService, nativeHostService, dialogService, productService),197getRendererDebugPort(extensionHostDebugService, environmentService.window.id)198]);199200if (extHostPort === undefined || rendererPort === undefined) {201return;202}203204const storage = instantiationService.createInstance(Storage);205storage.storeDebugOnNewWindow(extHostPort);206storage.storeRendererDebugOnNewWindow(rendererPort);207// Force local window since Chrome debugging only works locally208hostService.openWindow({ remoteAuthority: null });209}210}211212class Storage {213constructor(@IStorageService private readonly _storageService: IStorageService,) {214}215216storeDebugOnNewWindow(targetPort: number) {217this._storageService.store('debugExtensionHost.debugPort', targetPort, StorageScope.APPLICATION, StorageTarget.MACHINE);218}219220getAndDeleteDebugPortIfSet(): number | undefined {221const port = this._storageService.getNumber('debugExtensionHost.debugPort', StorageScope.APPLICATION);222if (port !== undefined) {223this._storageService.remove('debugExtensionHost.debugPort', StorageScope.APPLICATION);224}225return port;226}227228storeRendererDebugOnNewWindow(targetPort: number) {229this._storageService.store('debugRenderer.debugPort', targetPort, StorageScope.APPLICATION, StorageTarget.MACHINE);230}231232getAndDeleteRendererDebugPortIfSet(): number | undefined {233const port = this._storageService.getNumber('debugRenderer.debugPort', StorageScope.APPLICATION);234if (port !== undefined) {235this._storageService.remove('debugRenderer.debugPort', StorageScope.APPLICATION);236}237return port;238}239}240241const defaultDebugConfig = {242trace: true,243resolveSourceMapLocations: null,244eagerSources: true,245timeouts: {246sourceMapMinPause: 30_000,247sourceMapCumulativePause: 300_000,248},249};250251export class DebugExtensionsContribution extends Disposable implements IWorkbenchContribution {252constructor(253@IDebugService private readonly _debugService: IDebugService,254@IInstantiationService private readonly _instantiationService: IInstantiationService,255@IProgressService _progressService: IProgressService,256) {257super();258259const storage = this._instantiationService.createInstance(Storage);260const extHostPort = storage.getAndDeleteDebugPortIfSet();261const rendererPort = storage.getAndDeleteRendererDebugPortIfSet();262263// Start both debug sessions in parallel264const debugPromises: Promise<void>[] = [];265266if (extHostPort !== undefined) {267debugPromises.push(_progressService.withProgress({268location: ProgressLocation.Notification,269title: nls.localize('debugExtensionHost.progress', "Attaching Debugger To Extension Host"),270}, async () => {271// eslint-disable-next-line local/code-no-dangerous-type-assertions272await this._debugService.startDebugging(undefined, {273type: 'node',274name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"),275request: 'attach',276port: extHostPort,277...defaultDebugConfig,278} as IConfig);279}));280}281282if (rendererPort !== undefined) {283debugPromises.push(_progressService.withProgress({284location: ProgressLocation.Notification,285title: nls.localize('debugRenderer.progress', "Attaching Debugger To Renderer"),286}, async () => {287await this._debugService.startDebugging(undefined, {288type: 'chrome',289name: nls.localize('debugRenderer.launch.name', "Attach Renderer"),290request: 'attach',291port: rendererPort,292...defaultDebugConfig,293});294}));295}296297Promise.all(debugPromises);298}299}300301302