chore(vscode): update to 1.53.2
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
This commit is contained in:
@@ -4,17 +4,18 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron';
|
||||
import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform';
|
||||
import { release } from 'os';
|
||||
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';
|
||||
import { OpenContext } from 'vs/platform/windows/node/window';
|
||||
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { resolveShellEnv } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
|
||||
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
|
||||
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron';
|
||||
import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp';
|
||||
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
|
||||
import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -28,19 +29,17 @@ import { IOpenURLOptions, IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc';
|
||||
import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { ProxyAuthHandler2 } from 'vs/code/electron-main/auth2';
|
||||
import { FileProtocolHandler } from 'vs/code/electron-main/protocol';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { WorkspacesService } from 'vs/platform/workspaces/electron-main/workspacesService';
|
||||
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { getMachineId } from 'vs/base/node/id';
|
||||
import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
|
||||
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
|
||||
@@ -64,14 +63,13 @@ import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainSe
|
||||
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
|
||||
import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
|
||||
import { NativeURLService } from 'vs/platform/url/common/urlService';
|
||||
import { WorkspacesMainService, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { WorkspacesManagementMainService, IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
|
||||
import { statSync } from 'fs';
|
||||
import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
|
||||
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
|
||||
import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/electron-main/extensionHostDebugIpc';
|
||||
import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
|
||||
import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
|
||||
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
|
||||
import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService';
|
||||
@@ -81,7 +79,7 @@ import { stripComments } from 'vs/base/common/json';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
|
||||
import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker';
|
||||
import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker';
|
||||
import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService';
|
||||
@@ -90,6 +88,7 @@ import { isEqualOrParent } from 'vs/base/common/extpath';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
|
||||
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
private windowsMainService: IWindowsMainService | undefined;
|
||||
@@ -97,7 +96,7 @@ export class CodeApplication extends Disposable {
|
||||
private nativeHostMainService: INativeHostMainService | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly mainIpcServer: Server,
|
||||
private readonly mainIpcServer: NodeIPCServer,
|
||||
private readonly userEnv: IProcessEnvironment,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@@ -150,19 +149,19 @@ export class CodeApplication extends Disposable {
|
||||
event.preventDefault();
|
||||
});
|
||||
app.on('remote-get-global', (event, sender, module) => {
|
||||
this.logService.trace(`App#on(remote-get-global): prevented on ${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}`);
|
||||
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`);
|
||||
this.logService.trace(`app#on(remote-get-current-window): prevented`);
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
@@ -171,7 +170,7 @@ export class CodeApplication extends Disposable {
|
||||
return; // the driver needs access to web contents
|
||||
}
|
||||
|
||||
this.logService.trace(`App#on(remote-get-current-web-contents): prevented`);
|
||||
this.logService.trace(`app#on(remote-get-current-web-contents): prevented`);
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
@@ -271,9 +270,13 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
//#region Bootstrap IPC Handlers
|
||||
|
||||
let slowShellResolveWarningShown = false;
|
||||
ipcMain.on('vscode:fetchShellEnv', async event => {
|
||||
|
||||
// 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;
|
||||
const window = this.windowsMainService?.getWindowByWebContents(event.sender);
|
||||
|
||||
let replied = false;
|
||||
|
||||
@@ -291,11 +294,19 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
// Handle slow shell environment resolve calls:
|
||||
// - a warning after 3s but continue to resolve
|
||||
// - an error after 10s and stop trying to resolve
|
||||
// - 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(() => window?.sendWhenReady('vscode:showShellEnvSlowWarning', cts.token), 3000);
|
||||
const shellEnvTimeoutErrorHandle = setTimeout(function () {
|
||||
|
||||
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({});
|
||||
@@ -304,8 +315,11 @@ export class CodeApplication extends Disposable {
|
||||
// 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 wether to
|
||||
// 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: NodeJS.ProcessEnv;
|
||||
if (window?.config) {
|
||||
@@ -321,10 +335,10 @@ export class CodeApplication extends Disposable {
|
||||
acceptShellEnv(shellEnv);
|
||||
});
|
||||
|
||||
ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => {
|
||||
ipcMain.handle('vscode:writeNlsFile', async (event, path: unknown, data: unknown) => {
|
||||
const uri = this.validateNlsPath([path]);
|
||||
if (!uri || typeof data !== 'string') {
|
||||
return Promise.reject('Invalid operation (vscode:writeNlsFile)');
|
||||
throw new Error('Invalid operation (vscode:writeNlsFile)');
|
||||
}
|
||||
|
||||
return this.fileService.writeFile(uri, VSBuffer.fromString(data));
|
||||
@@ -333,7 +347,7 @@ export class CodeApplication extends Disposable {
|
||||
ipcMain.handle('vscode:readNlsFile', async (event, ...paths: unknown[]) => {
|
||||
const uri = this.validateNlsPath(paths);
|
||||
if (!uri) {
|
||||
return Promise.reject('Invalid operation (vscode:readNlsFile)');
|
||||
throw new Error('Invalid operation (vscode:readNlsFile)');
|
||||
}
|
||||
|
||||
return (await this.fileService.readFile(uri)).value.toString();
|
||||
@@ -427,16 +441,20 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// Spawn shared process after the first window has opened and 3s have passed
|
||||
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
|
||||
const sharedProcessClient = sharedProcess.whenIpcReady().then(() => {
|
||||
this.logService.trace('Shared process: IPC ready');
|
||||
const sharedProcessClient = (async () => {
|
||||
this.logService.trace('Main->SharedProcess#connect');
|
||||
|
||||
return connect(this.environmentService.sharedIPCHandle, 'main');
|
||||
});
|
||||
const sharedProcessReady = sharedProcess.whenReady().then(() => {
|
||||
this.logService.trace('Shared process: init ready');
|
||||
const port = await sharedProcess.connect();
|
||||
|
||||
this.logService.trace('Main->SharedProcess#connect: connection established');
|
||||
|
||||
return new MessagePortClient(port, 'main');
|
||||
})();
|
||||
const sharedProcessReady = (async () => {
|
||||
await sharedProcess.whenReady();
|
||||
|
||||
return sharedProcessClient;
|
||||
});
|
||||
})();
|
||||
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
|
||||
this._register(new RunOnceScheduler(async () => {
|
||||
sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env));
|
||||
@@ -454,12 +472,8 @@ export class CodeApplication extends Disposable {
|
||||
this._register(server);
|
||||
}
|
||||
|
||||
// Setup Auth Handler (TODO@ben remove old auth handler eventually)
|
||||
if (this.configurationService.getValue('window.enableExperimentalProxyLoginDialog') === false) {
|
||||
this._register(new ProxyAuthHandler());
|
||||
} else {
|
||||
this._register(appInstantiationService.createInstance(ProxyAuthHandler2));
|
||||
}
|
||||
// Setup Auth Handler
|
||||
this._register(appInstantiationService.createInstance(ProxyAuthHandler));
|
||||
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient, fileProtocolHandler));
|
||||
@@ -487,7 +501,7 @@ export class CodeApplication extends Disposable {
|
||||
return machineId;
|
||||
}
|
||||
|
||||
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<Client<string>>): Promise<IInstantiationService> {
|
||||
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
switch (process.platform) {
|
||||
@@ -496,8 +510,8 @@ export class CodeApplication extends Disposable {
|
||||
break;
|
||||
|
||||
case 'linux':
|
||||
if (process.env.SNAP && process.env.SNAP_REVISION) {
|
||||
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
|
||||
if (isLinuxSnap) {
|
||||
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env['SNAP'], process.env['SNAP_REVISION']]));
|
||||
} else {
|
||||
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
|
||||
}
|
||||
@@ -510,7 +524,6 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv]));
|
||||
services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
|
||||
services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess]));
|
||||
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
|
||||
services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
|
||||
|
||||
@@ -518,9 +531,9 @@ export class CodeApplication extends Disposable {
|
||||
services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId]));
|
||||
services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));
|
||||
services.set(IDisplayMainService, new SyncDescriptor(DisplayMainService));
|
||||
services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService));
|
||||
services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, [sharedProcess]));
|
||||
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
|
||||
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService));
|
||||
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService));
|
||||
services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
|
||||
services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService));
|
||||
|
||||
@@ -533,13 +546,13 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
|
||||
services.set(IURLService, new SyncDescriptor(NativeURLService));
|
||||
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
|
||||
services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService));
|
||||
|
||||
// Telemetry
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
|
||||
const appender = new TelemetryAppenderClient(channel);
|
||||
const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
|
||||
const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
|
||||
|
||||
@@ -589,7 +602,7 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
|
||||
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<MessagePortClient>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
|
||||
|
||||
// Register more Main IPC services
|
||||
const launchMainService = accessor.get(ILaunchMainService);
|
||||
@@ -622,10 +635,6 @@ export class CodeApplication extends Disposable {
|
||||
electronIpcServer.registerChannel('nativeHost', nativeHostChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel));
|
||||
|
||||
const sharedProcessMainService = accessor.get(ISharedProcessMainService);
|
||||
const sharedProcessChannel = createChannelReceiver(sharedProcessMainService);
|
||||
electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel);
|
||||
|
||||
const workspacesService = accessor.get(IWorkspacesService);
|
||||
const workspacesChannel = createChannelReceiver(workspacesService);
|
||||
electronIpcServer.registerChannel('workspaces', workspacesChannel);
|
||||
@@ -705,6 +714,8 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
|
||||
// Create a URL handler to open file URIs in the active window
|
||||
// or open new windows. The URL handler will be invoked from
|
||||
// protocol invocations outside of VSCode.
|
||||
const app = this;
|
||||
const environmentService = this.environmentService;
|
||||
urlService.registerHandler({
|
||||
@@ -718,13 +729,15 @@ export class CodeApplication extends Disposable {
|
||||
// Check for URIs to open in window
|
||||
const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri);
|
||||
if (windowOpenableFromProtocolLink) {
|
||||
windowsMainService.open({
|
||||
const [window] = windowsMainService.open({
|
||||
context: OpenContext.API,
|
||||
cli: { ...environmentService.args },
|
||||
urisToOpen: [windowOpenableFromProtocolLink],
|
||||
gotoLineMode: true
|
||||
});
|
||||
|
||||
window.focus(); // this should help ensuring that the right window gets focus when multiple are opened
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -832,7 +845,7 @@ export class CodeApplication extends Disposable {
|
||||
mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")),
|
||||
],
|
||||
cancelId: 1,
|
||||
message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath), product.nameShort),
|
||||
message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentService), product.nameShort),
|
||||
detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"),
|
||||
noLink: true
|
||||
});
|
||||
@@ -901,8 +914,25 @@ export class CodeApplication extends Disposable {
|
||||
// Signal phase: after window open
|
||||
this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
|
||||
|
||||
// Windows: install mutex
|
||||
const win32MutexName = product.win32MutexName;
|
||||
if (isWindows && win32MutexName) {
|
||||
try {
|
||||
const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
|
||||
const mutex = new WindowsMutex(win32MutexName);
|
||||
once(this.lifecycleMainService.onWillShutdown)(() => mutex.release());
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Remote Authorities
|
||||
this.handleRemoteAuthorities();
|
||||
protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => {
|
||||
callback({
|
||||
url: request.url.replace(/^vscode-remote-resource:/, 'http:'),
|
||||
method: request.method
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize update service
|
||||
const updateService = accessor.get(IUpdateService);
|
||||
@@ -934,19 +964,11 @@ export class CodeApplication extends Disposable {
|
||||
'}'
|
||||
];
|
||||
const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n'));
|
||||
|
||||
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private handleRemoteAuthorities(): void {
|
||||
protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => {
|
||||
callback({
|
||||
url: request.url.replace(/^vscode-remote-resource:/, 'http:'),
|
||||
method: request.method
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,28 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { app, AuthInfo, WebContents, Event as ElectronEvent, AuthenticationResponseDetails } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
|
||||
import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails {
|
||||
firstAuthAttempt?: boolean; // https://github.com/electron/electron/blob/84a42a050e7d45225e69df5bd2d2bf9f1037ea41/shell/browser/login_handler.cc#L70
|
||||
}
|
||||
|
||||
type LoginEvent = {
|
||||
event: ElectronEvent;
|
||||
webContents: WebContents;
|
||||
req: Request;
|
||||
authInfo: AuthInfo;
|
||||
cb: (username: string, password: string) => void;
|
||||
req: ElectronAuthenticationResponseDetails;
|
||||
|
||||
callback: (username?: string, password?: string) => void;
|
||||
};
|
||||
|
||||
type Credentials = {
|
||||
@@ -22,81 +32,211 @@ type Credentials = {
|
||||
password: string;
|
||||
};
|
||||
|
||||
enum ProxyAuthState {
|
||||
|
||||
/**
|
||||
* Initial state: we will try to use stored credentials
|
||||
* first to reply to the auth challenge.
|
||||
*/
|
||||
Initial = 1,
|
||||
|
||||
/**
|
||||
* We used stored credentials and are still challenged,
|
||||
* so we will show a login dialog next.
|
||||
*/
|
||||
StoredCredentialsUsed,
|
||||
|
||||
/**
|
||||
* Finally, if we showed a login dialog already, we will
|
||||
* not show any more login dialogs until restart to reduce
|
||||
* the UI noise.
|
||||
*/
|
||||
LoginDialogShown
|
||||
}
|
||||
|
||||
export class ProxyAuthHandler extends Disposable {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private static PROXY_CREDENTIALS_SERVICE_KEY = `${product.urlProtocol}.proxy-credentials`;
|
||||
|
||||
private retryCount = 0;
|
||||
private pendingProxyResolve: Promise<Credentials | undefined> | undefined = undefined;
|
||||
|
||||
constructor() {
|
||||
private state = ProxyAuthState.Initial;
|
||||
|
||||
private sessionCredentials: Credentials | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
|
||||
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
|
||||
@IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
const onLogin = Event.fromNodeEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
|
||||
const onLogin = Event.fromNodeEventEmitter<LoginEvent>(app, 'login', (event: ElectronEvent, webContents: WebContents, req: ElectronAuthenticationResponseDetails, authInfo: AuthInfo, callback) => ({ event, webContents, req, authInfo, callback }));
|
||||
this._register(onLogin(this.onLogin, this));
|
||||
}
|
||||
|
||||
private onLogin({ event, authInfo, cb }: LoginEvent): void {
|
||||
private async onLogin({ event, authInfo, req, callback }: LoginEvent): Promise<void> {
|
||||
if (!authInfo.isProxy) {
|
||||
return;
|
||||
return; // only for proxy
|
||||
}
|
||||
|
||||
if (this.retryCount++ > 1) {
|
||||
return;
|
||||
if (!this.pendingProxyResolve && this.state === ProxyAuthState.LoginDialogShown && req.firstAuthAttempt) {
|
||||
this.logService.trace('auth#onLogin (proxy) - exit - proxy dialog already shown');
|
||||
|
||||
return; // only one dialog per session at max (except when firstAuthAttempt: false which indicates a login problem)
|
||||
}
|
||||
|
||||
// Signal we handle this event on our own, otherwise
|
||||
// Electron will ignore our provided credentials.
|
||||
event.preventDefault();
|
||||
|
||||
const opts: BrowserWindowConstructorOptions = {
|
||||
alwaysOnTop: true,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
width: 450,
|
||||
height: 225,
|
||||
show: true,
|
||||
title: 'VS Code',
|
||||
webPreferences: {
|
||||
preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath,
|
||||
sandbox: true,
|
||||
contextIsolation: true,
|
||||
enableWebSQL: false,
|
||||
enableRemoteModule: false,
|
||||
spellcheck: false,
|
||||
devTools: false
|
||||
}
|
||||
};
|
||||
let credentials: Credentials | undefined = undefined;
|
||||
if (!this.pendingProxyResolve) {
|
||||
this.logService.trace('auth#onLogin (proxy) - no pending proxy handling found, starting new');
|
||||
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
opts.parent = focusedWindow;
|
||||
opts.modal = true;
|
||||
this.pendingProxyResolve = this.resolveProxyCredentials(authInfo);
|
||||
try {
|
||||
credentials = await this.pendingProxyResolve;
|
||||
} finally {
|
||||
this.pendingProxyResolve = undefined;
|
||||
}
|
||||
} else {
|
||||
this.logService.trace('auth#onLogin (proxy) - pending proxy handling found');
|
||||
|
||||
credentials = await this.pendingProxyResolve;
|
||||
}
|
||||
|
||||
const win = new BrowserWindow(opts);
|
||||
const windowUrl = FileAccess.asBrowserUri('vs/code/electron-sandbox/proxy/auth.html', require);
|
||||
const proxyUrl = `${authInfo.host}:${authInfo.port}`;
|
||||
const title = localize('authRequire', "Proxy Authentication Required");
|
||||
const message = localize('proxyauth', "The proxy {0} requires authentication.", proxyUrl);
|
||||
// According to Electron docs, it is fine to call back without
|
||||
// username or password to signal that the authentication was handled
|
||||
// by us, even though without having credentials received:
|
||||
//
|
||||
// > If `callback` is called without a username or password, the authentication
|
||||
// > request will be cancelled and the authentication error will be returned to the
|
||||
// > page.
|
||||
callback(credentials?.username, credentials?.password);
|
||||
}
|
||||
|
||||
const onWindowClose = () => cb('', '');
|
||||
win.on('close', onWindowClose);
|
||||
private async resolveProxyCredentials(authInfo: AuthInfo): Promise<Credentials | undefined> {
|
||||
this.logService.trace('auth#resolveProxyCredentials (proxy) - enter');
|
||||
|
||||
win.setMenu(null);
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
const data = { title, message };
|
||||
win.webContents.send('vscode:openProxyAuthDialog', data);
|
||||
});
|
||||
win.webContents.on('ipc-message', (event, channel, credentials: Credentials) => {
|
||||
if (channel === 'vscode:proxyAuthResponse') {
|
||||
const { username, password } = credentials;
|
||||
cb(username, password);
|
||||
win.removeListener('close', onWindowClose);
|
||||
win.close();
|
||||
try {
|
||||
const credentials = await this.doResolveProxyCredentials(authInfo);
|
||||
if (credentials) {
|
||||
this.logService.trace('auth#resolveProxyCredentials (proxy) - got credentials');
|
||||
|
||||
return credentials;
|
||||
} else {
|
||||
this.logService.trace('auth#resolveProxyCredentials (proxy) - did not get credentials');
|
||||
}
|
||||
} finally {
|
||||
this.logService.trace('auth#resolveProxyCredentials (proxy) - exit');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async doResolveProxyCredentials(authInfo: AuthInfo): Promise<Credentials | undefined> {
|
||||
this.logService.trace('auth#doResolveProxyCredentials - enter', authInfo);
|
||||
|
||||
// Compute a hash over the authentication info to be used
|
||||
// with the credentials store to return the right credentials
|
||||
// given the properties of the auth request
|
||||
// (see https://github.com/microsoft/vscode/issues/109497)
|
||||
const authInfoHash = String(hash({ scheme: authInfo.scheme, host: authInfo.host, port: authInfo.port }));
|
||||
|
||||
// Find any previously stored credentials
|
||||
let storedUsername: string | undefined = undefined;
|
||||
let storedPassword: string | undefined = undefined;
|
||||
try {
|
||||
const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
|
||||
if (encryptedSerializedProxyCredentials) {
|
||||
const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials));
|
||||
|
||||
storedUsername = credentials.username;
|
||||
storedPassword = credentials.password;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error); // handle errors by asking user for login via dialog
|
||||
}
|
||||
|
||||
// Reply with stored credentials unless we used them already.
|
||||
// In that case we need to show a login dialog again because
|
||||
// they seem invalid.
|
||||
if (this.state !== ProxyAuthState.StoredCredentialsUsed && typeof storedUsername === 'string' && typeof storedPassword === 'string') {
|
||||
this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found stored credentials to use');
|
||||
this.state = ProxyAuthState.StoredCredentialsUsed;
|
||||
|
||||
return { username: storedUsername, password: storedPassword };
|
||||
}
|
||||
|
||||
// Find suitable window to show dialog: prefer to show it in the
|
||||
// active window because any other network request will wait on
|
||||
// the credentials and we want the user to present the dialog.
|
||||
const window = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
|
||||
if (!window) {
|
||||
this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - no opened window found to show dialog in');
|
||||
|
||||
return undefined; // unexpected
|
||||
}
|
||||
|
||||
this.logService.trace(`auth#doResolveProxyCredentials (proxy) - asking window ${window.id} to handle proxy login`);
|
||||
|
||||
// Open proxy dialog
|
||||
const payload = {
|
||||
authInfo,
|
||||
username: this.sessionCredentials?.username ?? storedUsername, // prefer to show already used username (if any) over stored
|
||||
password: this.sessionCredentials?.password ?? storedPassword, // prefer to show already used password (if any) over stored
|
||||
replyChannel: `vscode:proxyAuthResponse:${generateUuid()}`
|
||||
};
|
||||
window.sendWhenReady('vscode:openProxyAuthenticationDialog', CancellationToken.None, payload);
|
||||
this.state = ProxyAuthState.LoginDialogShown;
|
||||
|
||||
// Handle reply
|
||||
const loginDialogCredentials = await new Promise<Credentials | undefined>(resolve => {
|
||||
const proxyAuthResponseHandler = async (event: ElectronEvent, channel: string, reply: Credentials & { remember: boolean } | undefined /* canceled */) => {
|
||||
if (channel === payload.replyChannel) {
|
||||
this.logService.trace(`auth#doResolveProxyCredentials - exit - received credentials from window ${window.id}`);
|
||||
window.win.webContents.off('ipc-message', proxyAuthResponseHandler);
|
||||
|
||||
// We got credentials from the window
|
||||
if (reply) {
|
||||
const credentials: Credentials = { username: reply.username, password: reply.password };
|
||||
|
||||
// Update stored credentials based on `remember` flag
|
||||
try {
|
||||
if (reply.remember) {
|
||||
const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials));
|
||||
await this.nativeHostMainService.setPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials);
|
||||
} else {
|
||||
await this.nativeHostMainService.deletePassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error); // handle gracefully
|
||||
}
|
||||
|
||||
resolve({ username: credentials.username, password: credentials.password });
|
||||
}
|
||||
|
||||
// We did not get any credentials from the window (e.g. cancelled)
|
||||
else {
|
||||
resolve(undefined);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.win.webContents.on('ipc-message', proxyAuthResponseHandler);
|
||||
});
|
||||
win.loadURL(windowUrl.toString(true));
|
||||
|
||||
// Remember credentials for the session in case
|
||||
// the credentials are wrong and we show the dialog
|
||||
// again
|
||||
this.sessionCredentials = loginDialogCredentials;
|
||||
|
||||
return loginDialogCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { app, AuthInfo, WebContents, Event as ElectronEvent, AuthenticationResponseDetails } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
|
||||
import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails {
|
||||
firstAuthAttempt?: boolean; // https://github.com/electron/electron/blob/84a42a050e7d45225e69df5bd2d2bf9f1037ea41/shell/browser/login_handler.cc#L70
|
||||
}
|
||||
|
||||
type LoginEvent = {
|
||||
event: ElectronEvent;
|
||||
authInfo: AuthInfo;
|
||||
req: ElectronAuthenticationResponseDetails;
|
||||
|
||||
callback: (username?: string, password?: string) => void;
|
||||
};
|
||||
|
||||
type Credentials = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
enum ProxyAuthState {
|
||||
|
||||
/**
|
||||
* Initial state: we will try to use stored credentials
|
||||
* first to reply to the auth challenge.
|
||||
*/
|
||||
Initial = 1,
|
||||
|
||||
/**
|
||||
* We used stored credentials and are still challenged,
|
||||
* so we will show a login dialog next.
|
||||
*/
|
||||
StoredCredentialsUsed,
|
||||
|
||||
/**
|
||||
* Finally, if we showed a login dialog already, we will
|
||||
* not show any more login dialogs until restart to reduce
|
||||
* the UI noise.
|
||||
*/
|
||||
LoginDialogShown
|
||||
}
|
||||
|
||||
export class ProxyAuthHandler2 extends Disposable {
|
||||
|
||||
private static PROXY_CREDENTIALS_SERVICE_KEY = `${product.urlProtocol}.proxy-credentials`;
|
||||
|
||||
private pendingProxyResolve: Promise<Credentials | undefined> | undefined = undefined;
|
||||
|
||||
private state = ProxyAuthState.Initial;
|
||||
|
||||
private sessionCredentials: Credentials | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
|
||||
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
|
||||
@IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
const onLogin = Event.fromNodeEventEmitter<LoginEvent>(app, 'login', (event: ElectronEvent, webContents: WebContents, req: ElectronAuthenticationResponseDetails, authInfo: AuthInfo, callback) => ({ event, webContents, req, authInfo, callback }));
|
||||
this._register(onLogin(this.onLogin, this));
|
||||
}
|
||||
|
||||
private async onLogin({ event, authInfo, req, callback }: LoginEvent): Promise<void> {
|
||||
if (!authInfo.isProxy) {
|
||||
return; // only for proxy
|
||||
}
|
||||
|
||||
if (!this.pendingProxyResolve && this.state === ProxyAuthState.LoginDialogShown && req.firstAuthAttempt) {
|
||||
this.logService.trace('auth#onLogin (proxy) - exit - proxy dialog already shown');
|
||||
|
||||
return; // only one dialog per session at max (except when firstAuthAttempt: false which indicates a login problem)
|
||||
}
|
||||
|
||||
// Signal we handle this event on our own, otherwise
|
||||
// Electron will ignore our provided credentials.
|
||||
event.preventDefault();
|
||||
|
||||
let credentials: Credentials | undefined = undefined;
|
||||
if (!this.pendingProxyResolve) {
|
||||
this.logService.trace('auth#onLogin (proxy) - no pending proxy handling found, starting new');
|
||||
|
||||
this.pendingProxyResolve = this.resolveProxyCredentials(authInfo);
|
||||
try {
|
||||
credentials = await this.pendingProxyResolve;
|
||||
} finally {
|
||||
this.pendingProxyResolve = undefined;
|
||||
}
|
||||
} else {
|
||||
this.logService.trace('auth#onLogin (proxy) - pending proxy handling found');
|
||||
|
||||
credentials = await this.pendingProxyResolve;
|
||||
}
|
||||
|
||||
// According to Electron docs, it is fine to call back without
|
||||
// username or password to signal that the authentication was handled
|
||||
// by us, even though without having credentials received:
|
||||
//
|
||||
// > If `callback` is called without a username or password, the authentication
|
||||
// > request will be cancelled and the authentication error will be returned to the
|
||||
// > page.
|
||||
callback(credentials?.username, credentials?.password);
|
||||
}
|
||||
|
||||
private async resolveProxyCredentials(authInfo: AuthInfo): Promise<Credentials | undefined> {
|
||||
this.logService.trace('auth#resolveProxyCredentials (proxy) - enter');
|
||||
|
||||
try {
|
||||
const credentials = await this.doResolveProxyCredentials(authInfo);
|
||||
if (credentials) {
|
||||
this.logService.trace('auth#resolveProxyCredentials (proxy) - got credentials');
|
||||
|
||||
return credentials;
|
||||
} else {
|
||||
this.logService.trace('auth#resolveProxyCredentials (proxy) - did not get credentials');
|
||||
}
|
||||
} finally {
|
||||
this.logService.trace('auth#resolveProxyCredentials (proxy) - exit');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async doResolveProxyCredentials(authInfo: AuthInfo): Promise<Credentials | undefined> {
|
||||
this.logService.trace('auth#doResolveProxyCredentials - enter', authInfo);
|
||||
|
||||
// Compute a hash over the authentication info to be used
|
||||
// with the credentials store to return the right credentials
|
||||
// given the properties of the auth request
|
||||
// (see https://github.com/microsoft/vscode/issues/109497)
|
||||
const authInfoHash = String(hash({ scheme: authInfo.scheme, host: authInfo.host, port: authInfo.port }));
|
||||
|
||||
// Find any previously stored credentials
|
||||
let storedUsername: string | undefined = undefined;
|
||||
let storedPassword: string | undefined = undefined;
|
||||
try {
|
||||
const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, ProxyAuthHandler2.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
|
||||
if (encryptedSerializedProxyCredentials) {
|
||||
const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials));
|
||||
|
||||
storedUsername = credentials.username;
|
||||
storedPassword = credentials.password;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error); // handle errors by asking user for login via dialog
|
||||
}
|
||||
|
||||
// Reply with stored credentials unless we used them already.
|
||||
// In that case we need to show a login dialog again because
|
||||
// they seem invalid.
|
||||
if (this.state !== ProxyAuthState.StoredCredentialsUsed && typeof storedUsername === 'string' && typeof storedPassword === 'string') {
|
||||
this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found stored credentials to use');
|
||||
this.state = ProxyAuthState.StoredCredentialsUsed;
|
||||
|
||||
return { username: storedUsername, password: storedPassword };
|
||||
}
|
||||
|
||||
// Find suitable window to show dialog: prefer to show it in the
|
||||
// active window because any other network request will wait on
|
||||
// the credentials and we want the user to present the dialog.
|
||||
const window = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
|
||||
if (!window) {
|
||||
this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - no opened window found to show dialog in');
|
||||
|
||||
return undefined; // unexpected
|
||||
}
|
||||
|
||||
this.logService.trace(`auth#doResolveProxyCredentials (proxy) - asking window ${window.id} to handle proxy login`);
|
||||
|
||||
// Open proxy dialog
|
||||
const payload = {
|
||||
authInfo,
|
||||
username: this.sessionCredentials?.username ?? storedUsername, // prefer to show already used username (if any) over stored
|
||||
password: this.sessionCredentials?.password ?? storedPassword, // prefer to show already used password (if any) over stored
|
||||
replyChannel: `vscode:proxyAuthResponse:${generateUuid()}`
|
||||
};
|
||||
window.sendWhenReady('vscode:openProxyAuthenticationDialog', CancellationToken.None, payload);
|
||||
this.state = ProxyAuthState.LoginDialogShown;
|
||||
|
||||
// Handle reply
|
||||
const loginDialogCredentials = await new Promise<Credentials | undefined>(resolve => {
|
||||
const proxyAuthResponseHandler = async (event: ElectronEvent, channel: string, reply: Credentials & { remember: boolean } | undefined /* canceled */) => {
|
||||
if (channel === payload.replyChannel) {
|
||||
this.logService.trace(`auth#doResolveProxyCredentials - exit - received credentials from window ${window.id}`);
|
||||
window.win.webContents.off('ipc-message', proxyAuthResponseHandler);
|
||||
|
||||
// We got credentials from the window
|
||||
if (reply) {
|
||||
const credentials: Credentials = { username: reply.username, password: reply.password };
|
||||
|
||||
// Update stored credentials based on `remember` flag
|
||||
try {
|
||||
if (reply.remember) {
|
||||
const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials));
|
||||
await this.nativeHostMainService.setPassword(undefined, ProxyAuthHandler2.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials);
|
||||
} else {
|
||||
await this.nativeHostMainService.deletePassword(undefined, ProxyAuthHandler2.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error); // handle gracefully
|
||||
}
|
||||
|
||||
resolve({ username: credentials.username, password: credentials.password });
|
||||
}
|
||||
|
||||
// We did not get any credentials from the window (e.g. cancelled)
|
||||
else {
|
||||
resolve(undefined);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.win.webContents.on('ipc-message', proxyAuthResponseHandler);
|
||||
});
|
||||
|
||||
// Remember credentials for the session in case
|
||||
// the credentials are wrong and we show the dialog
|
||||
// again
|
||||
this.sessionCredentials = loginDialogCredentials;
|
||||
|
||||
return loginDialogCredentials;
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,17 @@
|
||||
|
||||
import 'vs/platform/update/common/update.config.contribution';
|
||||
import { app, dialog } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import { unlinkSync } from 'fs';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
|
||||
import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { Server, serve, connect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Server as NodeIPCServer, serve as nodeIPCServe, connect as nodeIPCConnect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { Client as NodeIPCClient } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
@@ -29,17 +31,15 @@ import { ConfigurationService } from 'vs/platform/configuration/common/configura
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService';
|
||||
import { CodeApplication } from 'vs/code/electron-main/app';
|
||||
import { localize } from 'vs/nls';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { SignService } from 'vs/platform/sign/node/signService';
|
||||
import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
|
||||
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
@@ -53,6 +53,8 @@ import { rtrim, trim } from 'vs/base/common/strings';
|
||||
import { basename, resolve } from 'vs/base/common/path';
|
||||
import { coalesce, distinct } from 'vs/base/common/arrays';
|
||||
import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
|
||||
class ExpectedError extends Error {
|
||||
readonly isExpected = true;
|
||||
@@ -212,14 +214,14 @@ class CodeMain {
|
||||
return instanceEnvironment;
|
||||
}
|
||||
|
||||
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise<Server> {
|
||||
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise<NodeIPCServer> {
|
||||
|
||||
// Try to setup a server for running. If that succeeds it means
|
||||
// we are the first instance to startup. Otherwise it is likely
|
||||
// that another instance is already running.
|
||||
let server: Server;
|
||||
let server: NodeIPCServer;
|
||||
try {
|
||||
server = await serve(environmentService.mainIPCHandle);
|
||||
server = await nodeIPCServe(environmentService.mainIPCHandle);
|
||||
once(lifecycleMainService.onWillShutdown)(() => server.dispose());
|
||||
} catch (error) {
|
||||
|
||||
@@ -235,9 +237,9 @@ class CodeMain {
|
||||
}
|
||||
|
||||
// there's a running instance, let's connect to it
|
||||
let client: Client<string>;
|
||||
let client: NodeIPCClient<string>;
|
||||
try {
|
||||
client = await connect(environmentService.mainIPCHandle, 'main');
|
||||
client = await nodeIPCConnect(environmentService.mainIPCHandle, 'main');
|
||||
} catch (error) {
|
||||
|
||||
// Handle unexpected connection errors by showing a dialog to the user
|
||||
@@ -256,7 +258,7 @@ class CodeMain {
|
||||
// let's delete it, since we can't connect to it and then
|
||||
// retry the whole thing
|
||||
try {
|
||||
fs.unlinkSync(environmentService.mainIPCHandle);
|
||||
unlinkSync(environmentService.mainIPCHandle);
|
||||
} catch (error) {
|
||||
logService.warn('Could not delete obsolete instance handle', error);
|
||||
|
||||
@@ -293,11 +295,7 @@ class CodeMain {
|
||||
// Process Info
|
||||
if (args.status) {
|
||||
return instantiationService.invokeFunction(async () => {
|
||||
|
||||
// Create a diagnostic service connected to the existing shared process
|
||||
const sharedProcessClient = await connect(environmentService.sharedIPCHandle, 'main');
|
||||
const diagnosticsChannel = sharedProcessClient.getChannel('diagnostics');
|
||||
const diagnosticsService = createChannelSender<IDiagnosticsService>(diagnosticsChannel);
|
||||
const diagnosticsService = new DiagnosticsService(NullTelemetryService);
|
||||
const mainProcessInfo = await launchService.getMainProcessInfo();
|
||||
const remoteDiagnostics = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true });
|
||||
const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics);
|
||||
@@ -343,11 +341,11 @@ class CodeMain {
|
||||
|
||||
private handleStartupDataDirError(environmentService: IEnvironmentMainService, error: NodeJS.ErrnoException): void {
|
||||
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]);
|
||||
const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentService));
|
||||
|
||||
this.showStartupWarningDialog(
|
||||
localize('startupDataDirError', "Unable to write program user data."),
|
||||
localize('startupUserDataAndExtensionsDirErrorDetail', "Please make sure the following directories are writeable:\n\n{0}", directories.join('\n'))
|
||||
localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro
|
||||
import { session } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { isLinux, isPreferringBrowserCodeLoad } from 'vs/base/common/platform';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
|
||||
type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void };
|
||||
@@ -37,15 +37,17 @@ export class FileProtocolHandler extends Disposable {
|
||||
// Register vscode-file:// handler
|
||||
defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback));
|
||||
|
||||
// Block any file:// access (sandbox only)
|
||||
if (environmentService.args.__sandbox) {
|
||||
// Block any file:// access (explicitly enabled only)
|
||||
if (isPreferringBrowserCodeLoad) {
|
||||
this.logService.info(`Intercepting ${Schemas.file}: protocol and blocking it`);
|
||||
|
||||
defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback as unknown as ProtocolCallback));
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
this._register(toDisposable(() => {
|
||||
defaultSession.protocol.unregisterProtocol(Schemas.vscodeFileResource);
|
||||
if (environmentService.args.__sandbox) {
|
||||
if (isPreferringBrowserCodeLoad) {
|
||||
defaultSession.protocol.uninterceptProtocol(Schemas.file);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -3,25 +3,25 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { BrowserWindow, ipcMain, Event, MessagePortMain } from 'electron';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
import { BrowserWindow, ipcMain, WebContents, Event as ElectronEvent } from 'electron';
|
||||
import { ISharedProcess } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform';
|
||||
import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
|
||||
export class SharedProcess implements ISharedProcess {
|
||||
export class SharedProcess extends Disposable implements ISharedProcess {
|
||||
|
||||
private barrier = new Barrier();
|
||||
private readonly whenSpawnedBarrier = new Barrier();
|
||||
|
||||
private window: BrowserWindow | null = null;
|
||||
|
||||
private readonly _whenReady: Promise<void>;
|
||||
private window: BrowserWindow | undefined = undefined;
|
||||
private windowCloseListener: ((event: Event) => void) | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
private readonly machineId: string,
|
||||
@@ -31,17 +31,125 @@ export class SharedProcess implements ISharedProcess {
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IThemeMainService private readonly themeMainService: IThemeMainService
|
||||
) {
|
||||
// overall ready promise when shared process signals initialization is done
|
||||
this._whenReady = new Promise<void>(c => ipcMain.once('vscode:shared-process->electron-main=init-done', () => c(undefined)));
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get _whenIpcReady(): Promise<void> {
|
||||
private registerListeners(): void {
|
||||
|
||||
// Lifecycle
|
||||
this._register(this.lifecycleMainService.onWillShutdown(() => this.onWillShutdown()));
|
||||
|
||||
// Shared process connections from workbench windows
|
||||
ipcMain.on('vscode:createSharedProcessMessageChannel', async (e, nonce: string) => {
|
||||
this.logService.trace('SharedProcess: on vscode:createSharedProcessMessageChannel');
|
||||
|
||||
// await the shared process to be overall ready
|
||||
// we do not just wait for IPC ready because the
|
||||
// workbench window will communicate directly
|
||||
await this.whenReady();
|
||||
|
||||
// connect to the shared process window
|
||||
const port = await this.connect();
|
||||
|
||||
// Check back if the requesting window meanwhile closed
|
||||
// Since shared process is delayed on startup there is
|
||||
// a chance that the window close before the shared process
|
||||
// was ready for a connection.
|
||||
if (e.sender.isDestroyed()) {
|
||||
return port.close();
|
||||
}
|
||||
|
||||
// send the port back to the requesting window
|
||||
e.sender.postMessage('vscode:createSharedProcessMessageChannelResult', nonce, [port]);
|
||||
});
|
||||
}
|
||||
|
||||
private onWillShutdown(): void {
|
||||
const window = this.window;
|
||||
if (!window) {
|
||||
return; // possibly too early before created
|
||||
}
|
||||
|
||||
// Signal exit to shared process when shutting down
|
||||
if (!window.isDestroyed() && !window.webContents.isDestroyed()) {
|
||||
window.webContents.send('vscode:electron-main->shared-process=exit');
|
||||
}
|
||||
|
||||
// Shut the shared process down when we are quitting
|
||||
//
|
||||
// Note: because we veto the window close, we must first remove our veto.
|
||||
// Otherwise the application would never quit because the shared process
|
||||
// window is refusing to close!
|
||||
//
|
||||
if (this.windowCloseListener) {
|
||||
window.removeListener('close', this.windowCloseListener);
|
||||
this.windowCloseListener = undefined;
|
||||
}
|
||||
|
||||
// Electron seems to crash on Windows without this setTimeout :|
|
||||
setTimeout(() => {
|
||||
try {
|
||||
window.close();
|
||||
} catch (err) {
|
||||
// ignore, as electron is already shutting down
|
||||
}
|
||||
|
||||
this.window = undefined;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private _whenReady: Promise<void> | undefined = undefined;
|
||||
whenReady(): Promise<void> {
|
||||
if (!this._whenReady) {
|
||||
// Overall signal that the shared process window was loaded and
|
||||
// all services within have been created.
|
||||
this._whenReady = new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=init-done', () => {
|
||||
this.logService.trace('SharedProcess: Overall ready');
|
||||
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
|
||||
return this._whenReady;
|
||||
}
|
||||
|
||||
private _whenIpcReady: Promise<void> | undefined = undefined;
|
||||
private get whenIpcReady() {
|
||||
if (!this._whenIpcReady) {
|
||||
this._whenIpcReady = (async () => {
|
||||
|
||||
// Always wait for `spawn()`
|
||||
await this.whenSpawnedBarrier.wait();
|
||||
|
||||
// Create window for shared process
|
||||
this.createWindow();
|
||||
|
||||
// Listeners
|
||||
this.registerWindowListeners();
|
||||
|
||||
// Wait for window indicating that IPC connections are accepted
|
||||
await new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => {
|
||||
this.logService.trace('SharedProcess: IPC ready');
|
||||
|
||||
resolve();
|
||||
}));
|
||||
})();
|
||||
}
|
||||
|
||||
return this._whenIpcReady;
|
||||
}
|
||||
|
||||
private createWindow(): void {
|
||||
|
||||
// shared process is a hidden window by default
|
||||
this.window = new BrowserWindow({
|
||||
show: false,
|
||||
backgroundColor: this.themeMainService.getBackgroundColor(),
|
||||
webPreferences: {
|
||||
preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath,
|
||||
v8CacheOptions: browserCodeLoadingCacheStrategy,
|
||||
nodeIntegration: true,
|
||||
enableWebSQL: false,
|
||||
enableRemoteModule: false,
|
||||
@@ -52,118 +160,85 @@ export class SharedProcess implements ISharedProcess {
|
||||
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
|
||||
}
|
||||
});
|
||||
const config = {
|
||||
appRoot: this.environmentService.appRoot,
|
||||
|
||||
const config: ISharedProcessConfiguration = {
|
||||
machineId: this.machineId,
|
||||
windowId: this.window.id,
|
||||
appRoot: this.environmentService.appRoot,
|
||||
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
|
||||
backupWorkspacesPath: this.environmentService.backupWorkspacesPath,
|
||||
userEnv: this.userEnv,
|
||||
windowId: this.window.id
|
||||
sharedIPCHandle: this.environmentService.sharedIPCHandle,
|
||||
args: this.environmentService.args,
|
||||
logLevel: this.logService.getLevel()
|
||||
};
|
||||
|
||||
const windowUrl = (this.environmentService.sandbox ?
|
||||
FileAccess._asCodeFileUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require) :
|
||||
FileAccess.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require))
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` });
|
||||
this.window.loadURL(windowUrl.toString(true));
|
||||
// Load with config
|
||||
this.window.loadURL(FileAccess
|
||||
.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require)
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
|
||||
.toString(true)
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent the window from dying
|
||||
const onClose = (e: ElectronEvent) => {
|
||||
private registerWindowListeners(): void {
|
||||
if (!this.window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the window from closing
|
||||
this.windowCloseListener = (e: Event) => {
|
||||
this.logService.trace('SharedProcess#close prevented');
|
||||
|
||||
// We never allow to close the shared process unless we get explicitly disposed()
|
||||
e.preventDefault();
|
||||
|
||||
// Still hide the window though if visible
|
||||
if (this.window && this.window.isVisible()) {
|
||||
if (this.window?.isVisible()) {
|
||||
this.window.hide();
|
||||
}
|
||||
};
|
||||
|
||||
this.window.on('close', onClose);
|
||||
this.window.on('close', this.windowCloseListener);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
this.lifecycleMainService.onWillShutdown(() => {
|
||||
disposables.dispose();
|
||||
|
||||
// Shut the shared process down when we are quitting
|
||||
//
|
||||
// Note: because we veto the window close, we must first remove our veto.
|
||||
// Otherwise the application would never quit because the shared process
|
||||
// window is refusing to close!
|
||||
//
|
||||
if (this.window) {
|
||||
this.window.removeListener('close', onClose);
|
||||
}
|
||||
|
||||
// Electron seems to crash on Windows without this setTimeout :|
|
||||
setTimeout(() => {
|
||||
try {
|
||||
if (this.window) {
|
||||
this.window.close();
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore, as electron is already shutting down
|
||||
}
|
||||
|
||||
this.window = null;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
return new Promise<void>(c => {
|
||||
// send payload once shared process is ready to receive it
|
||||
disposables.add(Event.once(Event.fromNodeEventEmitter(ipcMain, 'vscode:shared-process->electron-main=ready-for-payload', ({ sender }: { sender: WebContents }) => sender))(sender => {
|
||||
sender.send('vscode:electron-main->shared-process=payload', {
|
||||
sharedIPCHandle: this.environmentService.sharedIPCHandle,
|
||||
args: this.environmentService.args,
|
||||
logLevel: this.logService.getLevel(),
|
||||
backupWorkspacesPath: this.environmentService.backupWorkspacesPath,
|
||||
nodeCachedDataDir: this.environmentService.nodeCachedDataDir
|
||||
});
|
||||
|
||||
// signal exit to shared process when we get disposed
|
||||
disposables.add(toDisposable(() => sender.send('vscode:electron-main->shared-process=exit')));
|
||||
|
||||
// complete IPC-ready promise when shared process signals this to us
|
||||
ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => c(undefined));
|
||||
}));
|
||||
});
|
||||
// Crashes & Unrsponsive & Failed to load
|
||||
this.window.webContents.on('render-process-gone', (event, details) => this.logService.error(`SharedProcess: crashed (detail: ${details?.reason})`));
|
||||
this.window.on('unresponsive', () => this.logService.error('SharedProcess: detected unresponsive window'));
|
||||
this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('SharedProcess: failed to load window, ', errorDescription));
|
||||
}
|
||||
|
||||
spawn(userEnv: NodeJS.ProcessEnv): void {
|
||||
this.userEnv = { ...this.userEnv, ...userEnv };
|
||||
this.barrier.open();
|
||||
|
||||
// Release barrier
|
||||
this.whenSpawnedBarrier.open();
|
||||
}
|
||||
|
||||
async whenReady(): Promise<void> {
|
||||
await this.barrier.wait();
|
||||
await this._whenReady;
|
||||
async connect(): Promise<MessagePortMain> {
|
||||
|
||||
// Wait for shared process being ready to accept connection
|
||||
await this.whenIpcReady;
|
||||
|
||||
// Connect and return message port
|
||||
const window = assertIsDefined(this.window);
|
||||
return connectMessagePort(window);
|
||||
}
|
||||
|
||||
async whenIpcReady(): Promise<void> {
|
||||
await this.barrier.wait();
|
||||
await this._whenIpcReady;
|
||||
}
|
||||
async toggle(): Promise<void> {
|
||||
|
||||
toggle(): void {
|
||||
if (!this.window || this.window.isVisible()) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
// wait for window to be created
|
||||
await this.whenIpcReady;
|
||||
|
||||
if (!this.window) {
|
||||
return; // possibly disposed already
|
||||
}
|
||||
}
|
||||
|
||||
show(): void {
|
||||
if (this.window) {
|
||||
if (this.window.isVisible()) {
|
||||
this.window.webContents.closeDevTools();
|
||||
this.window.hide();
|
||||
} else {
|
||||
this.window.show();
|
||||
this.window.webContents.openDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
if (this.window) {
|
||||
this.window.webContents.closeDevTools();
|
||||
this.window.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
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, Details } from 'electron';
|
||||
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, RenderProcessGoneDetails } 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';
|
||||
@@ -18,17 +18,17 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { browserCodeLoadingCacheStrategy, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
|
||||
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
@@ -102,32 +102,32 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
private hiddenTitleBarStyle: boolean | undefined;
|
||||
private showTimeoutHandle: NodeJS.Timeout | undefined;
|
||||
private _lastFocusTime: number;
|
||||
private _readyState: ReadyState;
|
||||
private _lastFocusTime = -1;
|
||||
private _readyState = ReadyState.NONE;
|
||||
private windowState: IWindowState;
|
||||
private currentMenuBarVisibility: MenuBarVisibility | undefined;
|
||||
|
||||
private representedFilename: string | undefined;
|
||||
private documentEdited: boolean | undefined;
|
||||
|
||||
private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[];
|
||||
private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[] = [];
|
||||
|
||||
private marketplaceHeadersPromise: Promise<object>;
|
||||
|
||||
private readonly touchBarGroups: TouchBarSegmentedControl[];
|
||||
private readonly touchBarGroups: TouchBarSegmentedControl[] = [];
|
||||
|
||||
private currentHttpProxy?: string;
|
||||
private currentNoProxy?: string;
|
||||
private currentHttpProxy: string | undefined = undefined;
|
||||
private currentNoProxy: string | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
config: IWindowCreationOptions,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IStorageMainService private readonly storageService: IStorageMainService,
|
||||
@IStorageMainService storageService: IStorageMainService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IThemeMainService private readonly themeMainService: IThemeMainService,
|
||||
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
|
||||
@IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService,
|
||||
@IBackupMainService private readonly backupMainService: IBackupMainService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService,
|
||||
@@ -135,11 +135,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
) {
|
||||
super();
|
||||
|
||||
this.touchBarGroups = [];
|
||||
this._lastFocusTime = -1;
|
||||
this._readyState = ReadyState.NONE;
|
||||
this.whenReadyCallbacks = [];
|
||||
|
||||
//#region create browser window
|
||||
{
|
||||
// Load window state
|
||||
@@ -164,6 +159,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
title: product.nameLong,
|
||||
webPreferences: {
|
||||
preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath,
|
||||
v8CacheOptions: browserCodeLoadingCacheStrategy,
|
||||
enableWebSQL: false,
|
||||
enableRemoteModule: false,
|
||||
spellcheck: false,
|
||||
@@ -185,13 +181,17 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
};
|
||||
|
||||
if (browserCodeLoadingCacheStrategy) {
|
||||
this.logService.info(`window#ctor: using vscode-file protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`);
|
||||
}
|
||||
|
||||
// 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 = path.join(this.environmentService.appRoot, 'resources/linux/code.png');
|
||||
options.icon = join(this.environmentService.appRoot, 'resources/linux/code.png');
|
||||
} else if (isWindows && !this.environmentService.isBuilt) {
|
||||
options.icon = path.join(this.environmentService.appRoot, 'resources/win32/code_150x150.png');
|
||||
options.icon = join(this.environmentService.appRoot, 'resources/win32/code_150x150.png');
|
||||
}
|
||||
|
||||
if (isMacintosh && !this.useNativeFullScreen()) {
|
||||
@@ -233,7 +233,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
|
||||
}
|
||||
|
||||
// TODO@Ben (Electron 4 regression): when running on multiple displays where the target display
|
||||
// TODO@bpasero (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)
|
||||
//
|
||||
@@ -275,10 +275,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this.createTouchBar();
|
||||
|
||||
// Request handling
|
||||
const that = this;
|
||||
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService, {
|
||||
get(key) { return that.storageService.get(key); },
|
||||
store(key, value) { that.storageService.store(key, value); }
|
||||
get(key) { return storageService.get(key); },
|
||||
store(key, value) { storageService.store(key, value); }
|
||||
});
|
||||
|
||||
// Eventing
|
||||
@@ -361,13 +360,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
get lastFocusTime(): number { return this._lastFocusTime; }
|
||||
|
||||
get backupPath(): string | undefined { return this.currentConfig ? this.currentConfig.backupPath : undefined; }
|
||||
get backupPath(): string | undefined { return this.currentConfig?.backupPath; }
|
||||
|
||||
get openedWorkspace(): IWorkspaceIdentifier | undefined { return this.currentConfig ? this.currentConfig.workspace : undefined; }
|
||||
get openedWorkspace(): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { return this.currentConfig?.workspace; }
|
||||
|
||||
get openedFolderUri(): URI | undefined { return this.currentConfig ? this.currentConfig.folderUri : undefined; }
|
||||
|
||||
get remoteAuthority(): string | undefined { return this.currentConfig ? this.currentConfig.remoteAuthority : undefined; }
|
||||
get remoteAuthority(): string | undefined { return this.currentConfig?.remoteAuthority; }
|
||||
|
||||
setReady(): void {
|
||||
this._readyState = ReadyState.READY;
|
||||
@@ -413,9 +410,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Crashes & Unrsponsive
|
||||
// Crashes & Unrsponsive & Failed to load
|
||||
this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details));
|
||||
this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE));
|
||||
this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('Main: failed to load workbench window, ', errorDescription));
|
||||
|
||||
// Window close
|
||||
this._win.on('closed', () => {
|
||||
@@ -424,24 +422,42 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this.dispose();
|
||||
});
|
||||
|
||||
// Prevent loading of svgs
|
||||
this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => {
|
||||
if (details.url.indexOf('.svg') > 0) {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri && !uri.scheme.match(/file/i) && uri.path.endsWith('.svg')) {
|
||||
const svgFileSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, 'devtools']);
|
||||
this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => {
|
||||
const uri = URI.parse(details.url);
|
||||
// Prevent loading of remote svgs
|
||||
if (uri && uri.path.endsWith('.svg')) {
|
||||
const safeScheme = svgFileSchemes.has(uri.scheme) ||
|
||||
uri.path.includes(Schemas.vscodeRemoteResource);
|
||||
if (!safeScheme) {
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
}
|
||||
|
||||
return callback({});
|
||||
return callback({ cancel: false });
|
||||
});
|
||||
|
||||
this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => {
|
||||
this._win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
const responseHeaders = details.responseHeaders as Record<string, (string) | (string[])>;
|
||||
|
||||
const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']);
|
||||
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
|
||||
return callback({ cancel: true });
|
||||
|
||||
if (contentType && Array.isArray(contentType)) {
|
||||
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 && uri.path.endsWith('.svg')) {
|
||||
if (svgFileSchemes.has(uri.scheme)) {
|
||||
responseHeaders['Content-Type'] = ['image/svg+xml'];
|
||||
return callback({ cancel: false, responseHeaders });
|
||||
}
|
||||
}
|
||||
|
||||
// remote extension schemes have the following format
|
||||
// http://127.0.0.1:<port>/vscode-remote-resource?path=
|
||||
if (!uri.path.includes(Schemas.vscodeRemoteResource) &&
|
||||
contentType.some(x => x.toLowerCase().includes('image/svg'))) {
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
}
|
||||
|
||||
return callback({ cancel: false });
|
||||
@@ -526,16 +542,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None);
|
||||
});
|
||||
|
||||
// Window Failed to load
|
||||
this._win.webContents.on('did-fail-load', (event: Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
|
||||
this.logService.warn('[electron event]: fail to load, ', errorDescription);
|
||||
});
|
||||
|
||||
// Handle configuration changes
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
|
||||
|
||||
// Handle Workspace events
|
||||
this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
|
||||
this._register(this.workspacesManagementMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
|
||||
|
||||
// Inject headers when requests are incoming
|
||||
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
|
||||
@@ -544,9 +555,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
private onWindowError(error: WindowError.UNRESPONSIVE): void;
|
||||
private onWindowError(error: WindowError.CRASHED, details: Details): void;
|
||||
private onWindowError(error: WindowError, details?: Details): void {
|
||||
this.logService.error(error === WindowError.CRASHED ? `[VS Code]: renderer process crashed (detail: ${details?.reason})` : '[VS Code]: detected unresponsive');
|
||||
private onWindowError(error: WindowError.CRASHED, details: RenderProcessGoneDetails): void;
|
||||
private onWindowError(error: WindowError, details?: RenderProcessGoneDetails): void {
|
||||
this.logService.error(error === WindowError.CRASHED ? `Main: renderer process crashed (detail: ${details?.reason})` : 'Main: detected unresponsive');
|
||||
|
||||
// If we run extension tests from CLI, showing a dialog is not
|
||||
// very helpful in this case. Rather, we bring down the test run
|
||||
@@ -568,7 +579,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// Unresponsive
|
||||
if (error === WindowError.UNRESPONSIVE) {
|
||||
if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) {
|
||||
// TODO@Ben Workaround for https://github.com/microsoft/vscode/issues/56994
|
||||
// TODO@bpasero 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
|
||||
@@ -581,9 +592,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this.dialogMainService.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(nls.localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(nls.localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(nls.localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message: nls.localize('appStalled', "The window is no longer responding"),
|
||||
detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
|
||||
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 no longer responding"),
|
||||
detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
|
||||
noLink: true
|
||||
}, this._win).then(result => {
|
||||
if (!this._win) {
|
||||
@@ -591,6 +602,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
if (result.response === 0) {
|
||||
this._win.webContents.forcefullyCrashRenderer(); // Calling reload() immediately after calling this method will force the reload to occur in a new process
|
||||
this.reload();
|
||||
} else if (result.response === 2) {
|
||||
this.destroyWindow();
|
||||
@@ -602,17 +614,17 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
else {
|
||||
let message: string;
|
||||
if (details && details.reason !== 'crashed') {
|
||||
message = nls.localize('appCrashedDetails', "The window has crashed (reason: '{0}')", details?.reason);
|
||||
message = localize('appCrashedDetails', "The window has crashed (reason: '{0}')", details?.reason);
|
||||
} else {
|
||||
message = nls.localize('appCrashed', "The window has crashed", details?.reason);
|
||||
message = localize('appCrashed', "The window has crashed", details?.reason);
|
||||
}
|
||||
|
||||
this.dialogMainService.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(nls.localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(nls.localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message,
|
||||
detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
|
||||
detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
|
||||
noLink: true
|
||||
}, this._win).then(result => {
|
||||
if (!this._win) {
|
||||
@@ -643,31 +655,32 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
private onConfigurationUpdated(): void {
|
||||
|
||||
// Menubar
|
||||
const newMenuBarVisibility = this.getMenuBarVisibility();
|
||||
if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
|
||||
this.currentMenuBarVisibility = newMenuBarVisibility;
|
||||
this.setMenuBarVisibility(newMenuBarVisibility);
|
||||
}
|
||||
// Do not set to empty configuration at startup if setting is empty to not override configuration through CLI options:
|
||||
const env = process.env;
|
||||
|
||||
// Proxy
|
||||
let newHttpProxy = (this.configurationService.getValue<string>('http.proxy') || '').trim()
|
||||
|| (env.https_proxy || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.HTTP_PROXY || '').trim() // Not standardized.
|
||||
|| (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 = (env.no_proxy || env.NO_PROXY || '').trim() || undefined; // Not standardized.
|
||||
|
||||
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: '',
|
||||
});
|
||||
this._win.webContents.session.setProxy({ proxyRules, proxyBypassRules, pacScript: '' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,7 +690,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
load(config: INativeWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
|
||||
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
|
||||
@@ -689,6 +702,15 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
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) {
|
||||
@@ -728,7 +750,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
// Load URL
|
||||
perf.mark('main:loadWindow');
|
||||
mark('code/willOpenNewWindow');
|
||||
this._win.loadURL(this.getUrl(configuration));
|
||||
|
||||
// Make window visible if it did not open in N seconds because this indicates an error
|
||||
@@ -747,11 +769,14 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this._onLoad.fire();
|
||||
}
|
||||
|
||||
reload(cli?: NativeParsedArgs): void {
|
||||
async reload(cli?: NativeParsedArgs): Promise<void> {
|
||||
|
||||
// Copy our current config for reuse
|
||||
const configuration = Object.assign({}, this.currentConfig);
|
||||
|
||||
// Validate workspace
|
||||
configuration.workspace = await this.validateWorkspace(configuration);
|
||||
|
||||
// Delete some properties we do not want during reload
|
||||
delete configuration.filesToOpenOrCreate;
|
||||
delete configuration.filesToDiff;
|
||||
@@ -761,17 +786,44 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// in extension development mode. These options are all development related.
|
||||
if (this.isExtensionDevelopmentHost && cli) {
|
||||
configuration.verbose = cli.verbose;
|
||||
configuration.debugId = cli.debugId;
|
||||
configuration['inspect-extensions'] = cli['inspect-extensions'];
|
||||
configuration['inspect-brk-extensions'] = cli['inspect-brk-extensions'];
|
||||
configuration.debugId = cli.debugId;
|
||||
configuration['extensions-dir'] = cli['extensions-dir'];
|
||||
}
|
||||
|
||||
configuration.isInitialStartup = false; // since this is a reload
|
||||
|
||||
// Load config
|
||||
const disableExtensions = cli ? cli['disable-extensions'] : undefined;
|
||||
this.load(configuration, true, disableExtensions);
|
||||
this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] });
|
||||
}
|
||||
|
||||
private async validateWorkspace(configuration: INativeWindowConfiguration): Promise<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined> {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
private getUrl(windowConfiguration: INativeWindowConfiguration): string {
|
||||
@@ -780,6 +832,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
windowConfiguration.windowId = this._win.id;
|
||||
windowConfiguration.sessionId = `window:${this._win.id}`;
|
||||
windowConfiguration.logLevel = this.logService.getLevel();
|
||||
windowConfiguration.logsPath = this.environmentService.logsPath;
|
||||
|
||||
// Set zoomlevel
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
|
||||
@@ -803,14 +856,14 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
windowConfiguration.maximized = this._win.isMaximized();
|
||||
|
||||
// Dump Perf Counters
|
||||
windowConfiguration.perfEntries = perf.exportEntries();
|
||||
windowConfiguration.perfMarks = getMarks();
|
||||
|
||||
// Parts splash
|
||||
windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json');
|
||||
windowConfiguration.partsSplashPath = join(this.environmentService.userDataPath, 'rapid_render.json');
|
||||
|
||||
// OS Info
|
||||
windowConfiguration.os = {
|
||||
release: os.release()
|
||||
release: release()
|
||||
};
|
||||
|
||||
// Config (combination of process.argv and window configuration)
|
||||
@@ -848,10 +901,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
workbench = 'vs/code/electron-browser/workbench/workbench.html';
|
||||
}
|
||||
|
||||
return (this.environmentService.sandbox ?
|
||||
FileAccess._asCodeFileUri(workbench, require) :
|
||||
FileAccess.asBrowserUri(workbench, require))
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }).toString(true);
|
||||
return FileAccess
|
||||
.asBrowserUri(workbench, require)
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
|
||||
.toString(true);
|
||||
}
|
||||
|
||||
serializeWindowState(): IWindowState {
|
||||
@@ -1161,7 +1214,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
if (visibility === 'toggle') {
|
||||
if (notify) {
|
||||
this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the Alt-key."));
|
||||
this.send('vscode:showInfoMessage', localize('hiddenMenuBar', "You can still access the menu bar by pressing the Alt-key."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,6 +1309,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
send(channel: string, ...args: any[]): void {
|
||||
if (this._win) {
|
||||
if (this._win.isDestroyed() || this._win.webContents.isDestroyed()) {
|
||||
this.logService.warn(`Sending IPC message to channel ${channel} for window that is destroyed`);
|
||||
return;
|
||||
}
|
||||
|
||||
this._win.webContents.send(channel, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user