Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { AbstractLifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycleService';
import { localize } from 'vs/nls';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IDisposable } from 'vs/base/common/lifecycle';
import { addDisposableListener } from 'vs/base/browser/dom';
export class BrowserLifecycleService extends AbstractLifecycleService {
declare readonly _serviceBrand: undefined;
private beforeUnloadDisposable: IDisposable | undefined = undefined;
constructor(
@ILogService readonly logService: ILogService
) {
super(logService);
this.registerListeners();
}
private registerListeners(): void {
// beforeUnload
this.beforeUnloadDisposable = addDisposableListener(window, 'beforeunload', (e: BeforeUnloadEvent) => this.onBeforeUnload(e));
}
private onBeforeUnload(event: BeforeUnloadEvent): void {
this.logService.info('[lifecycle] onBeforeUnload triggered');
this.doShutdown(() => {
// Veto handling
event.preventDefault();
event.returnValue = localize('lifecycleVeto', "Changes that you made may not be saved. Please check press 'Cancel' and try again.");
});
}
shutdown(): void {
this.logService.info('[lifecycle] shutdown triggered');
// Remove beforeunload listener that would prevent shutdown
this.beforeUnloadDisposable?.dispose();
// Handle shutdown without veto support
this.doShutdown();
}
private doShutdown(handleVeto?: () => void): void {
const logService = this.logService;
let veto = false;
// Before Shutdown
this._onBeforeShutdown.fire({
veto(value) {
if (typeof handleVeto === 'function') {
if (value === true) {
veto = true;
} else if (value instanceof Promise && !veto) {
logService.error('[lifecycle] Long running onBeforeShutdown currently not supported in the web');
veto = true;
}
}
},
reason: ShutdownReason.QUIT
});
// Veto: handle if provided
if (veto && typeof handleVeto === 'function') {
handleVeto();
return;
}
// No Veto: continue with Will Shutdown
this._onWillShutdown.fire({
join() {
logService.error('[lifecycle] Long running onWillShutdown currently not supported in the web');
},
reason: ShutdownReason.QUIT
});
// Finally end with Shutdown event
this._onShutdown.fire();
}
}
registerSingleton(ILifecycleService, BrowserLifecycleService);

View File

