Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction.ts
5267 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 { Codicon } from '../../../../base/common/codicons.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { randomPort } from '../../../../base/common/ports.js';
9
import * as nls from '../../../../nls.js';
10
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
11
import { Action2, MenuId } from '../../../../platform/actions/common/actions.js';
12
import { IExtensionHostDebugService } from '../../../../platform/debug/common/extensionHostDebug.js';
13
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
14
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
15
import { INativeHostService } from '../../../../platform/native/common/native.js';
16
import { IProductService } from '../../../../platform/product/common/productService.js';
17
import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';
18
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
19
import { ActiveEditorContext } from '../../../common/contextkeys.js';
20
import { IWorkbenchContribution } from '../../../common/contributions.js';
21
import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';
22
import { ExtensionHostKind } from '../../../services/extensions/common/extensionHostKind.js';
23
import { IExtensionService, IExtensionInspectInfo } from '../../../services/extensions/common/extensions.js';
24
import { IHostService } from '../../../services/host/browser/host.js';
25
import { IConfig, IDebugService } from '../../debug/common/debug.js';
26
import { RuntimeExtensionsEditor } from './runtimeExtensionsEditor.js';
27
import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';
28
29
interface IExtensionHostQuickPickItem extends IQuickPickItem {
30
portInfo: IExtensionInspectInfo;
31
}
32
33
// Shared helpers for debug actions
34
async function getExtensionHostPort(
35
extensionService: IExtensionService,
36
nativeHostService: INativeHostService,
37
dialogService: IDialogService,
38
productService: IProductService,
39
): Promise<number | undefined> {
40
const inspectPorts = await extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, false);
41
if (inspectPorts.length === 0) {
42
const res = await dialogService.confirm({
43
message: nls.localize('restart1', "Debug Extensions"),
44
detail: nls.localize('restart2', "In order to debug extensions a restart is required. Do you want to restart '{0}' now?", productService.nameLong),
45
primaryButton: nls.localize({ key: 'restart3', comment: ['&& denotes a mnemonic'] }, "&&Restart")
46
});
47
if (res.confirmed) {
48
await nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
49
}
50
return undefined;
51
}
52
if (inspectPorts.length > 1) {
53
console.warn(`There are multiple extension hosts available for debugging. Picking the first one...`);
54
}
55
return inspectPorts[0].port;
56
}
57
58
async function getRendererDebugPort(
59
extensionHostDebugService: IExtensionHostDebugService,
60
windowId: number,
61
): Promise<number | undefined> {
62
const result = await extensionHostDebugService.attachToCurrentWindowRenderer(windowId);
63
return result.success ? result.port : undefined;
64
}
65
66
export class DebugExtensionHostInDevToolsAction extends Action2 {
67
constructor() {
68
super({
69
id: 'workbench.extensions.action.devtoolsExtensionHost',
70
title: nls.localize2('openDevToolsForExtensionHost', 'Debug Extension Host In Dev Tools'),
71
category: Categories.Developer,
72
f1: true,
73
icon: Codicon.debugStart,
74
});
75
}
76
77
async run(accessor: ServicesAccessor): Promise<void> {
78
const extensionService = accessor.get(IExtensionService);
79
const nativeHostService = accessor.get(INativeHostService);
80
const quickInputService = accessor.get(IQuickInputService);
81
82
const inspectPorts = await extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, true);
83
84
if (inspectPorts.length === 0) {
85
console.log('[devtoolsExtensionHost] No extension host inspect ports found.');
86
return;
87
}
88
89
const items: IExtensionHostQuickPickItem[] = inspectPorts.filter(portInfo => portInfo.devtoolsUrl).map(portInfo => ({
90
label: portInfo.devtoolsLabel ?? `${portInfo.host}:${portInfo.port}`,
91
detail: `${portInfo.host}:${portInfo.port}`,
92
portInfo: portInfo
93
}));
94
95
if (items.length === 1) {
96
const portInfo = items[0].portInfo;
97
nativeHostService.openDevToolsWindow(portInfo.devtoolsUrl!);
98
return;
99
}
100
101
const selected = await quickInputService.pick<IExtensionHostQuickPickItem>(items, {
102
placeHolder: nls.localize('selectExtensionHost', "Pick extension host"),
103
matchOnDetail: true,
104
});
105
106
if (selected) {
107
const portInfo = selected.portInfo;
108
nativeHostService.openDevToolsWindow(portInfo.devtoolsUrl!);
109
}
110
}
111
}
112
113
export class DebugExtensionHostInNewWindowAction extends Action2 {
114
constructor() {
115
super({
116
id: 'workbench.extensions.action.debugExtensionHost',
117
title: nls.localize2('debugExtensionHost', "Debug Extension Host In New Window"),
118
category: Categories.Developer,
119
f1: true,
120
icon: Codicon.debugStart,
121
menu: {
122
id: MenuId.EditorTitle,
123
when: ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID),
124
group: 'navigation',
125
}
126
});
127
}
128
129
async run(accessor: ServicesAccessor): Promise<void> {
130
const extensionService = accessor.get(IExtensionService);
131
const nativeHostService = accessor.get(INativeHostService);
132
const dialogService = accessor.get(IDialogService);
133
const productService = accessor.get(IProductService);
134
const instantiationService = accessor.get(IInstantiationService);
135
const hostService = accessor.get(IHostService);
136
137
const port = await getExtensionHostPort(extensionService, nativeHostService, dialogService, productService);
138
if (port === undefined) {
139
return;
140
}
141
142
const storage = instantiationService.createInstance(Storage);
143
storage.storeDebugOnNewWindow(port);
144
hostService.openWindow();
145
}
146
}
147
148
export class DebugRendererInNewWindowAction extends Action2 {
149
constructor() {
150
super({
151
id: 'workbench.action.debugRenderer',
152
title: nls.localize2('debugRenderer', "Debug Renderer In New Window"),
153
category: Categories.Developer,
154
f1: true,
155
});
156
}
157
158
async run(accessor: ServicesAccessor): Promise<void> {
159
const extensionHostDebugService = accessor.get(IExtensionHostDebugService);
160
const environmentService = accessor.get(INativeWorkbenchEnvironmentService);
161
const instantiationService = accessor.get(IInstantiationService);
162
const hostService = accessor.get(IHostService);
163
164
const port = await getRendererDebugPort(extensionHostDebugService, environmentService.window.id);
165
if (port === undefined) {
166
return;
167
}
168
169
const storage = instantiationService.createInstance(Storage);
170
storage.storeRendererDebugOnNewWindow(port);
171
// Force local window since Chrome debugging only works locally
172
hostService.openWindow({ remoteAuthority: null });
173
}
174
}
175
176
export class DebugExtensionHostAndRendererAction extends Action2 {
177
constructor() {
178
super({
179
id: 'workbench.action.debugExtensionHostAndRenderer',
180
title: nls.localize2('debugExtensionHostAndRenderer', "Debug Extension Host and Renderer In New Window"),
181
category: Categories.Developer,
182
f1: true,
183
});
184
}
185
186
async run(accessor: ServicesAccessor): Promise<void> {
187
const extensionService = accessor.get(IExtensionService);
188
const nativeHostService = accessor.get(INativeHostService);
189
const dialogService = accessor.get(IDialogService);
190
const productService = accessor.get(IProductService);
191
const extensionHostDebugService = accessor.get(IExtensionHostDebugService);
192
const environmentService = accessor.get(INativeWorkbenchEnvironmentService);
193
const instantiationService = accessor.get(IInstantiationService);
194
const hostService = accessor.get(IHostService);
195
196
const [extHostPort, rendererPort] = await Promise.all([
197
getExtensionHostPort(extensionService, nativeHostService, dialogService, productService),
198
getRendererDebugPort(extensionHostDebugService, environmentService.window.id)
199
]);
200
201
if (extHostPort === undefined || rendererPort === undefined) {
202
return;
203
}
204
205
const storage = instantiationService.createInstance(Storage);
206
storage.storeDebugOnNewWindow(extHostPort);
207
storage.storeRendererDebugOnNewWindow(rendererPort);
208
// Force local window since Chrome debugging only works locally
209
hostService.openWindow({ remoteAuthority: null });
210
}
211
}
212
213
class Storage {
214
constructor(@IStorageService private readonly _storageService: IStorageService,) {
215
}
216
217
storeDebugOnNewWindow(targetPort: number) {
218
this._storageService.store('debugExtensionHost.debugPort', targetPort, StorageScope.APPLICATION, StorageTarget.MACHINE);
219
}
220
221
getAndDeleteDebugPortIfSet(): number | undefined {
222
const port = this._storageService.getNumber('debugExtensionHost.debugPort', StorageScope.APPLICATION);
223
if (port !== undefined) {
224
this._storageService.remove('debugExtensionHost.debugPort', StorageScope.APPLICATION);
225
}
226
return port;
227
}
228
229
storeRendererDebugOnNewWindow(targetPort: number) {
230
this._storageService.store('debugRenderer.debugPort', targetPort, StorageScope.APPLICATION, StorageTarget.MACHINE);
231
}
232
233
getAndDeleteRendererDebugPortIfSet(): number | undefined {
234
const port = this._storageService.getNumber('debugRenderer.debugPort', StorageScope.APPLICATION);
235
if (port !== undefined) {
236
this._storageService.remove('debugRenderer.debugPort', StorageScope.APPLICATION);
237
}
238
return port;
239
}
240
}
241
242
const defaultDebugConfig = {
243
trace: true,
244
resolveSourceMapLocations: null,
245
eagerSources: true,
246
timeouts: {
247
sourceMapMinPause: 30_000,
248
sourceMapCumulativePause: 300_000,
249
},
250
};
251
252
export class DebugExtensionsContribution extends Disposable implements IWorkbenchContribution {
253
constructor(
254
@IDebugService private readonly _debugService: IDebugService,
255
@IInstantiationService private readonly _instantiationService: IInstantiationService,
256
@IProgressService _progressService: IProgressService,
257
) {
258
super();
259
260
const storage = this._instantiationService.createInstance(Storage);
261
const extHostPort = storage.getAndDeleteDebugPortIfSet();
262
const rendererPort = storage.getAndDeleteRendererDebugPortIfSet();
263
264
// Start both debug sessions in parallel
265
const debugPromises: Promise<void>[] = [];
266
267
if (extHostPort !== undefined) {
268
debugPromises.push(_progressService.withProgress({
269
location: ProgressLocation.Notification,
270
title: nls.localize('debugExtensionHost.progress', "Attaching Debugger To Extension Host"),
271
}, async () => {
272
// eslint-disable-next-line local/code-no-dangerous-type-assertions
273
await this._debugService.startDebugging(undefined, {
274
type: 'node',
275
name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"),
276
request: 'attach',
277
port: extHostPort,
278
...defaultDebugConfig,
279
} as IConfig);
280
}));
281
}
282
283
if (rendererPort !== undefined) {
284
debugPromises.push(_progressService.withProgress({
285
location: ProgressLocation.Notification,
286
title: nls.localize('debugRenderer.progress', "Attaching Debugger To Renderer"),
287
}, async () => {
288
await this._debugService.startDebugging(undefined, {
289
type: 'chrome',
290
name: nls.localize('debugRenderer.launch.name', "Attach Renderer"),
291
request: 'attach',
292
port: rendererPort,
293
...defaultDebugConfig,
294
});
295
}));
296
}
297
298
Promise.all(debugPromises);
299
}
300
}
301
302