Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/lifecycle/browser/lifecycleService.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 { ShutdownReason, ILifecycleService, StartupKind } from '../common/lifecycle.js';
7
import { ILogService } from '../../../../platform/log/common/log.js';
8
import { AbstractLifecycleService } from '../common/lifecycleService.js';
9
import { localize } from '../../../../nls.js';
10
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
11
import { IDisposable } from '../../../../base/common/lifecycle.js';
12
import { addDisposableListener, EventType } from '../../../../base/browser/dom.js';
13
import { IStorageService, WillSaveStateReason } from '../../../../platform/storage/common/storage.js';
14
import { CancellationToken } from '../../../../base/common/cancellation.js';
15
import { mainWindow } from '../../../../base/browser/window.js';
16
17
export class BrowserLifecycleService extends AbstractLifecycleService {
18
19
private beforeUnloadListener: IDisposable | undefined = undefined;
20
private unloadListener: IDisposable | undefined = undefined;
21
22
private ignoreBeforeUnload = false;
23
24
private didUnload = false;
25
26
constructor(
27
@ILogService logService: ILogService,
28
@IStorageService storageService: IStorageService
29
) {
30
super(logService, storageService);
31
32
this.registerListeners();
33
}
34
35
private registerListeners(): void {
36
37
// Listen to `beforeUnload` to support to veto
38
this.beforeUnloadListener = addDisposableListener(mainWindow, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.onBeforeUnload(e));
39
40
// Listen to `pagehide` to support orderly shutdown
41
// We explicitly do not listen to `unload` event
42
// which would disable certain browser caching.
43
// We currently do not handle the `persisted` property
44
// (https://github.com/microsoft/vscode/issues/136216)
45
this.unloadListener = addDisposableListener(mainWindow, EventType.PAGE_HIDE, () => this.onUnload());
46
}
47
48
private onBeforeUnload(event: BeforeUnloadEvent): void {
49
50
// Before unload ignored (once)
51
if (this.ignoreBeforeUnload) {
52
this.logService.info('[lifecycle] onBeforeUnload triggered but ignored once');
53
54
this.ignoreBeforeUnload = false;
55
}
56
57
// Before unload with veto support
58
else {
59
this.logService.info('[lifecycle] onBeforeUnload triggered and handled with veto support');
60
61
this.doShutdown(() => this.vetoBeforeUnload(event));
62
}
63
}
64
65
private vetoBeforeUnload(event: BeforeUnloadEvent): void {
66
event.preventDefault();
67
event.returnValue = localize('lifecycleVeto', "Changes that you made may not be saved. Please check press 'Cancel' and try again.");
68
}
69
70
withExpectedShutdown(reason: ShutdownReason): Promise<void>;
71
withExpectedShutdown(reason: { disableShutdownHandling: true }, callback: Function): void;
72
withExpectedShutdown(reason: ShutdownReason | { disableShutdownHandling: true }, callback?: Function): Promise<void> | void {
73
74
// Standard shutdown
75
if (typeof reason === 'number') {
76
this.shutdownReason = reason;
77
78
// Ensure UI state is persisted
79
return this.storageService.flush(WillSaveStateReason.SHUTDOWN);
80
}
81
82
// Before unload handling ignored for duration of callback
83
else {
84
this.ignoreBeforeUnload = true;
85
try {
86
callback?.();
87
} finally {
88
this.ignoreBeforeUnload = false;
89
}
90
}
91
}
92
93
async shutdown(): Promise<void> {
94
this.logService.info('[lifecycle] shutdown triggered');
95
96
// An explicit shutdown renders our unload
97
// event handlers disabled, so dispose them.
98
this.beforeUnloadListener?.dispose();
99
this.unloadListener?.dispose();
100
101
// Ensure UI state is persisted
102
await this.storageService.flush(WillSaveStateReason.SHUTDOWN);
103
104
// Handle shutdown without veto support
105
this.doShutdown();
106
}
107
108
private doShutdown(vetoShutdown?: () => void): void {
109
const logService = this.logService;
110
111
// Optimistically trigger a UI state flush
112
// without waiting for it. The browser does
113
// not guarantee that this is being executed
114
// but if a dialog opens, we have a chance
115
// to succeed.
116
this.storageService.flush(WillSaveStateReason.SHUTDOWN);
117
118
let veto = false;
119
120
function handleVeto(vetoResult: boolean | Promise<boolean>, id: string) {
121
if (typeof vetoShutdown !== 'function') {
122
return; // veto handling disabled
123
}
124
125
if (vetoResult instanceof Promise) {
126
logService.error(`[lifecycle] Long running operations before shutdown are unsupported in the web (id: ${id})`);
127
128
veto = true; // implicitly vetos since we cannot handle promises in web
129
}
130
131
if (vetoResult === true) {
132
logService.info(`[lifecycle]: Unload was prevented (id: ${id})`);
133
134
veto = true;
135
}
136
}
137
138
// Before Shutdown
139
this._onBeforeShutdown.fire({
140
reason: ShutdownReason.QUIT,
141
veto(value, id) {
142
handleVeto(value, id);
143
},
144
finalVeto(valueFn, id) {
145
handleVeto(valueFn(), id); // in browser, trigger instantly because we do not support async anyway
146
}
147
});
148
149
// Veto: handle if provided
150
if (veto && typeof vetoShutdown === 'function') {
151
return vetoShutdown();
152
}
153
154
// No veto, continue to shutdown
155
return this.onUnload();
156
}
157
158
private onUnload(): void {
159
if (this.didUnload) {
160
return; // only once
161
}
162
163
this.didUnload = true;
164
this._willShutdown = true;
165
166
// Register a late `pageshow` listener specifically on unload
167
this._register(addDisposableListener(mainWindow, EventType.PAGE_SHOW, (e: PageTransitionEvent) => this.onLoadAfterUnload(e)));
168
169
// First indicate will-shutdown
170
const logService = this.logService;
171
this._onWillShutdown.fire({
172
reason: ShutdownReason.QUIT,
173
joiners: () => [], // Unsupported in web
174
token: CancellationToken.None, // Unsupported in web
175
join(promise, joiner) {
176
if (typeof promise === 'function') {
177
promise();
178
}
179
logService.error(`[lifecycle] Long running operations during shutdown are unsupported in the web (id: ${joiner.id})`);
180
},
181
force: () => { /* No-Op in web */ },
182
});
183
184
// Finally end with did-shutdown
185
this._onDidShutdown.fire();
186
}
187
188
private onLoadAfterUnload(event: PageTransitionEvent): void {
189
190
// We only really care about page-show events
191
// where the browser indicates to us that the
192
// page was restored from cache and not freshly
193
// loaded.
194
const wasRestoredFromCache = event.persisted;
195
if (!wasRestoredFromCache) {
196
return;
197
}
198
199
// At this point, we know that the page was restored from
200
// cache even though it was unloaded before,
201
// so in order to get back to a functional workbench, we
202
// currently can only reload the window
203
// Docs: https://web.dev/bfcache/#optimize-your-pages-for-bfcache
204
// Refs: https://github.com/microsoft/vscode/issues/136035
205
this.withExpectedShutdown({ disableShutdownHandling: true }, () => mainWindow.location.reload());
206
}
207
208
protected override doResolveStartupKind(): StartupKind | undefined {
209
let startupKind = super.doResolveStartupKind();
210
if (typeof startupKind !== 'number') {
211
const timing = performance.getEntriesByType('navigation').at(0) as PerformanceNavigationTiming | undefined;
212
if (timing?.type === 'reload') {
213
// MDN: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming/type#value
214
startupKind = StartupKind.ReloadedWindow;
215
}
216
}
217
218
return startupKind;
219
}
220
}
221
222
registerSingleton(ILifecycleService, BrowserLifecycleService, InstantiationType.Eager);
223
224