@@ -0,0 +1,189 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
/**
* An event that is send out when the window is about to close. Clients have a chance to veto
* the closing by either calling veto with a boolean "true" directly or with a promise that
* resolves to a boolean. Returning a promise is useful in cases of long running operations
* on shutdown.
*
* Note: It is absolutely important to avoid long running promises if possible. Please try hard
* to return a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
*/
export interface BeforeShutdownEvent {
/**
* Allows to veto the shutdown. The veto can be a long running operation but it
* will block the application from closing.
*/
veto(value: boolean | Promise<boolean>): void;
/**
* The reason why the application will be shutting down.
*/
readonly reason: ShutdownReason;
}
/**
* An event that is send out when the window closes. Clients have a chance to join the closing
* by providing a promise from the join method. Returning a promise is useful in cases of long
* running operations on shutdown.
*
* Note: It is absolutely important to avoid long running promises if possible. Please try hard
* to return a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
*/
export interface WillShutdownEvent {
/**
* Allows to join the shutdown. The promise can be a long running operation but it
* will block the application from closing.
*/
join(promise: Promise<void>): void;
/**
* The reason why the application is shutting down.
*/
readonly reason: ShutdownReason;
}
export const enum ShutdownReason {
/** Window is closed */
CLOSE = 1,
/** Application is quit */
QUIT = 2,
/** Window is reloaded */
RELOAD = 3,
/** Other configuration loaded into window */
LOAD = 4
}
export const enum StartupKind {
NewWindow = 1,
ReloadedWindow = 3,
ReopenedWindow = 4,
}
export function StartupKindToString(startupKind: StartupKind): string {
switch (startupKind) {
case StartupKind.NewWindow: return 'NewWindow';
case StartupKind.ReloadedWindow: return 'ReloadedWindow';
case StartupKind.ReopenedWindow: return 'ReopenedWindow';
}
}
export const enum LifecyclePhase {
/**
* The first phase signals that we are about to startup getting ready.
*/
Starting = 1,
/**
* Services are ready and the view is about to restore its state.
*/
Ready = 2,
/**
* Views, panels and editors have restored. For editors this means, that
* they show their contents fully.
*/
Restored = 3,
/**
* The last phase after views, panels and editors have restored and
* some time has passed (few seconds).
*/
Eventually = 4
}
export function LifecyclePhaseToString(phase: LifecyclePhase) {
switch (phase) {
case LifecyclePhase.Starting: return 'Starting';
case LifecyclePhase.Ready: return 'Ready';
case LifecyclePhase.Restored: return 'Restored';
case LifecyclePhase.Eventually: return 'Eventually';
}
}
/**
* A lifecycle service informs about lifecycle events of the
* application, such as shutdown.
*/
export interface ILifecycleService {
readonly _serviceBrand: undefined;
/**
* Value indicates how this window got loaded.
*/
readonly startupKind: StartupKind;
/**
* A flag indicating in what phase of the lifecycle we currently are.
*/
phase: LifecyclePhase;
/**
* Fired before shutdown happens. Allows listeners to veto against the
* shutdown to prevent it from happening.
*
* The event carries a shutdown reason that indicates how the shutdown was triggered.
*/
readonly onBeforeShutdown: Event<BeforeShutdownEvent>;
/**
* Fired when no client is preventing the shutdown from happening (from onBeforeShutdown).
* Can be used to save UI state even if that is long running through the WillShutdownEvent#join()
* method.
*
* The event carries a shutdown reason that indicates how the shutdown was triggered.
*/
readonly onWillShutdown: Event<WillShutdownEvent>;
/**
* Fired when the shutdown is about to happen after long running shutdown operations
* have finished (from onWillShutdown). This is the right place to dispose resources.
*/
readonly onShutdown: Event<void>;
/**
* Returns a promise that resolves when a certain lifecycle phase
* has started.
*/
when(phase: LifecyclePhase): Promise<void>;
/**
* Triggers a shutdown of the workbench. Depending on native or web, this can have
* different implementations and behaviour.
*
* **Note:** this should normally not be called. See related methods in `IHostService`
* and `INativeHostService` to close a window or quit the application.
*/
shutdown(): void;
}
export const NullLifecycleService: ILifecycleService = {
_serviceBrand: undefined,
onBeforeShutdown: Event.None,
onWillShutdown: Event.None,
onShutdown: Event.None,
phase: LifecyclePhase.Restored,
startupKind: StartupKind.NewWindow,
async when() { },
shutdown() { }
};

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { Barrier } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILifecycleService, BeforeShutdownEvent, WillShutdownEvent, StartupKind, LifecyclePhase, LifecyclePhaseToString } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { mark } from 'vs/base/common/performance';
export abstract class AbstractLifecycleService extends Disposable implements ILifecycleService {
declare readonly _serviceBrand: undefined;
protected readonly _onBeforeShutdown = this._register(new Emitter<BeforeShutdownEvent>());
readonly onBeforeShutdown = this._onBeforeShutdown.event;
protected readonly _onWillShutdown = this._register(new Emitter<WillShutdownEvent>());
readonly onWillShutdown = this._onWillShutdown.event;
protected readonly _onShutdown = this._register(new Emitter<void>());
readonly onShutdown = this._onShutdown.event;
protected _startupKind: StartupKind = StartupKind.NewWindow;
get startupKind(): StartupKind { return this._startupKind; }
private _phase: LifecyclePhase = LifecyclePhase.Starting;
get phase(): LifecyclePhase { return this._phase; }
private readonly phaseWhen = new Map<LifecyclePhase, Barrier>();
constructor(
@ILogService protected readonly logService: ILogService
) {
super();
}
set phase(value: LifecyclePhase) {
if (value < this.phase) {
throw new Error('Lifecycle cannot go backwards');
}
if (this._phase === value) {
return;
}
this.logService.trace(`lifecycle: phase changed (value: ${value})`);
this._phase = value;
mark(`LifecyclePhase/${LifecyclePhaseToString(value)}`);
const barrier = this.phaseWhen.get(this._phase);
if (barrier) {
barrier.open();
this.phaseWhen.delete(this._phase);
}
}
async when(phase: LifecyclePhase): Promise<void> {
if (phase <= this._phase) {
return;
}
let barrier = this.phaseWhen.get(phase);
if (!barrier) {
barrier = new Barrier();
this.phaseWhen.set(phase, barrier);
}
await barrier.wait();
}
/**
* Subclasses to implement the explicit shutdown method.
*/
abstract shutdown(): void;
}

View File

