chore(vscode): update to 1.54.2
This commit is contained in:
@ -275,6 +275,8 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
||||
|
||||
static QUERY_PARAM_PAYLOAD = 'payload';
|
||||
|
||||
readonly trusted = true;
|
||||
|
||||
constructor(
|
||||
public readonly workspace: IWorkspace,
|
||||
public readonly payload: object
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
@ -52,7 +53,7 @@ export class LanguagePackCachedDataCleaner extends Disposable {
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
|
||||
try {
|
||||
const installed: IStringDictionary<boolean> = Object.create(null);
|
||||
const metaData: LanguagePackFile = JSON.parse(await pfs.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
|
||||
const metaData: LanguagePackFile = JSON.parse(await fs.promises.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
|
||||
for (let locale of Object.keys(metaData)) {
|
||||
const entry = metaData[locale];
|
||||
installed[`${entry.hash}.${locale}`] = true;
|
||||
@ -80,7 +81,7 @@ export class LanguagePackCachedDataCleaner extends Disposable {
|
||||
continue;
|
||||
}
|
||||
const candidate = path.join(folder, entry);
|
||||
const stat = await pfs.stat(candidate);
|
||||
const stat = await fs.promises.stat(candidate);
|
||||
if (stat.isDirectory()) {
|
||||
const diff = now - stat.mtime.getTime();
|
||||
if (diff > maxAge) {
|
||||
|
@ -8,6 +8,7 @@ import { join, dirname, basename } from 'vs/base/common/path';
|
||||
import { readdir, rimraf } from 'vs/base/node/pfs';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Promises } from 'vs/base/common/async';
|
||||
|
||||
export class LogsDataCleaner extends Disposable {
|
||||
|
||||
@ -31,7 +32,7 @@ export class LogsDataCleaner extends Disposable {
|
||||
const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
|
||||
const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
|
||||
|
||||
return Promise.all(toDelete.map(name => rimraf(join(logsRoot, name))));
|
||||
return Promises.settled(toDelete.map(name => rimraf(join(logsRoot, name))));
|
||||
}).then(null, onUnexpectedError);
|
||||
}, 10 * 1000);
|
||||
|
||||
|
@ -3,10 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises } from 'fs';
|
||||
import { basename, dirname, join } from 'vs/base/common/path';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { readdir, rimraf, stat } from 'vs/base/node/pfs';
|
||||
import { readdir, rimraf } from 'vs/base/node/pfs';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
|
||||
export class NodeCachedDataCleaner {
|
||||
@ -54,7 +55,7 @@ export class NodeCachedDataCleaner {
|
||||
if (entry !== nodeCachedDataCurrent) {
|
||||
|
||||
const path = join(nodeCachedDataRootDir, entry);
|
||||
deletes.push(stat(path).then(stats => {
|
||||
deletes.push(promises.stat(path).then(stats => {
|
||||
// stat check
|
||||
// * only directories
|
||||
// * only when old enough
|
||||
|
@ -3,9 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises } from 'fs';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { readdir, readFile, rimraf } from 'vs/base/node/pfs';
|
||||
import { readdir, rimraf } from 'vs/base/node/pfs';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IBackupWorkspacesFormat } from 'vs/platform/backup/node/backup';
|
||||
@ -32,7 +33,7 @@ export class StorageDataCleaner extends Disposable {
|
||||
try {
|
||||
// Leverage the backup workspace file to find out which empty workspace is currently in use to
|
||||
// determine which empty workspace storage can safely be deleted
|
||||
const contents = await readFile(this.backupWorkspacesPath, 'utf8');
|
||||
const contents = await promises.readFile(this.backupWorkspacesPath, 'utf8');
|
||||
|
||||
const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat;
|
||||
const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(info => info.backupFolder);
|
||||
|
@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const bootstrap = bootstrapLib();
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
@ -38,5 +38,4 @@
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}());
|
||||
|
@ -3,12 +3,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import * as fs from 'fs';
|
||||
import { release } from 'os';
|
||||
import { gracefulify } from 'graceful-fs';
|
||||
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
|
||||
import { StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp';
|
||||
import { StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
@ -28,9 +29,8 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProp
|
||||
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogService } from 'vs/platform/log/common/log';
|
||||
import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
|
||||
import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogger } from 'vs/platform/log/common/log';
|
||||
import { LogLevelChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
|
||||
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@ -40,9 +40,11 @@ import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/co
|
||||
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
|
||||
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
|
||||
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
|
||||
import { IMainProcessService, MessagePortMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog';
|
||||
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
|
||||
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
@ -51,13 +53,12 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
|
||||
import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService';
|
||||
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
|
||||
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
|
||||
import { NativeStorageService2 } from 'vs/platform/storage/electron-sandbox/storageService2';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
|
||||
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
|
||||
@ -78,6 +79,11 @@ import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/con
|
||||
import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner';
|
||||
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal';
|
||||
import { LocalPtyService } from 'vs/platform/terminal/electron-browser/localPtyService';
|
||||
import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal';
|
||||
import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc';
|
||||
|
||||
class SharedProcessMain extends Disposable {
|
||||
|
||||
@ -142,13 +148,13 @@ class SharedProcessMain extends Disposable {
|
||||
|
||||
// Log
|
||||
const mainRouter = new StaticRouter(ctx => ctx === 'main');
|
||||
const loggerClient = new LoggerChannelClient(this.server.getChannel('logger', mainRouter)); // we only use this for log levels
|
||||
const logLevelClient = new LogLevelChannelClient(this.server.getChannel('logLevel', mainRouter)); // we only use this for log levels
|
||||
const multiplexLogger = this._register(new MultiplexLogService([
|
||||
this._register(new ConsoleLogService(this.configuration.logLevel)),
|
||||
this._register(new SpdLogService('sharedprocess', environmentService.logsPath, this.configuration.logLevel))
|
||||
this._register(new ConsoleLogger(this.configuration.logLevel)),
|
||||
this._register(new SpdLogLogger('sharedprocess', join(environmentService.logsPath, 'sharedprocess.log'), true, this.configuration.logLevel))
|
||||
]));
|
||||
|
||||
const logService = this._register(new FollowerLogService(loggerClient, multiplexLogger));
|
||||
const logService = this._register(new FollowerLogService(logLevelClient, multiplexLogger));
|
||||
services.set(ILogService, logService);
|
||||
|
||||
// Main Process
|
||||
@ -168,8 +174,8 @@ class SharedProcessMain extends Disposable {
|
||||
|
||||
await configurationService.initialize();
|
||||
|
||||
// Storage
|
||||
const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService);
|
||||
// Storage (global access only)
|
||||
const storageService = new NativeStorageService2(undefined, mainProcessService, environmentService);
|
||||
services.set(IStorageService, storageService);
|
||||
|
||||
await storageService.initialize();
|
||||
@ -182,7 +188,7 @@ class SharedProcessMain extends Disposable {
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
|
||||
// Native Host
|
||||
const nativeHostService = createChannelSender<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId });
|
||||
const nativeHostService = ProxyChannel.toService<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId });
|
||||
services.set(INativeHostService, nativeHostService);
|
||||
|
||||
// Download
|
||||
@ -256,51 +262,52 @@ class SharedProcessMain extends Disposable {
|
||||
services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService));
|
||||
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
|
||||
|
||||
// Terminal
|
||||
const localPtyService = this._register(new LocalPtyService(logService));
|
||||
services.set(ILocalPtyService, localPtyService);
|
||||
|
||||
return new InstantiationService(services);
|
||||
}
|
||||
|
||||
private initChannels(accessor: ServicesAccessor): void {
|
||||
|
||||
// Extensions Management
|
||||
const extensionManagementService = accessor.get(IExtensionManagementService);
|
||||
const channel = new ExtensionManagementChannel(extensionManagementService, () => null);
|
||||
const channel = new ExtensionManagementChannel(accessor.get(IExtensionManagementService), () => null);
|
||||
this.server.registerChannel('extensions', channel);
|
||||
|
||||
// Localizations
|
||||
const localizationsService = accessor.get(ILocalizationsService);
|
||||
const localizationsChannel = createChannelReceiver(localizationsService);
|
||||
const localizationsChannel = ProxyChannel.fromService(accessor.get(ILocalizationsService));
|
||||
this.server.registerChannel('localizations', localizationsChannel);
|
||||
|
||||
// Diagnostics
|
||||
const diagnosticsService = accessor.get(IDiagnosticsService);
|
||||
const diagnosticsChannel = createChannelReceiver(diagnosticsService);
|
||||
const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsService));
|
||||
this.server.registerChannel('diagnostics', diagnosticsChannel);
|
||||
|
||||
// Extension Tips
|
||||
const extensionTipsService = accessor.get(IExtensionTipsService);
|
||||
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
|
||||
const extensionTipsChannel = new ExtensionTipsChannel(accessor.get(IExtensionTipsService));
|
||||
this.server.registerChannel('extensionTipsService', extensionTipsChannel);
|
||||
|
||||
// Settings Sync
|
||||
const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService);
|
||||
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
|
||||
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(accessor.get(IUserDataSyncMachinesService));
|
||||
this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
|
||||
|
||||
const authTokenService = accessor.get(IUserDataSyncAccountService);
|
||||
const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService);
|
||||
this.server.registerChannel('userDataSyncAccount', authTokenChannel);
|
||||
const userDataSyncAccountChannel = new UserDataSyncAccountServiceChannel(accessor.get(IUserDataSyncAccountService));
|
||||
this.server.registerChannel('userDataSyncAccount', userDataSyncAccountChannel);
|
||||
|
||||
const userDataSyncStoreManagementService = accessor.get(IUserDataSyncStoreManagementService);
|
||||
const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(userDataSyncStoreManagementService);
|
||||
const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(accessor.get(IUserDataSyncStoreManagementService));
|
||||
this.server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel);
|
||||
|
||||
const userDataSyncService = accessor.get(IUserDataSyncService);
|
||||
const userDataSyncChannel = new UserDataSyncChannel(this.server, userDataSyncService, accessor.get(ILogService));
|
||||
const userDataSyncChannel = new UserDataSyncChannel(accessor.get(IUserDataSyncService), accessor.get(ILogService));
|
||||
this.server.registerChannel('userDataSync', userDataSyncChannel);
|
||||
|
||||
const userDataAutoSync = this._register(accessor.get(IInstantiationService).createInstance(UserDataAutoSyncService));
|
||||
const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync);
|
||||
this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel);
|
||||
|
||||
// Terminal
|
||||
const localPtyService = accessor.get(ILocalPtyService);
|
||||
const localPtyChannel = ProxyChannel.fromService(localPtyService);
|
||||
this.server.registerChannel(TerminalIpcChannels.LocalPty, localPtyChannel);
|
||||
}
|
||||
|
||||
private registerErrorHandler(logService: ILogService): void {
|
||||
|
@ -6,9 +6,9 @@
|
||||
/// <reference path="../../../../typings/require.d.ts" />
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Add a perf entry right from the top
|
||||
@ -179,5 +179,4 @@
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}());
|
||||
|
@ -3,25 +3,26 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron';
|
||||
import { release } from 'os';
|
||||
import { statSync } from 'fs';
|
||||
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron';
|
||||
import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform';
|
||||
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { resolveShellEnv } from 'vs/code/node/shellEnv';
|
||||
import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
|
||||
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
|
||||
import { getDelayedChannel, StaticRouter, ProxyChannel } 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 { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedProcess';
|
||||
import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@ -36,7 +37,7 @@ import product from 'vs/platform/product/common/product';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { FileProtocolHandler } from 'vs/code/electron-main/protocol';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWindowsMainService, ICodeWindow, OpenContext, WindowError } from 'vs/platform/windows/electron-main/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
@ -45,28 +46,25 @@ import { Win32UpdateService } from 'vs/platform/update/electron-main/updateServi
|
||||
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
|
||||
import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin';
|
||||
import { IssueMainService, IIssueMainService } from 'vs/platform/issue/electron-main/issueMainService';
|
||||
import { LoggerChannel } from 'vs/platform/log/common/logIpc';
|
||||
import { LoggerChannel, LogLevelChannel } from 'vs/platform/log/common/logIpc';
|
||||
import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
|
||||
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
|
||||
import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
|
||||
import { sep, posix, join, isAbsolute } from 'vs/base/common/path';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
|
||||
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
|
||||
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
|
||||
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
|
||||
import { StorageDatabaseChannel } from 'vs/platform/storage/electron-main/storageIpc';
|
||||
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
|
||||
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 { 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 { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/electron-main/extensionHostDebugIpc';
|
||||
import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
|
||||
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
|
||||
@ -90,17 +88,20 @@ import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/commo
|
||||
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
|
||||
/**
|
||||
* The main VS Code application. There will only ever be one instance,
|
||||
* even if the user starts many instances (e.g. from the command line).
|
||||
*/
|
||||
export class CodeApplication extends Disposable {
|
||||
private windowsMainService: IWindowsMainService | undefined;
|
||||
private dialogMainService: IDialogMainService | undefined;
|
||||
private nativeHostMainService: INativeHostMainService | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly mainIpcServer: NodeIPCServer,
|
||||
private readonly mainProcessNodeIpcServer: NodeIPCServer,
|
||||
private readonly userEnv: IProcessEnvironment,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IInstantiationService private readonly mainInstantiationService: IInstantiationService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
|
||||
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@ -166,7 +167,7 @@ export class CodeApplication extends Disposable {
|
||||
event.preventDefault();
|
||||
});
|
||||
app.on('remote-get-current-web-contents', event => {
|
||||
if (this.environmentService.args.driver) {
|
||||
if (this.environmentMainService.args.driver) {
|
||||
return; // the driver needs access to web contents
|
||||
}
|
||||
|
||||
@ -188,7 +189,7 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
const srcUri = uri.fsPath.toLowerCase();
|
||||
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
|
||||
const rootUri = URI.file(this.environmentMainService.appRoot).fsPath.toLowerCase();
|
||||
|
||||
return srcUri.startsWith(rootUri + sep);
|
||||
};
|
||||
@ -223,11 +224,19 @@ export class CodeApplication extends Disposable {
|
||||
this.nativeHostMainService?.openExternal(undefined, url);
|
||||
});
|
||||
|
||||
session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => {
|
||||
const webviewFrameUrl = 'about:blank?webviewFrame';
|
||||
|
||||
session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => {
|
||||
if (details.requestingUrl === webviewFrameUrl) {
|
||||
return callback(permission === 'clipboard-read');
|
||||
}
|
||||
return callback(false);
|
||||
});
|
||||
|
||||
session.defaultSession.setPermissionCheckHandler((webContents, permission /* 'media' */) => {
|
||||
session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => {
|
||||
if (details.requestingUrl === webviewFrameUrl) {
|
||||
return permission === 'clipboard-read';
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
@ -253,7 +262,7 @@ export class CodeApplication extends Disposable {
|
||||
runningTimeout = setTimeout(() => {
|
||||
this.windowsMainService?.open({
|
||||
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
|
||||
cli: this.environmentService.args,
|
||||
cli: this.environmentMainService.args,
|
||||
urisToOpen: macOpenFileURIs,
|
||||
gotoLineMode: false,
|
||||
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
|
||||
@ -326,7 +335,7 @@ export class CodeApplication extends Disposable {
|
||||
args = window.config;
|
||||
env = { ...process.env, ...window.config.userEnv };
|
||||
} else {
|
||||
args = this.environmentService.args;
|
||||
args = this.environmentMainService.args;
|
||||
env = process.env;
|
||||
}
|
||||
|
||||
@ -374,7 +383,7 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentService.cachedLanguagesPath, !isLinux)) {
|
||||
if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentMainService.cachedLanguagesPath, !isLinux)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -386,7 +395,7 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// take only the message and stack property
|
||||
const friendlyError = {
|
||||
message: err.message,
|
||||
message: `[uncaught exception in main]: ${err.message}`,
|
||||
stack: err.stack
|
||||
};
|
||||
|
||||
@ -402,8 +411,8 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
async startup(): Promise<void> {
|
||||
this.logService.debug('Starting VS Code');
|
||||
this.logService.debug(`from: ${this.environmentService.appRoot}`);
|
||||
this.logService.debug('args:', this.environmentService.args);
|
||||
this.logService.debug(`from: ${this.environmentMainService.appRoot}`);
|
||||
this.logService.debug('args:', this.environmentMainService.args);
|
||||
|
||||
// Make sure we associate the program with the app user model id
|
||||
// This will help Windows to associate the running program with
|
||||
@ -429,61 +438,45 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
// Setup Protocol Handler
|
||||
const fileProtocolHandler = this._register(this.instantiationService.createInstance(FileProtocolHandler));
|
||||
const fileProtocolHandler = this._register(this.mainInstantiationService.createInstance(FileProtocolHandler));
|
||||
|
||||
// Create Electron IPC Server
|
||||
const electronIpcServer = new ElectronIPCServer();
|
||||
// Main process server (electron IPC based)
|
||||
const mainProcessElectronServer = new ElectronIPCServer();
|
||||
|
||||
// Resolve unique machine ID
|
||||
this.logService.trace('Resolving machine identifier...');
|
||||
const machineId = await this.resolveMachineId();
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId}`);
|
||||
|
||||
// Spawn shared process after the first window has opened and 3s have passed
|
||||
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
|
||||
const sharedProcessClient = (async () => {
|
||||
this.logService.trace('Main->SharedProcess#connect');
|
||||
|
||||
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));
|
||||
}, 3000)).schedule();
|
||||
});
|
||||
// Shared process
|
||||
const { sharedProcess, sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId);
|
||||
|
||||
// Services
|
||||
const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessReady);
|
||||
const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady);
|
||||
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService);
|
||||
if (this.environmentMainService.driverHandle) {
|
||||
const server = await serveDriver(mainProcessElectronServer, this.environmentMainService.driverHandle, this.environmentMainService, appInstantiationService);
|
||||
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this.logService.info('Driver started at:', this.environmentMainService.driverHandle);
|
||||
this._register(server);
|
||||
}
|
||||
|
||||
// Setup Auth Handler
|
||||
this._register(appInstantiationService.createInstance(ProxyAuthHandler));
|
||||
|
||||
// Init Channels
|
||||
appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient));
|
||||
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient, fileProtocolHandler));
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer, fileProtocolHandler));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess));
|
||||
|
||||
// Tracing: Stop tracing after windows are ready if enabled
|
||||
if (this.environmentService.args.trace) {
|
||||
this.stopTracingEventually(windows);
|
||||
if (this.environmentMainService.args.trace) {
|
||||
appInstantiationService.invokeFunction(accessor => this.stopTracingEventually(accessor, windows));
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,9 +494,32 @@ export class CodeApplication extends Disposable {
|
||||
return machineId;
|
||||
}
|
||||
|
||||
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
|
||||
private setupSharedProcess(machineId: string): { sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>, sharedProcessClient: Promise<MessagePortClient> } {
|
||||
const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, this.userEnv));
|
||||
|
||||
const sharedProcessClient = (async () => {
|
||||
this.logService.trace('Main->SharedProcess#connect');
|
||||
|
||||
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;
|
||||
})();
|
||||
|
||||
return { sharedProcess, sharedProcessReady, sharedProcessClient };
|
||||
}
|
||||
|
||||
private async initServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
// Update
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
|
||||
@ -522,38 +538,63 @@ export class CodeApplication extends Disposable {
|
||||
break;
|
||||
}
|
||||
|
||||
// Windows
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv]));
|
||||
services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
|
||||
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
|
||||
services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
|
||||
|
||||
// Dialogs
|
||||
services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
|
||||
|
||||
// Launch
|
||||
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
|
||||
|
||||
// Diagnostics
|
||||
services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
|
||||
|
||||
// Issues
|
||||
services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
|
||||
|
||||
// Encryption
|
||||
services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId]));
|
||||
|
||||
// Keyboard Layout
|
||||
services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));
|
||||
|
||||
// Display
|
||||
services.set(IDisplayMainService, new SyncDescriptor(DisplayMainService));
|
||||
|
||||
// Native Host
|
||||
services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, [sharedProcess]));
|
||||
|
||||
// Webview Manager
|
||||
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
|
||||
|
||||
// Workspaces
|
||||
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService));
|
||||
services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService));
|
||||
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
|
||||
|
||||
// Menubar
|
||||
services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
|
||||
|
||||
// Extension URL Trust
|
||||
services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService));
|
||||
|
||||
const storageMainService = new StorageMainService(this.logService, this.environmentService);
|
||||
services.set(IStorageMainService, storageMainService);
|
||||
this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close()));
|
||||
// Storage
|
||||
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
|
||||
|
||||
const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService);
|
||||
// Backups
|
||||
const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService);
|
||||
services.set(IBackupMainService, backupMainService);
|
||||
|
||||
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
|
||||
// URL handling
|
||||
services.set(IURLService, new SyncDescriptor(NativeURLService));
|
||||
services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService));
|
||||
|
||||
// Telemetry
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
if (!this.environmentMainService.isExtensionDevelopment && !this.environmentMainService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
|
||||
const appender = new TelemetryAppenderClient(channel);
|
||||
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 commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, product.commit, product.version, machineId, product.msftInternalDomains, this.environmentMainService.installSourcePath);
|
||||
const piiPaths = [this.environmentMainService.appRoot, this.environmentMainService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
@ -564,123 +605,99 @@ export class CodeApplication extends Disposable {
|
||||
// Init services that require it
|
||||
await backupMainService.initialize();
|
||||
|
||||
return this.instantiationService.createChild(services);
|
||||
return this.mainInstantiationService.createChild(services);
|
||||
}
|
||||
|
||||
private stopTracingEventually(windows: ICodeWindow[]): void {
|
||||
this.logService.info(`Tracing: waiting for windows to get ready...`);
|
||||
private initChannels(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, sharedProcessClient: Promise<MessagePortClient>): void {
|
||||
|
||||
let recordingStopped = false;
|
||||
const stopRecording = async (timeout: boolean) => {
|
||||
if (recordingStopped) {
|
||||
return;
|
||||
}
|
||||
// Launch: this one is explicitly registered to the node.js
|
||||
// server because when a second instance starts up, that is
|
||||
// the only possible connection between the first and the
|
||||
// second instance. Electron IPC does not work across apps.
|
||||
const launchChannel = ProxyChannel.fromService(accessor.get(ILaunchMainService), { disableMarshalling: true });
|
||||
this.mainProcessNodeIpcServer.registerChannel('launch', launchChannel);
|
||||
|
||||
recordingStopped = true; // only once
|
||||
// Update
|
||||
const updateChannel = new UpdateChannel(accessor.get(IUpdateService));
|
||||
mainProcessElectronServer.registerChannel('update', updateChannel);
|
||||
|
||||
const path = await contentTracing.stopRecording(joinPath(this.environmentService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath);
|
||||
// Issues
|
||||
const issueChannel = ProxyChannel.fromService(accessor.get(IIssueMainService));
|
||||
mainProcessElectronServer.registerChannel('issue', issueChannel);
|
||||
|
||||
if (!timeout) {
|
||||
this.dialogMainService?.showMessageBox({
|
||||
type: 'info',
|
||||
message: localize('trace.message', "Successfully created trace."),
|
||||
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
|
||||
buttons: [localize('trace.ok', "OK")]
|
||||
}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
|
||||
} else {
|
||||
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
|
||||
}
|
||||
};
|
||||
// Encryption
|
||||
const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService));
|
||||
mainProcessElectronServer.registerChannel('encryption', encryptionChannel);
|
||||
|
||||
// Wait up to 30s before creating the trace anyways
|
||||
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
|
||||
// Keyboard Layout
|
||||
const keyboardLayoutChannel = ProxyChannel.fromService(accessor.get(IKeyboardLayoutMainService));
|
||||
mainProcessElectronServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
|
||||
|
||||
// Wait for all windows to get ready and stop tracing then
|
||||
Promise.all(windows.map(window => window.ready())).then(() => {
|
||||
clearTimeout(timeoutHandle);
|
||||
stopRecording(false);
|
||||
});
|
||||
}
|
||||
// Display
|
||||
const displayChannel = ProxyChannel.fromService(accessor.get(IDisplayMainService));
|
||||
mainProcessElectronServer.registerChannel('display', displayChannel);
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<MessagePortClient>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
|
||||
|
||||
// Register more Main IPC services
|
||||
const launchMainService = accessor.get(ILaunchMainService);
|
||||
const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true });
|
||||
this.mainIpcServer.registerChannel('launch', launchChannel);
|
||||
|
||||
// Register more Electron IPC services
|
||||
const updateService = accessor.get(IUpdateService);
|
||||
const updateChannel = new UpdateChannel(updateService);
|
||||
electronIpcServer.registerChannel('update', updateChannel);
|
||||
|
||||
const issueMainService = accessor.get(IIssueMainService);
|
||||
const issueChannel = createChannelReceiver(issueMainService);
|
||||
electronIpcServer.registerChannel('issue', issueChannel);
|
||||
|
||||
const encryptionMainService = accessor.get(IEncryptionMainService);
|
||||
const encryptionChannel = createChannelReceiver(encryptionMainService);
|
||||
electronIpcServer.registerChannel('encryption', encryptionChannel);
|
||||
|
||||
const keyboardLayoutMainService = accessor.get(IKeyboardLayoutMainService);
|
||||
const keyboardLayoutChannel = createChannelReceiver(keyboardLayoutMainService);
|
||||
electronIpcServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
|
||||
|
||||
const displayMainService = accessor.get(IDisplayMainService);
|
||||
const displayChannel = createChannelReceiver(displayMainService);
|
||||
electronIpcServer.registerChannel('display', displayChannel);
|
||||
|
||||
const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService);
|
||||
const nativeHostChannel = createChannelReceiver(this.nativeHostMainService);
|
||||
electronIpcServer.registerChannel('nativeHost', nativeHostChannel);
|
||||
// Native host (main & shared process)
|
||||
this.nativeHostMainService = accessor.get(INativeHostMainService);
|
||||
const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService);
|
||||
mainProcessElectronServer.registerChannel('nativeHost', nativeHostChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel));
|
||||
|
||||
const workspacesService = accessor.get(IWorkspacesService);
|
||||
const workspacesChannel = createChannelReceiver(workspacesService);
|
||||
electronIpcServer.registerChannel('workspaces', workspacesChannel);
|
||||
// Workspaces
|
||||
const workspacesChannel = ProxyChannel.fromService(accessor.get(IWorkspacesService));
|
||||
mainProcessElectronServer.registerChannel('workspaces', workspacesChannel);
|
||||
|
||||
const menubarMainService = accessor.get(IMenubarMainService);
|
||||
const menubarChannel = createChannelReceiver(menubarMainService);
|
||||
electronIpcServer.registerChannel('menubar', menubarChannel);
|
||||
// Menubar
|
||||
const menubarChannel = ProxyChannel.fromService(accessor.get(IMenubarMainService));
|
||||
mainProcessElectronServer.registerChannel('menubar', menubarChannel);
|
||||
|
||||
const urlService = accessor.get(IURLService);
|
||||
const urlChannel = createChannelReceiver(urlService);
|
||||
electronIpcServer.registerChannel('url', urlChannel);
|
||||
// URL handling
|
||||
const urlChannel = ProxyChannel.fromService(accessor.get(IURLService));
|
||||
mainProcessElectronServer.registerChannel('url', urlChannel);
|
||||
|
||||
const extensionUrlTrustService = accessor.get(IExtensionUrlTrustService);
|
||||
const extensionUrlTrustChannel = createChannelReceiver(extensionUrlTrustService);
|
||||
electronIpcServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel);
|
||||
// Extension URL Trust
|
||||
const extensionUrlTrustChannel = ProxyChannel.fromService(accessor.get(IExtensionUrlTrustService));
|
||||
mainProcessElectronServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel);
|
||||
|
||||
const webviewManagerService = accessor.get(IWebviewManagerService);
|
||||
const webviewChannel = createChannelReceiver(webviewManagerService);
|
||||
electronIpcServer.registerChannel('webview', webviewChannel);
|
||||
// Webview Manager
|
||||
const webviewChannel = ProxyChannel.fromService(accessor.get(IWebviewManagerService));
|
||||
mainProcessElectronServer.registerChannel('webview', webviewChannel);
|
||||
|
||||
const storageMainService = accessor.get(IStorageMainService);
|
||||
const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService));
|
||||
electronIpcServer.registerChannel('storage', storageChannel);
|
||||
// Storage (main & shared process)
|
||||
const storageChannel = this._register(new StorageDatabaseChannel(this.logService, accessor.get(IStorageMainService)));
|
||||
mainProcessElectronServer.registerChannel('storage', storageChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('storage', storageChannel));
|
||||
|
||||
const loggerChannel = new LoggerChannel(accessor.get(ILogService));
|
||||
electronIpcServer.registerChannel('logger', loggerChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
|
||||
// Log Level (main & shared process)
|
||||
const logLevelChannel = new LogLevelChannel(accessor.get(ILogService));
|
||||
mainProcessElectronServer.registerChannel('logLevel', logLevelChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('logLevel', logLevelChannel));
|
||||
|
||||
// Logger
|
||||
const loggerChannel = new LoggerChannel(accessor.get(ILoggerService),);
|
||||
mainProcessElectronServer.registerChannel('logger', loggerChannel);
|
||||
|
||||
// Extension Host Debug Broadcasting
|
||||
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
|
||||
mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel);
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
|
||||
fileProtocolHandler.injectWindowsMainService(windowsMainService);
|
||||
|
||||
// ExtensionHost Debug broadcast service
|
||||
electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
|
||||
const urlService = accessor.get(IURLService);
|
||||
const nativeHostMainService = accessor.get(INativeHostMainService);
|
||||
|
||||
// Signal phase: ready (services set)
|
||||
this.lifecycleMainService.phase = LifecycleMainPhase.Ready;
|
||||
|
||||
// Propagate to clients
|
||||
this.dialogMainService = accessor.get(IDialogMainService);
|
||||
// Forward windows main service to protocol handler
|
||||
fileProtocolHandler.injectWindowsMainService(this.windowsMainService);
|
||||
|
||||
// Check for initial URLs to handle from protocol link invocations
|
||||
const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = [];
|
||||
const pendingProtocolLinksToHandle = [
|
||||
// Windows/Linux: protocol handler invokes CLI with --open-url
|
||||
...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [],
|
||||
...this.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : [],
|
||||
|
||||
// macOS: open-url events
|
||||
...((<any>global).getOpenUrls() || []) as string[]
|
||||
@ -717,7 +734,7 @@ export class CodeApplication extends Disposable {
|
||||
// or open new windows. The URL handler will be invoked from
|
||||
// protocol invocations outside of VSCode.
|
||||
const app = this;
|
||||
const environmentService = this.environmentService;
|
||||
const environmentService = this.environmentMainService;
|
||||
urlService.registerHandler({
|
||||
async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
|
||||
|
||||
@ -768,14 +785,14 @@ export class CodeApplication extends Disposable {
|
||||
}));
|
||||
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
|
||||
const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
|
||||
const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
|
||||
const urlHandlerChannel = mainProcessElectronServer.getChannel('urlHandler', urlHandlerRouter);
|
||||
urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel));
|
||||
|
||||
// Watch Electron URLs and forward them to the UrlService
|
||||
this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentService));
|
||||
this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentMainService));
|
||||
|
||||
// Open our first window
|
||||
const args = this.environmentService.args;
|
||||
const args = this.environmentMainService.args;
|
||||
const macOpenFiles: string[] = (<any>global).macOpenFiles;
|
||||
const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
const hasCliArgs = args._.length;
|
||||
@ -845,7 +862,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, this.environmentService), 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.environmentMainService), 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
|
||||
});
|
||||
@ -909,11 +926,36 @@ export class CodeApplication extends Disposable {
|
||||
return { fileUri: URI.file(path) };
|
||||
}
|
||||
|
||||
private async afterWindowOpen(accessor: ServicesAccessor): Promise<void> {
|
||||
private async afterWindowOpen(accessor: ServicesAccessor, sharedProcess: SharedProcess): Promise<void> {
|
||||
|
||||
// Signal phase: after window open
|
||||
this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
|
||||
|
||||
// Observe shared process for errors
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
this._register(sharedProcess.onDidError(({ type, details }) => {
|
||||
|
||||
// Logging
|
||||
let message: string;
|
||||
if (typeof details === 'string') {
|
||||
message = details;
|
||||
} else {
|
||||
message = `SharedProcess: crashed (detail: ${details.reason})`;
|
||||
}
|
||||
onUnexpectedError(new Error(message));
|
||||
|
||||
// Telemetry
|
||||
type SharedProcessErrorClassification = {
|
||||
type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
|
||||
reason: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
|
||||
};
|
||||
type SharedProcessErrorEvent = {
|
||||
type: WindowError;
|
||||
reason: string | undefined;
|
||||
};
|
||||
telemetryService.publicLog2<SharedProcessErrorEvent, SharedProcessErrorClassification>('sharedprocesserror', { type, reason: typeof details !== 'string' ? details.reason : undefined });
|
||||
}));
|
||||
|
||||
// Windows: install mutex
|
||||
const win32MutexName = product.win32MutexName;
|
||||
if (isWindows && win32MutexName) {
|
||||
@ -941,13 +983,13 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
// Start to fetch shell environment (if needed) after window has opened
|
||||
resolveShellEnv(this.logService, this.environmentService.args, process.env);
|
||||
resolveShellEnv(this.logService, this.environmentMainService.args, process.env);
|
||||
|
||||
// If enable-crash-reporter argv is undefined then this is a fresh start,
|
||||
// based on telemetry.enableCrashreporter settings, generate a UUID which
|
||||
// will be used as crash reporter id and also update the json file.
|
||||
try {
|
||||
const argvContent = await this.fileService.readFile(this.environmentService.argvResource);
|
||||
const argvContent = await this.fileService.readFile(this.environmentMainService.argvResource);
|
||||
const argvString = argvContent.value.toString();
|
||||
const argvJSON = JSON.parse(stripComments(argvString));
|
||||
if (argvJSON['enable-crash-reporter'] === undefined) {
|
||||
@ -965,10 +1007,47 @@ 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));
|
||||
await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private stopTracingEventually(accessor: ServicesAccessor, windows: ICodeWindow[]): void {
|
||||
this.logService.info(`Tracing: waiting for windows to get ready...`);
|
||||
|
||||
const dialogMainService = accessor.get(IDialogMainService);
|
||||
|
||||
let recordingStopped = false;
|
||||
const stopRecording = async (timeout: boolean) => {
|
||||
if (recordingStopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
recordingStopped = true; // only once
|
||||
|
||||
const path = await contentTracing.stopRecording(joinPath(this.environmentMainService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath);
|
||||
|
||||
if (!timeout) {
|
||||
dialogMainService.showMessageBox({
|
||||
type: 'info',
|
||||
message: localize('trace.message', "Successfully created trace."),
|
||||
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
|
||||
buttons: [localize('trace.ok', "OK")]
|
||||
}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
|
||||
} else {
|
||||
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Wait up to 30s before creating the trace anyways
|
||||
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
|
||||
|
||||
// Wait for all windows to get ready and stop tracing then
|
||||
Promise.all(windows.map(window => window.ready())).then(() => {
|
||||
clearTimeout(timeoutHandle);
|
||||
stopRecording(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ export class ProxyAuthHandler extends Disposable {
|
||||
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);
|
||||
window.win?.webContents.off('ipc-message', proxyAuthResponseHandler);
|
||||
|
||||
// We got credentials from the window
|
||||
if (reply) {
|
||||
@ -229,7 +229,7 @@ export class ProxyAuthHandler extends Disposable {
|
||||
}
|
||||
};
|
||||
|
||||
window.win.webContents.on('ipc-message', proxyAuthResponseHandler);
|
||||
window.win?.webContents.on('ipc-message', proxyAuthResponseHandler);
|
||||
});
|
||||
|
||||
// Remember credentials for the session in case
|
||||
|
@ -5,15 +5,14 @@
|
||||
|
||||
import 'vs/platform/update/common/update.config.contribution';
|
||||
import { app, dialog } from 'electron';
|
||||
import { unlinkSync } from 'fs';
|
||||
import { promises, 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 { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
|
||||
import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ProxyChannel } 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';
|
||||
@ -21,7 +20,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { ILogService, ConsoleMainLogger, MultiplexLogService, getLogLevel, ILoggerService } from 'vs/platform/log/common/log';
|
||||
import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@ -32,9 +31,9 @@ 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 { getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { ExpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
@ -48,158 +47,143 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel';
|
||||
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import { rtrim, trim } from 'vs/base/common/strings';
|
||||
import { basename, resolve } from 'vs/base/common/path';
|
||||
import { basename, join, 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';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
|
||||
class ExpectedError extends Error {
|
||||
readonly isExpected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main VS Code entry point.
|
||||
*
|
||||
* Note: This class can exist more than once for example when VS Code is already
|
||||
* running and a second instance is started from the command line. It will always
|
||||
* try to communicate with an existing instance to prevent that 2 VS Code instances
|
||||
* are running at the same time.
|
||||
*/
|
||||
class CodeMain {
|
||||
|
||||
main(): void {
|
||||
try {
|
||||
this.startup();
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
app.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private async startup(): Promise<void> {
|
||||
|
||||
// Set the error handler early enough so that we are not getting the
|
||||
// default electron error dialog popping up
|
||||
setUnexpectedErrorHandler(err => console.error(err));
|
||||
|
||||
// Parse arguments
|
||||
let args: NativeParsedArgs;
|
||||
try {
|
||||
args = parseMainProcessArgv(process.argv);
|
||||
args = this.validatePaths(args);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
app.exit(1);
|
||||
// Resolve command line arguments
|
||||
const args = this.resolveArgs();
|
||||
|
||||
return;
|
||||
}
|
||||
// Create services
|
||||
const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService] = this.createServices(args);
|
||||
|
||||
// If we are started with --wait create a random temporary file
|
||||
// and pass it over to the starting instance. We can use this file
|
||||
// to wait for it to be deleted to monitor that the edited file
|
||||
// is closed and then exit the waiting process.
|
||||
//
|
||||
// Note: we are not doing this if the wait marker has been already
|
||||
// added as argument. This can happen if Code was started from CLI.
|
||||
if (args.wait && !args.waitMarkerFilePath) {
|
||||
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
|
||||
if (waitMarkerFilePath) {
|
||||
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
|
||||
args.waitMarkerFilePath = waitMarkerFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
// Launch
|
||||
this.startup(args);
|
||||
}
|
||||
|
||||
private async startup(args: NativeParsedArgs): Promise<void> {
|
||||
|
||||
// We need to buffer the spdlog logs until we are sure
|
||||
// we are the only instance running, otherwise we'll have concurrent
|
||||
// log file access on Windows (https://github.com/microsoft/vscode/issues/41218)
|
||||
const bufferLogService = new BufferLogService();
|
||||
|
||||
const [instantiationService, instanceEnvironment, environmentService] = this.createServices(args, bufferLogService);
|
||||
try {
|
||||
|
||||
// Init services
|
||||
await instantiationService.invokeFunction(async accessor => {
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
const stateService = accessor.get(IStateService);
|
||||
try {
|
||||
await this.initServices(environmentService, configurationService, stateService);
|
||||
} catch (error) {
|
||||
|
||||
try {
|
||||
await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService);
|
||||
} catch (error) {
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
this.handleStartupDataDirError(environmentService, error);
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
this.handleStartupDataDirError(environmentService, error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Startup
|
||||
await instantiationService.invokeFunction(async accessor => {
|
||||
const logService = accessor.get(ILogService);
|
||||
const lifecycleMainService = accessor.get(ILifecycleMainService);
|
||||
const fileService = accessor.get(IFileService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
|
||||
const mainIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true);
|
||||
// Create the main IPC server by trying to be the server
|
||||
// If this throws an error it means we are not the first
|
||||
// instance of VS Code running and so we would quit.
|
||||
const mainProcessNodeIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true);
|
||||
|
||||
bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
|
||||
// Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906)
|
||||
bufferLogService.logger = new SpdLogLogger('main', join(environmentService.logsPath, 'main.log'), true, bufferLogService.getLevel());
|
||||
|
||||
// Lifecycle
|
||||
once(lifecycleMainService.onWillShutdown)(() => {
|
||||
fileService.dispose();
|
||||
(configurationService as ConfigurationService).dispose();
|
||||
configurationService.dispose();
|
||||
});
|
||||
|
||||
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
|
||||
return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup();
|
||||
});
|
||||
} catch (error) {
|
||||
instantiationService.invokeFunction(this.quit, error);
|
||||
}
|
||||
}
|
||||
|
||||
private createServices(args: NativeParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService] {
|
||||
private createServices(args: NativeParsedArgs): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService] {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
// Environment
|
||||
const environmentService = new EnvironmentMainService(args);
|
||||
const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(IEnvironmentMainService, environmentService);
|
||||
|
||||
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
|
||||
// Log: We need to buffer the spdlog logs until we are sure
|
||||
// we are the only instance running, otherwise we'll have concurrent
|
||||
// log file access on Windows (https://github.com/microsoft/vscode/issues/41218)
|
||||
const bufferLogService = new BufferLogService();
|
||||
const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentService)), bufferLogService]);
|
||||
process.once('exit', () => logService.dispose());
|
||||
services.set(ILogService, logService);
|
||||
|
||||
// Files
|
||||
const fileService = new FileService(logService);
|
||||
services.set(IFileService, fileService);
|
||||
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
|
||||
services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource, fileService));
|
||||
// Logger
|
||||
services.set(ILoggerService, new LoggerService(logService, fileService));
|
||||
|
||||
// Configuration
|
||||
const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
|
||||
services.set(IConfigurationService, configurationService);
|
||||
|
||||
// Lifecycle
|
||||
services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
|
||||
services.set(IStateService, new SyncDescriptor(StateService));
|
||||
|
||||
// State
|
||||
const stateService = new StateService(environmentService, logService);
|
||||
services.set(IStateService, stateService);
|
||||
|
||||
// Request
|
||||
services.set(IRequestService, new SyncDescriptor(RequestMainService));
|
||||
|
||||
// Themes
|
||||
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
|
||||
|
||||
// Signing
|
||||
services.set(ISignService, new SyncDescriptor(SignService));
|
||||
|
||||
// Product
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
|
||||
// Tunnel
|
||||
services.set(ITunnelService, new SyncDescriptor(TunnelService));
|
||||
|
||||
return [new InstantiationService(services, true), instanceEnvironment, environmentService];
|
||||
return [new InstantiationService(services, true), instanceEnvironment, environmentService, configurationService, stateService, bufferLogService];
|
||||
}
|
||||
|
||||
private initServices(environmentService: IEnvironmentMainService, configurationService: ConfigurationService, stateService: StateService): Promise<unknown> {
|
||||
|
||||
// Environment service (paths)
|
||||
const environmentServiceInitialization = Promise.all<void | undefined>([
|
||||
environmentService.extensionsPath,
|
||||
environmentService.nodeCachedDataDir,
|
||||
environmentService.logsPath,
|
||||
environmentService.globalStorageHome.fsPath,
|
||||
environmentService.workspaceStorageHome.fsPath,
|
||||
environmentService.backupHome
|
||||
].map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
|
||||
|
||||
// Configuration service
|
||||
const configurationServiceInitialization = configurationService.initialize();
|
||||
|
||||
// State service
|
||||
const stateServiceInitialization = stateService.init();
|
||||
|
||||
return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]);
|
||||
}
|
||||
|
||||
private patchEnvironment(environmentService: IEnvironmentMainService): IProcessEnvironment {
|
||||
private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment {
|
||||
const instanceEnvironment: IProcessEnvironment = {
|
||||
VSCODE_IPC_HOOK: environmentService.mainIPCHandle
|
||||
VSCODE_IPC_HOOK: environmentMainService.mainIPCHandle
|
||||
};
|
||||
|
||||
['VSCODE_NLS_CONFIG', 'VSCODE_PORTABLE'].forEach(key => {
|
||||
@ -214,15 +198,36 @@ class CodeMain {
|
||||
return instanceEnvironment;
|
||||
}
|
||||
|
||||
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise<NodeIPCServer> {
|
||||
private initServices(environmentMainService: IEnvironmentMainService, configurationService: ConfigurationService, stateService: StateService): Promise<unknown> {
|
||||
|
||||
// Environment service (paths)
|
||||
const environmentServiceInitialization = Promise.all<void | undefined>([
|
||||
environmentMainService.extensionsPath,
|
||||
environmentMainService.nodeCachedDataDir,
|
||||
environmentMainService.logsPath,
|
||||
environmentMainService.globalStorageHome.fsPath,
|
||||
environmentMainService.workspaceStorageHome.fsPath,
|
||||
environmentMainService.backupHome
|
||||
].map(path => path ? promises.mkdir(path, { recursive: true }) : undefined));
|
||||
|
||||
// Configuration service
|
||||
const configurationServiceInitialization = configurationService.initialize();
|
||||
|
||||
// State service
|
||||
const stateServiceInitialization = stateService.init();
|
||||
|
||||
return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]);
|
||||
}
|
||||
|
||||
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentMainService: 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: NodeIPCServer;
|
||||
let mainProcessNodeIpcServer: NodeIPCServer;
|
||||
try {
|
||||
server = await nodeIPCServe(environmentService.mainIPCHandle);
|
||||
once(lifecycleMainService.onWillShutdown)(() => server.dispose());
|
||||
mainProcessNodeIpcServer = await nodeIPCServe(environmentMainService.mainIPCHandle);
|
||||
once(lifecycleMainService.onWillShutdown)(() => mainProcessNodeIpcServer.dispose());
|
||||
} catch (error) {
|
||||
|
||||
// Handle unexpected errors (the only expected error is EADDRINUSE that
|
||||
@ -230,7 +235,7 @@ class CodeMain {
|
||||
if (error.code !== 'EADDRINUSE') {
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
this.handleStartupDataDirError(environmentService, error);
|
||||
this.handleStartupDataDirError(environmentMainService, error);
|
||||
|
||||
// Any other runtime error is just printed to the console
|
||||
throw error;
|
||||
@ -239,7 +244,7 @@ class CodeMain {
|
||||
// there's a running instance, let's connect to it
|
||||
let client: NodeIPCClient<string>;
|
||||
try {
|
||||
client = await nodeIPCConnect(environmentService.mainIPCHandle, 'main');
|
||||
client = await nodeIPCConnect(environmentMainService.mainIPCHandle, 'main');
|
||||
} catch (error) {
|
||||
|
||||
// Handle unexpected connection errors by showing a dialog to the user
|
||||
@ -258,18 +263,18 @@ class CodeMain {
|
||||
// let's delete it, since we can't connect to it and then
|
||||
// retry the whole thing
|
||||
try {
|
||||
unlinkSync(environmentService.mainIPCHandle);
|
||||
unlinkSync(environmentMainService.mainIPCHandle);
|
||||
} catch (error) {
|
||||
logService.warn('Could not delete obsolete instance handle', error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, false);
|
||||
return this.doStartup(args, logService, environmentMainService, lifecycleMainService, instantiationService, false);
|
||||
}
|
||||
|
||||
// Tests from CLI require to be the only instance currently
|
||||
if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) {
|
||||
if (environmentMainService.extensionTestsLocationURI && !environmentMainService.debugExtensionHost.break) {
|
||||
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
|
||||
logService.error(msg);
|
||||
client.dispose();
|
||||
@ -290,7 +295,7 @@ class CodeMain {
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
const launchService = createChannelSender<ILaunchMainService>(client.getChannel('launch'), { disableMarshalling: true });
|
||||
const launchService = ProxyChannel.toService<ILaunchMainService>(client.getChannel('launch'), { disableMarshalling: true });
|
||||
|
||||
// Process Info
|
||||
if (args.status) {
|
||||
@ -336,12 +341,12 @@ class CodeMain {
|
||||
// instance to startup. Otherwise we would wrongly overwrite the PID
|
||||
process.env['VSCODE_PID'] = String(process.pid);
|
||||
|
||||
return server;
|
||||
return mainProcessNodeIpcServer;
|
||||
}
|
||||
|
||||
private handleStartupDataDirError(environmentService: IEnvironmentMainService, error: NodeJS.ErrnoException): void {
|
||||
private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, error: NodeJS.ErrnoException): void {
|
||||
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentService));
|
||||
const directories = coalesce([environmentMainService.userDataPath, environmentMainService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentMainService));
|
||||
|
||||
this.showStartupWarningDialog(
|
||||
localize('startupDataDirError', "Unable to write program user data."),
|
||||
@ -364,9 +369,9 @@ class CodeMain {
|
||||
});
|
||||
}
|
||||
|
||||
private async windowsAllowSetForegroundWindow(launchService: ILaunchMainService, logService: ILogService): Promise<void> {
|
||||
private async windowsAllowSetForegroundWindow(launchMainService: ILaunchMainService, logService: ILogService): Promise<void> {
|
||||
if (isWindows) {
|
||||
const processId = await launchService.getMainProcessId();
|
||||
const processId = await launchMainService.getMainProcessId();
|
||||
|
||||
logService.trace('Sending some foreground love to the running instance:', processId);
|
||||
|
||||
@ -403,7 +408,30 @@ class CodeMain {
|
||||
lifecycleMainService.kill(exitCode);
|
||||
}
|
||||
|
||||
//#region Helpers
|
||||
//#region Command line arguments utilities
|
||||
|
||||
private resolveArgs(): NativeParsedArgs {
|
||||
|
||||
// Parse arguments
|
||||
const args = this.validatePaths(parseMainProcessArgv(process.argv));
|
||||
|
||||
// If we are started with --wait create a random temporary file
|
||||
// and pass it over to the starting instance. We can use this file
|
||||
// to wait for it to be deleted to monitor that the edited file
|
||||
// is closed and then exit the waiting process.
|
||||
//
|
||||
// Note: we are not doing this if the wait marker has been already
|
||||
// added as argument. This can happen if Code was started from CLI.
|
||||
if (args.wait && !args.waitMarkerFilePath) {
|
||||
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
|
||||
if (waitMarkerFilePath) {
|
||||
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
|
||||
args.waitMarkerFilePath = waitMarkerFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
private validatePaths(args: NativeParsedArgs): NativeParsedArgs {
|
||||
|
||||
@ -484,11 +512,11 @@ class CodeMain {
|
||||
private toPath(pathWithLineAndCol: IPathWithLineAndColumn): string {
|
||||
const segments = [pathWithLineAndCol.path];
|
||||
|
||||
if (isNumber(pathWithLineAndCol.line)) {
|
||||
if (typeof pathWithLineAndCol.line === 'number') {
|
||||
segments.push(String(pathWithLineAndCol.line));
|
||||
}
|
||||
|
||||
if (isNumber(pathWithLineAndCol.column)) {
|
||||
if (typeof pathWithLineAndCol.column === 'number') {
|
||||
segments.push(String(pathWithLineAndCol.column));
|
||||
}
|
||||
|
||||
|
@ -31,33 +31,30 @@ export class FileProtocolHandler extends Disposable {
|
||||
// Define an initial set of roots we allow loading from
|
||||
// - appRoot : all files installed as part of the app
|
||||
// - extensions : all files shipped from extensions
|
||||
// - storage : all files in global and workspace storage (https://github.com/microsoft/vscode/issues/116735)
|
||||
this.validRoots.set(URI.file(environmentService.appRoot), true);
|
||||
this.validRoots.set(URI.file(environmentService.extensionsPath), true);
|
||||
this.validRoots.set(environmentService.globalStorageHome, true);
|
||||
this.validRoots.set(environmentService.workspaceStorageHome, true);
|
||||
|
||||
// Register vscode-file:// handler
|
||||
defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback));
|
||||
|
||||
// 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));
|
||||
}
|
||||
// Intercept any file:// access
|
||||
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 (isPreferringBrowserCodeLoad) {
|
||||
defaultSession.protocol.uninterceptProtocol(Schemas.file);
|
||||
}
|
||||
defaultSession.protocol.uninterceptProtocol(Schemas.file);
|
||||
}));
|
||||
}
|
||||
|
||||
injectWindowsMainService(windowsMainService: IWindowsMainService): void {
|
||||
this._register(windowsMainService.onWindowReady(window => {
|
||||
this._register(windowsMainService.onDidSignalReadyWindow(window => {
|
||||
if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) {
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(Event.any(window.onClose, window.onDestroy)(() => disposables.dispose()));
|
||||
disposables.add(Event.any(window.onDidClose, window.onDidDestroy)(() => disposables.dispose()));
|
||||
|
||||
// Allow access to extension development path
|
||||
if (window.config.extensionDevelopmentPath) {
|
||||
@ -85,10 +82,29 @@ export class FileProtocolHandler extends Disposable {
|
||||
}
|
||||
|
||||
private async handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) {
|
||||
const uri = URI.parse(request.url);
|
||||
const fileUri = URI.parse(request.url);
|
||||
|
||||
this.logService.error(`Refused to load resource ${uri.fsPath} from ${Schemas.file}: protocol`);
|
||||
callback({ error: -3 /* ABORTED */ });
|
||||
// isPreferringBrowserCodeLoad: false
|
||||
// => ensure the file path is in our expected roots
|
||||
if (!isPreferringBrowserCodeLoad) {
|
||||
if (this.validRoots.findSubstr(fileUri)) {
|
||||
return callback({
|
||||
path: fileUri.fsPath
|
||||
});
|
||||
}
|
||||
|
||||
this.logService.error(`${Schemas.file}: Refused to load resource ${fileUri.fsPath} from ${Schemas.file}: protocol (original URL: ${request.url})`);
|
||||
|
||||
return callback({ error: -3 /* ABORTED */ });
|
||||
}
|
||||
|
||||
// isPreferringBrowserCodeLoad: true
|
||||
// => block any file request
|
||||
else {
|
||||
this.logService.error(`Refused to load resource ${fileUri.fsPath} from ${Schemas.file}: protocol (original URL: ${request.url})`);
|
||||
|
||||
return callback({ error: -3 /* ABORTED */ });
|
||||
}
|
||||
}
|
||||
|
||||
private async handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) {
|
||||
@ -102,9 +118,10 @@ export class FileProtocolHandler extends Disposable {
|
||||
return callback({
|
||||
path: fileUri.fsPath
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath} from ${Schemas.vscodeFileResource}: protocol (original URL: ${request.url})`);
|
||||
|
||||
this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath}}`);
|
||||
callback({ error: -3 /* ABORTED */ });
|
||||
return callback({ error: -3 /* ABORTED */ });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,244 +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 { BrowserWindow, ipcMain, Event, MessagePortMain } from 'electron';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
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 { 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 extends Disposable implements ISharedProcess {
|
||||
|
||||
private readonly whenSpawnedBarrier = new Barrier();
|
||||
|
||||
private window: BrowserWindow | undefined = undefined;
|
||||
private windowCloseListener: ((event: Event) => void) | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
private readonly machineId: string,
|
||||
private userEnv: NodeJS.ProcessEnv,
|
||||
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IThemeMainService private readonly themeMainService: IThemeMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
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,
|
||||
spellcheck: false,
|
||||
nativeWindowOpen: true,
|
||||
images: false,
|
||||
webgl: false,
|
||||
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
sharedIPCHandle: this.environmentService.sharedIPCHandle,
|
||||
args: this.environmentService.args,
|
||||
logLevel: this.logService.getLevel()
|
||||
};
|
||||
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
|
||||
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?.isVisible()) {
|
||||
this.window.hide();
|
||||
}
|
||||
};
|
||||
|
||||
this.window.on('close', this.windowCloseListener);
|
||||
|
||||
// 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 };
|
||||
|
||||
// Release barrier
|
||||
this.whenSpawnedBarrier.open();
|
||||
}
|
||||
|
||||
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 toggle(): Promise<void> {
|
||||
|
||||
// wait for window to be created
|
||||
await this.whenIpcReady;
|
||||
|
||||
if (!this.window) {
|
||||
return; // possibly disposed already
|
||||
}
|
||||
|
||||
if (this.window.isVisible()) {
|
||||
this.window.webContents.closeDevTools();
|
||||
this.window.hide();
|
||||
} else {
|
||||
this.window.show();
|
||||
this.window.webContents.openDevTools();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Load issue reporter into window
|
||||
|
@ -22,11 +22,12 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
|
||||
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
|
||||
const MAX_URL_LENGTH = 2045;
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Load process explorer into window
|
||||
|
@ -6,9 +6,9 @@
|
||||
/// <reference path="../../../../typings/require.d.ts" />
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Add a perf entry right from the top
|
||||
@ -76,5 +76,4 @@
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}());
|
||||
|
@ -9,7 +9,7 @@ import { spawn, ChildProcess, SpawnOptions } from 'child_process';
|
||||
import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
|
||||
import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
|
||||
import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { isAbsolute, join } from 'vs/base/common/path';
|
||||
import { whenDeleted, writeFileSync } from 'vs/base/node/pfs';
|
||||
|
@ -28,12 +28,11 @@ import { RequestService } from 'vs/platform/request/node/requestService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
||||
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
||||
import { mkdirp, writeFile } from 'vs/base/node/pfs';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { ILogService, getLogLevel, LogLevel, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log';
|
||||
import { ILogService, getLogLevel, LogLevel, ConsoleLogger, MultiplexLogService, ILogger } from 'vs/platform/log/common/log';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog';
|
||||
import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
@ -46,6 +45,7 @@ import { LocalizationsService } from 'vs/platform/localizations/node/localizatio
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
class CliMain extends Disposable {
|
||||
|
||||
@ -73,6 +73,7 @@ class CliMain extends Disposable {
|
||||
|
||||
return instantiationService.invokeFunction(async accessor => {
|
||||
const logService = accessor.get(ILogService);
|
||||
const fileService = accessor.get(IFileService);
|
||||
const environmentService = accessor.get(INativeEnvironmentService);
|
||||
const extensionManagementCLIService = accessor.get(IExtensionManagementCLIService);
|
||||
|
||||
@ -83,7 +84,7 @@ class CliMain extends Disposable {
|
||||
this.registerErrorHandler(logService);
|
||||
|
||||
// Run based on argv
|
||||
await this.doRun(environmentService, extensionManagementCLIService);
|
||||
await this.doRun(environmentService, extensionManagementCLIService, fileService);
|
||||
|
||||
// Flush the remaining data in AI adapter (with 1s timeout)
|
||||
return raceTimeout(combinedAppender(...appenders).flush(), 1000);
|
||||
@ -99,14 +100,14 @@ class CliMain extends Disposable {
|
||||
services.set(INativeEnvironmentService, environmentService);
|
||||
|
||||
// Init folders
|
||||
await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? mkdirp(path) : undefined));
|
||||
await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? fs.promises.mkdir(path, { recursive: true }) : undefined));
|
||||
|
||||
// Log
|
||||
const logLevel = getLogLevel(environmentService);
|
||||
const loggers: ILogService[] = [];
|
||||
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
|
||||
const loggers: ILogger[] = [];
|
||||
loggers.push(new SpdLogLogger('cli', join(environmentService.logsPath, 'cli.log'), true, logLevel));
|
||||
if (logLevel === LogLevel.Trace) {
|
||||
loggers.push(new ConsoleLogService(logLevel));
|
||||
loggers.push(new ConsoleLogger(logLevel));
|
||||
}
|
||||
|
||||
const logService = this._register(new MultiplexLogService(loggers));
|
||||
@ -182,11 +183,11 @@ class CliMain extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService): Promise<void> {
|
||||
private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService, fileService: IFileService): Promise<void> {
|
||||
|
||||
// Install Source
|
||||
if (this.argv['install-source']) {
|
||||
return this.setInstallSource(environmentService, this.argv['install-source']);
|
||||
return this.setInstallSource(environmentService, fileService, this.argv['install-source']);
|
||||
}
|
||||
|
||||
// List Extensions
|
||||
@ -219,8 +220,8 @@ class CliMain extends Disposable {
|
||||
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
|
||||
}
|
||||
|
||||
private setInstallSource(environmentService: INativeEnvironmentService, installSource: string): Promise<void> {
|
||||
return writeFile(environmentService.installSourcePath, installSource.slice(0, 30));
|
||||
private async setInstallSource(environmentService: INativeEnvironmentService, fileService: IFileService, installSource: string): Promise<void> {
|
||||
await fileService.writeFile(URI.file(environmentService.installSourcePath), VSBuffer.fromString(installSource.slice(0, 30)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,138 +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 { spawn } from 'child_process';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { isWindows, platform } from 'vs/base/common/platform';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { getSystemShell } from 'vs/base/node/shell';
|
||||
|
||||
/**
|
||||
* We need to get the environment from a user's shell.
|
||||
* This should only be done when Code itself is not launched
|
||||
* from within a shell.
|
||||
*/
|
||||
export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: NodeJS.ProcessEnv): Promise<typeof process.env> {
|
||||
|
||||
// Skip if --force-disable-user-env
|
||||
if (args['force-disable-user-env']) {
|
||||
logService.trace('resolveShellEnv(): skipped (--force-disable-user-env)');
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Skip on windows
|
||||
else if (isWindows) {
|
||||
logService.trace('resolveShellEnv(): skipped (Windows)');
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Skip if running from CLI already
|
||||
else if (isLaunchedFromCli(env) && !args['force-user-env']) {
|
||||
logService.trace('resolveShellEnv(): skipped (VSCODE_CLI is set)');
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Otherwise resolve (macOS, Linux)
|
||||
else {
|
||||
if (isLaunchedFromCli(env)) {
|
||||
logService.trace('resolveShellEnv(): running (--force-user-env)');
|
||||
} else {
|
||||
logService.trace('resolveShellEnv(): running (macOS/Linux)');
|
||||
}
|
||||
|
||||
if (!unixShellEnvPromise) {
|
||||
unixShellEnvPromise = doResolveUnixShellEnv(logService);
|
||||
}
|
||||
|
||||
return unixShellEnvPromise;
|
||||
}
|
||||
}
|
||||
|
||||
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
|
||||
|
||||
async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof process.env> {
|
||||
const promise = new Promise<typeof process.env>(async (resolve, reject) => {
|
||||
const runAsNode = process.env['ELECTRON_RUN_AS_NODE'];
|
||||
logService.trace('getUnixShellEnvironment#runAsNode', runAsNode);
|
||||
|
||||
const noAttach = process.env['ELECTRON_NO_ATTACH_CONSOLE'];
|
||||
logService.trace('getUnixShellEnvironment#noAttach', noAttach);
|
||||
|
||||
const mark = generateUuid().replace(/-/g, '').substr(0, 12);
|
||||
const regex = new RegExp(mark + '(.*)' + mark);
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
ELECTRON_RUN_AS_NODE: '1',
|
||||
ELECTRON_NO_ATTACH_CONSOLE: '1'
|
||||
};
|
||||
|
||||
const command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`;
|
||||
logService.trace('getUnixShellEnvironment#env', env);
|
||||
logService.trace('getUnixShellEnvironment#spawn', command);
|
||||
|
||||
const systemShellUnix = await getSystemShell(platform);
|
||||
const child = spawn(systemShellUnix, ['-ilc', command], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'pipe', process.stderr],
|
||||
env
|
||||
});
|
||||
|
||||
const buffers: Buffer[] = [];
|
||||
child.on('error', () => resolve({}));
|
||||
child.stdout.on('data', b => buffers.push(b));
|
||||
|
||||
child.on('close', code => {
|
||||
if (code !== 0) {
|
||||
return reject(new Error('Failed to get environment'));
|
||||
}
|
||||
|
||||
const raw = Buffer.concat(buffers).toString('utf8');
|
||||
logService.trace('getUnixShellEnvironment#raw', raw);
|
||||
|
||||
const match = regex.exec(raw);
|
||||
const rawStripped = match ? match[1] : '{}';
|
||||
|
||||
try {
|
||||
const env = JSON.parse(rawStripped);
|
||||
|
||||
if (runAsNode) {
|
||||
env['ELECTRON_RUN_AS_NODE'] = runAsNode;
|
||||
} else {
|
||||
delete env['ELECTRON_RUN_AS_NODE'];
|
||||
}
|
||||
|
||||
if (noAttach) {
|
||||
env['ELECTRON_NO_ATTACH_CONSOLE'] = noAttach;
|
||||
} else {
|
||||
delete env['ELECTRON_NO_ATTACH_CONSOLE'];
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/22593#issuecomment-336050758
|
||||
delete env['XDG_RUNTIME_DIR'];
|
||||
|
||||
logService.trace('getUnixShellEnvironment#result', env);
|
||||
resolve(env);
|
||||
} catch (err) {
|
||||
logService.error('getUnixShellEnvironment#error', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
return await promise;
|
||||
} catch (error) {
|
||||
logService.error('getUnixShellEnvironment#error', toErrorMessage(error));
|
||||
|
||||
return {}; // ignore any errors
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user