Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/code/electron-main/app.ts
3292 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 { app, protocol, session, Session, systemPreferences, WebFrameMain } from 'electron';
7
import { addUNCHostToAllowlist, disableUNCAccessRestrictions } from '../../base/node/unc.js';
8
import { validatedIpcMain } from '../../base/parts/ipc/electron-main/ipcMain.js';
9
import { hostname, release } from 'os';
10
import { VSBuffer } from '../../base/common/buffer.js';
11
import { toErrorMessage } from '../../base/common/errorMessage.js';
12
import { Event } from '../../base/common/event.js';
13
import { parse } from '../../base/common/jsonc.js';
14
import { getPathLabel } from '../../base/common/labels.js';
15
import { Disposable, DisposableStore } from '../../base/common/lifecycle.js';
16
import { Schemas, VSCODE_AUTHORITY } from '../../base/common/network.js';
17
import { join, posix } from '../../base/common/path.js';
18
import { IProcessEnvironment, isLinux, isLinuxSnap, isMacintosh, isWindows, OS } from '../../base/common/platform.js';
19
import { assertType } from '../../base/common/types.js';
20
import { URI } from '../../base/common/uri.js';
21
import { generateUuid } from '../../base/common/uuid.js';
22
import { registerContextMenuListener } from '../../base/parts/contextmenu/electron-main/contextmenu.js';
23
import { getDelayedChannel, ProxyChannel, StaticRouter } from '../../base/parts/ipc/common/ipc.js';
24
import { Server as ElectronIPCServer } from '../../base/parts/ipc/electron-main/ipc.electron.js';
25
import { Client as MessagePortClient } from '../../base/parts/ipc/electron-main/ipc.mp.js';
26
import { Server as NodeIPCServer } from '../../base/parts/ipc/node/ipc.net.js';
27
import { IProxyAuthService, ProxyAuthService } from '../../platform/native/electron-main/auth.js';
28
import { localize } from '../../nls.js';
29
import { IBackupMainService } from '../../platform/backup/electron-main/backup.js';
30
import { BackupMainService } from '../../platform/backup/electron-main/backupMainService.js';
31
import { IConfigurationService } from '../../platform/configuration/common/configuration.js';
32
import { ElectronExtensionHostDebugBroadcastChannel } from '../../platform/debug/electron-main/extensionHostDebugIpc.js';
33
import { IDiagnosticsService } from '../../platform/diagnostics/common/diagnostics.js';
34
import { DiagnosticsMainService, IDiagnosticsMainService } from '../../platform/diagnostics/electron-main/diagnosticsMainService.js';
35
import { DialogMainService, IDialogMainService } from '../../platform/dialogs/electron-main/dialogMainService.js';
36
import { IEncryptionMainService } from '../../platform/encryption/common/encryptionService.js';
37
import { EncryptionMainService } from '../../platform/encryption/electron-main/encryptionMainService.js';
38
import { NativeBrowserElementsMainService, INativeBrowserElementsMainService } from '../../platform/browserElements/electron-main/nativeBrowserElementsMainService.js';
39
import { NativeParsedArgs } from '../../platform/environment/common/argv.js';
40
import { IEnvironmentMainService } from '../../platform/environment/electron-main/environmentMainService.js';
41
import { isLaunchedFromCli } from '../../platform/environment/node/argvHelper.js';
42
import { getResolvedShellEnv } from '../../platform/shell/node/shellEnv.js';
43
import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from '../../platform/extensions/common/extensionHostStarter.js';
44
import { ExtensionHostStarter } from '../../platform/extensions/electron-main/extensionHostStarter.js';
45
import { IExternalTerminalMainService } from '../../platform/externalTerminal/electron-main/externalTerminal.js';
46
import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from '../../platform/externalTerminal/node/externalTerminalService.js';
47
import { LOCAL_FILE_SYSTEM_CHANNEL_NAME } from '../../platform/files/common/diskFileSystemProviderClient.js';
48
import { IFileService } from '../../platform/files/common/files.js';
49
import { DiskFileSystemProviderChannel } from '../../platform/files/electron-main/diskFileSystemProviderServer.js';
50
import { DiskFileSystemProvider } from '../../platform/files/node/diskFileSystemProvider.js';
51
import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js';
52
import { IInstantiationService, ServicesAccessor } from '../../platform/instantiation/common/instantiation.js';
53
import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js';
54
import { ProcessMainService } from '../../platform/process/electron-main/processMainService.js';
55
import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from '../../platform/keyboardLayout/electron-main/keyboardLayoutMainService.js';
56
import { ILaunchMainService, LaunchMainService } from '../../platform/launch/electron-main/launchMainService.js';
57
import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from '../../platform/lifecycle/electron-main/lifecycleMainService.js';
58
import { ILoggerService, ILogService } from '../../platform/log/common/log.js';
59
import { IMenubarMainService, MenubarMainService } from '../../platform/menubar/electron-main/menubarMainService.js';
60
import { INativeHostMainService, NativeHostMainService } from '../../platform/native/electron-main/nativeHostMainService.js';
61
import { IProductService } from '../../platform/product/common/productService.js';
62
import { getRemoteAuthority } from '../../platform/remote/common/remoteHosts.js';
63
import { SharedProcess } from '../../platform/sharedProcess/electron-main/sharedProcess.js';
64
import { ISignService } from '../../platform/sign/common/sign.js';
65
import { IStateService } from '../../platform/state/node/state.js';
66
import { StorageDatabaseChannel } from '../../platform/storage/electron-main/storageIpc.js';
67
import { ApplicationStorageMainService, IApplicationStorageMainService, IStorageMainService, StorageMainService } from '../../platform/storage/electron-main/storageMainService.js';
68
import { resolveCommonProperties } from '../../platform/telemetry/common/commonProperties.js';
69
import { ITelemetryService, TelemetryLevel } from '../../platform/telemetry/common/telemetry.js';
70
import { TelemetryAppenderClient } from '../../platform/telemetry/common/telemetryIpc.js';
71
import { ITelemetryServiceConfig, TelemetryService } from '../../platform/telemetry/common/telemetryService.js';
72
import { getPiiPathsFromEnvironment, getTelemetryLevel, isInternalTelemetry, NullTelemetryService, supportsTelemetry } from '../../platform/telemetry/common/telemetryUtils.js';
73
import { IUpdateService } from '../../platform/update/common/update.js';
74
import { UpdateChannel } from '../../platform/update/common/updateIpc.js';
75
import { DarwinUpdateService } from '../../platform/update/electron-main/updateService.darwin.js';
76
import { LinuxUpdateService } from '../../platform/update/electron-main/updateService.linux.js';
77
import { SnapUpdateService } from '../../platform/update/electron-main/updateService.snap.js';
78
import { Win32UpdateService } from '../../platform/update/electron-main/updateService.win32.js';
79
import { IOpenURLOptions, IURLService } from '../../platform/url/common/url.js';
80
import { URLHandlerChannelClient, URLHandlerRouter } from '../../platform/url/common/urlIpc.js';
81
import { NativeURLService } from '../../platform/url/common/urlService.js';
82
import { ElectronURLListener } from '../../platform/url/electron-main/electronUrlListener.js';
83
import { IWebviewManagerService } from '../../platform/webview/common/webviewManagerService.js';
84
import { WebviewMainService } from '../../platform/webview/electron-main/webviewMainService.js';
85
import { isFolderToOpen, isWorkspaceToOpen, IWindowOpenable } from '../../platform/window/common/window.js';
86
import { getAllWindowsExcludingOffscreen, IWindowsMainService, OpenContext } from '../../platform/windows/electron-main/windows.js';
87
import { ICodeWindow } from '../../platform/window/electron-main/window.js';
88
import { WindowsMainService } from '../../platform/windows/electron-main/windowsMainService.js';
89
import { ActiveWindowManager } from '../../platform/windows/node/windowTracker.js';
90
import { hasWorkspaceFileExtension } from '../../platform/workspace/common/workspace.js';
91
import { IWorkspacesService } from '../../platform/workspaces/common/workspaces.js';
92
import { IWorkspacesHistoryMainService, WorkspacesHistoryMainService } from '../../platform/workspaces/electron-main/workspacesHistoryMainService.js';
93
import { WorkspacesMainService } from '../../platform/workspaces/electron-main/workspacesMainService.js';
94
import { IWorkspacesManagementMainService, WorkspacesManagementMainService } from '../../platform/workspaces/electron-main/workspacesManagementMainService.js';
95
import { IPolicyService } from '../../platform/policy/common/policy.js';
96
import { PolicyChannel } from '../../platform/policy/common/policyIpc.js';
97
import { IUserDataProfilesMainService } from '../../platform/userDataProfile/electron-main/userDataProfile.js';
98
import { IExtensionsProfileScannerService } from '../../platform/extensionManagement/common/extensionsProfileScannerService.js';
99
import { IExtensionsScannerService } from '../../platform/extensionManagement/common/extensionsScannerService.js';
100
import { ExtensionsScannerService } from '../../platform/extensionManagement/node/extensionsScannerService.js';
101
import { UserDataProfilesHandler } from '../../platform/userDataProfile/electron-main/userDataProfilesHandler.js';
102
import { ProfileStorageChangesListenerChannel } from '../../platform/userDataProfile/electron-main/userDataProfileStorageIpc.js';
103
import { Promises, RunOnceScheduler, runWhenGlobalIdle } from '../../base/common/async.js';
104
import { resolveMachineId, resolveSqmId, resolveDevDeviceId, validateDevDeviceId } from '../../platform/telemetry/electron-main/telemetryUtils.js';
105
import { ExtensionsProfileScannerService } from '../../platform/extensionManagement/node/extensionsProfileScannerService.js';
106
import { LoggerChannel } from '../../platform/log/electron-main/logIpc.js';
107
import { ILoggerMainService } from '../../platform/log/electron-main/loggerService.js';
108
import { IInitialProtocolUrls, IProtocolUrl } from '../../platform/url/electron-main/url.js';
109
import { IUtilityProcessWorkerMainService, UtilityProcessWorkerMainService } from '../../platform/utilityProcess/electron-main/utilityProcessWorkerMainService.js';
110
import { ipcUtilityProcessWorkerChannelName } from '../../platform/utilityProcess/common/utilityProcessWorkerService.js';
111
import { ILocalPtyService, LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from '../../platform/terminal/common/terminal.js';
112
import { ElectronPtyHostStarter } from '../../platform/terminal/electron-main/electronPtyHostStarter.js';
113
import { PtyHostService } from '../../platform/terminal/node/ptyHostService.js';
114
import { NODE_REMOTE_RESOURCE_CHANNEL_NAME, NODE_REMOTE_RESOURCE_IPC_METHOD_NAME, NodeRemoteResourceResponse, NodeRemoteResourceRouter } from '../../platform/remote/common/electronRemoteResources.js';
115
import { Lazy } from '../../base/common/lazy.js';
116
import { IAuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/electron-main/auxiliaryWindows.js';
117
import { AuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.js';
118
import { normalizeNFC } from '../../base/common/normalization.js';
119
import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js';
120
import { INativeMcpDiscoveryHelperService, NativeMcpDiscoveryHelperChannelName } from '../../platform/mcp/common/nativeMcpDiscoveryHelper.js';
121
import { NativeMcpDiscoveryHelperService } from '../../platform/mcp/node/nativeMcpDiscoveryHelperService.js';
122
import { IWebContentExtractorService } from '../../platform/webContentExtractor/common/webContentExtractor.js';
123
import { NativeWebContentExtractorService } from '../../platform/webContentExtractor/electron-main/webContentExtractorService.js';
124
import ErrorTelemetry from '../../platform/telemetry/electron-main/errorTelemetry.js';
125
126
/**
127
* The main VS Code application. There will only ever be one instance,
128
* even if the user starts many instances (e.g. from the command line).
129
*/
130
export class CodeApplication extends Disposable {
131
132
private static readonly SECURITY_PROTOCOL_HANDLING_CONFIRMATION_SETTING_KEY = {
133
[Schemas.file]: 'security.promptForLocalFileProtocolHandling' as const,
134
[Schemas.vscodeRemote]: 'security.promptForRemoteFileProtocolHandling' as const
135
};
136
137
private windowsMainService: IWindowsMainService | undefined;
138
private auxiliaryWindowsMainService: IAuxiliaryWindowsMainService | undefined;
139
private nativeHostMainService: INativeHostMainService | undefined;
140
141
constructor(
142
private readonly mainProcessNodeIpcServer: NodeIPCServer,
143
private readonly userEnv: IProcessEnvironment,
144
@IInstantiationService private readonly mainInstantiationService: IInstantiationService,
145
@ILogService private readonly logService: ILogService,
146
@ILoggerService private readonly loggerService: ILoggerService,
147
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
148
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
149
@IConfigurationService private readonly configurationService: IConfigurationService,
150
@IStateService private readonly stateService: IStateService,
151
@IFileService private readonly fileService: IFileService,
152
@IProductService private readonly productService: IProductService,
153
@IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService
154
) {
155
super();
156
157
this.configureSession();
158
this.registerListeners();
159
}
160
161
private configureSession(): void {
162
163
//#region Security related measures (https://electronjs.org/docs/tutorial/security)
164
//
165
// !!! DO NOT CHANGE without consulting the documentation !!!
166
//
167
168
const isUrlFromWindow = (requestingUrl?: string | undefined) => requestingUrl?.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}`);
169
const isUrlFromWebview = (requestingUrl: string | undefined) => requestingUrl?.startsWith(`${Schemas.vscodeWebview}://`);
170
171
const alwaysAllowedPermissions = new Set(['pointerLock', 'notifications']);
172
173
const allowedPermissionsInWebview = new Set([
174
...alwaysAllowedPermissions,
175
'clipboard-read',
176
'clipboard-sanitized-write',
177
// TODO(deepak1556): Should be removed once migration is complete
178
// https://github.com/microsoft/vscode/issues/239228
179
'deprecated-sync-clipboard-read',
180
]);
181
182
const allowedPermissionsInCore = new Set([
183
...alwaysAllowedPermissions,
184
'media',
185
'local-fonts',
186
// TODO(deepak1556): Should be removed once migration is complete
187
// https://github.com/microsoft/vscode/issues/239228
188
'deprecated-sync-clipboard-read',
189
]);
190
191
session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback, details) => {
192
if (isUrlFromWebview(details.requestingUrl)) {
193
return callback(allowedPermissionsInWebview.has(permission));
194
}
195
if (isUrlFromWindow(details.requestingUrl)) {
196
return callback(allowedPermissionsInCore.has(permission));
197
}
198
return callback(false);
199
});
200
201
session.defaultSession.setPermissionCheckHandler((_webContents, permission, _origin, details) => {
202
if (isUrlFromWebview(details.requestingUrl)) {
203
return allowedPermissionsInWebview.has(permission);
204
}
205
if (isUrlFromWindow(details.requestingUrl)) {
206
return allowedPermissionsInCore.has(permission);
207
}
208
return false;
209
});
210
211
//#endregion
212
213
//#region Request filtering
214
215
// Block all SVG requests from unsupported origins
216
const supportedSvgSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, Schemas.vscodeManagedRemoteResource, 'devtools']);
217
218
// But allow them if they are made from inside an webview
219
const isSafeFrame = (requestFrame: WebFrameMain | null | undefined): boolean => {
220
for (let frame: WebFrameMain | null | undefined = requestFrame; frame; frame = frame.parent) {
221
if (frame.url.startsWith(`${Schemas.vscodeWebview}://`)) {
222
return true;
223
}
224
}
225
return false;
226
};
227
228
const isSvgRequestFromSafeContext = (details: Electron.OnBeforeRequestListenerDetails | Electron.OnHeadersReceivedListenerDetails): boolean => {
229
return details.resourceType === 'xhr' || isSafeFrame(details.frame);
230
};
231
232
const isAllowedVsCodeFileRequest = (details: Electron.OnBeforeRequestListenerDetails) => {
233
const frame = details.frame;
234
if (!frame || !this.windowsMainService) {
235
return false;
236
}
237
238
// Check to see if the request comes from one of the main windows (or shared process) and not from embedded content
239
const windows = getAllWindowsExcludingOffscreen();
240
for (const window of windows) {
241
if (frame.processId === window.webContents.mainFrame.processId) {
242
return true;
243
}
244
}
245
246
return false;
247
};
248
249
const isAllowedWebviewRequest = (uri: URI, details: Electron.OnBeforeRequestListenerDetails): boolean => {
250
if (uri.path !== '/index.html') {
251
return true; // Only restrict top level page of webviews: index.html
252
}
253
254
const frame = details.frame;
255
if (!frame || !this.windowsMainService) {
256
return false;
257
}
258
259
// Check to see if the request comes from one of the main editor windows.
260
for (const window of this.windowsMainService.getWindows()) {
261
if (window.win) {
262
if (frame.processId === window.win.webContents.mainFrame.processId) {
263
return true;
264
}
265
}
266
}
267
268
return false;
269
};
270
271
session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
272
const uri = URI.parse(details.url);
273
if (uri.scheme === Schemas.vscodeWebview) {
274
if (!isAllowedWebviewRequest(uri, details)) {
275
this.logService.error('Blocked vscode-webview request', details.url);
276
return callback({ cancel: true });
277
}
278
}
279
280
if (uri.scheme === Schemas.vscodeFileResource) {
281
if (!isAllowedVsCodeFileRequest(details)) {
282
this.logService.error('Blocked vscode-file request', details.url);
283
return callback({ cancel: true });
284
}
285
}
286
287
// Block most svgs
288
if (uri.path.endsWith('.svg')) {
289
const isSafeResourceUrl = supportedSvgSchemes.has(uri.scheme);
290
if (!isSafeResourceUrl) {
291
return callback({ cancel: !isSvgRequestFromSafeContext(details) });
292
}
293
}
294
295
return callback({ cancel: false });
296
});
297
298
// Configure SVG header content type properly
299
// https://github.com/microsoft/vscode/issues/97564
300
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
301
const responseHeaders = details.responseHeaders as Record<string, (string) | (string[])>;
302
const contentTypes = (responseHeaders['content-type'] || responseHeaders['Content-Type']);
303
304
if (contentTypes && Array.isArray(contentTypes)) {
305
const uri = URI.parse(details.url);
306
if (uri.path.endsWith('.svg')) {
307
if (supportedSvgSchemes.has(uri.scheme)) {
308
responseHeaders['Content-Type'] = ['image/svg+xml'];
309
310
return callback({ cancel: false, responseHeaders });
311
}
312
}
313
314
// remote extension schemes have the following format
315
// http://127.0.0.1:<port>/vscode-remote-resource?path=
316
if (!uri.path.endsWith(Schemas.vscodeRemoteResource) && contentTypes.some(contentType => contentType.toLowerCase().includes('image/svg'))) {
317
return callback({ cancel: !isSvgRequestFromSafeContext(details) });
318
}
319
}
320
321
return callback({ cancel: false });
322
});
323
324
//#endregion
325
326
//#region Allow CORS for the PRSS CDN
327
328
// https://github.com/microsoft/vscode-remote-release/issues/9246
329
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
330
if (details.url.startsWith('https://vscode.download.prss.microsoft.com/')) {
331
const responseHeaders = details.responseHeaders ?? Object.create(null);
332
333
if (responseHeaders['Access-Control-Allow-Origin'] === undefined) {
334
responseHeaders['Access-Control-Allow-Origin'] = ['*'];
335
return callback({ cancel: false, responseHeaders });
336
}
337
}
338
339
return callback({ cancel: false });
340
});
341
342
//#endregion
343
344
//#region Code Cache
345
346
type SessionWithCodeCachePathSupport = Session & {
347
/**
348
* Sets code cache directory. By default, the directory will be `Code Cache` under
349
* the respective user data folder.
350
*/
351
setCodeCachePath?(path: string): void;
352
};
353
354
const defaultSession = session.defaultSession as unknown as SessionWithCodeCachePathSupport;
355
if (typeof defaultSession.setCodeCachePath === 'function' && this.environmentMainService.codeCachePath) {
356
// Make sure to partition Chrome's code cache folder
357
// in the same way as our code cache path to help
358
// invalidate caches that we know are invalid
359
// (https://github.com/microsoft/vscode/issues/120655)
360
defaultSession.setCodeCachePath(join(this.environmentMainService.codeCachePath, 'chrome'));
361
}
362
363
//#endregion
364
365
//#region UNC Host Allowlist (Windows)
366
367
if (isWindows) {
368
if (this.configurationService.getValue('security.restrictUNCAccess') === false) {
369
disableUNCAccessRestrictions();
370
} else {
371
addUNCHostToAllowlist(this.configurationService.getValue('security.allowedUNCHosts'));
372
}
373
}
374
375
//#endregion
376
}
377
378
private registerListeners(): void {
379
380
// Dispose on shutdown
381
Event.once(this.lifecycleMainService.onWillShutdown)(() => this.dispose());
382
383
// Contextmenu via IPC support
384
registerContextMenuListener();
385
386
// Accessibility change event
387
app.on('accessibility-support-changed', (event, accessibilitySupportEnabled) => {
388
this.windowsMainService?.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
389
});
390
391
// macOS dock activate
392
app.on('activate', async (event, hasVisibleWindows) => {
393
this.logService.trace('app#activate');
394
395
// Mac only event: open new window when we get activated
396
if (!hasVisibleWindows) {
397
await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK });
398
}
399
});
400
401
//#region Security related measures (https://electronjs.org/docs/tutorial/security)
402
//
403
// !!! DO NOT CHANGE without consulting the documentation !!!
404
//
405
app.on('web-contents-created', (event, contents) => {
406
407
// Auxiliary Window: delegate to `AuxiliaryWindow` class
408
if (contents?.opener?.url.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`)) {
409
this.logService.trace('[aux window] app.on("web-contents-created"): Registering auxiliary window');
410
411
this.auxiliaryWindowsMainService?.registerWindow(contents);
412
}
413
414
// Block any in-page navigation
415
contents.on('will-navigate', event => {
416
this.logService.error('webContents#will-navigate: Prevented webcontent navigation');
417
418
event.preventDefault();
419
});
420
421
// All Windows: only allow about:blank auxiliary windows to open
422
// For all other URLs, delegate to the OS.
423
contents.setWindowOpenHandler(details => {
424
425
// about:blank windows can open as window witho our default options
426
if (details.url === 'about:blank') {
427
this.logService.trace('[aux window] webContents#setWindowOpenHandler: Allowing auxiliary window to open on about:blank');
428
429
return {
430
action: 'allow',
431
overrideBrowserWindowOptions: this.auxiliaryWindowsMainService?.createWindow(details)
432
};
433
}
434
435
// Any other URL: delegate to OS
436
else {
437
this.logService.trace(`webContents#setWindowOpenHandler: Prevented opening window with URL ${details.url}}`);
438
439
this.nativeHostMainService?.openExternal(undefined, details.url);
440
441
return { action: 'deny' };
442
}
443
});
444
});
445
446
//#endregion
447
448
let macOpenFileURIs: IWindowOpenable[] = [];
449
let runningTimeout: Timeout | undefined = undefined;
450
app.on('open-file', (event, path) => {
451
path = normalizeNFC(path); // macOS only: normalize paths to NFC form
452
453
this.logService.trace('app#open-file: ', path);
454
event.preventDefault();
455
456
// Keep in array because more might come!
457
macOpenFileURIs.push(hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) });
458
459
// Clear previous handler if any
460
if (runningTimeout !== undefined) {
461
clearTimeout(runningTimeout);
462
runningTimeout = undefined;
463
}
464
465
// Handle paths delayed in case more are coming!
466
runningTimeout = setTimeout(async () => {
467
await this.windowsMainService?.open({
468
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
469
cli: this.environmentMainService.args,
470
urisToOpen: macOpenFileURIs,
471
gotoLineMode: false,
472
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
473
});
474
475
macOpenFileURIs = [];
476
runningTimeout = undefined;
477
}, 100);
478
});
479
480
app.on('new-window-for-tab', async () => {
481
await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
482
});
483
484
//#region Bootstrap IPC Handlers
485
486
validatedIpcMain.handle('vscode:fetchShellEnv', event => {
487
488
// Prefer to use the args and env from the target window
489
// when resolving the shell env. It is possible that
490
// a first window was opened from the UI but a second
491
// from the CLI and that has implications for whether to
492
// resolve the shell environment or not.
493
//
494
// Window can be undefined for e.g. the shared process
495
// that is not part of our windows registry!
496
const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process
497
let args: NativeParsedArgs;
498
let env: IProcessEnvironment;
499
if (window?.config) {
500
args = window.config;
501
env = { ...process.env, ...window.config.userEnv };
502
} else {
503
args = this.environmentMainService.args;
504
env = process.env;
505
}
506
507
// Resolve shell env
508
return this.resolveShellEnvironment(args, env, false);
509
});
510
511
validatedIpcMain.on('vscode:toggleDevTools', event => event.sender.toggleDevTools());
512
validatedIpcMain.on('vscode:openDevTools', event => event.sender.openDevTools());
513
514
validatedIpcMain.on('vscode:reloadWindow', event => event.sender.reload());
515
516
validatedIpcMain.handle('vscode:notifyZoomLevel', async (event, zoomLevel: number | undefined) => {
517
const window = this.windowsMainService?.getWindowByWebContents(event.sender);
518
if (window) {
519
window.notifyZoomLevel(zoomLevel);
520
}
521
});
522
523
//#endregion
524
}
525
526
async startup(): Promise<void> {
527
this.logService.debug('Starting VS Code');
528
this.logService.debug(`from: ${this.environmentMainService.appRoot}`);
529
this.logService.debug('args:', this.environmentMainService.args);
530
531
// Make sure we associate the program with the app user model id
532
// This will help Windows to associate the running program with
533
// any shortcut that is pinned to the taskbar and prevent showing
534
// two icons in the taskbar for the same app.
535
const win32AppUserModelId = this.productService.win32AppUserModelId;
536
if (isWindows && win32AppUserModelId) {
537
app.setAppUserModelId(win32AppUserModelId);
538
}
539
540
// Fix native tabs on macOS 10.13
541
// macOS enables a compatibility patch for any bundle ID beginning with
542
// "com.microsoft.", which breaks native tabs for VS Code when using this
543
// identifier (from the official build).
544
// Explicitly opt out of the patch here before creating any windows.
545
// See: https://github.com/microsoft/vscode/issues/35361#issuecomment-399794085
546
try {
547
if (isMacintosh && this.configurationService.getValue('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
548
systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
549
}
550
} catch (error) {
551
this.logService.error(error);
552
}
553
554
// Main process server (electron IPC based)
555
const mainProcessElectronServer = new ElectronIPCServer();
556
Event.once(this.lifecycleMainService.onWillShutdown)(e => {
557
if (e.reason === ShutdownReason.KILL) {
558
// When we go down abnormally, make sure to free up
559
// any IPC we accept from other windows to reduce
560
// the chance of doing work after we go down. Kill
561
// is special in that it does not orderly shutdown
562
// windows.
563
mainProcessElectronServer.dispose();
564
}
565
});
566
567
// Resolve unique machine ID
568
const [machineId, sqmId, devDeviceId] = await Promise.all([
569
resolveMachineId(this.stateService, this.logService),
570
resolveSqmId(this.stateService, this.logService),
571
resolveDevDeviceId(this.stateService, this.logService)
572
]);
573
574
// Shared process
575
const { sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId, sqmId, devDeviceId);
576
577
// Services
578
const appInstantiationService = await this.initServices(machineId, sqmId, devDeviceId, sharedProcessReady);
579
580
// Error telemetry
581
appInstantiationService.invokeFunction(accessor => this._register(new ErrorTelemetry(accessor.get(ILogService), accessor.get(ITelemetryService))));
582
583
// Auth Handler
584
appInstantiationService.invokeFunction(accessor => accessor.get(IProxyAuthService));
585
586
// Transient profiles handler
587
this._register(appInstantiationService.createInstance(UserDataProfilesHandler));
588
589
// Init Channels
590
appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient));
591
592
// Setup Protocol URL Handlers
593
const initialProtocolUrls = await appInstantiationService.invokeFunction(accessor => this.setupProtocolUrlHandlers(accessor, mainProcessElectronServer));
594
595
// Setup vscode-remote-resource protocol handler
596
this.setupManagedRemoteResourceUrlHandler(mainProcessElectronServer);
597
598
// Signal phase: ready - before opening first window
599
this.lifecycleMainService.phase = LifecycleMainPhase.Ready;
600
601
// Open Windows
602
await appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, initialProtocolUrls));
603
604
// Signal phase: after window open
605
this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
606
607
// Post Open Windows Tasks
608
this.afterWindowOpen();
609
610
// Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec)
611
const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => {
612
this._register(runWhenGlobalIdle(() => {
613
614
// Signal phase: eventually
615
this.lifecycleMainService.phase = LifecycleMainPhase.Eventually;
616
617
// Eventually Post Open Window Tasks
618
this.eventuallyAfterWindowOpen();
619
}, 2500));
620
}, 2500));
621
eventuallyPhaseScheduler.schedule();
622
}
623
624
private async setupProtocolUrlHandlers(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): Promise<IInitialProtocolUrls | undefined> {
625
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
626
const urlService = accessor.get(IURLService);
627
const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService);
628
const dialogMainService = accessor.get(IDialogMainService);
629
630
// Install URL handlers that deal with protocl URLs either
631
// from this process by opening windows and/or by forwarding
632
// the URLs into a window process to be handled there.
633
634
const app = this;
635
urlService.registerHandler({
636
async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
637
return app.handleProtocolUrl(windowsMainService, dialogMainService, urlService, uri, options);
638
}
639
});
640
641
const activeWindowManager = this._register(new ActiveWindowManager({
642
onDidOpenMainWindow: nativeHostMainService.onDidOpenMainWindow,
643
onDidFocusMainWindow: nativeHostMainService.onDidFocusMainWindow,
644
getActiveWindowId: () => nativeHostMainService.getActiveWindowId(-1)
645
}));
646
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
647
const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter, this.logService);
648
const urlHandlerChannel = mainProcessElectronServer.getChannel('urlHandler', urlHandlerRouter);
649
urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel));
650
651
const initialProtocolUrls = await this.resolveInitialProtocolUrls(windowsMainService, dialogMainService);
652
this._register(new ElectronURLListener(initialProtocolUrls?.urls, urlService, windowsMainService, this.environmentMainService, this.productService, this.logService));
653
654
return initialProtocolUrls;
655
}
656
657
private setupManagedRemoteResourceUrlHandler(mainProcessElectronServer: ElectronIPCServer) {
658
const notFound = (): Electron.ProtocolResponse => ({ statusCode: 404, data: 'Not found' });
659
const remoteResourceChannel = new Lazy(() => mainProcessElectronServer.getChannel(
660
NODE_REMOTE_RESOURCE_CHANNEL_NAME,
661
new NodeRemoteResourceRouter(),
662
));
663
664
protocol.registerBufferProtocol(Schemas.vscodeManagedRemoteResource, (request, callback) => {
665
const url = URI.parse(request.url);
666
if (!url.authority.startsWith('window:')) {
667
return callback(notFound());
668
}
669
670
remoteResourceChannel.value.call<NodeRemoteResourceResponse>(NODE_REMOTE_RESOURCE_IPC_METHOD_NAME, [url]).then(
671
r => callback({ ...r, data: Buffer.from(r.body, 'base64') }),
672
err => {
673
this.logService.warn('error dispatching remote resource call', err);
674
callback({ statusCode: 500, data: String(err) });
675
});
676
});
677
}
678
679
private async resolveInitialProtocolUrls(windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService): Promise<IInitialProtocolUrls | undefined> {
680
681
/**
682
* Protocol URL handling on startup is complex, refer to
683
* {@link IInitialProtocolUrls} for an explainer.
684
*/
685
686
// Windows/Linux: protocol handler invokes CLI with --open-url
687
const protocolUrlsFromCommandLine = this.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : [];
688
if (protocolUrlsFromCommandLine.length > 0) {
689
this.logService.trace('app#resolveInitialProtocolUrls() protocol urls from command line:', protocolUrlsFromCommandLine);
690
}
691
692
// macOS: open-url events that were received before the app is ready
693
const protocolUrlsFromEvent = ((<any>global).getOpenUrls() || []) as string[];
694
if (protocolUrlsFromEvent.length > 0) {
695
this.logService.trace(`app#resolveInitialProtocolUrls() protocol urls from macOS 'open-url' event:`, protocolUrlsFromEvent);
696
}
697
698
if (protocolUrlsFromCommandLine.length + protocolUrlsFromEvent.length === 0) {
699
return undefined;
700
}
701
702
const protocolUrls = [
703
...protocolUrlsFromCommandLine,
704
...protocolUrlsFromEvent
705
].map(url => {
706
try {
707
return { uri: URI.parse(url), originalUrl: url };
708
} catch {
709
this.logService.trace('app#resolveInitialProtocolUrls() protocol url failed to parse:', url);
710
711
return undefined;
712
}
713
});
714
715
const openables: IWindowOpenable[] = [];
716
const urls: IProtocolUrl[] = [];
717
for (const protocolUrl of protocolUrls) {
718
if (!protocolUrl) {
719
continue; // invalid
720
}
721
722
const windowOpenable = this.getWindowOpenableFromProtocolUrl(protocolUrl.uri);
723
if (windowOpenable) {
724
if (await this.shouldBlockOpenable(windowOpenable, windowsMainService, dialogMainService)) {
725
this.logService.trace('app#resolveInitialProtocolUrls() protocol url was blocked:', protocolUrl.uri.toString(true));
726
727
continue; // blocked
728
} else {
729
this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be handled as window to open:', protocolUrl.uri.toString(true), windowOpenable);
730
731
openables.push(windowOpenable); // handled as window to open
732
}
733
} else {
734
this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be passed to active window for handling:', protocolUrl.uri.toString(true));
735
736
urls.push(protocolUrl); // handled within active window
737
}
738
}
739
740
return { urls, openables };
741
}
742
743
private async shouldBlockOpenable(openable: IWindowOpenable, windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService): Promise<boolean> {
744
let openableUri: URI;
745
let message: string;
746
if (isWorkspaceToOpen(openable)) {
747
openableUri = openable.workspaceUri;
748
message = localize('confirmOpenMessageWorkspace', "An external application wants to open '{0}' in {1}. Do you want to open this workspace file?", openableUri.scheme === Schemas.file ? getPathLabel(openableUri, { os: OS, tildify: this.environmentMainService }) : openableUri.toString(true), this.productService.nameShort);
749
} else if (isFolderToOpen(openable)) {
750
openableUri = openable.folderUri;
751
message = localize('confirmOpenMessageFolder', "An external application wants to open '{0}' in {1}. Do you want to open this folder?", openableUri.scheme === Schemas.file ? getPathLabel(openableUri, { os: OS, tildify: this.environmentMainService }) : openableUri.toString(true), this.productService.nameShort);
752
} else {
753
openableUri = openable.fileUri;
754
message = localize('confirmOpenMessageFileOrFolder', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", openableUri.scheme === Schemas.file ? getPathLabel(openableUri, { os: OS, tildify: this.environmentMainService }) : openableUri.toString(true), this.productService.nameShort);
755
}
756
757
if (openableUri.scheme !== Schemas.file && openableUri.scheme !== Schemas.vscodeRemote) {
758
759
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
760
//
761
// NOTE: we currently only ask for confirmation for `file` and `vscode-remote`
762
// authorities here. There is an additional confirmation for `extension.id`
763
// authorities from within the window.
764
//
765
// IF YOU ARE PLANNING ON ADDING ANOTHER AUTHORITY HERE, MAKE SURE TO ALSO
766
// ADD IT TO THE CONFIRMATION CODE BELOW OR INSIDE THE WINDOW!
767
//
768
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
769
770
return false;
771
}
772
773
const askForConfirmation = this.configurationService.getValue<unknown>(CodeApplication.SECURITY_PROTOCOL_HANDLING_CONFIRMATION_SETTING_KEY[openableUri.scheme]);
774
if (askForConfirmation === false) {
775
return false; // not blocked via settings
776
}
777
778
const { response, checkboxChecked } = await dialogMainService.showMessageBox({
779
type: 'warning',
780
buttons: [
781
localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes"),
782
localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")
783
],
784
message,
785
detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"),
786
checkboxLabel: openableUri.scheme === Schemas.file ? localize('doNotAskAgainLocal', "Allow opening local paths without asking") : localize('doNotAskAgainRemote', "Allow opening remote paths without asking"),
787
cancelId: 1
788
});
789
790
if (response !== 0) {
791
return true; // blocked by user choice
792
}
793
794
if (checkboxChecked) {
795
// Due to https://github.com/microsoft/vscode/issues/195436, we can only
796
// update settings from within a window. But we do not know if a window
797
// is about to open or can already handle the request, so we have to send
798
// to any current window and any newly opening window.
799
const request = { channel: 'vscode:disablePromptForProtocolHandling', args: openableUri.scheme === Schemas.file ? 'local' : 'remote' };
800
windowsMainService.sendToFocused(request.channel, request.args);
801
windowsMainService.sendToOpeningWindow(request.channel, request.args);
802
}
803
804
return false; // not blocked by user choice
805
}
806
807
private getWindowOpenableFromProtocolUrl(uri: URI): IWindowOpenable | undefined {
808
if (!uri.path) {
809
return undefined;
810
}
811
812
// File path
813
if (uri.authority === Schemas.file) {
814
const fileUri = URI.file(uri.fsPath);
815
816
if (hasWorkspaceFileExtension(fileUri)) {
817
return { workspaceUri: fileUri };
818
}
819
820
return { fileUri };
821
}
822
823
// Remote path
824
else if (uri.authority === Schemas.vscodeRemote) {
825
826
// Example conversion:
827
// From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco
828
// To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco
829
830
const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */);
831
let authority: string;
832
let path: string;
833
if (secondSlash !== -1) {
834
authority = uri.path.substring(1, secondSlash);
835
path = uri.path.substring(secondSlash);
836
} else {
837
authority = uri.path.substring(1);
838
path = '/';
839
}
840
841
let query = uri.query;
842
const params = new URLSearchParams(uri.query);
843
if (params.get('windowId') === '_blank') {
844
// Make sure to unset any `windowId=_blank` here
845
// https://github.com/microsoft/vscode/issues/191902
846
params.delete('windowId');
847
query = params.toString();
848
}
849
850
const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query, fragment: uri.fragment });
851
852
if (hasWorkspaceFileExtension(path)) {
853
return { workspaceUri: remoteUri };
854
}
855
856
if (/:[\d]+$/.test(path)) {
857
// path with :line:column syntax
858
return { fileUri: remoteUri };
859
}
860
861
return { folderUri: remoteUri };
862
}
863
return undefined;
864
}
865
866
private async handleProtocolUrl(windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService, urlService: IURLService, uri: URI, options?: IOpenURLOptions): Promise<boolean> {
867
this.logService.trace('app#handleProtocolUrl():', uri.toString(true), options);
868
869
// Support 'workspace' URLs (https://github.com/microsoft/vscode/issues/124263)
870
if (uri.scheme === this.productService.urlProtocol && uri.path === 'workspace') {
871
uri = uri.with({
872
authority: 'file',
873
path: URI.parse(uri.query).path,
874
query: ''
875
});
876
}
877
878
let shouldOpenInNewWindow = false;
879
880
// We should handle the URI in a new window if the URL contains `windowId=_blank`
881
const params = new URLSearchParams(uri.query);
882
if (params.get('windowId') === '_blank') {
883
this.logService.trace(`app#handleProtocolUrl() found 'windowId=_blank' as parameter, setting shouldOpenInNewWindow=true:`, uri.toString(true));
884
885
params.delete('windowId');
886
uri = uri.with({ query: params.toString() });
887
888
shouldOpenInNewWindow = true;
889
}
890
891
// or if no window is open (macOS only)
892
else if (isMacintosh && windowsMainService.getWindowCount() === 0) {
893
this.logService.trace(`app#handleProtocolUrl() running on macOS with no window open, setting shouldOpenInNewWindow=true:`, uri.toString(true));
894
895
shouldOpenInNewWindow = true;
896
}
897
898
// Pass along whether the application is being opened via a Continue On flow
899
const continueOn = params.get('continueOn');
900
if (continueOn !== null) {
901
this.logService.trace(`app#handleProtocolUrl() found 'continueOn' as parameter:`, uri.toString(true));
902
903
params.delete('continueOn');
904
uri = uri.with({ query: params.toString() });
905
906
this.environmentMainService.continueOn = continueOn ?? undefined;
907
}
908
909
// Check if the protocol URL is a window openable to open...
910
const windowOpenableFromProtocolUrl = this.getWindowOpenableFromProtocolUrl(uri);
911
if (windowOpenableFromProtocolUrl) {
912
if (await this.shouldBlockOpenable(windowOpenableFromProtocolUrl, windowsMainService, dialogMainService)) {
913
this.logService.trace('app#handleProtocolUrl() protocol url was blocked:', uri.toString(true));
914
915
return true; // If openable should be blocked, behave as if it's handled
916
} else {
917
this.logService.trace('app#handleProtocolUrl() opening protocol url as window:', windowOpenableFromProtocolUrl, uri.toString(true));
918
919
const window = (await windowsMainService.open({
920
context: OpenContext.LINK,
921
cli: { ...this.environmentMainService.args },
922
urisToOpen: [windowOpenableFromProtocolUrl],
923
forceNewWindow: shouldOpenInNewWindow,
924
gotoLineMode: true
925
// remoteAuthority: will be determined based on windowOpenableFromProtocolUrl
926
})).at(0);
927
928
window?.focus(); // this should help ensuring that the right window gets focus when multiple are opened
929
930
return true;
931
}
932
}
933
934
// ...or if we should open in a new window and then handle it within that window
935
if (shouldOpenInNewWindow) {
936
this.logService.trace('app#handleProtocolUrl() opening empty window and passing in protocol url:', uri.toString(true));
937
938
const window = (await windowsMainService.open({
939
context: OpenContext.LINK,
940
cli: { ...this.environmentMainService.args },
941
forceNewWindow: true,
942
forceEmpty: true,
943
gotoLineMode: true,
944
remoteAuthority: getRemoteAuthority(uri)
945
})).at(0);
946
947
await window?.ready();
948
949
return urlService.open(uri, options);
950
}
951
952
this.logService.trace('app#handleProtocolUrl(): not handled', uri.toString(true), options);
953
954
return false;
955
}
956
957
private setupSharedProcess(machineId: string, sqmId: string, devDeviceId: string): { sharedProcessReady: Promise<MessagePortClient>; sharedProcessClient: Promise<MessagePortClient> } {
958
const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, sqmId, devDeviceId));
959
960
this._register(sharedProcess.onDidCrash(() => this.windowsMainService?.sendToFocused('vscode:reportSharedProcessCrash')));
961
962
const sharedProcessClient = (async () => {
963
this.logService.trace('Main->SharedProcess#connect');
964
965
const port = await sharedProcess.connect();
966
967
this.logService.trace('Main->SharedProcess#connect: connection established');
968
969
return new MessagePortClient(port, 'main');
970
})();
971
972
const sharedProcessReady = (async () => {
973
await sharedProcess.whenReady();
974
975
return sharedProcessClient;
976
})();
977
978
return { sharedProcessReady, sharedProcessClient };
979
}
980
981
private async initServices(machineId: string, sqmId: string, devDeviceId: string, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
982
const services = new ServiceCollection();
983
984
// Update
985
switch (process.platform) {
986
case 'win32':
987
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
988
break;
989
990
case 'linux':
991
if (isLinuxSnap) {
992
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env['SNAP'], process.env['SNAP_REVISION']]));
993
} else {
994
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
995
}
996
break;
997
998
case 'darwin':
999
services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
1000
break;
1001
}
1002
1003
// Windows
1004
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, sqmId, devDeviceId, this.userEnv], false));
1005
services.set(IAuxiliaryWindowsMainService, new SyncDescriptor(AuxiliaryWindowsMainService, undefined, false));
1006
1007
// Dialogs
1008
const dialogMainService = new DialogMainService(this.logService, this.productService);
1009
services.set(IDialogMainService, dialogMainService);
1010
1011
// Launch
1012
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService, undefined, false /* proxied to other processes */));
1013
1014
// Diagnostics
1015
services.set(IDiagnosticsMainService, new SyncDescriptor(DiagnosticsMainService, undefined, false /* proxied to other processes */));
1016
services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
1017
1018
// Encryption
1019
services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService));
1020
1021
// Browser Elements
1022
services.set(INativeBrowserElementsMainService, new SyncDescriptor(NativeBrowserElementsMainService, undefined, false /* proxied to other processes */));
1023
1024
// Keyboard Layout
1025
services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));
1026
1027
// Native Host
1028
services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, undefined, false /* proxied to other processes */));
1029
1030
// Web Contents Extractor
1031
services.set(IWebContentExtractorService, new SyncDescriptor(NativeWebContentExtractorService, undefined, false /* proxied to other processes */));
1032
1033
// Webview Manager
1034
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
1035
1036
// Menubar
1037
services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
1038
1039
// Extension Host Starter
1040
services.set(IExtensionHostStarter, new SyncDescriptor(ExtensionHostStarter));
1041
1042
// Storage
1043
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
1044
services.set(IApplicationStorageMainService, new SyncDescriptor(ApplicationStorageMainService));
1045
1046
// Terminal
1047
const ptyHostStarter = new ElectronPtyHostStarter({
1048
graceTime: LocalReconnectConstants.GraceTime,
1049
shortGraceTime: LocalReconnectConstants.ShortGraceTime,
1050
scrollback: this.configurationService.getValue<number>(TerminalSettingId.PersistentSessionScrollback) ?? 100
1051
}, this.configurationService, this.environmentMainService, this.lifecycleMainService, this.logService);
1052
const ptyHostService = new PtyHostService(
1053
ptyHostStarter,
1054
this.configurationService,
1055
this.logService,
1056
this.loggerService
1057
);
1058
services.set(ILocalPtyService, ptyHostService);
1059
1060
// External terminal
1061
if (isWindows) {
1062
services.set(IExternalTerminalMainService, new SyncDescriptor(WindowsExternalTerminalService));
1063
} else if (isMacintosh) {
1064
services.set(IExternalTerminalMainService, new SyncDescriptor(MacExternalTerminalService));
1065
} else if (isLinux) {
1066
services.set(IExternalTerminalMainService, new SyncDescriptor(LinuxExternalTerminalService));
1067
}
1068
1069
// Backups
1070
const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService, this.stateService);
1071
services.set(IBackupMainService, backupMainService);
1072
1073
// Workspaces
1074
const workspacesManagementMainService = new WorkspacesManagementMainService(this.environmentMainService, this.logService, this.userDataProfilesMainService, backupMainService, dialogMainService);
1075
services.set(IWorkspacesManagementMainService, workspacesManagementMainService);
1076
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService, undefined, false /* proxied to other processes */));
1077
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService, undefined, false));
1078
1079
// URL handling
1080
services.set(IURLService, new SyncDescriptor(NativeURLService, undefined, false /* proxied to other processes */));
1081
1082
// Telemetry
1083
if (supportsTelemetry(this.productService, this.environmentMainService)) {
1084
const isInternal = isInternalTelemetry(this.productService, this.configurationService);
1085
const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
1086
const appender = new TelemetryAppenderClient(channel);
1087
const commonProperties = resolveCommonProperties(release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, sqmId, devDeviceId, isInternal, this.productService.date);
1088
const piiPaths = getPiiPathsFromEnvironment(this.environmentMainService);
1089
const config: ITelemetryServiceConfig = { appenders: [appender], commonProperties, piiPaths, sendErrorTelemetry: true };
1090
1091
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config], false));
1092
} else {
1093
services.set(ITelemetryService, NullTelemetryService);
1094
}
1095
1096
// Default Extensions Profile Init
1097
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true));
1098
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true));
1099
1100
// Utility Process Worker
1101
services.set(IUtilityProcessWorkerMainService, new SyncDescriptor(UtilityProcessWorkerMainService, undefined, true));
1102
1103
// Proxy Auth
1104
services.set(IProxyAuthService, new SyncDescriptor(ProxyAuthService));
1105
1106
// MCP
1107
services.set(INativeMcpDiscoveryHelperService, new SyncDescriptor(NativeMcpDiscoveryHelperService));
1108
1109
1110
// Dev Only: CSS service (for ESM)
1111
services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true));
1112
1113
// Init services that require it
1114
await Promises.settled([
1115
backupMainService.initialize(),
1116
workspacesManagementMainService.initialize()
1117
]);
1118
1119
return this.mainInstantiationService.createChild(services);
1120
}
1121
1122
private initChannels(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, sharedProcessClient: Promise<MessagePortClient>): void {
1123
1124
// Channels registered to node.js are exposed to second instances
1125
// launching because that is the only way the second instance
1126
// can talk to the first instance. Electron IPC does not work
1127
// across apps until `requestSingleInstance` APIs are adopted.
1128
1129
const disposables = this._register(new DisposableStore());
1130
1131
const launchChannel = ProxyChannel.fromService(accessor.get(ILaunchMainService), disposables, { disableMarshalling: true });
1132
this.mainProcessNodeIpcServer.registerChannel('launch', launchChannel);
1133
1134
const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsMainService), disposables, { disableMarshalling: true });
1135
this.mainProcessNodeIpcServer.registerChannel('diagnostics', diagnosticsChannel);
1136
1137
// Policies (main & shared process)
1138
const policyChannel = disposables.add(new PolicyChannel(accessor.get(IPolicyService)));
1139
mainProcessElectronServer.registerChannel('policy', policyChannel);
1140
sharedProcessClient.then(client => client.registerChannel('policy', policyChannel));
1141
1142
// Local Files
1143
const diskFileSystemProvider = this.fileService.getProvider(Schemas.file);
1144
assertType(diskFileSystemProvider instanceof DiskFileSystemProvider);
1145
const fileSystemProviderChannel = disposables.add(new DiskFileSystemProviderChannel(diskFileSystemProvider, this.logService, this.environmentMainService));
1146
mainProcessElectronServer.registerChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME, fileSystemProviderChannel);
1147
sharedProcessClient.then(client => client.registerChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME, fileSystemProviderChannel));
1148
1149
// User Data Profiles
1150
const userDataProfilesService = ProxyChannel.fromService(accessor.get(IUserDataProfilesMainService), disposables);
1151
mainProcessElectronServer.registerChannel('userDataProfiles', userDataProfilesService);
1152
sharedProcessClient.then(client => client.registerChannel('userDataProfiles', userDataProfilesService));
1153
1154
// Update
1155
const updateChannel = new UpdateChannel(accessor.get(IUpdateService));
1156
mainProcessElectronServer.registerChannel('update', updateChannel);
1157
1158
// Process
1159
const processChannel = ProxyChannel.fromService(new ProcessMainService(this.logService, accessor.get(IDiagnosticsService), accessor.get(IDiagnosticsMainService)), disposables);
1160
mainProcessElectronServer.registerChannel('process', processChannel);
1161
1162
// Encryption
1163
const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService), disposables);
1164
mainProcessElectronServer.registerChannel('encryption', encryptionChannel);
1165
1166
// Browser Elements
1167
const browserElementsChannel = ProxyChannel.fromService(accessor.get(INativeBrowserElementsMainService), disposables);
1168
mainProcessElectronServer.registerChannel('browserElements', browserElementsChannel);
1169
sharedProcessClient.then(client => client.registerChannel('browserElements', browserElementsChannel));
1170
1171
// Signing
1172
const signChannel = ProxyChannel.fromService(accessor.get(ISignService), disposables);
1173
mainProcessElectronServer.registerChannel('sign', signChannel);
1174
1175
// Keyboard Layout
1176
const keyboardLayoutChannel = ProxyChannel.fromService(accessor.get(IKeyboardLayoutMainService), disposables);
1177
mainProcessElectronServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
1178
1179
// Native host (main & shared process)
1180
this.nativeHostMainService = accessor.get(INativeHostMainService);
1181
const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService, disposables);
1182
mainProcessElectronServer.registerChannel('nativeHost', nativeHostChannel);
1183
sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel));
1184
1185
// Web Content Extractor
1186
const webContentExtractorChannel = ProxyChannel.fromService(accessor.get(IWebContentExtractorService), disposables);
1187
mainProcessElectronServer.registerChannel('webContentExtractor', webContentExtractorChannel);
1188
1189
// Workspaces
1190
const workspacesChannel = ProxyChannel.fromService(accessor.get(IWorkspacesService), disposables);
1191
mainProcessElectronServer.registerChannel('workspaces', workspacesChannel);
1192
1193
// Menubar
1194
const menubarChannel = ProxyChannel.fromService(accessor.get(IMenubarMainService), disposables);
1195
mainProcessElectronServer.registerChannel('menubar', menubarChannel);
1196
1197
// URL handling
1198
const urlChannel = ProxyChannel.fromService(accessor.get(IURLService), disposables);
1199
mainProcessElectronServer.registerChannel('url', urlChannel);
1200
1201
// Webview Manager
1202
const webviewChannel = ProxyChannel.fromService(accessor.get(IWebviewManagerService), disposables);
1203
mainProcessElectronServer.registerChannel('webview', webviewChannel);
1204
1205
// Storage (main & shared process)
1206
const storageChannel = disposables.add((new StorageDatabaseChannel(this.logService, accessor.get(IStorageMainService))));
1207
mainProcessElectronServer.registerChannel('storage', storageChannel);
1208
sharedProcessClient.then(client => client.registerChannel('storage', storageChannel));
1209
1210
// Profile Storage Changes Listener (shared process)
1211
const profileStorageListener = disposables.add((new ProfileStorageChangesListenerChannel(accessor.get(IStorageMainService), accessor.get(IUserDataProfilesMainService), this.logService)));
1212
sharedProcessClient.then(client => client.registerChannel('profileStorageListener', profileStorageListener));
1213
1214
// Terminal
1215
const ptyHostChannel = ProxyChannel.fromService(accessor.get(ILocalPtyService), disposables);
1216
mainProcessElectronServer.registerChannel(TerminalIpcChannels.LocalPty, ptyHostChannel);
1217
1218
// External Terminal
1219
const externalTerminalChannel = ProxyChannel.fromService(accessor.get(IExternalTerminalMainService), disposables);
1220
mainProcessElectronServer.registerChannel('externalTerminal', externalTerminalChannel);
1221
1222
// MCP
1223
const mcpDiscoveryChannel = ProxyChannel.fromService(accessor.get(INativeMcpDiscoveryHelperService), disposables);
1224
mainProcessElectronServer.registerChannel(NativeMcpDiscoveryHelperChannelName, mcpDiscoveryChannel);
1225
1226
// Logger
1227
const loggerChannel = new LoggerChannel(accessor.get(ILoggerMainService),);
1228
mainProcessElectronServer.registerChannel('logger', loggerChannel);
1229
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
1230
1231
// Extension Host Debug Broadcasting
1232
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
1233
mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel);
1234
1235
// Extension Host Starter
1236
const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter), disposables);
1237
mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel);
1238
1239
// Utility Process Worker
1240
const utilityProcessWorkerChannel = ProxyChannel.fromService(accessor.get(IUtilityProcessWorkerMainService), disposables);
1241
mainProcessElectronServer.registerChannel(ipcUtilityProcessWorkerChannelName, utilityProcessWorkerChannel);
1242
}
1243
1244
private async openFirstWindow(accessor: ServicesAccessor, initialProtocolUrls: IInitialProtocolUrls | undefined): Promise<ICodeWindow[]> {
1245
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
1246
this.auxiliaryWindowsMainService = accessor.get(IAuxiliaryWindowsMainService);
1247
1248
const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP;
1249
const args = this.environmentMainService.args;
1250
1251
// First check for windows from protocol links to open
1252
if (initialProtocolUrls) {
1253
1254
// Openables can open as windows directly
1255
if (initialProtocolUrls.openables.length > 0) {
1256
return windowsMainService.open({
1257
context,
1258
cli: args,
1259
urisToOpen: initialProtocolUrls.openables,
1260
gotoLineMode: true,
1261
initialStartup: true
1262
// remoteAuthority: will be determined based on openables
1263
});
1264
}
1265
1266
// Protocol links with `windowId=_blank` on startup
1267
// should be handled in a special way:
1268
// We take the first one of these and open an empty
1269
// window for it. This ensures we are not restoring
1270
// all windows of the previous session.
1271
// If there are any more URLs like these, they will
1272
// be handled from the URL listeners installed later.
1273
1274
if (initialProtocolUrls.urls.length > 0) {
1275
for (const protocolUrl of initialProtocolUrls.urls) {
1276
const params = new URLSearchParams(protocolUrl.uri.query);
1277
if (params.get('windowId') === '_blank') {
1278
1279
// It is important here that we remove `windowId=_blank` from
1280
// this URL because here we open an empty window for it.
1281
1282
params.delete('windowId');
1283
protocolUrl.originalUrl = protocolUrl.uri.toString(true);
1284
protocolUrl.uri = protocolUrl.uri.with({ query: params.toString() });
1285
1286
return windowsMainService.open({
1287
context,
1288
cli: args,
1289
forceNewWindow: true,
1290
forceEmpty: true,
1291
gotoLineMode: true,
1292
initialStartup: true
1293
// remoteAuthority: will be determined based on openables
1294
});
1295
}
1296
}
1297
}
1298
}
1299
1300
const macOpenFiles: string[] = (<any>global).macOpenFiles;
1301
const hasCliArgs = args._.length;
1302
const hasFolderURIs = !!args['folder-uri'];
1303
const hasFileURIs = !!args['file-uri'];
1304
const noRecentEntry = args['skip-add-to-recently-opened'] === true;
1305
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
1306
const remoteAuthority = args.remote || undefined;
1307
const forceProfile = args.profile;
1308
const forceTempProfile = args['profile-temp'];
1309
1310
// Started without file/folder arguments
1311
if (!hasCliArgs && !hasFolderURIs && !hasFileURIs) {
1312
1313
// Force new window
1314
if (args['new-window'] || forceProfile || forceTempProfile) {
1315
return windowsMainService.open({
1316
context,
1317
cli: args,
1318
forceNewWindow: true,
1319
forceEmpty: true,
1320
noRecentEntry,
1321
waitMarkerFileURI,
1322
initialStartup: true,
1323
remoteAuthority,
1324
forceProfile,
1325
forceTempProfile
1326
});
1327
}
1328
1329
// mac: open-file event received on startup
1330
if (macOpenFiles.length) {
1331
return windowsMainService.open({
1332
context: OpenContext.DOCK,
1333
cli: args,
1334
urisToOpen: macOpenFiles.map(path => {
1335
path = normalizeNFC(path); // macOS only: normalize paths to NFC form
1336
1337
return (hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) });
1338
}),
1339
noRecentEntry,
1340
waitMarkerFileURI,
1341
initialStartup: true,
1342
// remoteAuthority: will be determined based on macOpenFiles
1343
});
1344
}
1345
}
1346
1347
// default: read paths from cli
1348
return windowsMainService.open({
1349
context,
1350
cli: args,
1351
forceNewWindow: args['new-window'],
1352
diffMode: args.diff,
1353
mergeMode: args.merge,
1354
noRecentEntry,
1355
waitMarkerFileURI,
1356
gotoLineMode: args.goto,
1357
initialStartup: true,
1358
remoteAuthority,
1359
forceProfile,
1360
forceTempProfile
1361
});
1362
}
1363
1364
private afterWindowOpen(): void {
1365
1366
// Windows: mutex
1367
this.installMutex();
1368
1369
// Remote Authorities
1370
protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => {
1371
callback({
1372
url: request.url.replace(/^vscode-remote-resource:/, 'http:'),
1373
method: request.method
1374
});
1375
});
1376
1377
// Start to fetch shell environment (if needed) after window has opened
1378
// Since this operation can take a long time, we want to warm it up while
1379
// the window is opening.
1380
// We also show an error to the user in case this fails.
1381
this.resolveShellEnvironment(this.environmentMainService.args, process.env, true);
1382
1383
// Crash reporter
1384
this.updateCrashReporterEnablement();
1385
1386
// macOS: rosetta translation warning
1387
if (isMacintosh && app.runningUnderARM64Translation) {
1388
this.windowsMainService?.sendToFocused('vscode:showTranslatedBuildWarning');
1389
}
1390
}
1391
1392
private async installMutex(): Promise<void> {
1393
const win32MutexName = this.productService.win32MutexName;
1394
if (isWindows && win32MutexName) {
1395
try {
1396
const WindowsMutex = await import('@vscode/windows-mutex');
1397
const mutex = new WindowsMutex.Mutex(win32MutexName);
1398
Event.once(this.lifecycleMainService.onWillShutdown)(() => mutex.release());
1399
} catch (error) {
1400
this.logService.error(error);
1401
}
1402
}
1403
}
1404
1405
private async resolveShellEnvironment(args: NativeParsedArgs, env: IProcessEnvironment, notifyOnError: boolean): Promise<typeof process.env> {
1406
try {
1407
return await getResolvedShellEnv(this.configurationService, this.logService, args, env);
1408
} catch (error) {
1409
const errorMessage = toErrorMessage(error);
1410
if (notifyOnError) {
1411
this.windowsMainService?.sendToFocused('vscode:showResolveShellEnvError', errorMessage);
1412
} else {
1413
this.logService.error(errorMessage);
1414
}
1415
}
1416
1417
return {};
1418
}
1419
1420
private async updateCrashReporterEnablement(): Promise<void> {
1421
1422
// If enable-crash-reporter argv is undefined then this is a fresh start,
1423
// based on `telemetry.enableCrashreporter` settings, generate a UUID which
1424
// will be used as crash reporter id and also update the json file.
1425
1426
try {
1427
const argvContent = await this.fileService.readFile(this.environmentMainService.argvResource);
1428
const argvString = argvContent.value.toString();
1429
const argvJSON = parse<{ 'enable-crash-reporter'?: boolean }>(argvString);
1430
const telemetryLevel = getTelemetryLevel(this.configurationService);
1431
const enableCrashReporter = telemetryLevel >= TelemetryLevel.CRASH;
1432
1433
// Initial startup
1434
if (argvJSON['enable-crash-reporter'] === undefined) {
1435
const additionalArgvContent = [
1436
'',
1437
' // Allows to disable crash reporting.',
1438
' // Should restart the app if the value is changed.',
1439
` "enable-crash-reporter": ${enableCrashReporter},`,
1440
'',
1441
' // Unique id used for correlating crash reports sent from this instance.',
1442
' // Do not edit this value.',
1443
` "crash-reporter-id": "${generateUuid()}"`,
1444
'}'
1445
];
1446
const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n'));
1447
1448
await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString));
1449
}
1450
1451
// Subsequent startup: update crash reporter value if changed
1452
else {
1453
const newArgvString = argvString.replace(/"enable-crash-reporter": .*,/, `"enable-crash-reporter": ${enableCrashReporter},`);
1454
if (newArgvString !== argvString) {
1455
await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString));
1456
}
1457
}
1458
} catch (error) {
1459
this.logService.error(error);
1460
1461
// Inform the user via notification
1462
this.windowsMainService?.sendToFocused('vscode:showArgvParseWarning');
1463
}
1464
}
1465
1466
private eventuallyAfterWindowOpen(): void {
1467
1468
// Validate Device ID is up to date (delay this as it has shown significant perf impact)
1469
// Refs: https://github.com/microsoft/vscode/issues/234064
1470
validateDevDeviceId(this.stateService, this.logService);
1471
}
1472
}
1473
1474