Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import electron from 'electron';
7
import { validatedIpcMain } from '../../../base/parts/ipc/electron-main/ipcMain.js';
8
import { Barrier, Promises, timeout } from '../../../base/common/async.js';
9
import { Emitter, Event } from '../../../base/common/event.js';
10
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
11
import { isMacintosh, isWindows } from '../../../base/common/platform.js';
12
import { cwd } from '../../../base/common/process.js';
13
import { assertReturnsDefined } from '../../../base/common/types.js';
14
import { NativeParsedArgs } from '../../environment/common/argv.js';
15
import { createDecorator } from '../../instantiation/common/instantiation.js';
16
import { ILogService } from '../../log/common/log.js';
17
import { IStateService } from '../../state/node/state.js';
18
import { ICodeWindow, LoadReason, UnloadReason } from '../../window/electron-main/window.js';
19
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from '../../workspace/common/workspace.js';
20
import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
21
import { IAuxiliaryWindow } from '../../auxiliaryWindow/electron-main/auxiliaryWindow.js';
22
import { getAllWindowsExcludingOffscreen } from '../../windows/electron-main/windows.js';
23
24
export const ILifecycleMainService = createDecorator<ILifecycleMainService>('lifecycleMainService');
25
26
interface WindowLoadEvent {
27
28
/**
29
* The window that is loaded to a new workspace.
30
*/
31
readonly window: ICodeWindow;
32
33
/**
34
* The workspace the window is loaded into.
35
*/
36
readonly workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined;
37
38
/**
39
* More details why the window loads to a new workspace.
40
*/
41
readonly reason: LoadReason;
42
}
43
44
export const enum ShutdownReason {
45
46
/**
47
* The application exits normally.
48
*/
49
QUIT = 1,
50
51
/**
52
* The application exits abnormally and is being
53
* killed with an exit code (e.g. from integration
54
* test run)
55
*/
56
KILL
57
}
58
59
export interface ShutdownEvent {
60
61
/**
62
* More details why the application is shutting down.
63
*/
64
reason: ShutdownReason;
65
66
/**
67
* Allows to join the shutdown. The promise can be a long running operation but it
68
* will block the application from closing.
69
*/
70
join(id: string, promise: Promise<void>): void;
71
}
72
73
export interface IRelaunchHandler {
74
75
/**
76
* Allows a handler to deal with relaunching the application. The return
77
* value indicates if the relaunch is handled or not.
78
*/
79
handleRelaunch(options?: IRelaunchOptions): boolean;
80
}
81
82
export interface IRelaunchOptions {
83
readonly addArgs?: string[];
84
readonly removeArgs?: string[];
85
}
86
87
export interface ILifecycleMainService {
88
89
readonly _serviceBrand: undefined;
90
91
/**
92
* Will be true if the program was restarted (e.g. due to explicit request or update).
93
*/
94
readonly wasRestarted: boolean;
95
96
/**
97
* Will be true if the program was requested to quit.
98
*/
99
readonly quitRequested: boolean;
100
101
/**
102
* A flag indicating in what phase of the lifecycle we currently are.
103
*/
104
phase: LifecycleMainPhase;
105
106
/**
107
* An event that fires when the application is about to shutdown before any window is closed.
108
* The shutdown can still be prevented by any window that vetos this event.
109
*/
110
readonly onBeforeShutdown: Event<void>;
111
112
/**
113
* An event that fires after the onBeforeShutdown event has been fired and after no window has
114
* vetoed the shutdown sequence. At this point listeners are ensured that the application will
115
* quit without veto.
116
*/
117
readonly onWillShutdown: Event<ShutdownEvent>;
118
119
/**
120
* An event that fires when a window is loading. This can either be a window opening for the
121
* first time or a window reloading or changing to another URL.
122
*/
123
readonly onWillLoadWindow: Event<WindowLoadEvent>;
124
125
/**
126
* An event that fires before a window closes. This event is fired after any veto has been dealt
127
* with so that listeners know for sure that the window will close without veto.
128
*/
129
readonly onBeforeCloseWindow: Event<ICodeWindow>;
130
131
/**
132
* Make a `ICodeWindow` known to the lifecycle main service.
133
*/
134
registerWindow(window: ICodeWindow): void;
135
136
/**
137
* Make a `IAuxiliaryWindow` known to the lifecycle main service.
138
*/
139
registerAuxWindow(auxWindow: IAuxiliaryWindow): void;
140
141
/**
142
* Reload a window. All lifecycle event handlers are triggered.
143
*/
144
reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise<void>;
145
146
/**
147
* Unload a window for the provided reason. All lifecycle event handlers are triggered.
148
*/
149
unload(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */>;
150
151
/**
152
* Restart the application with optional arguments (CLI). All lifecycle event handlers are triggered.
153
*/
154
relaunch(options?: IRelaunchOptions): Promise<void>;
155
156
/**
157
* Sets a custom handler for relaunching the application.
158
*/
159
setRelaunchHandler(handler: IRelaunchHandler): void;
160
161
/**
162
* Shutdown the application normally. All lifecycle event handlers are triggered.
163
*/
164
quit(willRestart?: boolean): Promise<boolean /* veto */>;
165
166
/**
167
* Forcefully shutdown the application and optionally set an exit code.
168
*
169
* This method should only be used in rare situations where it is important
170
* to set an exit code (e.g. running tests) or when the application is
171
* not in a healthy state and should terminate asap.
172
*
173
* This method does not fire the normal lifecycle events to the windows,
174
* that normally can be vetoed. Windows are destroyed without a chance
175
* of components to participate. The only lifecycle event handler that
176
* is triggered is `onWillShutdown` in the main process.
177
*/
178
kill(code?: number): Promise<void>;
179
180
/**
181
* Returns a promise that resolves when a certain lifecycle phase
182
* has started.
183
*/
184
when(phase: LifecycleMainPhase): Promise<void>;
185
}
186
187
export const enum LifecycleMainPhase {
188
189
/**
190
* The first phase signals that we are about to startup.
191
*/
192
Starting = 1,
193
194
/**
195
* Services are ready and first window is about to open.
196
*/
197
Ready = 2,
198
199
/**
200
* This phase signals a point in time after the window has opened
201
* and is typically the best place to do work that is not required
202
* for the window to open.
203
*/
204
AfterWindowOpen = 3,
205
206
/**
207
* The last phase after a window has opened and some time has passed
208
* (2-5 seconds).
209
*/
210
Eventually = 4
211
}
212
213
export class LifecycleMainService extends Disposable implements ILifecycleMainService {
214
215
declare readonly _serviceBrand: undefined;
216
217
private static readonly QUIT_AND_RESTART_KEY = 'lifecycle.quitAndRestart';
218
219
private readonly _onBeforeShutdown = this._register(new Emitter<void>());
220
readonly onBeforeShutdown = this._onBeforeShutdown.event;
221
222
private readonly _onWillShutdown = this._register(new Emitter<ShutdownEvent>());
223
readonly onWillShutdown = this._onWillShutdown.event;
224
225
private readonly _onWillLoadWindow = this._register(new Emitter<WindowLoadEvent>());
226
readonly onWillLoadWindow = this._onWillLoadWindow.event;
227
228
private readonly _onBeforeCloseWindow = this._register(new Emitter<ICodeWindow>());
229
readonly onBeforeCloseWindow = this._onBeforeCloseWindow.event;
230
231
private _quitRequested = false;
232
get quitRequested(): boolean { return this._quitRequested; }
233
234
private _wasRestarted: boolean = false;
235
get wasRestarted(): boolean { return this._wasRestarted; }
236
237
private _phase = LifecycleMainPhase.Starting;
238
get phase(): LifecycleMainPhase { return this._phase; }
239
240
private readonly windowToCloseRequest = new Set<number>();
241
private oneTimeListenerTokenGenerator = 0;
242
private windowCounter = 0;
243
244
private pendingQuitPromise: Promise<boolean> | undefined = undefined;
245
private pendingQuitPromiseResolve: { (veto: boolean): void } | undefined = undefined;
246
247
private pendingWillShutdownPromise: Promise<void> | undefined = undefined;
248
249
private readonly mapWindowIdToPendingUnload = new Map<number, Promise<boolean>>();
250
251
private readonly phaseWhen = new Map<LifecycleMainPhase, Barrier>();
252
253
private relaunchHandler: IRelaunchHandler | undefined = undefined;
254
255
constructor(
256
@ILogService private readonly logService: ILogService,
257
@IStateService private readonly stateService: IStateService,
258
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService
259
) {
260
super();
261
262
this.resolveRestarted();
263
this.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
264
}
265
266
private resolveRestarted(): void {
267
this._wasRestarted = !!this.stateService.getItem(LifecycleMainService.QUIT_AND_RESTART_KEY);
268
269
if (this._wasRestarted) {
270
// remove the marker right after if found
271
this.stateService.removeItem(LifecycleMainService.QUIT_AND_RESTART_KEY);
272
}
273
}
274
275
private registerListeners(): void {
276
277
// before-quit: an event that is fired if application quit was
278
// requested but before any window was closed.
279
const beforeQuitListener = () => {
280
if (this._quitRequested) {
281
return;
282
}
283
284
this.trace('Lifecycle#app.on(before-quit)');
285
this._quitRequested = true;
286
287
// Emit event to indicate that we are about to shutdown
288
this.trace('Lifecycle#onBeforeShutdown.fire()');
289
this._onBeforeShutdown.fire();
290
291
// macOS: can run without any window open. in that case we fire
292
// the onWillShutdown() event directly because there is no veto
293
// to be expected.
294
if (isMacintosh && this.windowCounter === 0) {
295
this.fireOnWillShutdown(ShutdownReason.QUIT);
296
}
297
};
298
electron.app.addListener('before-quit', beforeQuitListener);
299
300
// window-all-closed: an event that only fires when the last window
301
// was closed. We override this event to be in charge if app.quit()
302
// should be called or not.
303
const windowAllClosedListener = () => {
304
this.trace('Lifecycle#app.on(window-all-closed)');
305
306
// Windows/Linux: we quit when all windows have closed
307
// Mac: we only quit when quit was requested
308
if (this._quitRequested || !isMacintosh) {
309
electron.app.quit();
310
}
311
};
312
electron.app.addListener('window-all-closed', windowAllClosedListener);
313
314
// will-quit: an event that is fired after all windows have been
315
// closed, but before actually quitting.
316
electron.app.once('will-quit', e => {
317
this.trace('Lifecycle#app.on(will-quit) - begin');
318
319
// Prevent the quit until the shutdown promise was resolved
320
e.preventDefault();
321
322
// Start shutdown sequence
323
const shutdownPromise = this.fireOnWillShutdown(ShutdownReason.QUIT);
324
325
// Wait until shutdown is signaled to be complete
326
shutdownPromise.finally(() => {
327
this.trace('Lifecycle#app.on(will-quit) - after fireOnWillShutdown');
328
329
// Resolve pending quit promise now without veto
330
this.resolvePendingQuitPromise(false /* no veto */);
331
332
// Quit again, this time do not prevent this, since our
333
// will-quit listener is only installed "once". Also
334
// remove any listener we have that is no longer needed
335
336
electron.app.removeListener('before-quit', beforeQuitListener);
337
electron.app.removeListener('window-all-closed', windowAllClosedListener);
338
339
this.trace('Lifecycle#app.on(will-quit) - calling app.quit()');
340
341
electron.app.quit();
342
});
343
});
344
}
345
346
private fireOnWillShutdown(reason: ShutdownReason): Promise<void> {
347
if (this.pendingWillShutdownPromise) {
348
return this.pendingWillShutdownPromise; // shutdown is already running
349
}
350
351
const logService = this.logService;
352
this.trace('Lifecycle#onWillShutdown.fire()');
353
354
const joiners: Promise<void>[] = [];
355
356
this._onWillShutdown.fire({
357
reason,
358
join(id, promise) {
359
logService.trace(`Lifecycle#onWillShutdown - begin '${id}'`);
360
joiners.push(promise.finally(() => {
361
logService.trace(`Lifecycle#onWillShutdown - end '${id}'`);
362
}));
363
}
364
});
365
366
this.pendingWillShutdownPromise = (async () => {
367
368
// Settle all shutdown event joiners
369
try {
370
await Promises.settled(joiners);
371
} catch (error) {
372
this.logService.error(error);
373
}
374
375
// Then, always make sure at the end
376
// the state service is flushed.
377
try {
378
await this.stateService.close();
379
} catch (error) {
380
this.logService.error(error);
381
}
382
})();
383
384
return this.pendingWillShutdownPromise;
385
}
386
387
set phase(value: LifecycleMainPhase) {
388
if (value < this.phase) {
389
throw new Error('Lifecycle cannot go backwards');
390
}
391
392
if (this._phase === value) {
393
return;
394
}
395
396
this.trace(`lifecycle (main): phase changed (value: ${value})`);
397
398
this._phase = value;
399
400
const barrier = this.phaseWhen.get(this._phase);
401
if (barrier) {
402
barrier.open();
403
this.phaseWhen.delete(this._phase);
404
}
405
}
406
407
async when(phase: LifecycleMainPhase): Promise<void> {
408
if (phase <= this._phase) {
409
return;
410
}
411
412
let barrier = this.phaseWhen.get(phase);
413
if (!barrier) {
414
barrier = new Barrier();
415
this.phaseWhen.set(phase, barrier);
416
}
417
418
await barrier.wait();
419
}
420
421
registerWindow(window: ICodeWindow): void {
422
const windowListeners = new DisposableStore();
423
424
// track window count
425
this.windowCounter++;
426
427
// Window Will Load
428
windowListeners.add(window.onWillLoad(e => this._onWillLoadWindow.fire({ window, workspace: e.workspace, reason: e.reason })));
429
430
// Window Before Closing: Main -> Renderer
431
const win = assertReturnsDefined(window.win);
432
windowListeners.add(Event.fromNodeEventEmitter<electron.Event>(win, 'close')(e => {
433
434
// The window already acknowledged to be closed
435
const windowId = window.id;
436
if (this.windowToCloseRequest.has(windowId)) {
437
this.windowToCloseRequest.delete(windowId);
438
439
return;
440
}
441
442
this.trace(`Lifecycle#window.on('close') - window ID ${window.id}`);
443
444
// Otherwise prevent unload and handle it from window
445
e.preventDefault();
446
this.unload(window, UnloadReason.CLOSE).then(veto => {
447
if (veto) {
448
this.windowToCloseRequest.delete(windowId);
449
return;
450
}
451
452
this.windowToCloseRequest.add(windowId);
453
454
// Fire onBeforeCloseWindow before actually closing
455
this.trace(`Lifecycle#onBeforeCloseWindow.fire() - window ID ${windowId}`);
456
this._onBeforeCloseWindow.fire(window);
457
458
// No veto, close window now
459
window.close();
460
});
461
}));
462
windowListeners.add(Event.fromNodeEventEmitter<electron.Event>(win, 'closed')(() => {
463
this.trace(`Lifecycle#window.on('closed') - window ID ${window.id}`);
464
465
// update window count
466
this.windowCounter--;
467
468
// clear window listeners
469
windowListeners.dispose();
470
471
// if there are no more code windows opened, fire the onWillShutdown event, unless
472
// we are on macOS where it is perfectly fine to close the last window and
473
// the application continues running (unless quit was actually requested)
474
if (this.windowCounter === 0 && (!isMacintosh || this._quitRequested)) {
475
this.fireOnWillShutdown(ShutdownReason.QUIT);
476
}
477
}));
478
}
479
480
registerAuxWindow(auxWindow: IAuxiliaryWindow): void {
481
const win = assertReturnsDefined(auxWindow.win);
482
483
const windowListeners = new DisposableStore();
484
windowListeners.add(Event.fromNodeEventEmitter<electron.Event>(win, 'close')(e => {
485
this.trace(`Lifecycle#auxWindow.on('close') - window ID ${auxWindow.id}`);
486
487
if (this._quitRequested) {
488
this.trace(`Lifecycle#auxWindow.on('close') - preventDefault() because quit requested`);
489
490
// When quit is requested, Electron will close all
491
// auxiliary windows before closing the main windows.
492
// This prevents us from storing the auxiliary window
493
// state on shutdown and thus we prevent closing if
494
// quit is requested.
495
//
496
// Interestingly, this will not prevent the application
497
// from quitting because the auxiliary windows will still
498
// close once the owning window closes.
499
500
e.preventDefault();
501
}
502
}));
503
windowListeners.add(Event.fromNodeEventEmitter<electron.Event>(win, 'closed')(() => {
504
this.trace(`Lifecycle#auxWindow.on('closed') - window ID ${auxWindow.id}`);
505
506
windowListeners.dispose();
507
}));
508
}
509
510
async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise<void> {
511
512
// Only reload when the window has not vetoed this
513
const veto = await this.unload(window, UnloadReason.RELOAD);
514
if (!veto) {
515
window.reload(cli);
516
}
517
}
518
519
unload(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */> {
520
521
// Ensure there is only 1 unload running at the same time
522
const pendingUnloadPromise = this.mapWindowIdToPendingUnload.get(window.id);
523
if (pendingUnloadPromise) {
524
return pendingUnloadPromise;
525
}
526
527
// Start unload and remember in map until finished
528
const unloadPromise = this.doUnload(window, reason).finally(() => {
529
this.mapWindowIdToPendingUnload.delete(window.id);
530
});
531
this.mapWindowIdToPendingUnload.set(window.id, unloadPromise);
532
533
return unloadPromise;
534
}
535
536
private async doUnload(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */> {
537
538
// Always allow to unload a window that is not yet ready
539
if (!window.isReady) {
540
return false;
541
}
542
543
this.trace(`Lifecycle#unload() - window ID ${window.id}`);
544
545
// first ask the window itself if it vetos the unload
546
const windowUnloadReason = this._quitRequested ? UnloadReason.QUIT : reason;
547
const veto = await this.onBeforeUnloadWindowInRenderer(window, windowUnloadReason);
548
if (veto) {
549
this.trace(`Lifecycle#unload() - veto in renderer (window ID ${window.id})`);
550
551
return this.handleWindowUnloadVeto(veto);
552
}
553
554
// finally if there are no vetos, unload the renderer
555
await this.onWillUnloadWindowInRenderer(window, windowUnloadReason);
556
557
return false;
558
}
559
560
private handleWindowUnloadVeto(veto: boolean): boolean {
561
if (!veto) {
562
return false; // no veto
563
}
564
565
// a veto resolves any pending quit with veto
566
this.resolvePendingQuitPromise(true /* veto */);
567
568
// a veto resets the pending quit request flag
569
this._quitRequested = false;
570
571
return true; // veto
572
}
573
574
private resolvePendingQuitPromise(veto: boolean): void {
575
if (this.pendingQuitPromiseResolve) {
576
this.pendingQuitPromiseResolve(veto);
577
this.pendingQuitPromiseResolve = undefined;
578
this.pendingQuitPromise = undefined;
579
}
580
}
581
582
private onBeforeUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */> {
583
return new Promise<boolean>(resolve => {
584
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
585
const okChannel = `vscode:ok${oneTimeEventToken}`;
586
const cancelChannel = `vscode:cancel${oneTimeEventToken}`;
587
588
validatedIpcMain.once(okChannel, () => {
589
resolve(false); // no veto
590
});
591
592
validatedIpcMain.once(cancelChannel, () => {
593
resolve(true); // veto
594
});
595
596
window.send('vscode:onBeforeUnload', { okChannel, cancelChannel, reason });
597
});
598
}
599
600
private onWillUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Promise<void> {
601
return new Promise<void>(resolve => {
602
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
603
const replyChannel = `vscode:reply${oneTimeEventToken}`;
604
605
validatedIpcMain.once(replyChannel, () => resolve());
606
607
window.send('vscode:onWillUnload', { replyChannel, reason });
608
});
609
}
610
611
quit(willRestart?: boolean): Promise<boolean /* veto */> {
612
return this.doQuit(willRestart).then(veto => {
613
if (!veto && willRestart) {
614
// Windows: we are about to restart and as such we need to restore the original
615
// current working directory we had on startup to get the exact same startup
616
// behaviour. As such, we briefly change back to that directory and then when
617
// Code starts it will set it back to the installation directory again.
618
try {
619
if (isWindows) {
620
const currentWorkingDir = cwd();
621
if (currentWorkingDir !== process.cwd()) {
622
process.chdir(currentWorkingDir);
623
}
624
}
625
} catch (err) {
626
this.logService.error(err);
627
}
628
}
629
630
return veto;
631
});
632
}
633
634
private doQuit(willRestart?: boolean): Promise<boolean /* veto */> {
635
this.trace(`Lifecycle#quit() - begin (willRestart: ${willRestart})`);
636
637
if (this.pendingQuitPromise) {
638
this.trace('Lifecycle#quit() - returning pending quit promise');
639
640
return this.pendingQuitPromise;
641
}
642
643
// Remember if we are about to restart
644
if (willRestart) {
645
this.stateService.setItem(LifecycleMainService.QUIT_AND_RESTART_KEY, true);
646
}
647
648
this.pendingQuitPromise = new Promise(resolve => {
649
650
// Store as field to access it from a window cancellation
651
this.pendingQuitPromiseResolve = resolve;
652
653
// Calling app.quit() will trigger the close handlers of each opened window
654
// and only if no window vetoed the shutdown, we will get the will-quit event
655
this.trace('Lifecycle#quit() - calling app.quit()');
656
electron.app.quit();
657
});
658
659
return this.pendingQuitPromise;
660
}
661
662
private trace(msg: string): void {
663
if (this.environmentMainService.args['enable-smoke-test-driver']) {
664
this.logService.info(msg); // helps diagnose issues with exiting from smoke tests
665
} else {
666
this.logService.trace(msg);
667
}
668
}
669
670
setRelaunchHandler(handler: IRelaunchHandler): void {
671
this.relaunchHandler = handler;
672
}
673
674
async relaunch(options?: IRelaunchOptions): Promise<void> {
675
this.trace('Lifecycle#relaunch()');
676
677
const args = process.argv.slice(1);
678
if (options?.addArgs) {
679
args.push(...options.addArgs);
680
}
681
682
if (options?.removeArgs) {
683
for (const a of options.removeArgs) {
684
const idx = args.indexOf(a);
685
if (idx >= 0) {
686
args.splice(idx, 1);
687
}
688
}
689
}
690
691
const quitListener = () => {
692
if (!this.relaunchHandler?.handleRelaunch(options)) {
693
this.trace('Lifecycle#relaunch() - calling app.relaunch()');
694
electron.app.relaunch({ args });
695
}
696
};
697
electron.app.once('quit', quitListener);
698
699
// `app.relaunch()` does not quit automatically, so we quit first,
700
// check for vetoes and then relaunch from the `app.on('quit')` event
701
const veto = await this.quit(true /* will restart */);
702
if (veto) {
703
electron.app.removeListener('quit', quitListener);
704
}
705
}
706
707
async kill(code?: number): Promise<void> {
708
this.trace('Lifecycle#kill()');
709
710
// Give main process participants a chance to orderly shutdown
711
await this.fireOnWillShutdown(ShutdownReason.KILL);
712
713
// From extension tests we have seen issues where calling app.exit()
714
// with an opened window can lead to native crashes (Linux). As such,
715
// we should make sure to destroy any opened window before calling
716
// `app.exit()`.
717
//
718
// Note: Electron implements a similar logic here:
719
// https://github.com/electron/electron/blob/fe5318d753637c3903e23fc1ed1b263025887b6a/spec-main/window-helpers.ts#L5
720
721
await Promise.race([
722
723
// Still do not block more than 1s
724
timeout(1000),
725
726
// Destroy any opened window: we do not unload windows here because
727
// there is a chance that the unload is veto'd or long running due
728
// to a participant within the window. this is not wanted when we
729
// are asked to kill the application.
730
(async () => {
731
for (const window of getAllWindowsExcludingOffscreen()) {
732
if (window && !window.isDestroyed()) {
733
let whenWindowClosed: Promise<void>;
734
if (window.webContents && !window.webContents.isDestroyed()) {
735
whenWindowClosed = new Promise(resolve => window.once('closed', resolve));
736
} else {
737
whenWindowClosed = Promise.resolve();
738
}
739
740
window.destroy();
741
await whenWindowClosed;
742
}
743
}
744
})()
745
]);
746
747
// Now exit either after 1s or all windows destroyed
748
electron.app.exit(code);
749
}
750
}
751
752