/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { app, Details, GPUFeatureStatus, powerMonitor, protocol, session, Session, systemPreferences, WebFrameMain } from 'electron'; import { addUNCHostToAllowlist, disableUNCAccessRestrictions } from '../../base/node/unc.js'; import { validatedIpcMain } from '../../base/parts/ipc/electron-main/ipcMain.js'; import { hostname, release } from 'os'; import { initWindowsVersionInfo } from '../../base/node/windowsVersion.js'; import { VSBuffer } from '../../base/common/buffer.js'; import { toErrorMessage } from '../../base/common/errorMessage.js'; import { Event } from '../../base/common/event.js'; import { parse } from '../../base/common/jsonc.js'; import { getPathLabel } from '../../base/common/labels.js'; import { Disposable, DisposableStore } from '../../base/common/lifecycle.js'; import { Schemas, VSCODE_AUTHORITY } from '../../base/common/network.js'; import { join, posix } from '../../base/common/path.js'; import { INodeProcess, IProcessEnvironment, isLinux, isLinuxSnap, isMacintosh, isWindows, OS } from '../../base/common/platform.js'; import { assertType } from '../../base/common/types.js'; import { URI } from '../../base/common/uri.js'; import { generateUuid } from '../../base/common/uuid.js'; import { registerContextMenuListener } from '../../base/parts/contextmenu/electron-main/contextmenu.js'; import { getDelayedChannel, ProxyChannel, StaticRouter } from '../../base/parts/ipc/common/ipc.js'; import { Server as ElectronIPCServer } from '../../base/parts/ipc/electron-main/ipc.electron.js'; import { Client as MessagePortClient } from '../../base/parts/ipc/electron-main/ipc.mp.js'; import { Server as NodeIPCServer } from '../../base/parts/ipc/node/ipc.net.js'; import { IProxyAuthService, ProxyAuthService } from '../../platform/native/electron-main/auth.js'; import { localize } from '../../nls.js'; import { IBackupMainService } from '../../platform/backup/electron-main/backup.js'; import { BackupMainService } from '../../platform/backup/electron-main/backupMainService.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { ElectronExtensionHostDebugBroadcastChannel } from '../../platform/debug/electron-main/extensionHostDebugIpc.js'; import { IDiagnosticsService, IGPULogMessage } from '../../platform/diagnostics/common/diagnostics.js'; import { DiagnosticsMainService, IDiagnosticsMainService } from '../../platform/diagnostics/electron-main/diagnosticsMainService.js'; import { DialogMainService, IDialogMainService } from '../../platform/dialogs/electron-main/dialogMainService.js'; import { IEncryptionMainService } from '../../platform/encryption/common/encryptionService.js'; import { EncryptionMainService } from '../../platform/encryption/electron-main/encryptionMainService.js'; import { NativeBrowserElementsMainService, INativeBrowserElementsMainService } from '../../platform/browserElements/electron-main/nativeBrowserElementsMainService.js'; import { ipcBrowserViewChannelName } from '../../platform/browserView/common/browserView.js'; import { ipcBrowserViewGroupChannelName } from '../../platform/browserView/common/browserViewGroup.js'; import { BrowserViewMainService, IBrowserViewMainService } from '../../platform/browserView/electron-main/browserViewMainService.js'; import { BrowserViewGroupMainService, IBrowserViewGroupMainService } from '../../platform/browserView/electron-main/browserViewGroupMainService.js'; import { BrowserViewCDPProxyServer, IBrowserViewCDPProxyServer } from '../../platform/browserView/electron-main/browserViewCDPProxyServer.js'; import { NativeParsedArgs } from '../../platform/environment/common/argv.js'; import { IEnvironmentMainService } from '../../platform/environment/electron-main/environmentMainService.js'; import { isLaunchedFromCli } from '../../platform/environment/node/argvHelper.js'; import { getResolvedShellEnv } from '../../platform/shell/node/shellEnv.js'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from '../../platform/extensions/common/extensionHostStarter.js'; import { ExtensionHostStarter } from '../../platform/extensions/electron-main/extensionHostStarter.js'; import { IExternalTerminalMainService } from '../../platform/externalTerminal/electron-main/externalTerminal.js'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from '../../platform/externalTerminal/node/externalTerminalService.js'; import { LOCAL_FILE_SYSTEM_CHANNEL_NAME } from '../../platform/files/common/diskFileSystemProviderClient.js'; import { IFileService } from '../../platform/files/common/files.js'; import { DiskFileSystemProviderChannel } from '../../platform/files/electron-main/diskFileSystemProviderServer.js'; import { DiskFileSystemProvider } from '../../platform/files/node/diskFileSystemProvider.js'; import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js'; import { IInstantiationService, ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js'; import { ProcessMainService } from '../../platform/process/electron-main/processMainService.js'; import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from '../../platform/keyboardLayout/electron-main/keyboardLayoutMainService.js'; import { ILaunchMainService, LaunchMainService } from '../../platform/launch/electron-main/launchMainService.js'; import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from '../../platform/lifecycle/electron-main/lifecycleMainService.js'; import { ILoggerService, ILogService } from '../../platform/log/common/log.js'; import { IMenubarMainService, MenubarMainService } from '../../platform/menubar/electron-main/menubarMainService.js'; import { INativeHostMainService, NativeHostMainService } from '../../platform/native/electron-main/nativeHostMainService.js'; import { IMeteredConnectionService } from '../../platform/meteredConnection/common/meteredConnection.js'; import { METERED_CONNECTION_CHANNEL } from '../../platform/meteredConnection/common/meteredConnectionIpc.js'; import { MeteredConnectionChannel } from '../../platform/meteredConnection/electron-main/meteredConnectionChannel.js'; import { MeteredConnectionMainService } from '../../platform/meteredConnection/electron-main/meteredConnectionMainService.js'; import { IProductService } from '../../platform/product/common/productService.js'; import { getRemoteAuthority } from '../../platform/remote/common/remoteHosts.js'; import { SharedProcess } from '../../platform/sharedProcess/electron-main/sharedProcess.js'; import { ISignService } from '../../platform/sign/common/sign.js'; import { IStateService } from '../../platform/state/node/state.js'; import { StorageDatabaseChannel } from '../../platform/storage/electron-main/storageIpc.js'; import { ApplicationStorageMainService, IApplicationStorageMainService, IStorageMainService, StorageMainService } from '../../platform/storage/electron-main/storageMainService.js'; import { resolveCommonProperties } from '../../platform/telemetry/common/commonProperties.js'; import { ITelemetryService, TelemetryLevel } from '../../platform/telemetry/common/telemetry.js'; import { TelemetryAppenderClient } from '../../platform/telemetry/common/telemetryIpc.js'; import { ITelemetryServiceConfig, TelemetryService } from '../../platform/telemetry/common/telemetryService.js'; import { getPiiPathsFromEnvironment, getTelemetryLevel, isInternalTelemetry, NullTelemetryService, supportsTelemetry } from '../../platform/telemetry/common/telemetryUtils.js'; import { IUpdateService } from '../../platform/update/common/update.js'; import { UpdateChannel } from '../../platform/update/common/updateIpc.js'; import { DarwinUpdateService } from '../../platform/update/electron-main/updateService.darwin.js'; import { LinuxUpdateService } from '../../platform/update/electron-main/updateService.linux.js'; import { SnapUpdateService } from '../../platform/update/electron-main/updateService.snap.js'; import { Win32UpdateService } from '../../platform/update/electron-main/updateService.win32.js'; import { IOpenURLOptions, IURLService } from '../../platform/url/common/url.js'; import { URLHandlerChannelClient, URLHandlerRouter } from '../../platform/url/common/urlIpc.js'; import { NativeURLService } from '../../platform/url/common/urlService.js'; import { ElectronURLListener } from '../../platform/url/electron-main/electronUrlListener.js'; import { IWebviewManagerService } from '../../platform/webview/common/webviewManagerService.js'; import { WebviewMainService } from '../../platform/webview/electron-main/webviewMainService.js'; import { isFolderToOpen, isWorkspaceToOpen, IWindowOpenable } from '../../platform/window/common/window.js'; import { getAllWindowsExcludingOffscreen, IWindowsMainService, OpenContext } from '../../platform/windows/electron-main/windows.js'; import { ICodeWindow } from '../../platform/window/electron-main/window.js'; import { WindowsMainService } from '../../platform/windows/electron-main/windowsMainService.js'; import { ActiveWindowManager } from '../../platform/windows/node/windowTracker.js'; import { hasWorkspaceFileExtension } from '../../platform/workspace/common/workspace.js'; import { IWorkspacesService } from '../../platform/workspaces/common/workspaces.js'; import { IWorkspacesHistoryMainService, WorkspacesHistoryMainService } from '../../platform/workspaces/electron-main/workspacesHistoryMainService.js'; import { WorkspacesMainService } from '../../platform/workspaces/electron-main/workspacesMainService.js'; import { IWorkspacesManagementMainService, WorkspacesManagementMainService } from '../../platform/workspaces/electron-main/workspacesManagementMainService.js'; import { IPolicyService } from '../../platform/policy/common/policy.js'; import { PolicyChannel } from '../../platform/policy/common/policyIpc.js'; import { IUserDataProfilesMainService } from '../../platform/userDataProfile/electron-main/userDataProfile.js'; import { IExtensionsProfileScannerService } from '../../platform/extensionManagement/common/extensionsProfileScannerService.js'; import { IExtensionsScannerService } from '../../platform/extensionManagement/common/extensionsScannerService.js'; import { ExtensionsScannerService } from '../../platform/extensionManagement/node/extensionsScannerService.js'; import { UserDataProfilesHandler } from '../../platform/userDataProfile/electron-main/userDataProfilesHandler.js'; import { ProfileStorageChangesListenerChannel } from '../../platform/userDataProfile/electron-main/userDataProfileStorageIpc.js'; import { Promises, RunOnceScheduler, runWhenGlobalIdle } from '../../base/common/async.js'; import { CancellationToken } from '../../base/common/cancellation.js'; import { resolveMachineId, resolveSqmId, resolveDevDeviceId, validateDevDeviceId } from '../../platform/telemetry/electron-main/telemetryUtils.js'; import { ExtensionsProfileScannerService } from '../../platform/extensionManagement/node/extensionsProfileScannerService.js'; import { LoggerChannel } from '../../platform/log/electron-main/logIpc.js'; import { ILoggerMainService } from '../../platform/log/electron-main/loggerService.js'; import { IInitialProtocolUrls, IProtocolUrl } from '../../platform/url/electron-main/url.js'; import { IUtilityProcessWorkerMainService, UtilityProcessWorkerMainService } from '../../platform/utilityProcess/electron-main/utilityProcessWorkerMainService.js'; import { ipcUtilityProcessWorkerChannelName } from '../../platform/utilityProcess/common/utilityProcessWorkerService.js'; import { ILocalPtyService, LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from '../../platform/terminal/common/terminal.js'; import { ElectronPtyHostStarter } from '../../platform/terminal/electron-main/electronPtyHostStarter.js'; import { PtyHostService } from '../../platform/terminal/node/ptyHostService.js'; import { NODE_REMOTE_RESOURCE_CHANNEL_NAME, NODE_REMOTE_RESOURCE_IPC_METHOD_NAME, NodeRemoteResourceResponse, NodeRemoteResourceRouter } from '../../platform/remote/common/electronRemoteResources.js'; import { Lazy } from '../../base/common/lazy.js'; import { IAuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/electron-main/auxiliaryWindows.js'; import { AuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.js'; import { normalizeNFC } from '../../base/common/normalization.js'; import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; import { INativeMcpDiscoveryHelperService, NativeMcpDiscoveryHelperChannelName } from '../../platform/mcp/common/nativeMcpDiscoveryHelper.js'; import { NativeMcpDiscoveryHelperService } from '../../platform/mcp/node/nativeMcpDiscoveryHelperService.js'; import { IMcpGatewayService, McpGatewayChannelName } from '../../platform/mcp/common/mcpGateway.js'; import { McpGatewayService } from '../../platform/mcp/node/mcpGatewayService.js'; import { McpGatewayChannel } from '../../platform/mcp/node/mcpGatewayChannel.js'; import { IWebContentExtractorService } from '../../platform/webContentExtractor/common/webContentExtractor.js'; import { NativeWebContentExtractorService } from '../../platform/webContentExtractor/electron-main/webContentExtractorService.js'; import ErrorTelemetry from '../../platform/telemetry/electron-main/errorTelemetry.js'; /** * The main VS Code application. There will only ever be one instance, * even if the user starts many instances (e.g. from the command line). */ export class CodeApplication extends Disposable { private static readonly SECURITY_PROTOCOL_HANDLING_CONFIRMATION_SETTING_KEY = { [Schemas.file]: 'security.promptForLocalFileProtocolHandling' as const, [Schemas.vscodeRemote]: 'security.promptForRemoteFileProtocolHandling' as const }; private windowsMainService: IWindowsMainService | undefined; private auxiliaryWindowsMainService: IAuxiliaryWindowsMainService | undefined; private nativeHostMainService: INativeHostMainService | undefined; constructor( private readonly mainProcessNodeIpcServer: NodeIPCServer, private readonly userEnv: IProcessEnvironment, @IInstantiationService private readonly mainInstantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, @ILoggerService private readonly loggerService: ILoggerService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStateService private readonly stateService: IStateService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, @IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService ) { super(); this.configureSession(); this.registerListeners(); } private configureSession(): void { //#region Security related measures (https://electronjs.org/docs/tutorial/security) // // !!! DO NOT CHANGE without consulting the documentation !!! // const isUrlFromWindow = (requestingUrl?: string | undefined) => requestingUrl?.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}`); const isUrlFromWebview = (requestingUrl: string | undefined) => requestingUrl?.startsWith(`${Schemas.vscodeWebview}://`); const alwaysAllowedPermissions = new Set(['pointerLock', 'notifications']); const allowedPermissionsInWebview = new Set([ ...alwaysAllowedPermissions, 'clipboard-read', 'clipboard-sanitized-write', // TODO(deepak1556): Should be removed once migration is complete // https://github.com/microsoft/vscode/issues/239228 'deprecated-sync-clipboard-read', ]); const allowedPermissionsInCore = new Set([ ...alwaysAllowedPermissions, 'media', 'local-fonts', // TODO(deepak1556): Should be removed once migration is complete // https://github.com/microsoft/vscode/issues/239228 'deprecated-sync-clipboard-read', ]); session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback, details) => { if (isUrlFromWebview(details.requestingUrl)) { return callback(allowedPermissionsInWebview.has(permission)); } if (isUrlFromWindow(details.requestingUrl)) { return callback(allowedPermissionsInCore.has(permission)); } return callback(false); }); session.defaultSession.setPermissionCheckHandler((_webContents, permission, _origin, details) => { if (isUrlFromWebview(details.requestingUrl)) { return allowedPermissionsInWebview.has(permission); } if (isUrlFromWindow(details.requestingUrl)) { return allowedPermissionsInCore.has(permission); } return false; }); //#endregion //#region Request filtering // Block all SVG requests from unsupported origins const supportedSvgSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, Schemas.vscodeManagedRemoteResource, 'devtools']); // But allow them if they are made from inside an webview const isSafeFrame = (requestFrame: WebFrameMain | null | undefined): boolean => { for (let frame: WebFrameMain | null | undefined = requestFrame; frame; frame = frame.parent) { if (frame.url.startsWith(`${Schemas.vscodeWebview}://`)) { return true; } } return false; }; const isSvgRequestFromSafeContext = (details: Electron.OnBeforeRequestListenerDetails | Electron.OnHeadersReceivedListenerDetails): boolean => { return details.resourceType === 'xhr' || isSafeFrame(details.frame); }; const isAllowedVsCodeFileRequest = (details: Electron.OnBeforeRequestListenerDetails) => { const frame = details.frame; if (!frame || !this.windowsMainService) { return false; } // Check to see if the request comes from one of the main windows (or shared process) and not from embedded content const windows = getAllWindowsExcludingOffscreen(); for (const window of windows) { if (frame.processId === window.webContents.mainFrame.processId) { return true; } } return false; }; const isAllowedWebviewRequest = (uri: URI, details: Electron.OnBeforeRequestListenerDetails): boolean => { if (uri.path !== '/index.html') { return true; // Only restrict top level page of webviews: index.html } const frame = details.frame; if (!frame || !this.windowsMainService) { return false; } // Check to see if the request comes from one of the main editor windows. for (const window of this.windowsMainService.getWindows()) { if (window.win) { if (frame.processId === window.win.webContents.mainFrame.processId) { return true; } } } return false; }; session.defaultSession.webRequest.onBeforeRequest((details, callback) => { const uri = URI.parse(details.url); if (uri.scheme === Schemas.vscodeWebview) { if (!isAllowedWebviewRequest(uri, details)) { this.logService.error('Blocked vscode-webview request', details.url); return callback({ cancel: true }); } } if (uri.scheme === Schemas.vscodeFileResource) { if (!isAllowedVsCodeFileRequest(details)) { this.logService.error('Blocked vscode-file request', details.url); return callback({ cancel: true }); } } // Block most svgs if (uri.path.endsWith('.svg')) { const isSafeResourceUrl = supportedSvgSchemes.has(uri.scheme); if (!isSafeResourceUrl) { return callback({ cancel: !isSvgRequestFromSafeContext(details) }); } } return callback({ cancel: false }); }); // Configure SVG header content type properly // https://github.com/microsoft/vscode/issues/97564 session.defaultSession.webRequest.onHeadersReceived((details, callback) => { const responseHeaders = details.responseHeaders as Record; const contentTypes = (responseHeaders['content-type'] || responseHeaders['Content-Type']); if (contentTypes && Array.isArray(contentTypes)) { const uri = URI.parse(details.url); if (uri.path.endsWith('.svg')) { if (supportedSvgSchemes.has(uri.scheme)) { responseHeaders['Content-Type'] = ['image/svg+xml']; return callback({ cancel: false, responseHeaders }); } } // remote extension schemes have the following format // http://127.0.0.1:/vscode-remote-resource?path= if (!uri.path.endsWith(Schemas.vscodeRemoteResource) && contentTypes.some(contentType => contentType.toLowerCase().includes('image/svg'))) { return callback({ cancel: !isSvgRequestFromSafeContext(details) }); } } return callback({ cancel: false }); }); //#endregion //#region Allow CORS for the PRSS CDN // https://github.com/microsoft/vscode-remote-release/issues/9246 session.defaultSession.webRequest.onHeadersReceived((details, callback) => { if (details.url.startsWith('https://vscode.download.prss.microsoft.com/')) { const responseHeaders = details.responseHeaders ?? Object.create(null); if (responseHeaders['Access-Control-Allow-Origin'] === undefined) { responseHeaders['Access-Control-Allow-Origin'] = ['*']; return callback({ cancel: false, responseHeaders }); } } return callback({ cancel: false }); }); //#endregion //#region Code Cache type SessionWithCodeCachePathSupport = Session & { /** * Sets code cache directory. By default, the directory will be `Code Cache` under * the respective user data folder. */ setCodeCachePath?(path: string): void; }; const defaultSession = session.defaultSession as unknown as SessionWithCodeCachePathSupport; if (typeof defaultSession.setCodeCachePath === 'function' && this.environmentMainService.codeCachePath) { // Make sure to partition Chrome's code cache folder // in the same way as our code cache path to help // invalidate caches that we know are invalid // (https://github.com/microsoft/vscode/issues/120655) defaultSession.setCodeCachePath(join(this.environmentMainService.codeCachePath, 'chrome')); } //#endregion //#region UNC Host Allowlist (Windows) if (isWindows) { if (this.configurationService.getValue('security.restrictUNCAccess') === false) { disableUNCAccessRestrictions(); } else { addUNCHostToAllowlist(this.configurationService.getValue('security.allowedUNCHosts')); } } //#endregion } private registerListeners(): void { // Dispose on shutdown Event.once(this.lifecycleMainService.onWillShutdown)(() => this.dispose()); // Contextmenu via IPC support registerContextMenuListener(); // Accessibility change event app.on('accessibility-support-changed', (event, accessibilitySupportEnabled) => { this.windowsMainService?.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled); }); // macOS dock activate app.on('activate', async (event, hasVisibleWindows) => { this.logService.trace('app#activate'); // Mac only event: open new window when we get activated if (!hasVisibleWindows) { if ((process as INodeProcess).isEmbeddedApp || (this.environmentMainService.args['sessions'] && this.productService.quality !== 'stable')) { await this.windowsMainService?.openSessionsWindow({ context: OpenContext.DOCK }); } else { await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK }); } } }); //#region Security related measures (https://electronjs.org/docs/tutorial/security) // // !!! DO NOT CHANGE without consulting the documentation !!! // app.on('web-contents-created', (event, contents) => { // Auxiliary Window: delegate to `AuxiliaryWindow` class if (contents?.opener?.url.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`)) { this.logService.trace('[aux window] app.on("web-contents-created"): Registering auxiliary window'); this.auxiliaryWindowsMainService?.registerWindow(contents); } // Handle any in-page navigation contents.on('will-navigate', event => { if (BrowserViewMainService.isBrowserViewWebContents(contents)) { return; // Allow navigation in integrated browser views } this.logService.error('webContents#will-navigate: Prevented webcontent navigation'); event.preventDefault(); // Prevent any in-page navigation }); // All Windows: only allow about:blank auxiliary windows to open // For all other URLs, delegate to the OS. contents.setWindowOpenHandler(details => { // about:blank windows can open as window witho our default options if (details.url === 'about:blank') { this.logService.trace('[aux window] webContents#setWindowOpenHandler: Allowing auxiliary window to open on about:blank'); return { action: 'allow', overrideBrowserWindowOptions: this.auxiliaryWindowsMainService?.createWindow(details) }; } // Any other URL: delegate to OS else { this.logService.trace(`webContents#setWindowOpenHandler: Prevented opening window with URL ${details.url}}`); this.nativeHostMainService?.openExternal(undefined, details.url); return { action: 'deny' }; } }); }); //#endregion let macOpenFileURIs: IWindowOpenable[] = []; let runningTimeout: Timeout | undefined = undefined; app.on('open-file', (event, path) => { path = normalizeNFC(path); // macOS only: normalize paths to NFC form this.logService.trace('app#open-file: ', path); event.preventDefault(); // Keep in array because more might come! macOpenFileURIs.push(hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) }); // Clear previous handler if any if (runningTimeout !== undefined) { clearTimeout(runningTimeout); runningTimeout = undefined; } // Handle paths delayed in case more are coming! runningTimeout = setTimeout(async () => { await this.windowsMainService?.open({ context: OpenContext.DOCK /* can also be opening from finder while app is running */, cli: this.environmentMainService.args, urisToOpen: macOpenFileURIs, gotoLineMode: false, preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */ }); macOpenFileURIs = []; runningTimeout = undefined; }, 100); }); app.on('new-window-for-tab', async () => { await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button }); //#region Bootstrap IPC Handlers validatedIpcMain.handle('vscode:fetchShellEnv', event => { // Prefer to use the args and env from the target window // when resolving the shell env. It is possible that // a first window was opened from the UI but a second // from the CLI and that has implications for whether to // resolve the shell environment or not. // // Window can be undefined for e.g. the shared process // that is not part of our windows registry! const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process let args: NativeParsedArgs; let env: IProcessEnvironment; if (window?.config) { args = window.config; env = { ...process.env, ...window.config.userEnv }; } else { args = this.environmentMainService.args; env = process.env; } // Resolve shell env return this.resolveShellEnvironment(args, env, false); }); validatedIpcMain.on('vscode:toggleDevTools', event => event.sender.toggleDevTools()); validatedIpcMain.on('vscode:openDevTools', event => event.sender.openDevTools()); validatedIpcMain.on('vscode:reloadWindow', event => event.sender.reload()); validatedIpcMain.handle('vscode:notifyZoomLevel', async (event, zoomLevel: number | undefined) => { const window = this.windowsMainService?.getWindowByWebContents(event.sender); if (window) { window.notifyZoomLevel(zoomLevel); } }); //#endregion } async startup(): Promise { this.logService.debug('Starting VS Code'); this.logService.debug(`from: ${this.environmentMainService.appRoot}`); this.logService.debug('args:', this.environmentMainService.args); // Make sure we associate the program with the app user model id // This will help Windows to associate the running program with // any shortcut that is pinned to the taskbar and prevent showing // two icons in the taskbar for the same app. const win32AppUserModelId = this.productService.win32AppUserModelId; if (isWindows && win32AppUserModelId) { app.setAppUserModelId(win32AppUserModelId); } // Fix native tabs on macOS 10.13 // macOS enables a compatibility patch for any bundle ID beginning with // "com.microsoft.", which breaks native tabs for VS Code when using this // identifier (from the official build). // Explicitly opt out of the patch here before creating any windows. // See: https://github.com/microsoft/vscode/issues/35361#issuecomment-399794085 try { if (isMacintosh && this.configurationService.getValue('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) { systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true); } } catch (error) { this.logService.error(error); } // Main process server (electron IPC based) const mainProcessElectronServer = new ElectronIPCServer(); Event.once(this.lifecycleMainService.onWillShutdown)(e => { if (e.reason === ShutdownReason.KILL) { // When we go down abnormally, make sure to free up // any IPC we accept from other windows to reduce // the chance of doing work after we go down. Kill // is special in that it does not orderly shutdown // windows. mainProcessElectronServer.dispose(); } }); // Resolve unique machine ID const [machineId, sqmId, devDeviceId] = await Promise.all([ resolveMachineId(this.stateService, this.logService), resolveSqmId(this.stateService, this.logService), resolveDevDeviceId(this.stateService, this.logService) ]); // Shared process const { sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId, sqmId, devDeviceId); // Services const appInstantiationService = await this.initServices(machineId, sqmId, devDeviceId, sharedProcessReady); // Error telemetry appInstantiationService.invokeFunction(accessor => this._register(new ErrorTelemetry(accessor.get(ILogService), accessor.get(ITelemetryService)))); // Metered connection telemetry appInstantiationService.invokeFunction(accessor => { (accessor.get(IMeteredConnectionService) as MeteredConnectionMainService).setTelemetryService(accessor.get(ITelemetryService)); }); // Auth Handler appInstantiationService.invokeFunction(accessor => accessor.get(IProxyAuthService)); // Transient profiles handler this._register(appInstantiationService.createInstance(UserDataProfilesHandler)); // Init Channels appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient)); // Setup Protocol URL Handlers const initialProtocolUrls = await appInstantiationService.invokeFunction(accessor => this.setupProtocolUrlHandlers(accessor, mainProcessElectronServer)); // Setup vscode-remote-resource protocol handler this.setupManagedRemoteResourceUrlHandler(mainProcessElectronServer); // Signal phase: ready - before opening first window this.lifecycleMainService.phase = LifecycleMainPhase.Ready; // Open Windows await appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, initialProtocolUrls)); // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; // Post Open Windows Tasks this.afterWindowOpen(appInstantiationService); // Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec) const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => { this._register(runWhenGlobalIdle(() => { // Signal phase: eventually this.lifecycleMainService.phase = LifecycleMainPhase.Eventually; // Eventually Post Open Window Tasks this.eventuallyAfterWindowOpen(); }, 2500)); }, 2500)); eventuallyPhaseScheduler.schedule(); } private async setupProtocolUrlHandlers(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): Promise { const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); const urlService = accessor.get(IURLService); const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService); const dialogMainService = accessor.get(IDialogMainService); // Install URL handlers that deal with protocl URLs either // from this process by opening windows and/or by forwarding // the URLs into a window process to be handled there. const app = this; urlService.registerHandler({ async handleURL(uri: URI, options?: IOpenURLOptions): Promise { return app.handleProtocolUrl(windowsMainService, dialogMainService, urlService, uri, options); } }); const activeWindowManager = this._register(new ActiveWindowManager({ onDidOpenMainWindow: nativeHostMainService.onDidOpenMainWindow, onDidFocusMainWindow: nativeHostMainService.onDidFocusMainWindow, getActiveWindowId: () => nativeHostMainService.getActiveWindowId(-1) })); const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter, this.logService); const urlHandlerChannel = mainProcessElectronServer.getChannel('urlHandler', urlHandlerRouter); urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel)); const initialProtocolUrls = await this.resolveInitialProtocolUrls(windowsMainService, dialogMainService); this._register(new ElectronURLListener(initialProtocolUrls?.urls, urlService, windowsMainService, this.environmentMainService, this.productService, this.logService)); return initialProtocolUrls; } private setupManagedRemoteResourceUrlHandler(mainProcessElectronServer: ElectronIPCServer) { const notFound = (): Electron.ProtocolResponse => ({ statusCode: 404, data: 'Not found' }); const remoteResourceChannel = new Lazy(() => mainProcessElectronServer.getChannel( NODE_REMOTE_RESOURCE_CHANNEL_NAME, new NodeRemoteResourceRouter(), )); protocol.registerBufferProtocol(Schemas.vscodeManagedRemoteResource, (request, callback) => { const url = URI.parse(request.url); if (!url.authority.startsWith('window:')) { return callback(notFound()); } remoteResourceChannel.value.call(NODE_REMOTE_RESOURCE_IPC_METHOD_NAME, [url]).then( r => callback({ ...r, data: Buffer.from(r.body, 'base64') }), err => { this.logService.warn('error dispatching remote resource call', err); callback({ statusCode: 500, data: String(err) }); }); }); } private async resolveInitialProtocolUrls(windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService): Promise { /** * Protocol URL handling on startup is complex, refer to * {@link IInitialProtocolUrls} for an explainer. */ // Windows/Linux: protocol handler invokes CLI with --open-url const protocolUrlsFromCommandLine = this.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : []; if (protocolUrlsFromCommandLine.length > 0) { this.logService.trace('app#resolveInitialProtocolUrls() protocol urls from command line:', protocolUrlsFromCommandLine); } // macOS: open-url events that were received before the app is ready const protocolUrlsFromEvent = ((global as { getOpenUrls?: () => string[] }).getOpenUrls?.() || []); if (protocolUrlsFromEvent.length > 0) { this.logService.trace(`app#resolveInitialProtocolUrls() protocol urls from macOS 'open-url' event:`, protocolUrlsFromEvent); } if (protocolUrlsFromCommandLine.length + protocolUrlsFromEvent.length === 0) { return undefined; } const protocolUrls = [ ...protocolUrlsFromCommandLine, ...protocolUrlsFromEvent ].map(url => { try { return { uri: URI.parse(url), originalUrl: url }; } catch { this.logService.trace('app#resolveInitialProtocolUrls() protocol url failed to parse:', url); return undefined; } }); const openables: IWindowOpenable[] = []; const urls: IProtocolUrl[] = []; for (const protocolUrl of protocolUrls) { if (!protocolUrl) { continue; // invalid } const windowOpenable = this.getWindowOpenableFromProtocolUrl(protocolUrl.uri); if (windowOpenable) { if (await this.shouldBlockOpenable(windowOpenable, windowsMainService, dialogMainService)) { this.logService.trace('app#resolveInitialProtocolUrls() protocol url was blocked:', protocolUrl.uri.toString(true)); continue; // blocked } else { this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be handled as window to open:', protocolUrl.uri.toString(true), windowOpenable); openables.push(windowOpenable); // handled as window to open } } else { this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be passed to active window for handling:', protocolUrl.uri.toString(true)); urls.push(protocolUrl); // handled within active window } } return { urls, openables }; } private async shouldBlockOpenable(openable: IWindowOpenable, windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService): Promise { let openableUri: URI; let message: string; if (isWorkspaceToOpen(openable)) { openableUri = openable.workspaceUri; 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); } else if (isFolderToOpen(openable)) { openableUri = openable.folderUri; 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); } else { openableUri = openable.fileUri; 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); } if (openableUri.scheme !== Schemas.file && openableUri.scheme !== Schemas.vscodeRemote) { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: we currently only ask for confirmation for `file` and `vscode-remote` // authorities here. There is an additional confirmation for `extension.id` // authorities from within the window. // // IF YOU ARE PLANNING ON ADDING ANOTHER AUTHORITY HERE, MAKE SURE TO ALSO // ADD IT TO THE CONFIRMATION CODE BELOW OR INSIDE THE WINDOW! // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! return false; } const askForConfirmation = this.configurationService.getValue(CodeApplication.SECURITY_PROTOCOL_HANDLING_CONFIRMATION_SETTING_KEY[openableUri.scheme]); if (askForConfirmation === false) { return false; // not blocked via settings } const { response, checkboxChecked } = await dialogMainService.showMessageBox({ type: 'warning', buttons: [ localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes"), localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No") ], message, 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'"), checkboxLabel: openableUri.scheme === Schemas.file ? localize('doNotAskAgainLocal', "Allow opening local paths without asking") : localize('doNotAskAgainRemote', "Allow opening remote paths without asking"), cancelId: 1 }); if (response !== 0) { return true; // blocked by user choice } if (checkboxChecked) { // Due to https://github.com/microsoft/vscode/issues/195436, we can only // update settings from within a window. But we do not know if a window // is about to open or can already handle the request, so we have to send // to any current window and any newly opening window. const request = { channel: 'vscode:disablePromptForProtocolHandling', args: openableUri.scheme === Schemas.file ? 'local' : 'remote' }; windowsMainService.sendToFocused(request.channel, request.args); windowsMainService.sendToOpeningWindow(request.channel, request.args); } return false; // not blocked by user choice } private getWindowOpenableFromProtocolUrl(uri: URI): IWindowOpenable | undefined { if (!uri.path) { return undefined; } // File path if (uri.authority === Schemas.file) { const fileUri = URI.file(uri.fsPath); if (hasWorkspaceFileExtension(fileUri)) { return { workspaceUri: fileUri }; } return { fileUri }; } // Remote path else if (uri.authority === Schemas.vscodeRemote) { // Example conversion: // From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco // To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */); let authority: string; let path: string; if (secondSlash !== -1) { authority = uri.path.substring(1, secondSlash); path = uri.path.substring(secondSlash); } else { authority = uri.path.substring(1); path = '/'; } let query = uri.query; const params = new URLSearchParams(uri.query); if (params.get('windowId') === '_blank') { // Make sure to unset any `windowId=_blank` here // https://github.com/microsoft/vscode/issues/191902 params.delete('windowId'); query = params.toString(); } const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query, fragment: uri.fragment }); if (hasWorkspaceFileExtension(path)) { return { workspaceUri: remoteUri }; } if (/:[\d]+$/.test(path)) { // path with :line:column syntax return { fileUri: remoteUri }; } return { folderUri: remoteUri }; } return undefined; } private async handleProtocolUrl(windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService, urlService: IURLService, uri: URI, options?: IOpenURLOptions): Promise { this.logService.trace('app#handleProtocolUrl():', uri.toString(true), options); // Support 'workspace' URLs (https://github.com/microsoft/vscode/issues/124263) if (uri.scheme === this.productService.urlProtocol && uri.path === 'workspace') { uri = uri.with({ authority: 'file', path: URI.parse(uri.query).path, query: '' }); } let shouldOpenInNewWindow = false; // We should handle the URI in a new window if the URL contains `windowId=_blank` const params = new URLSearchParams(uri.query); if (params.get('windowId') === '_blank') { this.logService.trace(`app#handleProtocolUrl() found 'windowId=_blank' as parameter, setting shouldOpenInNewWindow=true:`, uri.toString(true)); params.delete('windowId'); uri = uri.with({ query: params.toString() }); shouldOpenInNewWindow = true; } // or if no window is open (macOS only) else if (isMacintosh && windowsMainService.getWindowCount() === 0) { this.logService.trace(`app#handleProtocolUrl() running on macOS with no window open, setting shouldOpenInNewWindow=true:`, uri.toString(true)); shouldOpenInNewWindow = true; } // Pass along whether the application is being opened via a Continue On flow const continueOn = params.get('continueOn'); if (continueOn !== null) { this.logService.trace(`app#handleProtocolUrl() found 'continueOn' as parameter:`, uri.toString(true)); params.delete('continueOn'); uri = uri.with({ query: params.toString() }); this.environmentMainService.continueOn = continueOn ?? undefined; } // Extract session parameter to open a specific chat session in the target window const session = params.get('session'); if (session !== null) { this.logService.trace(`app#handleProtocolUrl() found 'session' as parameter:`, uri.toString(true)); params.delete('session'); uri = uri.with({ query: params.toString() }); } // Check if the protocol URL is a window openable to open... const windowOpenableFromProtocolUrl = this.getWindowOpenableFromProtocolUrl(uri); if (windowOpenableFromProtocolUrl) { if (await this.shouldBlockOpenable(windowOpenableFromProtocolUrl, windowsMainService, dialogMainService)) { this.logService.trace('app#handleProtocolUrl() protocol url was blocked:', uri.toString(true)); return true; // If openable should be blocked, behave as if it's handled } else { this.logService.trace('app#handleProtocolUrl() opening protocol url as window:', windowOpenableFromProtocolUrl, uri.toString(true)); const window = (await windowsMainService.open({ context: OpenContext.LINK, cli: { ...this.environmentMainService.args }, urisToOpen: [windowOpenableFromProtocolUrl], forceNewWindow: shouldOpenInNewWindow, gotoLineMode: true // remoteAuthority: will be determined based on windowOpenableFromProtocolUrl })).at(0); window?.focus(); // this should help ensuring that the right window gets focus when multiple are opened // Open chat session in the target window if requested if (window && session) { window.sendWhenReady('vscode:openChatSession', CancellationToken.None, session); } return true; } } // ...or if we should open in a new window and then handle it within that window if (shouldOpenInNewWindow) { this.logService.trace('app#handleProtocolUrl() opening empty window and passing in protocol url:', uri.toString(true)); const window = (await windowsMainService.open({ context: OpenContext.LINK, cli: { ...this.environmentMainService.args }, forceNewWindow: true, forceEmpty: true, gotoLineMode: true, remoteAuthority: getRemoteAuthority(uri) })).at(0); await window?.ready(); return urlService.open(uri, options); } this.logService.trace('app#handleProtocolUrl(): not handled', uri.toString(true), options); return false; } private setupSharedProcess(machineId: string, sqmId: string, devDeviceId: string): { sharedProcessReady: Promise; sharedProcessClient: Promise } { const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, sqmId, devDeviceId)); this._register(sharedProcess.onDidCrash(() => this.windowsMainService?.sendToFocused('vscode:reportSharedProcessCrash'))); const sharedProcessClient = (async () => { this.logService.trace('Main->SharedProcess#connect'); const port = await sharedProcess.connect(); this.logService.trace('Main->SharedProcess#connect: connection established'); return new MessagePortClient(port, 'main'); })(); const sharedProcessReady = (async () => { await sharedProcess.whenReady(); return sharedProcessClient; })(); return { sharedProcessReady, sharedProcessClient }; } private async initServices(machineId: string, sqmId: string, devDeviceId: string, sharedProcessReady: Promise): Promise { const services = new ServiceCollection(); // Update switch (process.platform) { case 'win32': services.set(IUpdateService, new SyncDescriptor(Win32UpdateService)); break; case 'linux': if (isLinuxSnap) { services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env['SNAP'], process.env['SNAP_REVISION']])); } else { services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService)); } break; case 'darwin': services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService)); break; } // Windows services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, sqmId, devDeviceId, this.userEnv], false)); services.set(IAuxiliaryWindowsMainService, new SyncDescriptor(AuxiliaryWindowsMainService, undefined, false)); // Dialogs const dialogMainService = new DialogMainService(this.logService, this.productService); services.set(IDialogMainService, dialogMainService); // Launch services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService, undefined, false /* proxied to other processes */)); // Diagnostics services.set(IDiagnosticsMainService, new SyncDescriptor(DiagnosticsMainService, undefined, false /* proxied to other processes */)); services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); // Encryption services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService)); // Browser Elements services.set(INativeBrowserElementsMainService, new SyncDescriptor(NativeBrowserElementsMainService, undefined, false /* proxied to other processes */)); // Browser View services.set(IBrowserViewCDPProxyServer, new SyncDescriptor(BrowserViewCDPProxyServer, undefined, true)); services.set(IBrowserViewMainService, new SyncDescriptor(BrowserViewMainService, undefined, false /* proxied to other processes */)); services.set(IBrowserViewGroupMainService, new SyncDescriptor(BrowserViewGroupMainService, undefined, false /* proxied to other processes */)); // Keyboard Layout services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService)); // Native Host services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, undefined, false /* proxied to other processes */)); // Metered Connection const meteredConnectionService = new MeteredConnectionMainService(this.configurationService); services.set(IMeteredConnectionService, meteredConnectionService); // Web Contents Extractor services.set(IWebContentExtractorService, new SyncDescriptor(NativeWebContentExtractorService, undefined, false /* proxied to other processes */)); // Webview Manager services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService)); // Menubar services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService)); // Extension Host Starter services.set(IExtensionHostStarter, new SyncDescriptor(ExtensionHostStarter)); // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); services.set(IApplicationStorageMainService, new SyncDescriptor(ApplicationStorageMainService)); // Terminal const ptyHostStarter = new ElectronPtyHostStarter({ graceTime: LocalReconnectConstants.GraceTime, shortGraceTime: LocalReconnectConstants.ShortGraceTime, scrollback: this.configurationService.getValue(TerminalSettingId.PersistentSessionScrollback) ?? 100 }, this.configurationService, this.environmentMainService, this.lifecycleMainService, this.logService); const ptyHostService = new PtyHostService( ptyHostStarter, this.configurationService, this.logService, this.loggerService ); services.set(ILocalPtyService, ptyHostService); // External terminal if (isWindows) { services.set(IExternalTerminalMainService, new SyncDescriptor(WindowsExternalTerminalService)); } else if (isMacintosh) { services.set(IExternalTerminalMainService, new SyncDescriptor(MacExternalTerminalService)); } else if (isLinux) { services.set(IExternalTerminalMainService, new SyncDescriptor(LinuxExternalTerminalService)); } // Backups const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService, this.stateService); services.set(IBackupMainService, backupMainService); // Workspaces const workspacesManagementMainService = new WorkspacesManagementMainService(this.environmentMainService, this.logService, this.userDataProfilesMainService, backupMainService, dialogMainService); services.set(IWorkspacesManagementMainService, workspacesManagementMainService); services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService, undefined, false /* proxied to other processes */)); services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService, undefined, false)); // URL handling services.set(IURLService, new SyncDescriptor(NativeURLService, undefined, false /* proxied to other processes */)); // Telemetry if (supportsTelemetry(this.productService, this.environmentMainService)) { const isInternal = isInternalTelemetry(this.productService, this.configurationService); const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); const commonProperties = resolveCommonProperties(release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, sqmId, devDeviceId, isInternal, this.productService.date); const piiPaths = getPiiPathsFromEnvironment(this.environmentMainService); const config: ITelemetryServiceConfig = { appenders: [appender], commonProperties, piiPaths, sendErrorTelemetry: true }; services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config], false)); } else { services.set(ITelemetryService, NullTelemetryService); } // Default Extensions Profile Init services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true)); // Utility Process Worker services.set(IUtilityProcessWorkerMainService, new SyncDescriptor(UtilityProcessWorkerMainService, undefined, true)); // Proxy Auth services.set(IProxyAuthService, new SyncDescriptor(ProxyAuthService)); // MCP services.set(INativeMcpDiscoveryHelperService, new SyncDescriptor(NativeMcpDiscoveryHelperService)); services.set(IMcpGatewayService, new SyncDescriptor(McpGatewayService)); // Dev Only: CSS service (for ESM) services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true)); // Init services that require it await Promises.settled([ backupMainService.initialize(), workspacesManagementMainService.initialize() ]); return this.mainInstantiationService.createChild(services); } private initChannels(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, sharedProcessClient: Promise): void { // Channels registered to node.js are exposed to second instances // launching because that is the only way the second instance // can talk to the first instance. Electron IPC does not work // across apps until `requestSingleInstance` APIs are adopted. const disposables = this._register(new DisposableStore()); const launchChannel = ProxyChannel.fromService(accessor.get(ILaunchMainService), disposables, { disableMarshalling: true }); this.mainProcessNodeIpcServer.registerChannel('launch', launchChannel); const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsMainService), disposables, { disableMarshalling: true }); this.mainProcessNodeIpcServer.registerChannel('diagnostics', diagnosticsChannel); // Policies (main & shared process) const policyChannel = disposables.add(new PolicyChannel(accessor.get(IPolicyService))); mainProcessElectronServer.registerChannel('policy', policyChannel); sharedProcessClient.then(client => client.registerChannel('policy', policyChannel)); // Local Files const diskFileSystemProvider = this.fileService.getProvider(Schemas.file); assertType(diskFileSystemProvider instanceof DiskFileSystemProvider); const fileSystemProviderChannel = disposables.add(new DiskFileSystemProviderChannel(diskFileSystemProvider, this.logService, this.environmentMainService)); mainProcessElectronServer.registerChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME, fileSystemProviderChannel); sharedProcessClient.then(client => client.registerChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME, fileSystemProviderChannel)); // User Data Profiles const userDataProfilesService = ProxyChannel.fromService(accessor.get(IUserDataProfilesMainService), disposables); mainProcessElectronServer.registerChannel('userDataProfiles', userDataProfilesService); sharedProcessClient.then(client => client.registerChannel('userDataProfiles', userDataProfilesService)); // Update const updateChannel = new UpdateChannel(accessor.get(IUpdateService)); mainProcessElectronServer.registerChannel('update', updateChannel); // Metered Connection const meteredConnectionChannel = new MeteredConnectionChannel(accessor.get(IMeteredConnectionService) as MeteredConnectionMainService); mainProcessElectronServer.registerChannel(METERED_CONNECTION_CHANNEL, meteredConnectionChannel); sharedProcessClient.then(client => client.registerChannel(METERED_CONNECTION_CHANNEL, meteredConnectionChannel)); // Process const processChannel = ProxyChannel.fromService(new ProcessMainService(this.logService, accessor.get(IDiagnosticsService), accessor.get(IDiagnosticsMainService)), disposables); mainProcessElectronServer.registerChannel('process', processChannel); // Encryption const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService), disposables); mainProcessElectronServer.registerChannel('encryption', encryptionChannel); // Browser Elements const browserElementsChannel = ProxyChannel.fromService(accessor.get(INativeBrowserElementsMainService), disposables); mainProcessElectronServer.registerChannel('browserElements', browserElementsChannel); sharedProcessClient.then(client => client.registerChannel('browserElements', browserElementsChannel)); // Browser View const browserViewChannel = ProxyChannel.fromService(accessor.get(IBrowserViewMainService), disposables); mainProcessElectronServer.registerChannel(ipcBrowserViewChannelName, browserViewChannel); sharedProcessClient.then(client => client.registerChannel(ipcBrowserViewChannelName, browserViewChannel)); // Browser View Group const browserViewGroupChannel = ProxyChannel.fromService(accessor.get(IBrowserViewGroupMainService), disposables); mainProcessElectronServer.registerChannel(ipcBrowserViewGroupChannelName, browserViewGroupChannel); sharedProcessClient.then(client => client.registerChannel(ipcBrowserViewGroupChannelName, browserViewGroupChannel)); // Signing const signChannel = ProxyChannel.fromService(accessor.get(ISignService), disposables); mainProcessElectronServer.registerChannel('sign', signChannel); // Keyboard Layout const keyboardLayoutChannel = ProxyChannel.fromService(accessor.get(IKeyboardLayoutMainService), disposables); mainProcessElectronServer.registerChannel('keyboardLayout', keyboardLayoutChannel); // Native host (main & shared process) this.nativeHostMainService = accessor.get(INativeHostMainService); const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService, disposables); mainProcessElectronServer.registerChannel('nativeHost', nativeHostChannel); sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel)); // Web Content Extractor const webContentExtractorChannel = ProxyChannel.fromService(accessor.get(IWebContentExtractorService), disposables); mainProcessElectronServer.registerChannel('webContentExtractor', webContentExtractorChannel); // Workspaces const workspacesChannel = ProxyChannel.fromService(accessor.get(IWorkspacesService), disposables); mainProcessElectronServer.registerChannel('workspaces', workspacesChannel); // Menubar const menubarChannel = ProxyChannel.fromService(accessor.get(IMenubarMainService), disposables); mainProcessElectronServer.registerChannel('menubar', menubarChannel); // URL handling const urlChannel = ProxyChannel.fromService(accessor.get(IURLService), disposables); mainProcessElectronServer.registerChannel('url', urlChannel); // Webview Manager const webviewChannel = ProxyChannel.fromService(accessor.get(IWebviewManagerService), disposables); mainProcessElectronServer.registerChannel('webview', webviewChannel); // Storage (main & shared process) const storageChannel = disposables.add((new StorageDatabaseChannel(this.logService, accessor.get(IStorageMainService)))); mainProcessElectronServer.registerChannel('storage', storageChannel); sharedProcessClient.then(client => client.registerChannel('storage', storageChannel)); // Profile Storage Changes Listener (shared process) const profileStorageListener = disposables.add((new ProfileStorageChangesListenerChannel(accessor.get(IStorageMainService), accessor.get(IUserDataProfilesMainService), this.logService))); sharedProcessClient.then(client => client.registerChannel('profileStorageListener', profileStorageListener)); // Terminal const ptyHostChannel = ProxyChannel.fromService(accessor.get(ILocalPtyService), disposables); mainProcessElectronServer.registerChannel(TerminalIpcChannels.LocalPty, ptyHostChannel); // External Terminal const externalTerminalChannel = ProxyChannel.fromService(accessor.get(IExternalTerminalMainService), disposables); mainProcessElectronServer.registerChannel('externalTerminal', externalTerminalChannel); // MCP const mcpDiscoveryChannel = ProxyChannel.fromService(accessor.get(INativeMcpDiscoveryHelperService), disposables); mainProcessElectronServer.registerChannel(NativeMcpDiscoveryHelperChannelName, mcpDiscoveryChannel); const mcpGatewayChannel = this._register(new McpGatewayChannel(mainProcessElectronServer, accessor.get(IMcpGatewayService))); mainProcessElectronServer.registerChannel(McpGatewayChannelName, mcpGatewayChannel); // Logger const loggerChannel = new LoggerChannel(accessor.get(ILoggerMainService),); mainProcessElectronServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); // Extension Host Debug Broadcasting const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService)); mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel); // Extension Host Starter const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter), disposables); mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel); // Utility Process Worker const utilityProcessWorkerChannel = ProxyChannel.fromService(accessor.get(IUtilityProcessWorkerMainService), disposables); mainProcessElectronServer.registerChannel(ipcUtilityProcessWorkerChannelName, utilityProcessWorkerChannel); } private async openFirstWindow(accessor: ServicesAccessor, initialProtocolUrls: IInitialProtocolUrls | undefined): Promise { const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); this.auxiliaryWindowsMainService = accessor.get(IAuxiliaryWindowsMainService); const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP; const args = this.environmentMainService.args; // Handle sessions window first based on context if ((process as INodeProcess).isEmbeddedApp || (args['sessions'] && this.productService.quality !== 'stable')) { return windowsMainService.openSessionsWindow({ context, contextWindowId: undefined }); } // Then check for windows from protocol links to open if (initialProtocolUrls) { // Openables can open as windows directly if (initialProtocolUrls.openables.length > 0) { return windowsMainService.open({ context, cli: args, urisToOpen: initialProtocolUrls.openables, gotoLineMode: true, initialStartup: true // remoteAuthority: will be determined based on openables }); } // Protocol links with `windowId=_blank` on startup // should be handled in a special way: // We take the first one of these and open an empty // window for it. This ensures we are not restoring // all windows of the previous session. // If there are any more URLs like these, they will // be handled from the URL listeners installed later. if (initialProtocolUrls.urls.length > 0) { for (const protocolUrl of initialProtocolUrls.urls) { const params = new URLSearchParams(protocolUrl.uri.query); if (params.get('windowId') === '_blank') { // It is important here that we remove `windowId=_blank` from // this URL because here we open an empty window for it. params.delete('windowId'); protocolUrl.originalUrl = protocolUrl.uri.toString(true); protocolUrl.uri = protocolUrl.uri.with({ query: params.toString() }); return windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, gotoLineMode: true, initialStartup: true // remoteAuthority: will be determined based on openables }); } } } } const macOpenFiles: string[] = (global as { macOpenFiles?: string[] }).macOpenFiles ?? []; const hasCliArgs = args._.length; const hasFolderURIs = !!args['folder-uri']; const hasFileURIs = !!args['file-uri']; const noRecentEntry = args['skip-add-to-recently-opened'] === true; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; const remoteAuthority = args.remote || undefined; const forceProfile = args.profile; const forceTempProfile = args['profile-temp']; // Started without file/folder arguments if (!hasCliArgs && !hasFolderURIs && !hasFileURIs) { // Force new window if (args['new-window'] || forceProfile || forceTempProfile) { return windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, noRecentEntry, waitMarkerFileURI, initialStartup: true, remoteAuthority, forceProfile, forceTempProfile }); } // mac: open-file event received on startup if (macOpenFiles.length) { return windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(path => { path = normalizeNFC(path); // macOS only: normalize paths to NFC form return (hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) }); }), noRecentEntry, waitMarkerFileURI, initialStartup: true, // remoteAuthority: will be determined based on macOpenFiles }); } } // default: read paths from cli return windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'], diffMode: args.diff, mergeMode: args.merge, noRecentEntry, waitMarkerFileURI, gotoLineMode: args.goto, initialStartup: true, remoteAuthority, forceProfile, forceTempProfile }); } private afterWindowOpen(instantiationService: IInstantiationService): void { // Accurate Windows version info if (isWindows) { initWindowsVersionInfo(); } // Windows: mutex this.installMutex(); // Remote Authorities protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => { callback({ url: request.url.replace(/^vscode-remote-resource:/, 'http:'), method: request.method }); }); // Start to fetch shell environment (if needed) after window has opened // Since this operation can take a long time, we want to warm it up while // the window is opening. // We also show an error to the user in case this fails. this.resolveShellEnvironment(this.environmentMainService.args, process.env, true); // Crash reporter this.updateCrashReporterEnablement(); // macOS: rosetta translation warning if (isMacintosh && app.runningUnderARM64Translation) { this.windowsMainService?.sendToFocused('vscode:showTranslatedBuildWarning'); } // Power telemetry instantiationService.invokeFunction(accessor => { const telemetryService = accessor.get(ITelemetryService); type PowerEvent = { readonly idleState: string; readonly idleTime: number; readonly thermalState: string; readonly onBattery: boolean; }; type PowerEventClassification = { idleState: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The system idle state (active, idle, locked, unknown).' }; idleTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The system idle time in seconds.' }; thermalState: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The system thermal state (unknown, nominal, fair, serious, critical).' }; onBattery: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the system is running on battery power.' }; owner: 'chrmarti'; comment: 'Tracks OS power suspend and resume events for reliability insights.'; }; const getPowerEventData = (): PowerEvent => ({ idleState: powerMonitor.getSystemIdleState(60), idleTime: powerMonitor.getSystemIdleTime(), thermalState: powerMonitor.getCurrentThermalState(), onBattery: powerMonitor.isOnBatteryPower() }); this._register(Event.fromNodeEventEmitter(powerMonitor, 'suspend')(() => { telemetryService.publicLog2('power.suspend', getPowerEventData()); })); this._register(Event.fromNodeEventEmitter(powerMonitor, 'resume')(() => { telemetryService.publicLog2('power.resume', getPowerEventData()); })); }); // GPU crash telemetry for skia graphite out of order recording failures // Refs https://github.com/microsoft/vscode/issues/284162 if (isMacintosh) { instantiationService.invokeFunction(accessor => { const telemetryService = accessor.get(ITelemetryService); type GPUFeatureStatusWithSkiaGraphite = GPUFeatureStatus & { skia_graphite: string; }; const initialGpuFeatureStatus = app.getGPUFeatureStatus() as GPUFeatureStatusWithSkiaGraphite; const skiaGraphiteEnabled: string = initialGpuFeatureStatus['skia_graphite']; if (skiaGraphiteEnabled === 'enabled') { this._register(Event.fromNodeEventEmitter<{ details: Details }>(app, 'child-process-gone', (event, details) => ({ event, details }))(({ details }) => { if (details.type === 'GPU' && details.reason === 'crashed') { const currentGpuFeatureStatus = app.getGPUFeatureStatus(); const currentRasterizationStatus: string = currentGpuFeatureStatus['rasterization']; if (currentRasterizationStatus !== 'enabled') { // Get last 10 GPU log messages (only the message field) let gpuLogMessages: string[] = []; type AppWithGPULogMethod = typeof app & { getGPULogMessages(): IGPULogMessage[]; }; const customApp = app as AppWithGPULogMethod; if (typeof customApp.getGPULogMessages === 'function') { gpuLogMessages = customApp.getGPULogMessages().slice(-10).map(log => log.message); } type GpuCrashEvent = { readonly gpuFeatureStatus: string; readonly gpuLogMessages: string; }; type GpuCrashClassification = { gpuFeatureStatus: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Current GPU feature status.' }; gpuLogMessages: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Last 10 GPU log messages before crash.' }; owner: 'deepak1556'; comment: 'Tracks GPU process crashes that would result in fallback mode.'; }; telemetryService.publicLog2('gpu.crash.fallback', { gpuFeatureStatus: JSON.stringify(currentGpuFeatureStatus), gpuLogMessages: JSON.stringify(gpuLogMessages) }); } } })); } }); } } private async installMutex(): Promise { const win32MutexName = this.productService.win32MutexName; if (isWindows && win32MutexName) { try { const WindowsMutex = await import('@vscode/windows-mutex'); const mutex = new WindowsMutex.Mutex(win32MutexName); Event.once(this.lifecycleMainService.onWillShutdown)(() => mutex.release()); } catch (error) { this.logService.error(error); } } } private async resolveShellEnvironment(args: NativeParsedArgs, env: IProcessEnvironment, notifyOnError: boolean): Promise { try { return await getResolvedShellEnv(this.configurationService, this.logService, args, env); } catch (error) { const errorMessage = toErrorMessage(error); if (notifyOnError) { this.windowsMainService?.sendToFocused('vscode:showResolveShellEnvError', errorMessage); } else { this.logService.error(errorMessage); } } return {}; } private async updateCrashReporterEnablement(): Promise { // If enable-crash-reporter argv is undefined then this is a fresh start, // based on `telemetry.enableCrashreporter` settings, generate a UUID which // will be used as crash reporter id and also update the json file. try { const argvContent = await this.fileService.readFile(this.environmentMainService.argvResource); const argvString = argvContent.value.toString(); const argvJSON = parse<{ 'enable-crash-reporter'?: boolean }>(argvString); const telemetryLevel = getTelemetryLevel(this.configurationService); const enableCrashReporter = telemetryLevel >= TelemetryLevel.CRASH; // Initial startup if (argvJSON['enable-crash-reporter'] === undefined) { const additionalArgvContent = [ '', ' // Allows to disable crash reporting.', ' // Should restart the app if the value is changed.', ` "enable-crash-reporter": ${enableCrashReporter},`, '', ' // Unique id used for correlating crash reports sent from this instance.', ' // Do not edit this value.', ` "crash-reporter-id": "${generateUuid()}"`, '}' ]; const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n')); await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString)); } // Subsequent startup: update crash reporter value if changed else { const newArgvString = argvString.replace(/"enable-crash-reporter": .*,/, `"enable-crash-reporter": ${enableCrashReporter},`); if (newArgvString !== argvString) { await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString)); } } } catch (error) { this.logService.error(error); // Inform the user via notification this.windowsMainService?.sendToFocused('vscode:showArgvParseWarning'); } } private eventuallyAfterWindowOpen(): void { // Validate Device ID is up to date (delay this as it has shown significant perf impact) // Refs: https://github.com/microsoft/vscode/issues/234064 validateDevDeviceId(this.stateService, this.logService); } }