chore(vscode): update to 1.56.0

This commit is contained in:
Akash Satheesan
2021-04-30 20:25:17 +05:30
1749 changed files with 88014 additions and 43316 deletions

View File

@@ -3,13 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isMacintosh, isLinux, isWeb, IProcessEnvironment, isNative } from 'vs/base/common/platform';
import { isMacintosh, isLinux, isWeb, isNative } from 'vs/base/common/platform';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { LogLevel } from 'vs/platform/log/common/log';
import { PerformanceMark } from 'vs/base/common/performance';
import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes';
export const WindowMinimumSize = {
WIDTH: 400,
@@ -218,8 +219,6 @@ export interface IColorScheme {
}
export interface IWindowConfiguration {
sessionId: string;
remoteAuthority?: string;
colorScheme: IColorScheme;
@@ -231,32 +230,34 @@ export interface IWindowConfiguration {
export interface IOSConfiguration {
readonly release: string;
readonly hostname: string;
}
export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs {
export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs, ISandboxConfiguration {
mainPid: number;
windowId: number;
machineId: string;
appRoot: string;
execPath: string;
backupPath?: string;
nodeCachedDataDir?: string;
homeDir: string;
tmpDir: string;
userDataDir: string;
partsSplashPath: string;
workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier;
isInitialStartup?: boolean;
logLevel: LogLevel;
zoomLevel?: number;
fullscreen?: boolean;
maximized?: boolean;
accessibilitySupport?: boolean;
perfMarks: PerformanceMark[];
userEnv: IProcessEnvironment;
filesToWait?: IPathsToWaitFor;
os: IOSConfiguration;

View File

@@ -3,17 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { release } from 'os';
import { join } from 'vs/base/common/path';
import { localize } from 'vs/nls';
import { getMarks, mark } from 'vs/base/common/performance';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, RenderProcessGoneDetails } from 'electron';
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, Event, RenderProcessGoneDetails, WebFrameMain } from 'electron';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IProductService } from 'vs/platform/product/common/productService';
import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
@@ -32,11 +30,12 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IStorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
import { ByteSize, IFileService } from 'vs/platform/files/common/files';
import { IFileService } from 'vs/platform/files/common/files';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { CancellationToken } from 'vs/base/common/cancellation';
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol';
export interface IWindowCreationOptions {
state: IWindowState;
@@ -48,6 +47,11 @@ interface ITouchBarSegment extends SegmentedControlSegment {
id: string;
}
interface ILoadOptions {
isReload?: boolean;
disableExtensions?: boolean;
}
const enum ReadyState {
/**
@@ -73,7 +77,7 @@ const enum ReadyState {
export class CodeWindow extends Disposable implements ICodeWindow {
private static readonly MAX_URL_LENGTH = 2 * ByteSize.MB; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
//#region Events
private readonly _onWillLoad = this._register(new Emitter<ILoadEvent>());
readonly onWillLoad = this._onWillLoad.event;
@@ -87,6 +91,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private readonly _onDidDestroy = this._register(new Emitter<void>());
readonly onDidDestroy = this._onDidDestroy.event;
//#endregion
private hiddenTitleBarStyle: boolean | undefined;
private showTimeoutHandle: NodeJS.Timeout | undefined;
private windowState: IWindowState;
@@ -104,6 +110,36 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private currentHttpProxy: string | undefined = undefined;
private currentNoProxy: string | undefined = undefined;
private _id: number;
get id(): number { return this._id; }
private _win: BrowserWindow;
get win(): BrowserWindow | null { return this._win; }
private _lastFocusTime = -1;
get lastFocusTime(): number { return this._lastFocusTime; }
get backupPath(): string | undefined { return this.currentConfig?.backupPath; }
get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this.currentConfig?.workspace; }
get remoteAuthority(): string | undefined { return this.currentConfig?.remoteAuthority; }
private pendingLoadConfig: INativeWindowConfiguration | undefined;
private currentConfig: INativeWindowConfiguration | undefined;
get config(): INativeWindowConfiguration | undefined { return this.currentConfig; }
private readonly configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl<INativeWindowConfiguration>());
get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; }
get isExtensionDevelopmentHost(): boolean { return !!(this.currentConfig?.extensionDevelopmentPath); }
get isExtensionTestHost(): boolean { return !!(this.currentConfig?.extensionTestsPath); }
get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.currentConfig?.debugId; }
constructor(
config: IWindowCreationOptions,
@ILogService private readonly logService: ILogService,
@@ -118,7 +154,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
@IDialogMainService private readonly dialogMainService: IDialogMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
@IProductService private readonly productService: IProductService
@IProductService private readonly productService: IProductService,
@IProtocolMainService private readonly protocolMainService: IProtocolMainService
) {
super();
@@ -132,7 +169,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen);
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
const windowSettings = this.configurationService.getValue<IWindowSettings | undefined>('window');
const options: BrowserWindowConstructorOptions = {
width: this.windowState.width,
@@ -146,30 +183,35 @@ export class CodeWindow extends Disposable implements ICodeWindow {
title: this.productService.nameLong,
webPreferences: {
preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath,
additionalArguments: this.environmentMainService.sandbox ?
[`--vscode-window-config=${this.configObjectUrl.resource.toString()}`, '--context-isolation' /* TODO@bpasero: Use process.contextIsolateed when 13-x-y is adopted (https://github.com/electron/electron/pull/28030) */] :
[`--vscode-window-config=${this.configObjectUrl.resource.toString()}`],
v8CacheOptions: browserCodeLoadingCacheStrategy,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
nativeWindowOpen: true,
webviewTag: true,
zoomFactor: zoomLevelToZoomFactor(windowConfig?.zoomLevel),
zoomFactor: zoomLevelToZoomFactor(windowSettings?.zoomLevel),
...this.environmentMainService.sandbox ?
// Sandbox
{
sandbox: true,
contextIsolation: true
sandbox: true
} :
// No Sandbox
{
nodeIntegration: true
nodeIntegration: true,
contextIsolation: false
}
}
};
if (browserCodeLoadingCacheStrategy) {
this.logService.info(`window#ctor: using vscode-file protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`);
this.logService.info(`window#ctor: using vscode-file:// protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`);
} else {
this.logService.trace(`window#ctor: vscode-file:// protocol is explicitly disabled`);
}
// Apply icon to window
@@ -188,12 +230,12 @@ export class CodeWindow extends Disposable implements ICodeWindow {
if (isMacintosh) {
options.acceptFirstMouse = true; // enabled by default
if (windowConfig?.clickThroughInactive === false) {
if (windowSettings?.clickThroughInactive === false) {
options.acceptFirstMouse = false;
}
}
const useNativeTabs = isMacintosh && windowConfig?.nativeTabs === true;
const useNativeTabs = isMacintosh && windowSettings?.nativeTabs === true;
if (useNativeTabs) {
options.tabbingIdentifier = this.productService.nameShort; // this opts in to sierra tabs
}
@@ -207,8 +249,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
// Create the browser window.
// Create the browser window
mark('code/willCreateCodeBrowserWindow');
this._win = new BrowserWindow(options);
mark('code/didCreateCodeBrowserWindow');
this._id = this._win.id;
// Open devtools if instructed from command line args
@@ -240,6 +285,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
if (isFullscreenOrMaximized) {
mark('code/willMaximizeCodeWindow');
this._win.maximize();
if (this.windowState.mode === WindowMode.Fullscreen) {
@@ -249,6 +295,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
if (!this._win.isVisible()) {
this._win.show(); // to reduce flicker from the default window size to maximize, we only show after maximize
}
mark('code/didMaximizeCodeWindow');
}
this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too
@@ -271,25 +318,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.registerListeners();
}
private pendingLoadConfig: INativeWindowConfiguration | undefined;
private currentConfig: INativeWindowConfiguration | undefined;
get config(): INativeWindowConfiguration | undefined { return this.currentConfig; }
private _id: number;
get id(): number { return this._id; }
private _win: BrowserWindow;
get win(): BrowserWindow | null { return this._win; }
get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; }
get isExtensionDevelopmentHost(): boolean { return !!(this.currentConfig?.extensionDevelopmentPath); }
get isExtensionTestHost(): boolean { return !!(this.currentConfig?.extensionTestsPath); }
get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.currentConfig?.debugId; }
setRepresentedFilename(filename: string): void {
if (isMacintosh) {
this._win.setRepresentedFilename(filename);
@@ -345,15 +373,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._win.focus();
}
private _lastFocusTime = -1;
get lastFocusTime(): number { return this._lastFocusTime; }
get backupPath(): string | undefined { return this.currentConfig?.backupPath; }
get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this.currentConfig?.workspace; }
get remoteAuthority(): string | undefined { return this.currentConfig?.remoteAuthority; }
private readyState = ReadyState.NONE;
setReady(): void {
@@ -400,7 +419,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private registerListeners(): void {
// Crashes & Unrsponsive & Failed to load
// Crashes & Unresponsive & Failed to load
this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE));
this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details));
this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.onWindowError(WindowError.LOAD, errorDescription));
@@ -413,15 +432,25 @@ export class CodeWindow extends Disposable implements ICodeWindow {
});
// Block all SVG requests from unsupported origins
const svgFileSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, 'devtools']);
const supportedSvgSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, 'devtools']); // TODO: handle webview origin
// But allow them if the are made from inside an webview
const isSafeFrame = (requestFrame: WebFrameMain | undefined): boolean => {
for (let frame: WebFrameMain | null | undefined = requestFrame; frame; frame = frame.parent) {
if (frame.url.startsWith(`${Schemas.vscodeWebview}://`)) {
return true;
}
}
return false;
};
this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => {
const uri = URI.parse(details.url);
// Prevent loading of remote svgs
if (uri.path.endsWith('.svg')) {
const safeScheme = svgFileSchemes.has(uri.scheme) || uri.path.includes(Schemas.vscodeRemoteResource);
if (!safeScheme) {
return callback({ cancel: true });
const isSafeResourceUrl = supportedSvgSchemes.has(uri.scheme) || uri.path.includes(Schemas.vscodeRemoteResource);
if (!isSafeResourceUrl) {
const isSafeContext = isSafeFrame(details.frame);
return callback({ cancel: !isSafeContext });
}
}
@@ -429,17 +458,15 @@ export class CodeWindow extends Disposable implements ICodeWindow {
});
// Configure SVG header content type properly
// https://github.com/microsoft/vscode/issues/97564
this._win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
const responseHeaders = details.responseHeaders as Record<string, (string) | (string[])>;
const contentTypes = (responseHeaders['content-type'] || responseHeaders['Content-Type']);
if (contentTypes && Array.isArray(contentTypes)) {
const uri = URI.parse(details.url);
// https://github.com/microsoft/vscode/issues/97564
// ensure local svg files have Content-Type image/svg+xml
if (uri.path.endsWith('.svg')) {
if (svgFileSchemes.has(uri.scheme)) {
if (supportedSvgSchemes.has(uri.scheme)) {
responseHeaders['Content-Type'] = ['image/svg+xml'];
return callback({ cancel: false, responseHeaders });
@@ -449,7 +476,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// remote extension schemes have the following format
// http://127.0.0.1:<port>/vscode-remote-resource?path=
if (!uri.path.includes(Schemas.vscodeRemoteResource) && contentTypes.some(contentType => contentType.toLowerCase().includes('image/svg'))) {
return callback({ cancel: true });
const isSafeContext = isSafeFrame(details.frame);
return callback({ cancel: !isSafeContext });
}
}
@@ -680,58 +708,17 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
load(config: INativeWindowConfiguration, { isReload, disableExtensions }: { isReload?: boolean, disableExtensions?: boolean } = Object.create(null)): void {
// 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)
const currentUserEnv = (this.currentConfig ?? this.pendingLoadConfig)?.userEnv;
if (currentUserEnv && isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(config.userEnv)) {
config.userEnv = { ...currentUserEnv, ...config.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(config.userEnv, {
CHROME_CRASHPAD_PIPE_NAME: process.env['CHROME_CRASHPAD_PIPE_NAME']
});
}
// 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 === ReadyState.NONE) {
this.currentConfig = config;
}
// 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 = config;
this.readyState = ReadyState.NAVIGATING;
}
// Add disable-extensions to the config, but do not preserve it on currentConfig or
// pendingLoadConfig so that it is applied only on this load
const configuration = { ...config };
if (disableExtensions !== undefined) {
configuration['disable-extensions'] = disableExtensions;
}
load(configuration: INativeWindowConfiguration, options: ILoadOptions = Object.create(null)): void {
// Clear Document Edited if needed
if (this.isDocumentEdited()) {
if (!isReload || !this.backupMainService.isHotExitEnabled()) {
if (!options.isReload || !this.backupMainService.isHotExitEnabled()) {
this.setDocumentEdited(false);
}
}
// Clear Title and Filename if needed
if (!isReload) {
if (!options.isReload) {
if (this.getRepresentedFilename()) {
this.setRepresentedFilename('');
}
@@ -739,9 +726,30 @@ export class CodeWindow extends Disposable implements ICodeWindow {
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 === ReadyState.NONE) {
this.currentConfig = 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;
this.readyState = ReadyState.NAVIGATING;
}
// Load URL
mark('code/willOpenNewWindow');
this._win.loadURL(this.getUrl(configuration));
this._win.loadURL(FileAccess.asBrowserUri(this.environmentMainService.sandbox ?
'vs/code/electron-sandbox/workbench/workbench.html' :
'vs/code/electron-browser/workbench/workbench.html', require
).toString(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
@@ -759,6 +767,45 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._onWillLoad.fire({ workspace: configuration.workspace });
}
private updateConfiguration(configuration: INativeWindowConfiguration, options: ILoadOptions): void {
// 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)
const currentUserEnv = (this.currentConfig ?? this.pendingLoadConfig)?.userEnv;
if (currentUserEnv && isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(configuration.userEnv)) {
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();
// 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?: NativeParsedArgs): Promise<void> {
// Copy our current config for reuse
@@ -816,87 +863,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return configuration.workspace;
}
private getUrl(windowConfiguration: INativeWindowConfiguration): string {
// Set window ID
windowConfiguration.windowId = this._win.id;
windowConfiguration.sessionId = `window:${this._win.id}`;
windowConfiguration.logLevel = this.logService.getLevel();
windowConfiguration.logsPath = this.environmentMainService.logsPath;
// Set zoomlevel
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
const zoomLevel = windowConfig?.zoomLevel;
if (typeof zoomLevel === 'number') {
windowConfiguration.zoomLevel = zoomLevel;
}
// Set fullscreen state
windowConfiguration.fullscreen = this.isFullScreen;
// Set Accessibility Config
windowConfiguration.colorScheme = {
dark: nativeTheme.shouldUseDarkColors,
highContrast: nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors
};
windowConfiguration.autoDetectHighContrast = windowConfig?.autoDetectHighContrast ?? true;
windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled;
// Title style related
windowConfiguration.maximized = this._win.isMaximized();
// Dump Perf Counters
windowConfiguration.perfMarks = getMarks();
// Parts splash
windowConfiguration.partsSplashPath = join(this.environmentMainService.userDataPath, 'rapid_render.json');
// OS Info
windowConfiguration.os = {
release: release()
};
// Config (combination of process.argv and window configuration)
const environment = parseArgs(process.argv, OPTIONS);
const config = Object.assign(environment, windowConfiguration) as unknown as { [key: string]: unknown };
for (const key in config) {
const configValue = config[key];
if (configValue === undefined || configValue === null || configValue === '' || configValue === false) {
delete config[key]; // only send over properties that have a true value
}
}
// In the unlikely event of the URL becoming larger than 2MB, remove parts of
// it that are not under our control. Mainly, the user environment can be very
// large depending on user configuration, so we can only remove it in that case.
let configUrl = this.doGetUrl(config);
if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.');
configUrl = this.doGetUrl({ ...config, userEnv: undefined });
if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.');
}
}
return configUrl;
}
private doGetUrl(config: object): string {
let workbench: string;
if (this.environmentMainService.sandbox) {
workbench = 'vs/code/electron-sandbox/workbench/workbench.html';
} else {
workbench = 'vs/code/electron-browser/workbench/workbench.html';
}
return FileAccess
.asBrowserUri(workbench, require)
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
.toString(true);
}
serializeWindowState(): IWindowState {
if (!this._win) {
return defaultWindowState();
@@ -969,6 +935,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
private restoreWindowState(state?: IWindowState): [IWindowState, boolean? /* has multiple displays */] {
mark('code/willRestoreCodeWindowState');
let hasMultipleDisplays = false;
if (state) {
try {
@@ -981,6 +949,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
mark('code/didRestoreCodeWindowState');
return [state || defaultWindowState(), hasMultipleDisplays];
}
@@ -1382,7 +1352,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return segments;
}
dispose(): void {
override dispose(): void {
super.dispose();
if (this.showTimeoutHandle) {

View File

@@ -4,6 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { statSync } from 'fs';
import { release, hostname } from 'os';
import product from 'vs/platform/product/common/product';
import { mark, getMarks } from 'vs/base/common/performance';
import { basename, normalize, join, posix } from 'vs/base/common/path';
import { localize } from 'vs/nls';
import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays';
@@ -13,13 +16,13 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IStateService } from 'vs/platform/state/node/state';
import { CodeWindow } from 'vs/platform/windows/electron-main/window';
import { BrowserWindow, MessageBoxOptions, WebContents } from 'electron';
import { ILifecycleMainService, UnloadReason, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { app, BrowserWindow, MessageBoxOptions, nativeTheme, WebContents } from 'electron';
import { ILifecycleMainService, UnloadReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest, IPathsToWaitFor, INativeWindowConfiguration, INativeOpenFileRequest } from 'vs/platform/windows/common/windows';
import { findWindowOnFile, findWindowOnWorkspaceOrFolder, findWindowOnExtensionDevelopmentPath } from 'vs/platform/windows/electron-main/windowsFinder';
import { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IProductService } from 'vs/platform/product/common/productService';
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IOpenEmptyConfiguration, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
@@ -33,7 +36,7 @@ import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { IWindowState, WindowsStateHandler } from 'vs/platform/windows/electron-main/windowsStateHandler';
import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier, IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
import { once } from 'vs/base/common/functional';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
import { isWindowsDriveLetter, toSlashes, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath';
@@ -42,6 +45,7 @@ import { getPathLabel } from 'vs/base/common/labels';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IFileService } from 'vs/platform/files/common/files';
import { cwd } from 'vs/base/common/process';
import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol';
//#region Helper Interfaces
@@ -151,17 +155,38 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IDialogMainService private readonly dialogMainService: IDialogMainService,
@IFileService private readonly fileService: IFileService,
@IProductService private readonly productService: IProductService
@IProductService private readonly productService: IProductService,
@IProtocolMainService private readonly protocolMainService: IProtocolMainService
) {
super();
this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
this.registerListeners();
}
private registerListeners(): void {
// Signal a window is ready after having entered a workspace
this._register(this.workspacesManagementMainService.onDidEnterWorkspace(event => this._onDidSignalReadyWindow.fire(event.window)));
// Update valid roots in protocol service for extension dev windows
this._register(this.onDidSignalReadyWindow(window => {
if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) {
const disposables = new DisposableStore();
disposables.add(Event.any(window.onDidClose, window.onDidDestroy)(() => disposables.dispose()));
// Allow access to extension development path
if (window.config.extensionDevelopmentPath) {
for (const extensionDevelopmentPath of window.config.extensionDevelopmentPath) {
disposables.add(this.protocolMainService.addValidFileRoot(URI.file(extensionDevelopmentPath)));
}
}
// Allow access to extension tests path
if (window.config.extensionTestsPath) {
disposables.add(this.protocolMainService.addValidFileRoot(URI.file(window.config.extensionTestsPath)));
}
}
}));
}
openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[] {
@@ -316,7 +341,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// process can continue. We do this by deleting the waitMarkerFilePath.
const waitMarkerFileURI = openConfig.waitMarkerFileURI;
if (openConfig.context === OpenContext.CLI && waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) {
usedWindows[0].whenClosedOrLoaded.then(() => this.fileService.del(waitMarkerFileURI), () => undefined);
(async () => {
await usedWindows[0].whenClosedOrLoaded;
try {
await this.fileService.del(waitMarkerFileURI);
} catch (error) {
// ignore - could have been deleted from the window already
}
})();
}
return usedWindows;
@@ -719,7 +752,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// folder or file paths
const cliPaths = cli._;
for (const cliPath of cliPaths) {
const path = cli.remote ? this.doResolvePathRemote(cliPath, cli.remote) : this.doResolveFilePath(cliPath, pathResolveOptions);
const path = pathResolveOptions.remoteAuthority ? this.doResolvePathRemote(cliPath, pathResolveOptions) : this.doResolveFilePath(cliPath, pathResolveOptions);
if (path) {
pathsToOpen.push(path);
}
@@ -878,11 +911,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
let columnNumber: number | undefined;
if (options.gotoLineMode) {
const parsedPath = parseLineAndColumnAware(path);
lineNumber = parsedPath.line;
columnNumber = parsedPath.column;
path = parsedPath.path;
({ path, line: lineNumber, column: columnNumber } = parseLineAndColumnAware(path));
}
// Ensure the path is normalized and absolute
@@ -925,8 +954,17 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return undefined;
}
private doResolvePathRemote(path: string, remoteAuthority: string, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined {
private doResolvePathRemote(path: string, options: IPathResolveOptions): IPathToOpen | undefined {
const first = path.charCodeAt(0);
const remoteAuthority = options.remoteAuthority;
// Extract line/col information from path
let lineNumber: number | undefined;
let columnNumber: number | undefined;
if (options.gotoLineMode) {
({ path, line: lineNumber, column: columnNumber } = parseLineAndColumnAware(path));
}
// make absolute
if (first !== CharCode.Slash) {
@@ -941,21 +979,21 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// guess the file type:
// - if it ends with a slash it's a folder
// - if it has a file extension, it's a file or a workspace
// - if in goto line mode or if it has a file extension, it's a file or a workspace
// - by defaults it's a folder
if (path.charCodeAt(path.length - 1) !== CharCode.Slash) {
// file name ends with .code-workspace
if (hasWorkspaceFileExtension(path)) {
if (forceOpenWorkspaceAsFile) {
return { fileUri: uri, remoteAuthority };
if (options.forceOpenWorkspaceAsFile) {
return { fileUri: uri, lineNumber, columnNumber, remoteAuthority: options.remoteAuthority };
}
return { workspace: getWorkspaceIdentifier(uri), remoteAuthority };
}
// file name starts with a dot or has an file extension
else if (posix.basename(path).indexOf('.') !== -1) {
return { fileUri: uri, remoteAuthority };
else if (options.gotoLineMode || posix.basename(path).indexOf('.') !== -1) {
return { fileUri: uri, lineNumber, columnNumber, remoteAuthority };
}
}
@@ -1112,33 +1150,60 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
// Build `INativeWindowConfiguration` from config and options
const configuration = { ...options.cli } as INativeWindowConfiguration;
configuration.appRoot = this.environmentMainService.appRoot;
configuration.machineId = this.machineId;
configuration.nodeCachedDataDir = this.environmentMainService.nodeCachedDataDir;
configuration.mainPid = process.pid;
configuration.execPath = process.execPath;
configuration.userEnv = { ...this.initialUserEnv, ...options.userEnv };
configuration.isInitialStartup = options.initialStartup;
configuration.workspace = options.workspace;
configuration.remoteAuthority = options.remoteAuthority;
// Build up the window configuration from provided options, config and environment
const configuration: INativeWindowConfiguration = {
const filesToOpen = options.filesToOpen;
if (filesToOpen) {
configuration.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate;
configuration.filesToDiff = filesToOpen.filesToDiff;
configuration.filesToWait = filesToOpen.filesToWait;
}
// Inherit CLI arguments from environment and/or
// the specific properties from this launch if provided
...this.environmentMainService.args,
...options.cli,
// if we know the backup folder upfront (for empty windows to restore), we can set it
// directly here which helps for restoring UI state associated with that window.
// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
// loading the window.
if (options.emptyWindowBackupInfo) {
configuration.backupPath = join(this.environmentMainService.backupHome, options.emptyWindowBackupInfo.backupFolder);
}
machineId: this.machineId,
windowId: -1, // Will be filled in by the window once loaded later
mainPid: process.pid,
appRoot: this.environmentMainService.appRoot,
execPath: process.execPath,
nodeCachedDataDir: this.environmentMainService.nodeCachedDataDir,
partsSplashPath: join(this.environmentMainService.userDataPath, 'rapid_render.json'),
// If we know the backup folder upfront (for empty windows to restore), we can set it
// directly here which helps for restoring UI state associated with that window.
// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
// loading the window.
backupPath: options.emptyWindowBackupInfo ? join(this.environmentMainService.backupHome, options.emptyWindowBackupInfo.backupFolder) : undefined,
homeDir: this.environmentMainService.userHome.fsPath,
tmpDir: this.environmentMainService.tmpDir.fsPath,
userDataDir: this.environmentMainService.userDataPath,
remoteAuthority: options.remoteAuthority,
workspace: options.workspace,
userEnv: { ...this.initialUserEnv, ...options.userEnv },
filesToOpenOrCreate: options.filesToOpen?.filesToOpenOrCreate,
filesToDiff: options.filesToOpen?.filesToDiff,
filesToWait: options.filesToOpen?.filesToWait,
logLevel: this.logService.getLevel(),
logsPath: this.environmentMainService.logsPath,
product,
isInitialStartup: options.initialStartup,
perfMarks: getMarks(),
os: { release: release(), hostname: hostname() },
zoomLevel: typeof windowConfig?.zoomLevel === 'number' ? windowConfig.zoomLevel : undefined,
autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true,
accessibilitySupport: app.accessibilitySupportEnabled,
colorScheme: {
dark: nativeTheme.shouldUseDarkColors,
highContrast: nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors
}
};
let window: ICodeWindow | undefined;
if (!options.forceNewWindow && !options.forceNewTabbedWindow) {
@@ -1153,11 +1218,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
const state = this.windowsStateHandler.getNewWindowState(configuration);
// Create the window
mark('code/willCreateCodeWindow');
const createdWindow = window = this.instantiationService.createInstance(CodeWindow, {
state,
extensionDevelopmentPath: configuration.extensionDevelopmentPath,
isExtensionTestHost: !!configuration.extensionTestsPath
});
mark('code/didCreateCodeWindow');
// Add as window tab if configured (macOS only)
if (options.forceNewTabbedWindow) {
@@ -1205,6 +1272,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
}
// Update window identifier and session now
// that we have the window object in hand.
configuration.windowId = window.id;
// If the window was already loaded, make sure to unload it
// first and only load the new configuration if that was
// not vetoed