Skip to content

Commit 72963ca

Browse files
author
Benjamin Pasero
committed
lifecycle: allow main side unload participation
1 parent a51d89e commit 72963ca

3 files changed

Lines changed: 89 additions & 37 deletions

File tree

src/vs/platform/lifecycle/common/lifecycle.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,34 @@ export const NullLifecycleService: ILifecycleService = {
9696
onWillShutdown: Event.None,
9797
onShutdown: Event.None
9898
};
99+
100+
// Shared veto handling across main and renderer
101+
export function handleVetos(vetos: (boolean | TPromise<boolean>)[], onError: (error: Error) => void): TPromise<boolean /* veto */> {
102+
if (vetos.length === 0) {
103+
return TPromise.as(false);
104+
}
105+
106+
const promises: TPromise<void>[] = [];
107+
let lazyValue = false;
108+
109+
for (let valueOrPromise of vetos) {
110+
111+
// veto, done
112+
if (valueOrPromise === true) {
113+
return TPromise.as(true);
114+
}
115+
116+
if (TPromise.is(valueOrPromise)) {
117+
promises.push(valueOrPromise.then(value => {
118+
if (value) {
119+
lazyValue = true; // veto, done
120+
}
121+
}, err => {
122+
onError(err); // error, treated like a veto, done
123+
lazyValue = true;
124+
}));
125+
}
126+
}
127+
128+
return TPromise.join(promises).then(() => lazyValue);
129+
}

src/vs/platform/lifecycle/electron-main/lifecycleMain.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Event, { Emitter } from 'vs/base/common/event';
1414
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1515
import { ICodeWindow } from "vs/platform/windows/electron-main/windows";
1616
import { ReadyState } from 'vs/platform/windows/common/windows';
17+
import { handleVetos } from "vs/platform/lifecycle/common/lifecycle";
1718

1819
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
1920

@@ -24,6 +25,12 @@ export enum UnloadReason {
2425
LOAD = 4
2526
}
2627

28+
export interface IWindowUnloadEvent {
29+
window: ICodeWindow;
30+
reason: UnloadReason;
31+
veto(value: boolean | TPromise<boolean>): void;
32+
}
33+
2734
export interface ILifecycleService {
2835
_serviceBrand: any;
2936

@@ -46,6 +53,11 @@ export interface ILifecycleService {
4653
*/
4754
onBeforeWindowClose: Event<ICodeWindow>;
4855

56+
/**
57+
* An even that can be vetoed to prevent a window from being unloaded.
58+
*/
59+
onBeforeWindowUnload: Event<IWindowUnloadEvent>;
60+
4961
ready(): void;
5062
registerWindow(codeWindow: ICodeWindow): void;
5163

@@ -78,6 +90,9 @@ export class LifecycleService implements ILifecycleService {
7890
private _onBeforeWindowClose = new Emitter<ICodeWindow>();
7991
onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
8092

93+
private _onBeforeWindowUnload = new Emitter<IWindowUnloadEvent>();
94+
onBeforeWindowUnload: Event<IWindowUnloadEvent> = this._onBeforeWindowUnload.event;
95+
8196
constructor(
8297
@IEnvironmentService private environmentService: IEnvironmentService,
8398
@ILogService private logService: ILogService,
@@ -173,6 +188,32 @@ export class LifecycleService implements ILifecycleService {
173188

174189
this.logService.log('Lifecycle#unload()', codeWindow.id);
175190

191+
const windowUnloadReason = this.quitRequested ? UnloadReason.QUIT : reason;
192+
193+
// first ask the window itself if it vetos the unload
194+
return this.doUnloadWindowInRenderer(codeWindow, windowUnloadReason).then(veto => {
195+
if (veto) {
196+
return this.handleVeto(veto);
197+
}
198+
199+
// then check for vetos in the main side
200+
return this.doUnloadWindowInMain(codeWindow, windowUnloadReason).then(veto => this.handleVeto(veto));
201+
});
202+
}
203+
204+
private handleVeto(veto: boolean): boolean {
205+
206+
// Any cancellation also cancels a pending quit if present
207+
if (veto && this.pendingQuitPromiseComplete) {
208+
this.pendingQuitPromiseComplete(true /* veto */);
209+
this.pendingQuitPromiseComplete = null;
210+
this.pendingQuitPromise = null;
211+
}
212+
213+
return veto;
214+
}
215+
216+
private doUnloadWindowInRenderer(codeWindow: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
176217
return new TPromise<boolean>((c) => {
177218
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
178219
const okChannel = `vscode:ok${oneTimeEventToken}`;
@@ -183,21 +224,27 @@ export class LifecycleService implements ILifecycleService {
183224
});
184225

185226
ipc.once(cancelChannel, () => {
186-
187-
// Any cancellation also cancels a pending quit if present
188-
if (this.pendingQuitPromiseComplete) {
189-
this.pendingQuitPromiseComplete(true /* veto */);
190-
this.pendingQuitPromiseComplete = null;
191-
this.pendingQuitPromise = null;
192-
}
193-
194227
c(true); // veto
195228
});
196229

197-
codeWindow.send('vscode:beforeUnload', { okChannel, cancelChannel, reason: this.quitRequested ? UnloadReason.QUIT : reason });
230+
codeWindow.send('vscode:beforeUnload', { okChannel, cancelChannel, reason });
198231
});
199232
}
200233

234+
private doUnloadWindowInMain(window: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
235+
const vetos: (boolean | TPromise<boolean>)[] = [];
236+
237+
this._onBeforeWindowUnload.fire({
238+
reason,
239+
window,
240+
veto(value) {
241+
vetos.push(value);
242+
}
243+
});
244+
245+
return handleVetos(vetos, err => this.logService.error(err));
246+
}
247+
201248
/**
202249
* A promise that completes to indicate if the quit request has been veto'd
203250
* by the user or not.

src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { TPromise } from 'vs/base/common/winjs.base';
88
import Severity from 'vs/base/common/severity';
99
import { toErrorMessage } from 'vs/base/common/errorMessage';
10-
import { ILifecycleService, ShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
10+
import { ILifecycleService, ShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
1111
import { IMessageService } from 'vs/platform/message/common/message';
1212
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
1313
import { ipcRenderer as ipc } from 'electron';
@@ -104,32 +104,6 @@ export class LifecycleService implements ILifecycleService {
104104
reason
105105
});
106106

107-
if (vetos.length === 0) {
108-
return TPromise.as(false);
109-
}
110-
111-
const promises: TPromise<void>[] = [];
112-
let lazyValue = false;
113-
114-
for (let valueOrPromise of vetos) {
115-
116-
// veto, done
117-
if (valueOrPromise === true) {
118-
return TPromise.as(true);
119-
}
120-
121-
if (TPromise.is(valueOrPromise)) {
122-
promises.push(valueOrPromise.then(value => {
123-
if (value) {
124-
lazyValue = true; // veto, done
125-
}
126-
}, err => {
127-
// error, treated like a veto, done
128-
this._messageService.show(Severity.Error, toErrorMessage(err));
129-
lazyValue = true;
130-
}));
131-
}
132-
}
133-
return TPromise.join(promises).then(() => lazyValue);
107+
return handleVetos(vetos, err => this._messageService.show(Severity.Error, toErrorMessage(err)));
134108
}
135109
}

0 commit comments

Comments
 (0)