@@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
import { ShutdownReason, StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { onUnexpectedError } from 'vs/base/common/errors';
import { AbstractLifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycleService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import Severity from 'vs/base/common/severity';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
export class NativeLifecycleService extends AbstractLifecycleService {
private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason';
declare readonly _serviceBrand: undefined;
private shutdownReason: ShutdownReason | undefined;
constructor(
@INotificationService private readonly notificationService: INotificationService,
@INativeHostService private readonly nativeHostService: INativeHostService,
@IStorageService readonly storageService: IStorageService,
@ILogService readonly logService: ILogService
) {
super(logService);
this._startupKind = this.resolveStartupKind();
this.registerListeners();
}
private resolveStartupKind(): StartupKind {
const lastShutdownReason = this.storageService.getNumber(NativeLifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE);
this.storageService.remove(NativeLifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE);
let startupKind: StartupKind;
if (lastShutdownReason === ShutdownReason.RELOAD) {
startupKind = StartupKind.ReloadedWindow;
} else if (lastShutdownReason === ShutdownReason.LOAD) {
startupKind = StartupKind.ReopenedWindow;
} else {
startupKind = StartupKind.NewWindow;
}
this.logService.trace(`lifecycle: starting up (startup kind: ${this._startupKind})`);
return startupKind;
}
private registerListeners(): void {
const windowId = this.nativeHostService.windowId;
// Main side indicates that window is about to unload, check for vetos
ipcRenderer.on('vscode:onBeforeUnload', (event: unknown, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => {
this.logService.trace(`lifecycle: onBeforeUnload (reason: ${reply.reason})`);
// trigger onBeforeShutdown events and veto collecting
this.handleBeforeShutdown(reply.reason).then(veto => {
if (veto) {
this.logService.trace('lifecycle: onBeforeUnload prevented via veto');
ipcRenderer.send(reply.cancelChannel, windowId);
} else {
this.logService.trace('lifecycle: onBeforeUnload continues without veto');
this.shutdownReason = reply.reason;
ipcRenderer.send(reply.okChannel, windowId);
}
});
});
// Main side indicates that we will indeed shutdown
ipcRenderer.on('vscode:onWillUnload', async (event: unknown, reply: { replyChannel: string, reason: ShutdownReason }) => {
this.logService.trace(`lifecycle: onWillUnload (reason: ${reply.reason})`);
// trigger onWillShutdown events and joining
await this.handleWillShutdown(reply.reason);
// trigger onShutdown event now that we know we will quit
this._onShutdown.fire();
// acknowledge to main side
ipcRenderer.send(reply.replyChannel, windowId);
});
// Save shutdown reason to retrieve on next startup
this.storageService.onWillSaveState(e => {
if (e.reason === WillSaveStateReason.SHUTDOWN) {
this.storageService.store(NativeLifecycleService.LAST_SHUTDOWN_REASON_KEY, this.shutdownReason, StorageScope.WORKSPACE);
}
});
}
private handleBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
const vetos: (boolean | Promise<boolean>)[] = [];
this._onBeforeShutdown.fire({
veto(value) {
vetos.push(value);
},
reason
});
return handleVetos(vetos, error => this.onShutdownError(reason, error));
}
private async handleWillShutdown(reason: ShutdownReason): Promise<void> {
const joiners: Promise<void>[] = [];
this._onWillShutdown.fire({
join(promise) {
if (promise) {
joiners.push(promise);
}
},
reason
});
try {
await Promise.all(joiners);
} catch (error) {
this.onShutdownError(reason, error);
}
}
private onShutdownError(reason: ShutdownReason, error: Error): void {
let message: string;
switch (reason) {
case ShutdownReason.CLOSE:
message = localize('errorClose', "An unexpected error was thrown while attempting to close the window ({0}).", toErrorMessage(error));
break;
case ShutdownReason.QUIT:
message = localize('errorQuit', "An unexpected error was thrown while attempting to quit the application ({0}).", toErrorMessage(error));
break;
case ShutdownReason.RELOAD:
message = localize('errorReload', "An unexpected error was thrown while attempting to reload the window ({0}).", toErrorMessage(error));
break;
case ShutdownReason.LOAD:
message = localize('errorLoad', "An unexpected error was thrown while attempting to change the workspace of the window ({0}).", toErrorMessage(error));
break;
}
this.notificationService.notify({
severity: Severity.Error,
message,
sticky: true
});
onUnexpectedError(error);
}
shutdown(): void {
this.nativeHostService.closeWindow();
}
}
registerSingleton(ILifecycleService, NativeLifecycleService);