Merge from vscode e3c4990c67c40213af168300d1cfeb71d680f877 (#16569)

This commit is contained in:
Cory Rivera
2021-08-25 16:28:29 -07:00
committed by GitHub
parent ab1112bfb3
commit cb7b7da0a4
1752 changed files with 59525 additions and 33878 deletions

View File

@@ -5,7 +5,7 @@
import { release, hostname } from 'os';
import { statSync } from 'fs';
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron';
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session, Session } from 'electron';
import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform';
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
@@ -23,7 +23,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IOpenURLOptions, IURLService } from 'vs/platform/url/common/url';
@@ -71,7 +71,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService';
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { IFileService } from 'vs/platform/files/common/files';
import { stripComments } from 'vs/base/common/json';
import { generateUuid } from 'vs/base/common/uuid';
import { VSBuffer } from 'vs/base/common/buffer';
@@ -81,12 +81,14 @@ import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platfo
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { isEqualOrParent } from 'vs/base/common/extpath';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
import { once } from 'vs/base/common/functional';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { ISignService } from 'vs/platform/sign/common/sign';
import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal';
import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService';
/**
* The main VS Code application. There will only ever be one instance,
@@ -105,15 +107,66 @@ export class CodeApplication extends Disposable {
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStateService private readonly stateService: IStateService,
@IStateMainService private readonly stateMainService: IStateMainService,
@IFileService private readonly fileService: IFileService,
@IProductService private readonly productService: IProductService
) {
super();
this.configureSession();
this.registerListeners();
}
private configureSession(): void {
//#region Security related measures (https://electronjs.org/docs/tutorial/security)
//
// !!! DO NOT CHANGE without consulting the documentation !!!
//
const isUrlFromWebview = (requestingUrl: string) => requestingUrl.startsWith(`${Schemas.vscodeWebview}://`);
session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return callback(permission === 'clipboard-read');
}
return callback(false);
});
session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return permission === 'clipboard-read';
}
return false;
});
//#endregion
//#region Code Cache
type SessionWithCodeCachePathSupport = typeof Session & {
/**
* Sets code cache directory. By default, the directory will be `Code Cache` under
* the respective user data folder.
*/
setCodeCachePath?(path: string): void;
};
const defaultSession = session.defaultSession as unknown as SessionWithCodeCachePathSupport;
if (typeof defaultSession.setCodeCachePath === 'function' && this.environmentMainService.codeCachePath) {
// Make sure to partition Chrome's code cache folder
// in the same way as our code cache path to help
// invalidate caches that we know are invalid
// (https://github.com/microsoft/vscode/issues/120655)
defaultSession.setCodeCachePath(join(this.environmentMainService.codeCachePath, 'chrome'));
}
//#endregion
}
private registerListeners(): void {
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
@@ -146,37 +199,6 @@ export class CodeApplication extends Disposable {
//
// !!! DO NOT CHANGE without consulting the documentation !!!
//
app.on('remote-require', (event, sender, module) => {
this.logService.trace('app#on(remote-require): prevented');
event.preventDefault();
});
app.on('remote-get-global', (event, sender, module) => {
this.logService.trace(`app#on(remote-get-global): prevented on ${module}`);
event.preventDefault();
});
app.on('remote-get-builtin', (event, sender, module) => {
this.logService.trace(`app#on(remote-get-builtin): prevented on ${module}`);
if (module !== 'clipboard') {
event.preventDefault();
}
});
app.on('remote-get-current-window', event => {
this.logService.trace(`app#on(remote-get-current-window): prevented`);
event.preventDefault();
});
app.on('remote-get-current-web-contents', event => {
if (this.environmentMainService.args.driver) {
return; // the driver needs access to web contents
}
this.logService.trace(`app#on(remote-get-current-web-contents): prevented`);
event.preventDefault();
});
app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
@@ -220,34 +242,17 @@ export class CodeApplication extends Disposable {
event.preventDefault();
});
contents.on('new-window', (event, url) => {
event.preventDefault(); // prevent code that wants to open links
contents.setWindowOpenHandler(({ url }) => {
this.nativeHostMainService?.openExternal(undefined, url);
});
const isUrlFromWebview = (requestingUrl: string) =>
requestingUrl.startsWith(`${Schemas.vscodeWebview}://`);
session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return callback(permission === 'clipboard-read');
}
return callback(false);
});
session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return permission === 'clipboard-read';
}
return false;
return { action: 'deny' };
});
});
//#endregion
let macOpenFileURIs: IWindowOpenable[] = [];
let runningTimeout: NodeJS.Timeout | null = null;
let runningTimeout: NodeJS.Timeout | undefined = undefined;
app.on('open-file', (event, path) => {
this.logService.trace('app#open-file: ', path);
event.preventDefault();
@@ -256,9 +261,9 @@ export class CodeApplication extends Disposable {
macOpenFileURIs.push(this.getWindowOpenableFromPathSync(path));
// Clear previous handler if any
if (runningTimeout !== null) {
if (runningTimeout !== undefined) {
clearTimeout(runningTimeout);
runningTimeout = null;
runningTimeout = undefined;
}
// Handle paths delayed in case more are coming!
@@ -272,7 +277,7 @@ export class CodeApplication extends Disposable {
});
macOpenFileURIs = [];
runningTimeout = null;
runningTimeout = undefined;
}, 100);
});
@@ -282,71 +287,29 @@ export class CodeApplication extends Disposable {
//#region Bootstrap IPC Handlers
let slowShellResolveWarningShown = false;
ipcMain.handle('vscode:fetchShellEnv', event => {
return new Promise(async resolve => {
// DO NOT remove: not only usual windows are fetching the
// shell environment but also shared process, issue reporter
// etc, so we need to reply via `webContents` always
const webContents = event.sender;
// Prefer to use the args and env from the target window
// when resolving the shell env. It is possible that
// a first window was opened from the UI but a second
// from the CLI and that has implications for whether to
// resolve the shell environment or not.
//
// Window can be undefined for e.g. the shared process
// that is not part of our windows registry!
const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process
let args: NativeParsedArgs;
let env: IProcessEnvironment;
if (window?.config) {
args = window.config;
env = { ...process.env, ...window.config.userEnv };
} else {
args = this.environmentMainService.args;
env = process.env;
}
let replied = false;
function acceptShellEnv(env: IProcessEnvironment): void {
clearTimeout(shellEnvSlowWarningHandle);
clearTimeout(shellEnvTimeoutErrorHandle);
if (!replied) {
replied = true;
if (!webContents.isDestroyed()) {
resolve(env);
}
}
}
// Handle slow shell environment resolve calls:
// - a warning after 3s but continue to resolve (only once in active window)
// - an error after 10s and stop trying to resolve (in every window where this happens)
const cts = new CancellationTokenSource();
const shellEnvSlowWarningHandle = setTimeout(() => {
if (!slowShellResolveWarningShown) {
this.windowsMainService?.sendToFocused('vscode:showShellEnvSlowWarning', cts.token);
slowShellResolveWarningShown = true;
}
}, 3000);
const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process!!
const shellEnvTimeoutErrorHandle = setTimeout(() => {
cts.dispose(true);
window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None);
acceptShellEnv({});
}, 10000);
// Prefer to use the args and env from the target window
// when resolving the shell env. It is possible that
// a first window was opened from the UI but a second
// from the CLI and that has implications for whether to
// resolve the shell environment or not.
//
// Window can be undefined for e.g. the shared process
// that is not part of our windows registry!
let args: NativeParsedArgs;
let env: IProcessEnvironment;
if (window?.config) {
args = window.config;
env = { ...process.env, ...window.config.userEnv };
} else {
args = this.environmentMainService.args;
env = process.env;
}
// Resolve shell env
const shellEnv = await resolveShellEnv(this.logService, args, env);
acceptShellEnv(shellEnv);
});
// Resolve shell env
return resolveShellEnv(this.logService, args, env);
});
ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => {
@@ -419,18 +382,6 @@ export class CodeApplication extends Disposable {
this.logService.debug(`from: ${this.environmentMainService.appRoot}`);
this.logService.debug('args:', this.environmentMainService.args);
// TODO@bpasero TODO@deepak1556 workaround for #120655
try {
const cachedDataPath = URI.file(this.environmentMainService.chromeCachedDataDir);
this.logService.trace(`Deleting Chrome cached data path: ${cachedDataPath.fsPath}`);
await this.fileService.del(cachedDataPath, { recursive: true });
} catch (error) {
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
this.logService.error(error);
}
}
// Make sure we associate the program with the app user model id
// This will help Windows to associate the running program with
// any shortcut that is pinned to the taskbar and prevent showing
@@ -498,11 +449,11 @@ export class CodeApplication extends Disposable {
// We cache the machineId for faster lookups on startup
// and resolve it only once initially if not cached or we need to replace the macOS iBridge device
let machineId = this.stateService.getItem<string>(machineIdKey);
let machineId = this.stateMainService.getItem<string>(machineIdKey);
if (!machineId || (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead')) {
machineId = await getMachineId();
this.stateService.setItem(machineIdKey, machineId);
this.stateMainService.setItem(machineIdKey, machineId);
}
return machineId;
@@ -593,6 +544,15 @@ export class CodeApplication extends Disposable {
// Storage
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
// External terminal
if (isWindows) {
services.set(IExternalTerminalMainService, new SyncDescriptor(WindowsExternalTerminalService));
} else if (isMacintosh) {
services.set(IExternalTerminalMainService, new SyncDescriptor(MacExternalTerminalService));
} else if (isLinux) {
services.set(IExternalTerminalMainService, new SyncDescriptor(LinuxExternalTerminalService));
}
// Backups
const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService);
services.set(IBackupMainService, backupMainService);
@@ -679,6 +639,10 @@ export class CodeApplication extends Disposable {
mainProcessElectronServer.registerChannel('storage', storageChannel);
sharedProcessClient.then(client => client.registerChannel('storage', storageChannel));
// External Terminal
const externalTerminalChannel = ProxyChannel.fromService(accessor.get(IExternalTerminalMainService));
mainProcessElectronServer.registerChannel('externalTerminal', externalTerminalChannel);
// Log Level (main & shared process)
const logLevelChannel = new LogLevelChannel(accessor.get(ILogService));
mainProcessElectronServer.registerChannel('logLevel', logLevelChannel);
@@ -705,16 +669,18 @@ export class CodeApplication extends Disposable {
// Check for initial URLs to handle from protocol link invocations
const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = [];
const pendingProtocolLinksToHandle = [
// Windows/Linux: protocol handler invokes CLI with --open-url
...this.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : [],
// macOS: open-url events
...((<any>global).getOpenUrls() || []) as string[]
].map(url => {
try {
return { uri: URI.parse(url), url };
} catch {
return null;
return undefined;
}
}).filter((obj): obj is { uri: URI, url: string } => {
if (!obj) {
@@ -744,8 +710,16 @@ export class CodeApplication extends Disposable {
// protocol invocations outside of VSCode.
const app = this;
const environmentService = this.environmentMainService;
const productService = this.productService;
urlService.registerHandler({
async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
if (uri.scheme === productService.urlProtocol && uri.path === 'workspace') {
uri = uri.with({
authority: 'file',
path: URI.parse(uri.query).path,
query: ''
});
}
// If URI should be blocked, behave as if it's handled
if (app.shouldBlockURI(uri)) {
@@ -760,7 +734,7 @@ export class CodeApplication extends Disposable {
cli: { ...environmentService.args },
urisToOpen: [windowOpenableFromProtocolLink],
gotoLineMode: true
/* remoteAuthority will be determined based on windowOpenableFromProtocolLink */
// remoteAuthority: will be determined based on windowOpenableFromProtocolLink
});
window.focus(); // this should help ensuring that the right window gets focus when multiple are opened
@@ -823,7 +797,7 @@ export class CodeApplication extends Disposable {
urisToOpen: pendingWindowOpenablesFromProtocolLinks,
gotoLineMode: true,
initialStartup: true
/* remoteAuthority will be determined based on pendingWindowOpenablesFromProtocolLinks */
// remoteAuthority: will be determined based on pendingWindowOpenablesFromProtocolLinks
});
}
@@ -850,7 +824,7 @@ export class CodeApplication extends Disposable {
noRecentEntry,
waitMarkerFileURI,
initialStartup: true,
/* remoteAuthority will be determined based on macOpenFiles */
// remoteAuthority: will be determined based on macOpenFiles
});
}
@@ -957,10 +931,16 @@ export class CodeApplication extends Disposable {
// Logging
let message: string;
if (typeof details === 'string') {
message = details;
} else {
message = `SharedProcess: crashed (detail: ${details.reason})`;
switch (type) {
case WindowError.UNRESPONSIVE:
message = 'SharedProcess: detected unresponsive window';
break;
case WindowError.CRASHED:
message = `SharedProcess: crashed (detail: ${details?.reason ?? '<unknown>'}, code: ${details?.exitCode ?? '<unknown>'})`;
break;
case WindowError.LOAD:
message = `SharedProcess: failed to load (detail: ${details?.reason ?? '<unknown>'}, code: ${details?.exitCode ?? '<unknown>'})`;
break;
}
onUnexpectedError(new Error(message));
@@ -968,18 +948,21 @@ export class CodeApplication extends Disposable {
type SharedProcessErrorClassification = {
type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
reason: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
code: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
visible: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
shuttingdown: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
};
type SharedProcessErrorEvent = {
type: WindowError;
reason: string | undefined;
code: number | undefined;
visible: boolean;
shuttingdown: boolean;
};
telemetryService.publicLog2<SharedProcessErrorEvent, SharedProcessErrorClassification>('sharedprocesserror', {
type,
reason: typeof details !== 'string' ? details?.reason : undefined,
reason: details?.reason,
code: details?.exitCode,
visible: sharedProcess.isVisible(),
shuttingdown: willShutdown
});
@@ -1012,7 +995,16 @@ export class CodeApplication extends Disposable {
}
// Start to fetch shell environment (if needed) after window has opened
resolveShellEnv(this.logService, this.environmentMainService.args, process.env);
// Since this operation can take a long time, we want to warm it up while
// the window is opening.
// We also print a warning if the resolution takes longer than 10s.
(async () => {
const slowResolveShellEnvWarning = this._register(new RunOnceScheduler(() => this.logService.warn('Resolving your shell environment is taking more than 10s. Please review your shell configuration. Learn more at https://go.microsoft.com/fwlink/?linkid=2149667.'), 10000));
slowResolveShellEnvWarning.schedule();
await resolveShellEnv(this.logService, this.environmentMainService.args, process.env);
slowResolveShellEnvWarning.dispose();
})();
// If enable-crash-reporter argv is undefined then this is a fresh start,
// based on telemetry.enableCrashreporter settings, generate a UUID which