UNPKG

sussudio

Version:

An unofficial VS Code Internal API

928 lines 70.2 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { app, BrowserWindow, nativeImage, screen, systemPreferences, TouchBar } from 'electron'; import { DeferredPromise, RunOnceScheduler, timeout } from "../../../base/common/async.mjs"; import { CancellationToken } from "../../../base/common/cancellation.mjs"; import { toErrorMessage } from "../../../base/common/errorMessage.mjs"; import { Emitter } from "../../../base/common/event.mjs"; import { mnemonicButtonLabel } from "../../../base/common/labels.mjs"; import { Disposable } from "../../../base/common/lifecycle.mjs"; import { FileAccess, Schemas } from "../../../base/common/network.mjs"; import { join } from "../../../base/common/path.mjs"; import { getMarks, mark } from "../../../base/common/performance.mjs"; import { isLinux, isMacintosh, isWindows } from "../../../base/common/platform.mjs"; import { URI } from "../../../base/common/uri.mjs"; import { localize } from "../../../nls.mjs"; import { IBackupMainService } from "../../backup/electron-main/backup.mjs"; import { IConfigurationService } from "../../configuration/common/configuration.mjs"; import { IDialogMainService } from "../../dialogs/electron-main/dialogMainService.mjs"; import { IEnvironmentMainService } from "../../environment/electron-main/environmentMainService.mjs"; import { isLaunchedFromCli } from "../../environment/node/argvHelper.mjs"; import { IFileService } from "../../files/common/files.mjs"; import { ILifecycleMainService } from "../../lifecycle/electron-main/lifecycleMainService.mjs"; import { ILogService } from "../../log/common/log.mjs"; import { IProductService } from "../../product/common/productService.mjs"; import { IProtocolMainService } from "../../protocol/electron-main/protocol.mjs"; import { resolveMarketplaceHeaders } from "../../externalServices/common/marketplace.mjs"; import { IApplicationStorageMainService, IStorageMainService } from "../../storage/electron-main/storageMainService.mjs"; import { ITelemetryService } from "../../telemetry/common/telemetry.mjs"; import { ThemeIcon } from "../../theme/common/themeService.mjs"; import { IThemeMainService } from "../../theme/electron-main/themeMainService.mjs"; import { getMenuBarVisibility, getTitleBarStyle, useWindowControlsOverlay, WindowMinimumSize, zoomLevelToZoomFactor } from "../../window/common/window.mjs"; import { IWindowsMainService } from "./windows.mjs"; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, toWorkspaceIdentifier } from "../../workspace/common/workspace.mjs"; import { IWorkspacesManagementMainService } from "../../workspaces/electron-main/workspacesManagementMainService.mjs"; import { defaultWindowState } from "../../window/electron-main/window.mjs"; import { Color } from "../../../base/common/color.mjs"; import { IPolicyService } from "../../policy/common/policy.mjs"; import { IStateMainService } from "../../state/electron-main/state.mjs"; import { IUserDataProfilesMainService } from "../../userDataProfile/electron-main/userDataProfile.mjs"; import { INativeHostMainService } from "../../native/electron-main/nativeHostMainService.mjs"; import { OneDataSystemAppender } from "../../telemetry/node/1dsAppender.mjs"; import { TelemetryService } from "../../telemetry/common/telemetryService.mjs"; import { getPiiPathsFromEnvironment, isInternalTelemetry, supportsTelemetry } from "../../telemetry/common/telemetryUtils.mjs"; import { resolveCommonProperties } from "../../telemetry/common/commonProperties.mjs"; import { hostname, release } from 'os'; import { resolveMachineId } from "../../telemetry/electron-main/telemetryUtils.mjs"; var ReadyState; (function (ReadyState) { /** * This window has not loaded anything yet * and this is the initial state of every * window. */ ReadyState[ReadyState["NONE"] = 0] = "NONE"; /** * This window is navigating, either for the * first time or subsequent times. */ ReadyState[ReadyState["NAVIGATING"] = 1] = "NAVIGATING"; /** * This window has finished loading and is ready * to forward IPC requests to the web contents. */ ReadyState[ReadyState["READY"] = 2] = "READY"; })(ReadyState || (ReadyState = {})); let CodeWindow = class CodeWindow extends Disposable { logService; environmentMainService; policyService; userDataProfilesService; fileService; applicationStorageMainService; storageMainService; configurationService; themeMainService; workspacesManagementMainService; backupMainService; telemetryService; dialogMainService; lifecycleMainService; productService; protocolMainService; windowsMainService; stateMainService; nativeHostMainService; static windowControlHeightStateStorageKey = 'windowControlHeight'; static sandboxState = undefined; //#region Events _onWillLoad = this._register(new Emitter()); onWillLoad = this._onWillLoad.event; _onDidSignalReady = this._register(new Emitter()); onDidSignalReady = this._onDidSignalReady.event; _onDidTriggerSystemContextMenu = this._register(new Emitter()); onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event; _onDidClose = this._register(new Emitter()); onDidClose = this._onDidClose.event; _onDidDestroy = this._register(new Emitter()); onDidDestroy = this._onDidDestroy.event; //#endregion //#region Properties _id; get id() { return this._id; } _win; get win() { return this._win; } _lastFocusTime = -1; get lastFocusTime() { return this._lastFocusTime; } get backupPath() { return this._config?.backupPath; } get openedWorkspace() { return this._config?.workspace; } get profile() { if (!this.config) { return undefined; } const profile = this.userDataProfilesService.profiles.find(profile => profile.id === this.config?.profiles.profile.id); if (this.isExtensionDevelopmentHost && profile) { return profile; } return this.userDataProfilesService.getProfileForWorkspace(this.config.workspace ?? toWorkspaceIdentifier(this.backupPath, this.isExtensionDevelopmentHost)) ?? this.userDataProfilesService.defaultProfile; } get remoteAuthority() { return this._config?.remoteAuthority; } _config; get config() { return this._config; } get isExtensionDevelopmentHost() { return !!(this._config?.extensionDevelopmentPath); } get isExtensionTestHost() { return !!(this._config?.extensionTestsPath); } get isExtensionDevelopmentTestFromCli() { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this._config?.debugId; } //#endregion windowState; currentMenuBarVisibility; // TODO@electron workaround for https://github.com/electron/electron/issues/35360 // where on macOS the window will report a wrong state for `isFullScreen()` while // transitioning into and out of native full screen. transientIsNativeFullScreen = undefined; joinNativeFullScreenTransition = undefined; representedFilename; documentEdited; hasWindowControlOverlay = false; whenReadyCallbacks = []; touchBarGroups = []; currentHttpProxy = undefined; currentNoProxy = undefined; configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); pendingLoadConfig; wasLoaded = false; constructor(config, logService, environmentMainService, policyService, userDataProfilesService, fileService, applicationStorageMainService, storageMainService, configurationService, themeMainService, workspacesManagementMainService, backupMainService, telemetryService, dialogMainService, lifecycleMainService, productService, protocolMainService, windowsMainService, stateMainService, nativeHostMainService) { super(); this.logService = logService; this.environmentMainService = environmentMainService; this.policyService = policyService; this.userDataProfilesService = userDataProfilesService; this.fileService = fileService; this.applicationStorageMainService = applicationStorageMainService; this.storageMainService = storageMainService; this.configurationService = configurationService; this.themeMainService = themeMainService; this.workspacesManagementMainService = workspacesManagementMainService; this.backupMainService = backupMainService; this.telemetryService = telemetryService; this.dialogMainService = dialogMainService; this.lifecycleMainService = lifecycleMainService; this.productService = productService; this.protocolMainService = protocolMainService; this.windowsMainService = windowsMainService; this.stateMainService = stateMainService; this.nativeHostMainService = nativeHostMainService; //#region create browser window { // Load window state const [state, hasMultipleDisplays] = this.restoreWindowState(config.state); this.windowState = state; this.logService.trace('window#ctor: using window state', state); // In case we are maximized or fullscreen, only show later // after the call to maximize/fullscreen (see below) const isFullscreenOrMaximized = (this.windowState.mode === 0 /* WindowMode.Maximized */ || this.windowState.mode === 3 /* WindowMode.Fullscreen */); if (typeof CodeWindow.sandboxState === 'undefined') { // we should only check this once so that we do not end up // with some windows in sandbox mode and some not! CodeWindow.sandboxState = this.stateMainService.getItem('window.experimental.useSandbox', false); } const windowSettings = this.configurationService.getValue('window'); let useSandbox = false; if (typeof windowSettings?.experimental?.useSandbox === 'boolean') { useSandbox = windowSettings.experimental.useSandbox; } else if (this.productService.quality === 'stable' && CodeWindow.sandboxState) { useSandbox = true; } else { useSandbox = typeof this.productService.quality === 'string' && this.productService.quality !== 'stable'; } const options = { width: this.windowState.width, height: this.windowState.height, x: this.windowState.x, y: this.windowState.y, backgroundColor: this.themeMainService.getBackgroundColor(), minWidth: WindowMinimumSize.WIDTH, minHeight: WindowMinimumSize.HEIGHT, show: !isFullscreenOrMaximized, title: this.productService.nameLong, webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js').fsPath, additionalArguments: [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`], v8CacheOptions: this.environmentMainService.useCodeCache ? 'bypassHeatCheck' : 'none', enableWebSQL: false, spellcheck: false, zoomFactor: zoomLevelToZoomFactor(windowSettings?.zoomLevel), autoplayPolicy: 'user-gesture-required', // Enable experimental css highlight api https://chromestatus.com/feature/5436441440026624 // Refs https://github.com/microsoft/vscode/issues/140098 enableBlinkFeatures: 'HighlightAPI', ...useSandbox ? // Sandbox { sandbox: true } : // No Sandbox { nodeIntegration: true, contextIsolation: false } }, experimentalDarkMode: true }; // Apply icon to window // Linux: always // Windows: only when running out of sources, otherwise an icon is set by us on the executable if (isLinux) { options.icon = join(this.environmentMainService.appRoot, 'resources/linux/code.png'); } else if (isWindows && !this.environmentMainService.isBuilt) { options.icon = join(this.environmentMainService.appRoot, 'resources/win32/code_150x150.png'); } if (isMacintosh && !this.useNativeFullScreen()) { options.fullscreenable = false; // enables simple fullscreen mode } if (isMacintosh) { options.acceptFirstMouse = true; // enabled by default if (windowSettings?.clickThroughInactive === false) { options.acceptFirstMouse = false; } } const useNativeTabs = isMacintosh && windowSettings?.nativeTabs === true; if (useNativeTabs) { options.tabbingIdentifier = this.productService.nameShort; // this opts in to sierra tabs } const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom'; if (useCustomTitleStyle) { options.titleBarStyle = 'hidden'; if (!isMacintosh) { options.frame = false; } if (useWindowControlsOverlay(this.configurationService)) { // This logic will not perfectly guess the right colors // to use on initialization, but prefer to keep things // simple as it is temporary and not noticeable const titleBarColor = this.themeMainService.getWindowSplash()?.colorInfo.titleBarBackground ?? this.themeMainService.getBackgroundColor(); const symbolColor = Color.fromHex(titleBarColor).isDarker() ? '#FFFFFF' : '#000000'; options.titleBarOverlay = { height: 29, color: titleBarColor, symbolColor }; this.hasWindowControlOverlay = true; } } // Create the browser window mark('code/willCreateCodeBrowserWindow'); this._win = new BrowserWindow(options); mark('code/didCreateCodeBrowserWindow'); this._id = this._win.id; if (isMacintosh && useCustomTitleStyle) { this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any } // Update the window controls immediately based on cached values if (useCustomTitleStyle && ((isWindows && useWindowControlsOverlay(this.configurationService)) || isMacintosh)) { const cachedWindowControlHeight = this.stateMainService.getItem((CodeWindow.windowControlHeightStateStorageKey)); if (cachedWindowControlHeight) { this.updateWindowControls({ height: cachedWindowControlHeight }); } } // Windows Custom System Context Menu // See https://github.com/electron/electron/issues/24893 // // The purpose of this is to allow for the context menu in the Windows Title Bar // // Currently, all mouse events in the title bar are captured by the OS // thus we need to capture them here with a window hook specific to Windows // and then forward them to the correct window. if (isWindows && useCustomTitleStyle) { const WM_INITMENU = 0x0116; // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-initmenu // This sets up a listener for the window hook. This is a Windows-only API provided by electron. this._win.hookWindowMessage(WM_INITMENU, () => { const [x, y] = this._win.getPosition(); const cursorPos = screen.getCursorScreenPoint(); const cx = cursorPos.x - x; const cy = cursorPos.y - y; // In some cases, show the default system context menu // 1) The mouse position is not within the title bar // 2) The mouse position is within the title bar, but over the app icon // We do not know the exact title bar height but we make an estimate based on window height const shouldTriggerDefaultSystemContextMenu = () => { // Use the custom context menu when over the title bar, but not over the app icon // The app icon is estimated to be 30px wide // The title bar is estimated to be the max of 35px and 15% of the window height if (cx > 30 && cy >= 0 && cy <= Math.max(this._win.getBounds().height * 0.15, 35)) { return false; } return true; }; if (!shouldTriggerDefaultSystemContextMenu()) { // This is necessary to make sure the native system context menu does not show up. this._win.setEnabled(false); this._win.setEnabled(true); this._onDidTriggerSystemContextMenu.fire({ x: cx, y: cy }); } return 0; }); } // TODO@electron (Electron 4 regression): when running on multiple displays where the target display // to open the window has a larger resolution than the primary display, the window will not size // correctly unless we set the bounds again (https://github.com/microsoft/vscode/issues/74872) // // Extended to cover Windows as well as Mac (https://github.com/microsoft/vscode/issues/146499) // // However, when running with native tabs with multiple windows we cannot use this workaround // because there is a potential that the new window will be added as native tab instead of being // a window on its own. In that case calling setBounds() would cause https://github.com/microsoft/vscode/issues/75830 if ((isMacintosh || isWindows) && hasMultipleDisplays && (!useNativeTabs || BrowserWindow.getAllWindows().length === 1)) { if ([this.windowState.width, this.windowState.height, this.windowState.x, this.windowState.y].every(value => typeof value === 'number')) { this._win.setBounds({ width: this.windowState.width, height: this.windowState.height, x: this.windowState.x, y: this.windowState.y }); } } if (isFullscreenOrMaximized) { mark('code/willMaximizeCodeWindow'); // this call may or may not show the window, depends // on the platform: currently on Windows and Linux will // show the window as active. To be on the safe side, // we show the window at the end of this block. this._win.maximize(); if (this.windowState.mode === 3 /* WindowMode.Fullscreen */) { this.setFullScreen(true); } // to reduce flicker from the default window size // to maximize or fullscreen, we only show after this._win.show(); mark('code/didMaximizeCodeWindow'); } this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too } //#endregion // Open devtools if instructed from command line args if (this.environmentMainService.args['open-devtools'] === true) { this._win.webContents.openDevTools(); } // respect configured menu bar visibility this.onConfigurationUpdated(); // macOS: touch bar support this.createTouchBar(); // Eventing this.registerListeners(); } setRepresentedFilename(filename) { if (isMacintosh) { this._win.setRepresentedFilename(filename); } else { this.representedFilename = filename; } } getRepresentedFilename() { if (isMacintosh) { return this._win.getRepresentedFilename(); } return this.representedFilename; } setDocumentEdited(edited) { if (isMacintosh) { this._win.setDocumentEdited(edited); } this.documentEdited = edited; } isDocumentEdited() { if (isMacintosh) { return this._win.isDocumentEdited(); } return !!this.documentEdited; } focus(options) { // macOS: Electron > 7.x changed its behaviour to not // bring the application to the foreground when a window // is focused programmatically. Only via `app.focus` and // the option `steal: true` can you get the previous // behaviour back. The only reason to use this option is // when a window is getting focused while the application // is not in the foreground. if (isMacintosh && options?.force) { app.focus({ steal: true }); } if (!this._win) { return; } if (this._win.isMinimized()) { this._win.restore(); } this._win.focus(); } readyState = 0 /* ReadyState.NONE */; setReady() { this.logService.trace(`window#load: window reported ready (id: ${this._id})`); this.readyState = 2 /* ReadyState.READY */; // inform all waiting promises that we are ready now while (this.whenReadyCallbacks.length) { this.whenReadyCallbacks.pop()(this); } // Events this._onDidSignalReady.fire(); } ready() { return new Promise(resolve => { if (this.isReady) { return resolve(this); } // otherwise keep and call later when we are ready this.whenReadyCallbacks.push(resolve); }); } get isReady() { return this.readyState === 2 /* ReadyState.READY */; } get whenClosedOrLoaded() { return new Promise(resolve => { function handle() { closeListener.dispose(); loadListener.dispose(); resolve(); } const closeListener = this.onDidClose(() => handle()); const loadListener = this.onWillLoad(() => handle()); }); } registerListeners() { // Window error conditions to handle this._win.on('unresponsive', () => this.onWindowError(1 /* WindowError.UNRESPONSIVE */)); this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(2 /* WindowError.PROCESS_GONE */, details)); this._win.webContents.on('did-fail-load', (event, exitCode, reason) => this.onWindowError(3 /* WindowError.LOAD */, { reason, exitCode })); // Prevent windows/iframes from blocking the unload // through DOM events. We have our own logic for // unloading a window that should not be confused // with the DOM way. // (https://github.com/microsoft/vscode/issues/122736) this._win.webContents.on('will-prevent-unload', event => { event.preventDefault(); }); // Window close this._win.on('closed', () => { this._onDidClose.fire(); this.dispose(); }); // Remember that we loaded this._win.webContents.on('did-finish-load', () => { // Associate properties from the load request if provided if (this.pendingLoadConfig) { this._config = this.pendingLoadConfig; this.pendingLoadConfig = undefined; } }); // Window Focus this._win.on('focus', () => { this._lastFocusTime = Date.now(); }); // Window (Un)Maximize this._win.on('maximize', (e) => { if (this._config) { this._config.maximized = true; } app.emit('browser-window-maximize', e, this._win); }); this._win.on('unmaximize', (e) => { if (this._config) { this._config.maximized = false; } app.emit('browser-window-unmaximize', e, this._win); }); // Window Fullscreen this._win.on('enter-full-screen', () => { this.sendWhenReady('vscode:enterFullScreen', CancellationToken.None); this.joinNativeFullScreenTransition?.complete(); this.joinNativeFullScreenTransition = undefined; }); this._win.on('leave-full-screen', () => { this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None); this.joinNativeFullScreenTransition?.complete(); this.joinNativeFullScreenTransition = undefined; }); // Handle configuration changes this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); // Handle Workspace events this._register(this.workspacesManagementMainService.onDidDeleteUntitledWorkspace(e => this.onDidDeleteUntitledWorkspace(e))); // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, async (details, cb) => { const headers = await this.getMarketplaceHeaders(); cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) }); }); } marketplaceHeadersPromise; getMarketplaceHeaders() { if (!this.marketplaceHeadersPromise) { this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.productService.version, this.productService, this.environmentMainService, this.configurationService, this.fileService, this.applicationStorageMainService, this.telemetryService); } return this.marketplaceHeadersPromise; } async onWindowError(type, details) { switch (type) { case 2 /* WindowError.PROCESS_GONE */: this.logService.error(`CodeWindow: renderer process gone (reason: ${details?.reason || '<unknown>'}, code: ${details?.exitCode || '<unknown>'})`); break; case 1 /* WindowError.UNRESPONSIVE */: this.logService.error('CodeWindow: detected unresponsive'); break; case 3 /* WindowError.LOAD */: this.logService.error(`CodeWindow: failed to load (reason: ${details?.reason || '<unknown>'}, code: ${details?.exitCode || '<unknown>'})`); break; } this.telemetryService.publicLog2('windowerror', { type, reason: details?.reason, code: details?.exitCode }); // Inform User if non-recoverable switch (type) { case 1 /* WindowError.UNRESPONSIVE */: case 2 /* WindowError.PROCESS_GONE */: // If we run extension tests from CLI, we want to signal // back this state to the test runner by exiting with a // non-zero exit code. if (this.isExtensionDevelopmentTestFromCli) { this.lifecycleMainService.kill(1); return; } // If we run smoke tests, want to proceed with an orderly // shutdown as much as possible by destroying the window // and then calling the normal `quit` routine. if (this.environmentMainService.args['enable-smoke-test-driver']) { await this.destroyWindow(false, false); this.lifecycleMainService.quit(); // still allow for an orderly shutdown return; } // Unresponsive if (type === 1 /* WindowError.UNRESPONSIVE */) { if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) { // TODO@electron Workaround for https://github.com/microsoft/vscode/issues/56994 // In certain cases the window can report unresponsiveness because a breakpoint was hit // and the process is stopped executing. The most typical cases are: // - devtools are opened and debugging happens // - window is an extensions development host that is being debugged // - window is an extension test development host that is being debugged return; } // Show Dialog const result = await this.dialogMainService.showMessageBox({ title: this.productService.nameLong, type: 'warning', buttons: [ mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close")) ], message: localize('appStalled', "The window is not responding"), detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."), noLink: true, defaultId: 0, cancelId: 1, checkboxLabel: this._config?.workspace ? localize('doNotRestoreEditors', "Don't restore editors") : undefined }, this._win); // Handle choice if (result.response !== 1 /* keep waiting */) { const reopen = result.response === 0; await this.destroyWindow(reopen, result.checkboxChecked); } } // Process gone else if (type === 2 /* WindowError.PROCESS_GONE */) { // Windows: running as admin with AppLocker enabled is unsupported // when sandbox: true. // we cannot detect AppLocker use currently, but make a // guess based on the reason and exit code. if (isWindows && details?.reason === 'launch-failed' && details.exitCode === 18 && await this.nativeHostMainService.isAdmin(undefined)) { await this.handleWindowsAdminCrash(details); } // Any other crash: offer to restart else { let message; if (!details) { message = localize('appGone', "The window terminated unexpectedly"); } else { message = localize('appGoneDetails', "The window terminated unexpectedly (reason: '{0}', code: '{1}')", details.reason, details.exitCode ?? '<unknown>'); } // Show Dialog const result = await this.dialogMainService.showMessageBox({ title: this.productService.nameLong, type: 'warning', buttons: [ this._config?.workspace ? mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")) : mnemonicButtonLabel(localize({ key: 'newWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close")) ], message, detail: this._config?.workspace ? localize('appGoneDetailWorkspace', "We are sorry for the inconvenience. You can reopen the window to continue where you left off.") : localize('appGoneDetailEmptyWindow', "We are sorry for the inconvenience. You can open a new empty window to start again."), noLink: true, defaultId: 0, checkboxLabel: this._config?.workspace ? localize('doNotRestoreEditors', "Don't restore editors") : undefined }, this._win); // Handle choice const reopen = result.response === 0; await this.destroyWindow(reopen, result.checkboxChecked); } } break; } } async handleWindowsAdminCrash(details) { // Prepare telemetry event (TODO@bpasero remove me eventually) const appenders = []; const isInternal = isInternalTelemetry(this.productService, this.configurationService); if (supportsTelemetry(this.productService, this.environmentMainService)) { if (this.productService.aiConfig && this.productService.aiConfig.ariaKey) { appenders.push(new OneDataSystemAppender(isInternal, 'monacoworkbench', null, this.productService.aiConfig.ariaKey)); } const { installSourcePath } = this.environmentMainService; const machineId = await resolveMachineId(this.stateMainService); const config = { appenders, sendErrorTelemetry: false, commonProperties: resolveCommonProperties(this.fileService, release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, isInternal, installSourcePath), piiPaths: getPiiPathsFromEnvironment(this.environmentMainService) }; const telemetryService = new TelemetryService(config, this.configurationService, this.productService); await telemetryService.publicLog2('windowadminerror', { reason: details.reason, code: details.exitCode }); } // Inform user const result = await this.dialogMainService.showMessageBox({ title: this.productService.nameLong, type: 'error', buttons: [ mnemonicButtonLabel(localize({ key: 'learnMore', comment: ['&& denotes a mnemonic'] }, "&&Learn More")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close")) ], message: localize('appGoneAdminMessage', "Running as administrator is not supported in your environment"), detail: localize('appGoneAdminDetail', "We are sorry for the inconvenience. Please try again without administrator privileges.", this.productService.nameLong), noLink: true, defaultId: 0 }, this._win); if (result.response === 0) { await this.nativeHostMainService.openExternal(undefined, 'https://go.microsoft.com/fwlink/?linkid=2220179'); } // Ensure to await flush telemetry await Promise.all(appenders.map(appender => appender.flush())); // Exit await this.destroyWindow(false, false); } async destroyWindow(reopen, skipRestoreEditors) { const workspace = this._config?.workspace; // check to discard editor state first if (skipRestoreEditors && workspace) { try { const workspaceStorage = this.storageMainService.workspaceStorage(workspace); await workspaceStorage.init(); workspaceStorage.delete('memento/workbench.parts.editor'); await workspaceStorage.close(); } catch (error) { this.logService.error(error); } } // 'close' event will not be fired on destroy(), so signal crash via explicit event this._onDidDestroy.fire(); // make sure to destroy the window as its renderer process is gone this._win?.destroy(); // ask the windows service to open a new fresh window if specified if (reopen && this._config) { // We have to reconstruct a openable from the current workspace let uriToOpen = undefined; let forceEmpty = undefined; if (isSingleFolderWorkspaceIdentifier(workspace)) { uriToOpen = { folderUri: workspace.uri }; } else if (isWorkspaceIdentifier(workspace)) { uriToOpen = { workspaceUri: workspace.configPath }; } else { forceEmpty = true; } // Delegate to windows service const [window] = await this.windowsMainService.open({ context: 5 /* OpenContext.API */, userEnv: this._config.userEnv, cli: { ...this.environmentMainService.args, _: [] // we pass in the workspace to open explicitly via `urisToOpen` }, urisToOpen: uriToOpen ? [uriToOpen] : undefined, forceEmpty, forceNewWindow: true, remoteAuthority: this.remoteAuthority }); window.focus(); } } onDidDeleteUntitledWorkspace(workspace) { // Make sure to update our workspace config if we detect that it // was deleted if (this._config?.workspace?.id === workspace.id) { this._config.workspace = undefined; } } onConfigurationUpdated(e) { // Menubar const newMenuBarVisibility = this.getMenuBarVisibility(); if (newMenuBarVisibility !== this.currentMenuBarVisibility) { this.currentMenuBarVisibility = newMenuBarVisibility; this.setMenuBarVisibility(newMenuBarVisibility); } // Proxy let newHttpProxy = (this.configurationService.getValue('http.proxy') || '').trim() || (process.env['https_proxy'] || process.env['HTTPS_PROXY'] || process.env['http_proxy'] || process.env['HTTP_PROXY'] || '').trim() // Not standardized. || undefined; if (newHttpProxy?.endsWith('/')) { newHttpProxy = newHttpProxy.substr(0, newHttpProxy.length - 1); } const newNoProxy = (process.env['no_proxy'] || process.env['NO_PROXY'] || '').trim() || undefined; // Not standardized. if ((newHttpProxy || '').indexOf('@') === -1 && (newHttpProxy !== this.currentHttpProxy || newNoProxy !== this.currentNoProxy)) { this.currentHttpProxy = newHttpProxy; this.currentNoProxy = newNoProxy; const proxyRules = newHttpProxy || ''; const proxyBypassRules = newNoProxy ? `${newNoProxy},<local>` : '<local>'; this.logService.trace(`Setting proxy to '${proxyRules}', bypassing '${proxyBypassRules}'`); this._win.webContents.session.setProxy({ proxyRules, proxyBypassRules, pacScript: '' }); } } addTabbedWindow(window) { if (isMacintosh && window.win) { this._win.addTabbedWindow(window.win); } } load(configuration, options = Object.create(null)) { this.logService.trace(`window#load: attempt to load window (id: ${this._id})`); // Clear Document Edited if needed if (this.isDocumentEdited()) { if (!options.isReload || !this.backupMainService.isHotExitEnabled()) { this.setDocumentEdited(false); } } // Clear Title and Filename if needed if (!options.isReload) { if (this.getRepresentedFilename()) { this.setRepresentedFilename(''); } this._win.setTitle(this.productService.nameLong); } // Update configuration values based on our window context // and set it into the config object URL for usage. this.updateConfiguration(configuration, options); // If this is the first time the window is loaded, we associate the paths // directly with the window because we assume the loading will just work if (this.readyState === 0 /* ReadyState.NONE */) { this._config = configuration; } // Otherwise, the window is currently showing a folder and if there is an // unload handler preventing the load, we cannot just associate the paths // because the loading might be vetoed. Instead we associate it later when // the window load event has fired. else { this.pendingLoadConfig = configuration; } // Indicate we are navigting now this.readyState = 1 /* ReadyState.NAVIGATING */; // Load URL this._win.loadURL(FileAccess.asBrowserUri(`vs/code/electron-sandbox/workbench/workbench${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); // Remember that we did load const wasLoaded = this.wasLoaded; this.wasLoaded = true; // Make window visible if it did not open in N seconds because this indicates an error // Only do this when running out of sources and not when running tests if (!this.environmentMainService.isBuilt && !this.environmentMainService.extensionTestsLocationURI) { this._register(new RunOnceScheduler(() => { if (this._win && !this._win.isVisible() && !this._win.isMinimized()) { this._win.show(); this.focus({ force: true }); this._win.webContents.openDevTools(); } }, 10000)).schedule(); } // Event this._onWillLoad.fire({ workspace: configuration.workspace, reason: options.isReload ? 3 /* LoadReason.RELOAD */ : wasLoaded ? 2 /* LoadReason.LOAD */ : 1 /* LoadReason.INITIAL */ }); } updateConfiguration(configuration, options) { // If this window was loaded before from the command line // (as indicated by VSCODE_CLI environment), make sure to // preserve that user environment in subsequent loads, // unless the new configuration context was also a CLI // (for https://github.com/microsoft/vscode/issues/108571) // Also, preserve the environment if we're loading from an // extension development host that had its environment set // (for https://github.com/microsoft/vscode/issues/123508) const currentUserEnv = (this._config ?? this.pendingLoadConfig)?.userEnv; if (currentUserEnv) { const shouldPreserveLaunchCliEnvironment = isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(configuration.userEnv); const shouldPreserveDebugEnvironmnet = this.isExtensionDevelopmentHost; if (shouldPreserveLaunchCliEnvironment || shouldPreserveDebugEnvironmnet) { configuration.userEnv = { ...currentUserEnv, ...configuration.userEnv }; // still allow to override certain environment as passed in } } // If named pipe was instantiated for the crashpad_handler process, reuse the same // pipe for new app instances connecting to the original app instance. // Ref: https://github.com/microsoft/vscode/issues/115874 if (process.env['CHROME_CRASHPAD_PIPE_NAME']) { Object.assign(configuration.userEnv, { CHROME_CRASHPAD_PIPE_NAME: process.env['CHROME_CRASHPAD_PIPE_NAME'] }); } // Add disable-extensions to the config, but do not preserve it on currentConfig or // pendingLoadConfig so that it is applied only on this load if (options.disableExtensions !== undefined) { configuration['disable-extensions'] = options.disableExtensions; } // Update window related properties configuration.fullscreen = this.isFullScreen; configuration.maximized = this._win.isMaximized(); configuration.partsSplash = this.themeMainService.getWindowSplash(); // Update with latest perf marks mark('code/willOpenNewWindow'); configuration.perfMarks = getMarks(); // Update in config object URL for usage in renderer this.configObjectUrl.update(configuration); } async reload(cli) { // Copy our current config for reuse const configuration = Object.assign({}, this._config); // Validate workspace configuration.workspace = await this.validateWorkspaceBeforeReload(configuration); // Delete some properties we do not want during reload delete configuration.filesToOpenOrCreate; delete configuration.filesToDiff; delete configuration.filesToMerge; delete configuration.filesToWait; // Some configuration things get inherited if the window is being reloaded and we are // in extension development mode. These options are all development related. if (this.isExtensionDevelopmentHost && cli) { configuration.verbose = cli.verbose; configuration.debugId = cli.debugId; configuration.extensionEnvironment = cli.extensionEnvironment; configuration['inspect-extensions'] = cli['inspect-extensions']; configuration['inspect-brk-extensions'] = cli['inspect-brk-extensions']; configuration['extensions-dir'] = cli['extensions-dir']; } configuration.accessibilitySupport = app.isAccessibilitySupportEnabled(); configuration.isInitialStartup = false; // since this is a reload configuration.policiesData = this.policyService.serialize(); // set policies data again configuration.continueOn = this.environmentMainService.continueOn; configuration.profiles = { all: this.userDataProfilesService.profiles, profile: this.profile || this.userDataProfilesService.defaultProfile }; configuration.logLevel = this.logService.getLevel(); // Load config this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] }); } async validateWorkspaceBeforeReload(configuration) { // Multi folder if (isWorkspaceIdentifier(configuration.workspace)) { const configPath = configuration.workspace.configPath; if (configPath.scheme === Schemas.file) { const workspaceExists = await this.fileService.exists(configPath); if (!workspaceExists) { return undefined; } } } // Single folder else if (isSingleFolderWorkspaceIdentifier(configuration.workspace)) { const uri = configuration.workspace.uri; if (uri.scheme === Schemas.file) { const folderExists = await this.fileService.exists(uri); if (!folderExists) { return undefined; } } } // Workspace is valid return configuration.workspace; } serializeWindowState() { if (!this._win) { return defaultWindowState(); } // fullscreen gets special treatment if (this.isFullScreen) { let display; try { display = screen.getDisplayMatching(this.getBounds()); } catch (error) { // Electron has weird conditions under which it throws errors // e.g. https://github.com/microsoft/vscode/issues/100334 when // large numbers are passed in } const defaultState = defaultWindowState(); const res = { mode: 3 /* WindowMode.Fullscreen */, display: display ? display.id : undefined, // Still carry over window dimensions from previous sessions /