From 07eb3487f0f9f30c7bd2a9ae724b8b9733424082 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 3 Jun 2019 11:17:53 +0200 Subject: [PATCH] debt - introduce lifecycle phases on electron-main and adopt --- src/vs/base/node/config.ts | 4 +- src/vs/code/code.main.ts | 6 - src/vs/code/electron-main/app.ts | 366 ++++++++---------- src/vs/code/electron-main/keyboard.ts | 32 -- src/vs/code/electron-main/logUploader.ts | 4 +- src/vs/code/electron-main/main.ts | 45 ++- src/vs/code/electron-main/window.ts | 6 - src/vs/code/electron-main/windows.ts | 64 ++- src/vs/code/node/cli.ts | 1 + .../environment/node/environmentService.ts | 5 +- .../electron-main/historyMainService.ts | 20 +- .../launch/electron-main/launchService.ts | 6 +- src/vs/platform/lifecycle/common/lifecycle.ts | 4 +- .../lifecycle/electron-main/lifecycleMain.ts | 97 ++++- src/vs/platform/state/node/stateService.ts | 39 +- .../platform/windows/electron-main/windows.ts | 1 - tslint.json | 1 - 17 files changed, 388 insertions(+), 313 deletions(-) delete mode 100644 src/vs/code/code.main.ts delete mode 100644 src/vs/code/electron-main/keyboard.ts diff --git a/src/vs/base/node/config.ts b/src/vs/base/node/config.ts index a221a94c867..fe8d7538d85 100644 --- a/src/vs/base/node/config.ts +++ b/src/vs/base/node/config.ts @@ -109,10 +109,10 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { try { this.parseErrors = []; res = this.options.parse ? this.options.parse(raw, this.parseErrors) : json.parse(raw, this.parseErrors); + return res || this.options.defaultConfig; } catch (error) { - // Ignore parsing errors - return this.options.defaultConfig; + return this.options.defaultConfig; // Ignore parsing errors } } diff --git a/src/vs/code/code.main.ts b/src/vs/code/code.main.ts deleted file mode 100644 index 074dca196e1..00000000000 --- a/src/vs/code/code.main.ts +++ /dev/null @@ -1,6 +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 'vs/platform/update/node/update.config.contribution'; \ No newline at end of file diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index cee5824571f..240dd189202 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -9,7 +9,7 @@ import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows'; import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc'; import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; -import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateChannel } from 'vs/platform/update/node/updateIpc'; @@ -36,12 +36,11 @@ import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; import product from 'vs/platform/product/node/product'; import pkg from 'vs/platform/product/node/package'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService } from 'vs/platform/history/common/history'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard'; import { URI } from 'vs/base/common/uri'; import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc'; import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; @@ -90,12 +89,7 @@ export class CodeApplication extends Disposable { private static APP_ICON_REFRESH_KEY = 'macOSAppIconRefresh7'; - private windowsMainService: IWindowsMainService; - - private electronIpcServer: ElectronIPCServer; - - private sharedProcess: SharedProcess; - private sharedProcessClient: Promise; + private windowsMainService: IWindowsMainService | undefined; constructor( private readonly mainIpcServer: Server, @@ -109,9 +103,6 @@ export class CodeApplication extends Disposable { ) { super(); - this._register(mainIpcServer); - this._register(configurationService); - this.registerListeners(); } @@ -122,12 +113,12 @@ export class CodeApplication extends Disposable { process.on('uncaughtException', err => this.onUnexpectedError(err)); process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); - // Contextmenu via IPC support - registerContextMenuListener(); - // Dispose on shutdown this.lifecycleService.onWillShutdown(() => this.dispose()); + // Contextmenu via IPC support + registerContextMenuListener(); + app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => { if (this.windowsMainService) { this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled); @@ -200,7 +191,7 @@ export class CodeApplication extends Disposable { event.preventDefault(); // Keep in array because more might come! - macOpenFileURIs.push(getURIToOpenFromPathSync(path)); + macOpenFileURIs.push(this.getURIToOpenFromPathSync(path)); // Clear previous handler if any if (runningTimeout !== null) { @@ -217,6 +208,7 @@ export class CodeApplication extends Disposable { urisToOpen: macOpenFileURIs, preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */ }); + macOpenFileURIs = []; runningTimeout = null; } @@ -224,7 +216,9 @@ export class CodeApplication extends Disposable { }); app.on('new-window-for-tab', () => { - this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button + if (this.windowsMainService) { + this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button + } }); ipc.on('vscode:exit', (event: Event, code: number) => { @@ -234,25 +228,26 @@ export class CodeApplication extends Disposable { this.lifecycleService.kill(code); }); - ipc.on('vscode:fetchShellEnv', (event: Event) => { + ipc.on('vscode:fetchShellEnv', async (event: Event) => { const webContents = event.sender; - getShellEnvironment(this.logService).then(shellEnv => { + + try { + const shellEnv = await getShellEnvironment(this.logService); if (!webContents.isDestroyed()) { webContents.send('vscode:acceptShellEnv', shellEnv); } - }, err => { + } catch (error) { if (!webContents.isDestroyed()) { webContents.send('vscode:acceptShellEnv', {}); } - this.logService.error('Error fetching shell env', err); - }); + this.logService.error('Error fetching shell env', error); + } }); ipc.on('vscode:extensionHostDebug', (_: Event, windowId: number, broadcast: any) => { if (this.windowsMainService) { - // Send to all windows (except sender window) - this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]); + this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]); // Send to all windows (except sender window) } }); @@ -261,11 +256,28 @@ export class CodeApplication extends Disposable { ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload()); - powerMonitor.on('resume', () => { // After waking up from sleep - if (this.windowsMainService) { - this.windowsMainService.sendToAll('vscode:osResume', undefined); - } - }); + // After waking up from sleep (after window opened) + (async () => { + await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen); + + powerMonitor.on('resume', () => { + if (this.windowsMainService) { + this.windowsMainService.sendToAll('vscode:osResume', undefined); + } + }); + })(); + + // Keyboard layout changes (after window opened) + (async () => { + await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen); + + const nativeKeymap = await import('native-keymap'); + nativeKeymap.onDidChangeKeyboardLayout(() => { + if (this.windowsMainService) { + this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false); + } + }); + })(); } private onUnexpectedError(err: Error): void { @@ -317,24 +329,31 @@ export class CodeApplication extends Disposable { } // Create Electron IPC Server - this.electronIpcServer = new ElectronIPCServer(); + const electronIpcServer = 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 - this.sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); - this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); + // Spawn shared process after the first window has opened and 3s have passed + const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); + const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); + this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { + this._register(new RunOnceScheduler(async () => { + const userEnv = await getShellEnvironment(this.logService); + + sharedProcess.spawn(userEnv); + }, 3000)).schedule(); + }); // Services - const appInstantiationService = await this.initServices(machineId); + const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessClient); // Create driver if (this.environmentService.driverHandle) { (async () => { - const server = await serveDriver(this.electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService); + const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService); this.logService.info('Driver started at:', this.environmentService.driverHandle); this._register(server); @@ -346,10 +365,10 @@ export class CodeApplication extends Disposable { this._register(authHandler); // Open Windows - const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor)); + const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient)); // Post Open Windows Tasks - appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); + this.afterWindowOpen(); // Tracing: Stop tracing after windows are ready if enabled if (this.environmentService.args.trace) { @@ -371,6 +390,63 @@ export class CodeApplication extends Disposable { return machineId; } + private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessClient: Promise>): Promise { + const services = new ServiceCollection(); + + switch (process.platform) { + case 'win32': + services.set(IUpdateService, new SyncDescriptor(Win32UpdateService)); + break; + + case 'linux': + if (process.env.SNAP && process.env.SNAP_REVISION) { + services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION])); + } else { + services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService)); + } + break; + + case 'darwin': + services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService)); + break; + } + + services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv])); + services.set(IWindowsService, new SyncDescriptor(WindowsService, [sharedProcess])); + services.set(ILaunchService, new SyncDescriptor(LaunchService)); + services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv])); + services.set(IMenubarService, new SyncDescriptor(MenubarService)); + + const storageMainService = new StorageMainService(this.logService, this.environmentService); + services.set(IStorageMainService, storageMainService); + this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); + + const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService); + services.set(IBackupMainService, backupMainService); + + services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService)); + services.set(IURLService, new SyncDescriptor(URLService)); + services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService)); + + // Telemetry + if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { + const channel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('telemetryAppender'))); + const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)); + const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath); + const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath]; + const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths }; + + services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); + } else { + services.set(ITelemetryService, NullTelemetryService); + } + + // Init services that require it + await backupMainService.initialize(); + + return this.instantiationService.createChild(services); + } + private stopTracingEventually(windows: ICodeWindow[]): void { this.logService.info(`Tracing: waiting for windows to get ready...`); @@ -384,12 +460,14 @@ export class CodeApplication extends Disposable { contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { if (!timeout) { - this.windowsMainService.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")] - }, this.windowsMainService.getLastActiveWindow()); + if (this.windowsMainService) { + this.windowsMainService.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")] + }, this.windowsMainService.getLastActiveWindow()); + } } else { this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); } @@ -406,73 +484,7 @@ export class CodeApplication extends Disposable { }); } - private async initServices(machineId: string): Promise { - const services = new ServiceCollection(); - - if (process.platform === 'win32') { - services.set(IUpdateService, new SyncDescriptor(Win32UpdateService)); - } else if (process.platform === 'linux') { - if (process.env.SNAP && process.env.SNAP_REVISION) { - services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION])); - } else { - services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService)); - } - } else if (process.platform === 'darwin') { - services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService)); - } - - services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId])); - services.set(IWindowsService, new SyncDescriptor(WindowsService, [this.sharedProcess])); - services.set(ILaunchService, new SyncDescriptor(LaunchService)); - services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv])); - services.set(IMenubarService, new SyncDescriptor(MenubarService)); - services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); - services.set(IBackupMainService, new SyncDescriptor(BackupMainService)); - services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService)); - services.set(IURLService, new SyncDescriptor(URLService)); - services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService)); - - // Telemetry - if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { - const channel = getDelayedChannel(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender'))); - const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)); - const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath); - const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath]; - const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths }; - - services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); - } else { - services.set(ITelemetryService, NullTelemetryService); - } - - const appInstantiationService = this.instantiationService.createChild(services); - - // Init services that require it - await appInstantiationService.invokeFunction(accessor => Promise.all([ - this.initStorageService(accessor), - this.initBackupService(accessor) - ])); - - return appInstantiationService; - } - - private initStorageService(accessor: ServicesAccessor): Promise { - const storageMainService = accessor.get(IStorageMainService) as StorageMainService; - - // Ensure to close storage on shutdown - this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); - - return Promise.resolve(); - } - - private initBackupService(accessor: ServicesAccessor): Promise { - const backupMainService = accessor.get(IBackupMainService) as BackupMainService; - - return backupMainService.initialize(); - } - - private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] { - const appInstantiationService = accessor.get(IInstantiationService); + private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>): ICodeWindow[] { // Register more Main IPC services const launchService = accessor.get(ILaunchService); @@ -482,40 +494,40 @@ export class CodeApplication extends Disposable { // Register more Electron IPC services const updateService = accessor.get(IUpdateService); const updateChannel = new UpdateChannel(updateService); - this.electronIpcServer.registerChannel('update', updateChannel); + electronIpcServer.registerChannel('update', updateChannel); const issueService = accessor.get(IIssueService); const issueChannel = new IssueChannel(issueService); - this.electronIpcServer.registerChannel('issue', issueChannel); + electronIpcServer.registerChannel('issue', issueChannel); const workspacesService = accessor.get(IWorkspacesMainService); - const workspacesChannel = appInstantiationService.createInstance(WorkspacesChannel, workspacesService); - this.electronIpcServer.registerChannel('workspaces', workspacesChannel); + const workspacesChannel = new WorkspacesChannel(workspacesService); + electronIpcServer.registerChannel('workspaces', workspacesChannel); const windowsService = accessor.get(IWindowsService); const windowsChannel = new WindowsChannel(windowsService); - this.electronIpcServer.registerChannel('windows', windowsChannel); - this.sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel)); + electronIpcServer.registerChannel('windows', windowsChannel); + sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel)); const menubarService = accessor.get(IMenubarService); const menubarChannel = new MenubarChannel(menubarService); - this.electronIpcServer.registerChannel('menubar', menubarChannel); + electronIpcServer.registerChannel('menubar', menubarChannel); const urlService = accessor.get(IURLService); const urlChannel = new URLServiceChannel(urlService); - this.electronIpcServer.registerChannel('url', urlChannel); + electronIpcServer.registerChannel('url', urlChannel); const storageMainService = accessor.get(IStorageMainService); const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService as StorageMainService)); - this.electronIpcServer.registerChannel('storage', storageChannel); + electronIpcServer.registerChannel('storage', storageChannel); // Log level management const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService)); - this.electronIpcServer.registerChannel('loglevel', logLevelChannel); - this.sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel)); + electronIpcServer.registerChannel('loglevel', logLevelChannel); + sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel)); - // Lifecycle - (this.lifecycleService as LifecycleService).ready(); + // Signal phase: ready (services set) + this.lifecycleService.phase = LifecycleMainPhase.Ready; // Propagate to clients const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this @@ -523,7 +535,7 @@ export class CodeApplication extends Disposable { // Create a URL handler which forwards to the last active window const activeWindowManager = new ActiveWindowManager(windowsService); const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); - const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', activeWindowRouter); + const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', activeWindowRouter); const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel); // On Mac, Code can be running without any open windows, so we must create a window to handle urls, @@ -553,11 +565,9 @@ export class CodeApplication extends Disposable { // Watch Electron URLs and forward them to the UrlService const args = this.environmentService.args; const urls = args['open-url'] ? args._urls : []; - const urlListener = new ElectronURLListener(urls || [], urlService, this.windowsMainService); + const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService); this._register(urlListener); - this.windowsMainService.ready(this.userEnv); - // Open our first window const macOpenFiles: string[] = (global).macOpenFiles; const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; @@ -567,9 +577,9 @@ export class CodeApplication extends Disposable { const noRecentEntry = args['skip-add-to-recently-opened'] === true; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; + // new window if "-n" was used without paths if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { - // new window if "-n" was used without paths - return this.windowsMainService.open({ + return windowsMainService.open({ context, cli: args, forceNewWindow: true, @@ -582,10 +592,10 @@ export class CodeApplication extends Disposable { // mac: open-file event received on startup if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { - return this.windowsMainService.open({ + return windowsMainService.open({ context: OpenContext.DOCK, cli: args, - urisToOpen: macOpenFiles.map(getURIToOpenFromPathSync), + urisToOpen: macOpenFiles.map(file => this.getURIToOpenFromPathSync(file)), noRecentEntry, waitMarkerFileURI, initialStartup: true @@ -593,7 +603,7 @@ export class CodeApplication extends Disposable { } // default: read paths from cli - return this.windowsMainService.open({ + return windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']), @@ -604,62 +614,31 @@ export class CodeApplication extends Disposable { }); } - private afterWindowOpen(accessor: ServicesAccessor): void { - const windowsMainService = accessor.get(IWindowsMainService); - const historyMainService = accessor.get(IHistoryMainService); - - if (isWindows) { - - // Setup Windows mutex - try { - const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex; - const windowsMutex = new Mutex(product.win32MutexName); - this._register(toDisposable(() => windowsMutex.release())); - } catch (e) { - if (!this.environmentService.isBuilt) { - windowsMainService.showMessageBox({ - title: product.nameLong, - type: 'warning', - message: 'Failed to load windows-mutex!', - detail: e.toString(), - noLink: true - }); - } + private getURIToOpenFromPathSync(path: string): IURIToOpen { + try { + const fileStat = statSync(path); + if (fileStat.isDirectory()) { + return { folderUri: URI.file(path) }; } - // Ensure Windows foreground love module - try { - // tslint:disable-next-line:no-unused-expression - require.__$__nodeRequire('windows-foreground-love'); - } catch (e) { - if (!this.environmentService.isBuilt) { - windowsMainService.showMessageBox({ - title: product.nameLong, - type: 'warning', - message: 'Failed to load windows-foreground-love!', - detail: e.toString(), - noLink: true - }); - } + if (hasWorkspaceFileExtension(path)) { + return { workspaceUri: URI.file(path) }; } + } catch (error) { + // ignore errors } + return { fileUri: URI.file(path) }; + } + + private afterWindowOpen(): void { + + // Signal phase: after window open + this.lifecycleService.phase = LifecycleMainPhase.AfterWindowOpen; + // Remote Authorities this.handleRemoteAuthorities(); - // Keyboard layout changes - KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => { - this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false); - }); - - // Jump List - historyMainService.updateWindowsJumpList(); - historyMainService.onRecentlyOpenedChange(() => historyMainService.updateWindowsJumpList()); - - // Start shared process after a while - const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment(this.logService).then(userEnv => this.sharedProcess.spawn(userEnv)), 3000)); - sharedProcessSpawn.schedule(); - // Helps application icon refresh after an update with new icon is installed (macOS) // TODO@Ben remove after a couple of releases if (isMacintosh) { @@ -697,8 +676,9 @@ export class CodeApplication extends Disposable { constructor(authority: string, host: string, port: number) { this._authority = authority; + const options: IConnectionOptions = { - isBuilt: isBuilt, + isBuilt, commit: product.commit, webSocketFactory: nodeWebSocketFactory, addressProvider: { @@ -707,6 +687,7 @@ export class CodeApplication extends Disposable { } } }; + this._connection = connectRemoteAgentManagement(options, authority, `main`); this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000); } @@ -720,6 +701,7 @@ export class CodeApplication extends Disposable { async getClient(): Promise> { this._disposeRunner.schedule(); const connection = await this._connection; + return connection.client; } } @@ -727,7 +709,9 @@ export class CodeApplication extends Disposable { const resolvedAuthorities = new Map(); ipc.on('vscode:remoteAuthorityResolved', (event: Electron.Event, data: ResolvedAuthority) => { this.logService.info('Received resolved authority', data.authority); + resolvedAuthorities.set(data.authority, data); + // Make sure to close and remove any existing connections if (connectionPool.has(data.authority)) { connectionPool.get(data.authority)!.dispose(); @@ -736,15 +720,19 @@ export class CodeApplication extends Disposable { const resolveAuthority = (authority: string): ResolvedAuthority | null => { this.logService.info('Resolving authority', authority); + if (authority.indexOf('+') >= 0) { if (resolvedAuthorities.has(authority)) { return withUndefinedAsNull(resolvedAuthorities.get(authority)); } + this.logService.info('Didnot find resolved authority for', authority); + return null; } else { const [host, strPort] = authority.split(':'); const port = parseInt(strPort, 10); + return { authority, host, port }; } }; @@ -753,6 +741,7 @@ export class CodeApplication extends Disposable { if (request.method !== 'GET') { return callback(undefined); } + const uri = URI.parse(request.url); let activeConnection: ActiveConnection | undefined; @@ -764,9 +753,11 @@ export class CodeApplication extends Disposable { callback(undefined); return; } + activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port); connectionPool.set(uri.authority, activeConnection); } + try { const rawClient = await activeConnection!.getClient(); if (connectionPool.has(uri.authority)) { // not disposed in the meantime @@ -785,16 +776,3 @@ export class CodeApplication extends Disposable { }); } } - -function getURIToOpenFromPathSync(path: string): IURIToOpen { - try { - const fileStat = statSync(path); - if (fileStat.isDirectory()) { - return { folderUri: URI.file(path) }; - } else if (hasWorkspaceFileExtension(path)) { - return { workspaceUri: URI.file(path) }; - } - } catch (error) { - } - return { fileUri: URI.file(path) }; -} diff --git a/src/vs/code/electron-main/keyboard.ts b/src/vs/code/electron-main/keyboard.ts deleted file mode 100644 index 2ec1623f173..00000000000 --- a/src/vs/code/electron-main/keyboard.ts +++ /dev/null @@ -1,32 +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 * as nativeKeymap from 'native-keymap'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; - -export class KeyboardLayoutMonitor { - - public static readonly INSTANCE = new KeyboardLayoutMonitor(); - - private readonly _emitter: Emitter; - private _registered: boolean; - - private constructor() { - this._emitter = new Emitter(); - this._registered = false; - } - - public onDidChangeKeyboardLayout(callback: () => void): IDisposable { - if (!this._registered) { - this._registered = true; - - nativeKeymap.onDidChangeKeyboardLayout(() => { - this._emitter.fire(); - }); - } - return this._emitter.event(callback); - } -} \ No newline at end of file diff --git a/src/vs/code/electron-main/logUploader.ts b/src/vs/code/electron-main/logUploader.ts index 28ce4f7f1c1..4e601fe8a65 100644 --- a/src/vs/code/electron-main/logUploader.ts +++ b/src/vs/code/electron-main/logUploader.ts @@ -22,10 +22,10 @@ interface PostResult { class Endpoint { private constructor( - public readonly url: string + readonly url: string ) { } - public static getFromProduct(): Endpoint | undefined { + static getFromProduct(): Endpoint | undefined { const logUploaderUrl = product.logUploaderUrl; return logUploaderUrl ? new Endpoint(logUploaderUrl) : undefined; } diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 0fb2ccfad31..12d0c606893 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/code/code.main'; +import 'vs/platform/update/node/update.config.contribution'; import { app, dialog } from 'electron'; import { assign } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; @@ -39,6 +39,7 @@ import { uploadLogs } from 'vs/code/electron-main/logUploader'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { Client } from 'vs/base/parts/ipc/common/ipc.net'; +import { once } from 'vs/base/common/functional'; class ExpectedError extends Error { readonly isExpected = true; @@ -90,17 +91,14 @@ class CodeMain { // log file access on Windows (https://github.com/Microsoft/vscode/issues/41218) const bufferLogService = new BufferLogService(); - const instantiationService = this.createServices(args, bufferLogService); + const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService); try { + + // Init services await instantiationService.invokeFunction(async accessor => { const environmentService = accessor.get(IEnvironmentService); const stateService = accessor.get(IStateService); - const logService = accessor.get(ILogService); - // Patch `process.env` with the instance's environment - const instanceEnvironment = this.patchEnvironment(environmentService); - - // Startup try { await this.initServices(environmentService, stateService as StateService); } catch (error) { @@ -110,9 +108,19 @@ class CodeMain { throw error; } + }); + + // Startup + await instantiationService.invokeFunction(async accessor => { + const environmentService = accessor.get(IEnvironmentService); + const logService = accessor.get(ILogService); + const lifecycleService = accessor.get(ILifecycleService); + const configurationService = accessor.get(IConfigurationService); + + const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true); - const mainIpcServer = await this.doStartup(logService, environmentService, instantiationService, true); bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel()); + once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose()); return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); }); @@ -121,16 +129,17 @@ class CodeMain { } } - private createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService { + private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] { const services = new ServiceCollection(); const environmentService = new EnvironmentService(args, process.execPath); + const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment + services.set(IEnvironmentService, environmentService); const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]); process.once('exit', () => logService.dispose()); - - services.set(IEnvironmentService, environmentService); services.set(ILogService, logService); + services.set(ILifecycleService, new SyncDescriptor(LifecycleService)); services.set(IStateService, new SyncDescriptor(StateService)); services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath])); @@ -138,12 +147,12 @@ class CodeMain { services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); services.set(IThemeMainService, new SyncDescriptor(ThemeMainService)); - return new InstantiationService(services, true); + return [new InstantiationService(services, true), instanceEnvironment]; } private initServices(environmentService: IEnvironmentService, stateService: StateService): Promise { - // Ensure paths for environment service exist + // Environment service (paths) const environmentServiceInitialization = Promise.all([ environmentService.extensionsPath, environmentService.nodeCachedDataDir, @@ -175,7 +184,7 @@ class CodeMain { return instanceEnvironment; } - private async doStartup(logService: ILogService, environmentService: IEnvironmentService, instantiationService: IInstantiationService, retry: boolean): Promise { + private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleService: ILifecycleService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely @@ -183,6 +192,7 @@ class CodeMain { let server: Server; try { server = await serve(environmentService.mainIPCHandle); + once(lifecycleService.onWillShutdown)(() => server.dispose()); } catch (error) { // Handle unexpected errors (the only expected error is EADDRINUSE that @@ -226,10 +236,11 @@ class CodeMain { fs.unlinkSync(environmentService.mainIPCHandle); } catch (error) { logService.warn('Could not delete obsolete instance handle', error); + throw error; } - return this.doStartup(logService, environmentService, instantiationService, false); + return this.doStartup(logService, environmentService, lifecycleService, instantiationService, false); } // Tests from CLI require to be the only instance currently @@ -261,8 +272,8 @@ class CodeMain { if (environmentService.args.status) { return instantiationService.invokeFunction(async accessor => { const diagnostics = await accessor.get(IDiagnosticsService).getDiagnostics(launchClient); - console.log(diagnostics); + throw new ExpectedError(); }); } @@ -300,12 +311,14 @@ class CodeMain { // Print --status usage info if (environmentService.args.status) { logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); + throw new ExpectedError('Terminating...'); } // Log uploader usage info if (typeof environmentService.args['upload-logs'] !== 'undefined') { logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.'); + throw new ExpectedError('Terminating...'); } diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index e09a9a41dbd..b933aa0f33b 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -203,12 +203,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { return !!this.config.extensionTestsPath; } - /* - get extensionDevelopmentPaths(): string | string[] | undefined { - return this.config.extensionDevelopmentPath; - } - */ - get config(): IWindowConfiguration { return this.currentConfig; } diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index b0213a2876e..b7446dc4074 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -15,7 +15,7 @@ import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; import { hasArgs, asArray } from 'vs/platform/environment/node/argv'; import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; -import { ILifecycleService, UnloadReason, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleService, UnloadReason, LifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, isFileToOpen, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; @@ -38,6 +38,7 @@ import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { once } from 'vs/base/common/functional'; const enum WindowError { UNRESPONSIVE = 1, @@ -159,9 +160,7 @@ export class WindowsManager implements IWindowsMainService { private static readonly windowsStateStorageKey = 'windowsState'; - private static WINDOWS: ICodeWindow[] = []; - - private initialUserEnv: IProcessEnvironment; + private static readonly WINDOWS: ICodeWindow[] = []; private readonly windowsState: IWindowsState; private lastClosedWindowState?: IWindowState; @@ -170,19 +169,20 @@ export class WindowsManager implements IWindowsMainService { private readonly workspacesManager: WorkspacesManager; private _onWindowReady = new Emitter(); - onWindowReady: CommonEvent = this._onWindowReady.event; + readonly onWindowReady: CommonEvent = this._onWindowReady.event; private _onWindowClose = new Emitter(); - onWindowClose: CommonEvent = this._onWindowClose.event; + readonly onWindowClose: CommonEvent = this._onWindowClose.event; private _onWindowLoad = new Emitter(); - onWindowLoad: CommonEvent = this._onWindowLoad.event; + readonly onWindowLoad: CommonEvent = this._onWindowLoad.event; private _onWindowsCountChanged = new Emitter(); - onWindowsCountChanged: CommonEvent = this._onWindowsCountChanged.event; + readonly onWindowsCountChanged: CommonEvent = this._onWindowsCountChanged.event; constructor( private readonly machineId: string, + private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, @IStateService private readonly stateService: IStateService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -203,12 +203,50 @@ export class WindowsManager implements IWindowsMainService { this.dialogs = new Dialogs(stateService, this); this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this); + + this.lifecycleService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); + this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.setupNativeHelpers()); } - ready(initialUserEnv: IProcessEnvironment): void { - this.initialUserEnv = initialUserEnv; + private setupNativeHelpers(): void { + if (isWindows) { - this.registerListeners(); + // Setup Windows mutex + try { + const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex; + const mutex = new WindowsMutex(product.win32MutexName); + once(this.lifecycleService.onWillShutdown)(() => mutex.release()); + } catch (e) { + this.logService.error(e); + + if (!this.environmentService.isBuilt) { + this.showMessageBox({ + title: product.nameLong, + type: 'warning', + message: 'Failed to load windows-mutex!', + detail: e.toString(), + noLink: true + }); + } + } + + // Dev only: Ensure Windows foreground love module is present + if (!this.environmentService.isBuilt) { + try { + require.__$__nodeRequire('windows-foreground-love'); + } catch (e) { + this.logService.error(e); + + this.showMessageBox({ + title: product.nameLong, + type: 'warning', + message: 'Failed to load windows-foreground-love!', + detail: e.toString(), + noLink: true + }); + } + } + } } private registerListeners(): void { @@ -543,6 +581,7 @@ export class WindowsManager implements IWindowsMainService { // Find suitable window or folder path to open files in const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0]; + // only look at the windows with correct authority const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs!.remoteAuthority); @@ -639,7 +678,6 @@ export class WindowsManager implements IWindowsMainService { // Handle folders to open (instructed and to restore) const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => getComparisonKey(folder.folderUri)); // prevent duplicates - if (allFoldersToOpen.length > 0) { // Check for existing instances @@ -713,7 +751,9 @@ export class WindowsManager implements IWindowsMainService { if (fileInputs && !emptyToOpen) { emptyToOpen++; } + const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined); + for (let i = 0; i < emptyToOpen; i++) { usedWindows.push(this.openInBrowserWindow({ userEnv: openConfig.userEnv, diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index d5f5e622c9f..e434b71a863 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -57,6 +57,7 @@ export async function main(argv: string[]): Promise { else if (shouldSpawnCliProcess(args)) { const cli = await new Promise((c, e) => require(['vs/code/node/cliProcessMain'], c, e)); await cli.main(args); + return; } diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 1e8288bbf1a..d3c0ab75d9e 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -287,6 +287,7 @@ export function parseDebugPort(debugArg: string | undefined, debugBrkArg: string const portStr = debugBrkArg || debugArg; const port = Number(portStr) || (!isBuild ? defaultBuildPort : null); const brk = port ? Boolean(!!debugBrkArg) : false; + return { port, break: brk, debugId }; } @@ -301,9 +302,9 @@ function parsePathArg(arg: string | undefined, process: NodeJS.Process): string if (path.normalize(arg) === resolved) { return resolved; - } else { - return path.resolve(process.env['VSCODE_CWD'] || process.cwd(), arg); } + + return path.resolve(process.env['VSCODE_CWD'] || process.cwd(), arg); } export function parseUserDataDir(args: ParsedArgs, process: NodeJS.Process): string { diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 4429cc10574..5116c3387d0 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -22,6 +22,8 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label'; import { toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/history/electron-main/historyStorage'; import { exists } from 'vs/base/node/pfs'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; export class HistoryMainService implements IHistoryMainService { @@ -37,10 +39,10 @@ export class HistoryMainService implements IHistoryMainService { private static readonly recentlyOpenedStorageKey = 'openedPathsList'; - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private _onRecentlyOpenedChange = new Emitter(); - onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; + readonly onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; private macOSRecentDocumentsUpdater: ThrottledDelayer; @@ -48,9 +50,21 @@ export class HistoryMainService implements IHistoryMainService { @IStateService private readonly stateService: IStateService, @ILogService private readonly logService: ILogService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @ILifecycleService lifecycleService: ILifecycleService ) { this.macOSRecentDocumentsUpdater = new ThrottledDelayer(800); + + lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); + } + + private handleWindowsJumpList(): void { + if (!isWindows) { + return; // only on windows + } + + this.updateWindowsJumpList(); + this.onRecentlyOpenedChange(() => this.updateWindowsJumpList()); } addRecentlyOpened(newlyAdded: IRecent[]): void { diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 091c8704a2c..d5d81a70bf8 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -8,7 +8,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURLService } from 'vs/platform/url/common/url'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { OpenContext, IWindowSettings } from 'vs/platform/windows/common/windows'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { whenDeleted } from 'vs/base/node/pfs'; @@ -107,7 +107,7 @@ export class LaunchChannel implements IServerChannel { export class LaunchChannelClient implements ILaunchService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; constructor(private channel: IChannel) { } @@ -134,7 +134,7 @@ export class LaunchChannelClient implements ILaunchService { export class LaunchService implements ILaunchService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; constructor( @ILogService private readonly logService: ILogService, diff --git a/src/vs/platform/lifecycle/common/lifecycle.ts b/src/vs/platform/lifecycle/common/lifecycle.ts index 18782a0d0b3..0cc2199b349 100644 --- a/src/vs/platform/lifecycle/common/lifecycle.ts +++ b/src/vs/platform/lifecycle/common/lifecycle.ts @@ -29,7 +29,7 @@ export interface BeforeShutdownEvent { /** * The reason why the application will be shutting down. */ - reason: ShutdownReason; + readonly reason: ShutdownReason; } /** @@ -51,7 +51,7 @@ export interface WillShutdownEvent { /** * The reason why the application is shutting down. */ - reason: ShutdownReason; + readonly reason: ShutdownReason; } export const enum ShutdownReason { diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts index c218a17fa09..9a0702376bd 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts @@ -7,11 +7,12 @@ import { ipcMain as ipc, app } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/common/state'; import { Event, Emitter } from 'vs/base/common/event'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; +import { Barrier } from 'vs/base/common/async'; export const ILifecycleService = createDecorator('lifecycleService'); @@ -38,42 +39,48 @@ export interface ShutdownEvent { } export interface ILifecycleService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; /** * Will be true if the program was restarted (e.g. due to explicit request or update). */ - wasRestarted: boolean; + readonly wasRestarted: boolean; /** * Will be true if the program was requested to quit. */ - quitRequested: boolean; + readonly quitRequested: boolean; + + /** + * A flag indicating in what phase of the lifecycle we currently are. + */ + phase: LifecycleMainPhase; /** * An event that fires when the application is about to shutdown before any window is closed. * The shutdown can still be prevented by any window that vetos this event. */ - onBeforeShutdown: Event; + readonly onBeforeShutdown: Event; /** * An event that fires after the onBeforeShutdown event has been fired and after no window has * vetoed the shutdown sequence. At this point listeners are ensured that the application will * quit without veto. */ - onWillShutdown: Event; + readonly onWillShutdown: Event; /** * An event that fires before a window closes. This event is fired after any veto has been dealt * with so that listeners know for sure that the window will close without veto. */ - onBeforeWindowClose: Event; + readonly onBeforeWindowClose: Event; /** * An event that fires before a window is about to unload. Listeners can veto this event to prevent * the window from unloading. */ - onBeforeWindowUnload: Event; + readonly onBeforeWindowUnload: Event; /** * Unload a window for the provided reason. All lifecycle event handlers are triggered. @@ -94,6 +101,32 @@ export interface ILifecycleService { * Forcefully shutdown the application. No livecycle event handlers are triggered. */ kill(code?: number): void; + + /** + * Returns a promise that resolves when a certain lifecycle phase + * has started. + */ + when(phase: LifecycleMainPhase): Promise; +} + +export const enum LifecycleMainPhase { + + /** + * The first phase signals that we are about to startup. + */ + Starting = 1, + + /** + * Services are ready and first window is about to open. + */ + Ready = 2, + + /** + * This phase signals a point in time after the window has opened + * and is typically the best place to do work that is not required + * for the window to open. + */ + AfterWindowOpen = 3 } export class LifecycleService extends Disposable implements ILifecycleService { @@ -129,6 +162,11 @@ export class LifecycleService extends Disposable implements ILifecycleService { private readonly _onBeforeWindowUnload = this._register(new Emitter()); readonly onBeforeWindowUnload: Event = this._onBeforeWindowUnload.event; + private _phase: LifecycleMainPhase = LifecycleMainPhase.Starting; + get phase(): LifecycleMainPhase { return this._phase; } + + private phaseWhen = new Map(); + constructor( @ILogService private readonly logService: ILogService, @IStateService private readonly stateService: IStateService @@ -136,6 +174,7 @@ export class LifecycleService extends Disposable implements ILifecycleService { super(); this.handleRestarted(); + this.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); } private handleRestarted(): void { @@ -146,10 +185,6 @@ export class LifecycleService extends Disposable implements ILifecycleService { } } - ready(): void { - this.registerListeners(); - } - private registerListeners(): void { // before-quit: an event that is fired if application quit was @@ -238,6 +273,40 @@ export class LifecycleService extends Disposable implements ILifecycleService { return this.pendingWillShutdownPromise; } + set phase(value: LifecycleMainPhase) { + if (value < this.phase) { + throw new Error('Lifecycle cannot go backwards'); + } + + if (this._phase === value) { + return; + } + + this.logService.trace(`lifecycle (main): phase changed (value: ${value})`); + + this._phase = value; + + const barrier = this.phaseWhen.get(this._phase); + if (barrier) { + barrier.open(); + this.phaseWhen.delete(this._phase); + } + } + + async when(phase: LifecycleMainPhase): Promise { + if (phase <= this._phase) { + return; + } + + let barrier = this.phaseWhen.get(phase); + if (!barrier) { + barrier = new Barrier(); + this.phaseWhen.set(phase, barrier); + } + + await barrier.wait(); + } + registerWindow(window: ICodeWindow): void { // track window count @@ -390,10 +459,6 @@ export class LifecycleService extends Disposable implements ILifecycleService { }); } - /** - * A promise that completes to indicate if the quit request has been veto'd - * by the user or not. - */ quit(fromUpdate?: boolean): Promise { if (this.pendingQuitPromise) { return this.pendingQuitPromise; diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index 257e1756aef..d0a48280bbe 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -27,22 +27,17 @@ export class FileStorage { } async init(): Promise { - try { - const contents = await readFile(this.dbPath); - - try { - this.lastFlushedSerializedDatabase = contents.toString(); - this._database = JSON.parse(this.lastFlushedSerializedDatabase); - } catch (error) { - this._database = {}; - } - } catch (error) { - if (error.code !== 'ENOENT') { - this.onError(error); - } - - this._database = {}; + if (this._database) { + return; // return if database was already loaded } + + const database = await this.loadAsync(); + + if (this._database) { + return; // return if database was already loaded + } + + this._database = database; } private loadSync(): object { @@ -59,6 +54,20 @@ export class FileStorage { } } + private async loadAsync(): Promise { + try { + this.lastFlushedSerializedDatabase = (await readFile(this.dbPath)).toString(); + + return JSON.parse(this.lastFlushedSerializedDatabase); + } catch (error) { + if (error.code !== 'ENOENT') { + this.onError(error); + } + + return {}; + } + } + getItem(key: string, defaultValue: T): T; getItem(key: string, defaultValue?: T): T | undefined; getItem(key: string, defaultValue?: T): T | undefined { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 2095d2e400b..cb72892f40e 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -92,7 +92,6 @@ export interface IWindowsMainService { readonly onWindowClose: Event; // methods - ready(initialUserEnv: IProcessEnvironment): void; reload(win: ICodeWindow, cli?: ParsedArgs): void; enterWorkspace(win: ICodeWindow, path: URI): Promise; closeWorkspace(win: ICodeWindow): void; diff --git a/tslint.json b/tslint.json index 5afacd6023d..2d3f8afc80f 100644 --- a/tslint.json +++ b/tslint.json @@ -541,7 +541,6 @@ "**/vs/base/parts/**/{common,browser,node,electron-main}/**", "**/vs/platform/**/{common,browser,node,electron-main}/**", "**/vs/code/**/{common,browser,node,electron-main}/**", - "**/vs/code/code.main", "*" // node modules ] },