sussudio
Version:
An unofficial VS Code Internal API
928 lines • 70.2 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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
/