@theia/core 1.21.0-next.17 → 1.21.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/common-frontend-contribution.d.ts +5 -2
- package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/common-frontend-contribution.js +21 -4
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/dialogs.d.ts +1 -0
- package/lib/browser/dialogs.d.ts.map +1 -1
- package/lib/browser/dialogs.js +11 -1
- package/lib/browser/dialogs.js.map +1 -1
- package/lib/browser/frontend-application.d.ts +15 -2
- package/lib/browser/frontend-application.d.ts.map +1 -1
- package/lib/browser/frontend-application.js +8 -1
- package/lib/browser/frontend-application.js.map +1 -1
- package/lib/browser/shell/application-shell-mouse-tracker.js +2 -2
- package/lib/browser/shell/application-shell-mouse-tracker.js.map +1 -1
- package/lib/browser/shell/application-shell.d.ts +0 -13
- package/lib/browser/shell/application-shell.d.ts.map +1 -1
- package/lib/browser/shell/application-shell.js +1 -16
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.d.ts +2 -0
- package/lib/browser/shell/shell-layout-restorer.d.ts.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.js +13 -6
- package/lib/browser/shell/shell-layout-restorer.js.map +1 -1
- package/lib/browser/window/default-window-service.d.ts +19 -2
- package/lib/browser/window/default-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-window-service.js +67 -12
- package/lib/browser/window/default-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.spec.js +3 -3
- package/lib/browser/window/default-window-service.spec.js.map +1 -1
- package/lib/browser/window/test/mock-window-service.d.ts +3 -1
- package/lib/browser/window/test/mock-window-service.d.ts.map +1 -1
- package/lib/browser/window/test/mock-window-service.js +3 -1
- package/lib/browser/window/test/mock-window-service.js.map +1 -1
- package/lib/browser/window/window-service.d.ts +20 -6
- package/lib/browser/window/window-service.d.ts.map +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.d.ts +2 -0
- package/lib/electron-browser/menu/electron-menu-contribution.d.ts.map +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js +6 -0
- package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
- package/lib/electron-browser/window/electron-window-service.d.ts +7 -5
- package/lib/electron-browser/window/electron-window-service.d.ts.map +1 -1
- package/lib/electron-browser/window/electron-window-service.js +20 -42
- package/lib/electron-browser/window/electron-window-service.js.map +1 -1
- package/lib/electron-common/messaging/electron-messages.d.ts +27 -0
- package/lib/electron-common/messaging/electron-messages.d.ts.map +1 -1
- package/lib/electron-common/messaging/electron-messages.js +24 -1
- package/lib/electron-common/messaging/electron-messages.js.map +1 -1
- package/lib/electron-main/electron-main-application.d.ts +9 -0
- package/lib/electron-main/electron-main-application.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application.js +57 -14
- package/lib/electron-main/electron-main-application.js.map +1 -1
- package/package.json +3 -3
- package/src/browser/common-frontend-contribution.ts +23 -6
- package/src/browser/dialogs.ts +9 -0
- package/src/browser/frontend-application.ts +19 -2
- package/src/browser/shell/application-shell-mouse-tracker.ts +2 -2
- package/src/browser/shell/application-shell.ts +1 -18
- package/src/browser/shell/shell-layout-restorer.ts +12 -6
- package/src/browser/window/default-window-service.spec.ts +3 -3
- package/src/browser/window/default-window-service.ts +69 -13
- package/src/browser/window/test/mock-window-service.ts +3 -1
- package/src/browser/window/window-service.ts +22 -8
- package/src/electron-browser/menu/electron-menu-contribution.ts +6 -1
- package/src/electron-browser/window/electron-window-service.ts +21 -41
- package/src/electron-common/messaging/electron-messages.ts +29 -0
- package/src/electron-main/electron-main-application.ts +74 -15
|
@@ -18,14 +18,16 @@ import { inject, injectable, named } from 'inversify';
|
|
|
18
18
|
import { Event, Emitter } from '../../common';
|
|
19
19
|
import { CorePreferences } from '../core-preferences';
|
|
20
20
|
import { ContributionProvider } from '../../common/contribution-provider';
|
|
21
|
-
import { FrontendApplicationContribution, FrontendApplication } from '../frontend-application';
|
|
21
|
+
import { FrontendApplicationContribution, FrontendApplication, OnWillStopAction } from '../frontend-application';
|
|
22
22
|
import { WindowService } from './window-service';
|
|
23
23
|
import { DEFAULT_WINDOW_HASH } from '../../common/window';
|
|
24
|
+
import { confirmExit } from '../dialogs';
|
|
24
25
|
|
|
25
26
|
@injectable()
|
|
26
27
|
export class DefaultWindowService implements WindowService, FrontendApplicationContribution {
|
|
27
28
|
|
|
28
29
|
protected frontendApplication: FrontendApplication;
|
|
30
|
+
protected allowVetoes = true;
|
|
29
31
|
|
|
30
32
|
protected onUnloadEmitter = new Emitter<void>();
|
|
31
33
|
get onUnload(): Event<void> {
|
|
@@ -53,26 +55,40 @@ export class DefaultWindowService implements WindowService, FrontendApplicationC
|
|
|
53
55
|
this.openNewWindow(`#${DEFAULT_WINDOW_HASH}`);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Returns a list of actions that {@link FrontendApplicationContribution}s would like to take before shutdown
|
|
60
|
+
* It is expected that this will succeed - i.e. return an empty array - at most once per session. If no vetoes are received
|
|
61
|
+
* during any cycle, no further checks will be made. In that case, shutdown should proceed unconditionally.
|
|
62
|
+
*/
|
|
63
|
+
protected collectContributionUnloadVetoes(): OnWillStopAction[] {
|
|
64
|
+
const vetoes = [];
|
|
65
|
+
if (this.allowVetoes) {
|
|
66
|
+
const shouldConfirmExit = this.corePreferences['application.confirmExit'];
|
|
67
|
+
for (const contribution of this.contributions.getContributions()) {
|
|
68
|
+
const veto = contribution.onWillStop?.(this.frontendApplication);
|
|
69
|
+
if (veto && shouldConfirmExit !== 'never') { // Ignore vetoes if we should not prompt the user on exit.
|
|
70
|
+
if (OnWillStopAction.is(veto)) {
|
|
71
|
+
vetoes.push(veto);
|
|
72
|
+
} else {
|
|
73
|
+
vetoes.push({ reason: 'No reason given', action: () => false });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (vetoes.length === 0 && shouldConfirmExit === 'always') {
|
|
78
|
+
vetoes.push({ reason: 'application.confirmExit preference', action: () => confirmExit() });
|
|
79
|
+
}
|
|
80
|
+
if (vetoes.length === 0) {
|
|
81
|
+
this.allowVetoes = false;
|
|
62
82
|
}
|
|
63
83
|
}
|
|
64
|
-
return
|
|
84
|
+
return vetoes;
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
/**
|
|
68
88
|
* Implement the mechanism to detect unloading of the page.
|
|
69
89
|
*/
|
|
70
90
|
protected registerUnloadListeners(): void {
|
|
71
|
-
window.addEventListener('beforeunload', event =>
|
|
72
|
-
if (!this.canUnload()) {
|
|
73
|
-
return this.preventUnload(event);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
91
|
+
window.addEventListener('beforeunload', event => this.handleBeforeUnloadEvent(event));
|
|
76
92
|
// In a browser, `unload` is correctly fired when the page unloads, unlike Electron.
|
|
77
93
|
// If `beforeunload` is cancelled, the user will be prompted to leave or stay.
|
|
78
94
|
// If the user stays, the page won't be unloaded, so `unload` is not fired.
|
|
@@ -80,6 +96,43 @@ export class DefaultWindowService implements WindowService, FrontendApplicationC
|
|
|
80
96
|
window.addEventListener('unload', () => this.onUnloadEmitter.fire());
|
|
81
97
|
}
|
|
82
98
|
|
|
99
|
+
async isSafeToShutDown(): Promise<boolean> {
|
|
100
|
+
const vetoes = this.collectContributionUnloadVetoes();
|
|
101
|
+
if (vetoes.length === 0) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
console.debug('Shutdown prevented by', vetoes.map(({ reason }) => reason).join(', '));
|
|
105
|
+
const resolvedVetoes = await Promise.allSettled(vetoes.map(({ action }) => action()));
|
|
106
|
+
if (resolvedVetoes.every(resolution => resolution.status === 'rejected' || resolution.value === true)) {
|
|
107
|
+
console.debug('OnWillStop actions resolved; allowing shutdown');
|
|
108
|
+
this.allowVetoes = false;
|
|
109
|
+
return true;
|
|
110
|
+
} else {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setSafeToShutDown(): void {
|
|
116
|
+
this.allowVetoes = false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Called when the `window` is about to `unload` its resources.
|
|
121
|
+
* At this point, the `document` is still visible and the [`BeforeUnloadEvent`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event)
|
|
122
|
+
* event will be canceled if the return value of this method is `false`.
|
|
123
|
+
*
|
|
124
|
+
* In Electron, handleCloseRequestEvent is is run instead.
|
|
125
|
+
*/
|
|
126
|
+
protected handleBeforeUnloadEvent(event: BeforeUnloadEvent): string | void {
|
|
127
|
+
const vetoes = this.collectContributionUnloadVetoes();
|
|
128
|
+
if (vetoes.length) {
|
|
129
|
+
// In the browser, we don't call the functions because this has to finish in a single tick, so we treat any desired action as a veto.
|
|
130
|
+
console.debug('Shutdown prevented by', vetoes.map(({ reason }) => reason).join(', '));
|
|
131
|
+
return this.preventUnload(event);
|
|
132
|
+
}
|
|
133
|
+
console.debug('Shutdown will proceed.');
|
|
134
|
+
}
|
|
135
|
+
|
|
83
136
|
/**
|
|
84
137
|
* Notify the browser that we do not want to unload.
|
|
85
138
|
*
|
|
@@ -95,4 +148,7 @@ export class DefaultWindowService implements WindowService, FrontendApplicationC
|
|
|
95
148
|
return '';
|
|
96
149
|
}
|
|
97
150
|
|
|
151
|
+
reload(): void {
|
|
152
|
+
window.location.reload();
|
|
153
|
+
}
|
|
98
154
|
}
|
|
@@ -21,6 +21,8 @@ import { WindowService } from '../window-service';
|
|
|
21
21
|
export class MockWindowService implements WindowService {
|
|
22
22
|
openNewWindow(): undefined { return undefined; }
|
|
23
23
|
openNewDefaultWindow(): void { }
|
|
24
|
-
|
|
24
|
+
reload(): void { }
|
|
25
|
+
isSafeToShutDown(): Promise<boolean> { return Promise.resolve(true); }
|
|
26
|
+
setSafeToShutDown(): void { }
|
|
25
27
|
get onUnload(): Event<void> { return Event.None; }
|
|
26
28
|
}
|
|
@@ -23,7 +23,6 @@ import { NewWindowOptions } from '../../common/window';
|
|
|
23
23
|
export const WindowService = Symbol('WindowService');
|
|
24
24
|
|
|
25
25
|
export interface WindowService {
|
|
26
|
-
|
|
27
26
|
/**
|
|
28
27
|
* Opens a new window and loads the content from the given URL.
|
|
29
28
|
* In a browser, opening a new Theia tab or open a link is the same thing.
|
|
@@ -37,17 +36,32 @@ export interface WindowService {
|
|
|
37
36
|
*/
|
|
38
37
|
openNewDefaultWindow(): void;
|
|
39
38
|
|
|
40
|
-
/**
|
|
41
|
-
* Called when the `window` is about to `unload` its resources.
|
|
42
|
-
* At this point, the `document` is still visible and the [`BeforeUnloadEvent`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event)
|
|
43
|
-
* event will be canceled if the return value of this method is `false`.
|
|
44
|
-
*/
|
|
45
|
-
canUnload(): boolean;
|
|
46
|
-
|
|
47
39
|
/**
|
|
48
40
|
* Fires when the `window` unloads. The unload event is inevitable. On this event, the frontend application can save its state and release resource.
|
|
49
41
|
* Saving the state and releasing any resources must be a synchronous call. Any asynchronous calls invoked after emitting this event might be ignored.
|
|
50
42
|
*/
|
|
51
43
|
readonly onUnload: Event<void>;
|
|
52
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Checks `FrontendApplicationContribution#willStop` for impediments to shutdown and runs any actions returned.
|
|
47
|
+
* Can be used safely in browser and Electron when triggering reload or shutdown programmatically.
|
|
48
|
+
* Should _only_ be called before a shutdown - if this returns `true`, `FrontendApplicationContribution#willStop`
|
|
49
|
+
* will not be called again in the current session. I.e. if this return `true`, the shutdown should proceed without
|
|
50
|
+
* further condition.
|
|
51
|
+
*/
|
|
52
|
+
isSafeToShutDown(): Promise<boolean>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Will prevent subsequent checks of `FrontendApplicationContribution#willStop`. Should only be used after requesting
|
|
56
|
+
* user confirmation.
|
|
57
|
+
*
|
|
58
|
+
* This is primarily intended programmatic restarts due to e.g. change of display language. It allows for a single confirmation
|
|
59
|
+
* of intent, rather than one warning and then several warnings from other contributions.
|
|
60
|
+
*/
|
|
61
|
+
setSafeToShutDown(): void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reloads the window according to platform.
|
|
65
|
+
*/
|
|
66
|
+
reload(): void;
|
|
53
67
|
}
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from '../../common';
|
|
23
23
|
import {
|
|
24
24
|
ApplicationShell, codicon, ConfirmDialog, KeybindingContribution, KeybindingRegistry,
|
|
25
|
-
PreferenceScope, Widget, FrontendApplication, FrontendApplicationContribution, CommonMenus, CommonCommands, Dialog
|
|
25
|
+
PreferenceScope, Widget, FrontendApplication, FrontendApplicationContribution, CommonMenus, CommonCommands, Dialog,
|
|
26
26
|
} from '../../browser';
|
|
27
27
|
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
|
|
28
28
|
import { FrontendApplicationStateService, FrontendApplicationState } from '../../browser/frontend-application-state';
|
|
@@ -30,6 +30,7 @@ import { FrontendApplicationConfigProvider } from '../../browser/frontend-applic
|
|
|
30
30
|
import { RequestTitleBarStyle, Restart, TitleBarStyleAtStartup, TitleBarStyleChanged } from '../../electron-common/messaging/electron-messages';
|
|
31
31
|
import { ZoomLevel } from '../window/electron-window-preferences';
|
|
32
32
|
import { BrowserMenuBarContribution } from '../../browser/menu/browser-menu-plugin';
|
|
33
|
+
import { WindowService } from '../../browser/window/window-service';
|
|
33
34
|
|
|
34
35
|
import '../../../src/electron-browser/menu/electron-menu-style.css';
|
|
35
36
|
|
|
@@ -84,6 +85,9 @@ export class ElectronMenuContribution extends BrowserMenuBarContribution impleme
|
|
|
84
85
|
@inject(FrontendApplicationStateService)
|
|
85
86
|
protected readonly stateService: FrontendApplicationStateService;
|
|
86
87
|
|
|
88
|
+
@inject(WindowService)
|
|
89
|
+
protected readonly windowService: WindowService;
|
|
90
|
+
|
|
87
91
|
protected titleBarStyleChangeFlag = false;
|
|
88
92
|
protected titleBarStyle?: string;
|
|
89
93
|
|
|
@@ -241,6 +245,7 @@ export class ElectronMenuContribution extends BrowserMenuBarContribution impleme
|
|
|
241
245
|
cancel: Dialog.CANCEL
|
|
242
246
|
});
|
|
243
247
|
if (await dialog.open()) {
|
|
248
|
+
this.windowService.setSafeToShutDown();
|
|
244
249
|
electron.ipcRenderer.send(Restart);
|
|
245
250
|
}
|
|
246
251
|
}
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
********************************************************************************/
|
|
16
16
|
|
|
17
17
|
import { injectable, inject, postConstruct } from 'inversify';
|
|
18
|
-
import
|
|
18
|
+
import * as electron from '../../../shared/electron';
|
|
19
19
|
import { NewWindowOptions } from '../../common/window';
|
|
20
20
|
import { DefaultWindowService } from '../../browser/window/default-window-service';
|
|
21
21
|
import { ElectronMainWindowService } from '../../electron-common/electron-main-window-service';
|
|
22
22
|
import { ElectronWindowPreferences } from './electron-window-preferences';
|
|
23
|
+
import { CloseRequestArguments, CLOSE_REQUESTED_SIGNAL, RELOAD_REQUESTED_SIGNAL, StopReason } from '../../electron-common/messaging/electron-messages';
|
|
23
24
|
|
|
24
25
|
@injectable()
|
|
25
26
|
export class ElectronWindowService extends DefaultWindowService {
|
|
@@ -59,49 +60,24 @@ export class ElectronWindowService extends DefaultWindowService {
|
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
registerUnloadListeners(): void {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Unloading process ongoing, do nothing:
|
|
66
|
-
return this.preventUnload(event);
|
|
67
|
-
} else if (this.closeOnUnload || this.canUnload()) {
|
|
68
|
-
// Let the window close and notify clients:
|
|
69
|
-
delete event.returnValue;
|
|
70
|
-
this.onUnloadEmitter.fire();
|
|
71
|
-
return;
|
|
72
|
-
} else {
|
|
73
|
-
this.isUnloading = true;
|
|
74
|
-
// Fix https://github.com/eclipse-theia/theia/issues/8186#issuecomment-742624480
|
|
75
|
-
// On Electron/Linux doing `showMessageBoxSync` does not seems to block the closing
|
|
76
|
-
// process long enough and closes the window no matter what you click on (yes/no).
|
|
77
|
-
// Instead we'll prevent closing right away, ask for confirmation and finally close.
|
|
78
|
-
setTimeout(() => {
|
|
79
|
-
if (this.shouldUnload()) {
|
|
80
|
-
this.closeOnUnload = true;
|
|
81
|
-
window.close();
|
|
82
|
-
}
|
|
83
|
-
this.isUnloading = false;
|
|
84
|
-
});
|
|
85
|
-
return this.preventUnload(event);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
63
|
+
protected registerUnloadListeners(): void {
|
|
64
|
+
electron.ipcRenderer.on(CLOSE_REQUESTED_SIGNAL, (_event, closeRequestEvent: CloseRequestArguments) => this.handleCloseRequestedEvent(closeRequestEvent));
|
|
65
|
+
window.addEventListener('unload', () => this.onUnloadEmitter.fire());
|
|
88
66
|
}
|
|
89
67
|
|
|
90
68
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
69
|
+
* Run when ElectronMain detects a `close` event and emits a `close-requested` event.
|
|
70
|
+
* Should send an event to `electron.ipcRenderer` on the event's `confirmChannel` if it is safe to exit
|
|
71
|
+
* after running FrontentApplication `onWillStop` handlers or on the `cancelChannel` if it is not safe to exit.
|
|
94
72
|
*/
|
|
95
|
-
protected
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
return response === 0; // 'Yes', close the window.
|
|
73
|
+
protected async handleCloseRequestedEvent(event: CloseRequestArguments): Promise<void> {
|
|
74
|
+
const safeToClose = await this.isSafeToShutDown();
|
|
75
|
+
if (safeToClose) {
|
|
76
|
+
console.debug(`Shutting down because of ${StopReason[event.reason]} request.`);
|
|
77
|
+
electron.ipcRenderer.send(event.confirmChannel);
|
|
78
|
+
} else {
|
|
79
|
+
electron.ipcRenderer.send(event.cancelChannel);
|
|
80
|
+
}
|
|
105
81
|
}
|
|
106
82
|
|
|
107
83
|
/**
|
|
@@ -109,9 +85,13 @@ export class ElectronWindowService extends DefaultWindowService {
|
|
|
109
85
|
*/
|
|
110
86
|
protected updateWindowZoomLevel(): void {
|
|
111
87
|
const preferredZoomLevel = this.electronWindowPreferences['window.zoomLevel'];
|
|
112
|
-
const webContents = remote.getCurrentWindow().webContents;
|
|
88
|
+
const webContents = electron.remote.getCurrentWindow().webContents;
|
|
113
89
|
if (webContents.getZoomLevel() !== preferredZoomLevel) {
|
|
114
90
|
webContents.setZoomLevel(preferredZoomLevel);
|
|
115
91
|
}
|
|
116
92
|
}
|
|
93
|
+
|
|
94
|
+
reload(): void {
|
|
95
|
+
electron.ipcRenderer.send(RELOAD_REQUESTED_SIGNAL);
|
|
96
|
+
}
|
|
117
97
|
}
|
|
@@ -18,3 +18,32 @@ export const RequestTitleBarStyle = 'requestTitleBarStyle';
|
|
|
18
18
|
export const TitleBarStyleChanged = 'titleBarStyleChanged';
|
|
19
19
|
export const TitleBarStyleAtStartup = 'titleBarStyleAtStartup';
|
|
20
20
|
export const Restart = 'restart';
|
|
21
|
+
/**
|
|
22
|
+
* Emitted by main when close requested.
|
|
23
|
+
*/
|
|
24
|
+
export const CLOSE_REQUESTED_SIGNAL = 'close-requested';
|
|
25
|
+
/**
|
|
26
|
+
* Emitted by window when a reload is requested.
|
|
27
|
+
*/
|
|
28
|
+
export const RELOAD_REQUESTED_SIGNAL = 'reload-requested';
|
|
29
|
+
|
|
30
|
+
export enum StopReason {
|
|
31
|
+
/**
|
|
32
|
+
* Closing the window with no prospect of restart.
|
|
33
|
+
*/
|
|
34
|
+
Close,
|
|
35
|
+
/**
|
|
36
|
+
* Reload without closing the window.
|
|
37
|
+
*/
|
|
38
|
+
Reload,
|
|
39
|
+
/**
|
|
40
|
+
* Reload that includes closing the window.
|
|
41
|
+
*/
|
|
42
|
+
Restart, // eslint-disable-line @typescript-eslint/no-shadow
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CloseRequestArguments {
|
|
46
|
+
confirmChannel: string;
|
|
47
|
+
cancelChannel: string;
|
|
48
|
+
reason: StopReason;
|
|
49
|
+
}
|
|
@@ -31,7 +31,14 @@ import { ElectronSecurityTokenService } from './electron-security-token-service'
|
|
|
31
31
|
import { ElectronSecurityToken } from '../electron-common/electron-token';
|
|
32
32
|
import Storage = require('electron-store');
|
|
33
33
|
import { isOSX, isWindows } from '../common';
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
CLOSE_REQUESTED_SIGNAL,
|
|
36
|
+
RELOAD_REQUESTED_SIGNAL,
|
|
37
|
+
RequestTitleBarStyle,
|
|
38
|
+
Restart, StopReason,
|
|
39
|
+
TitleBarStyleAtStartup,
|
|
40
|
+
TitleBarStyleChanged
|
|
41
|
+
} from '../electron-common/messaging/electron-messages';
|
|
35
42
|
import { DEFAULT_WINDOW_HASH } from '../common/window';
|
|
36
43
|
|
|
37
44
|
const createYargs: (argv?: string[], cwd?: string) => Argv = require('yargs/yargs');
|
|
@@ -192,6 +199,8 @@ export class ElectronMainApplication {
|
|
|
192
199
|
protected useNativeWindowFrame: boolean = true;
|
|
193
200
|
protected didUseNativeWindowFrameOnStart = new Map<number, boolean>();
|
|
194
201
|
protected restarting = false;
|
|
202
|
+
protected closeIsConfirmed = new Set<number>();
|
|
203
|
+
protected closeRequested = 0;
|
|
195
204
|
|
|
196
205
|
get config(): FrontendApplicationConfig {
|
|
197
206
|
if (!this._config) {
|
|
@@ -256,6 +265,7 @@ export class ElectronMainApplication {
|
|
|
256
265
|
this.attachSaveWindowState(electronWindow);
|
|
257
266
|
this.attachGlobalShortcuts(electronWindow);
|
|
258
267
|
this.restoreMaximizedState(electronWindow, options);
|
|
268
|
+
this.attachCloseListeners(electronWindow, options);
|
|
259
269
|
return electronWindow;
|
|
260
270
|
}
|
|
261
271
|
|
|
@@ -433,19 +443,20 @@ export class ElectronMainApplication {
|
|
|
433
443
|
* Catch certain keybindings to prevent reloading the window using keyboard shortcuts.
|
|
434
444
|
*/
|
|
435
445
|
protected attachGlobalShortcuts(electronWindow: BrowserWindow): void {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
446
|
+
const handler = this.config.electron?.disallowReloadKeybinding
|
|
447
|
+
? () => { }
|
|
448
|
+
: () => this.reload(electronWindow);
|
|
449
|
+
const accelerators = ['CmdOrCtrl+R', 'F5'];
|
|
450
|
+
electronWindow.on('focus', () => {
|
|
451
|
+
for (const accelerator of accelerators) {
|
|
452
|
+
globalShortcut.register(accelerator, handler);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
electronWindow.on('blur', () => {
|
|
456
|
+
for (const accelerator of accelerators) {
|
|
457
|
+
globalShortcut.unregister(accelerator);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
449
460
|
}
|
|
450
461
|
|
|
451
462
|
protected restoreMaximizedState(electronWindow: BrowserWindow, options: TheiaBrowserWindowOptions): void {
|
|
@@ -456,6 +467,43 @@ export class ElectronMainApplication {
|
|
|
456
467
|
}
|
|
457
468
|
}
|
|
458
469
|
|
|
470
|
+
protected attachCloseListeners(electronWindow: BrowserWindow, options: TheiaBrowserWindowOptions): void {
|
|
471
|
+
electronWindow.on('close', async event => {
|
|
472
|
+
// User has already indicated that it is OK to close this window.
|
|
473
|
+
if (this.closeIsConfirmed.has(electronWindow.id)) {
|
|
474
|
+
this.closeIsConfirmed.delete(electronWindow.id);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
event.preventDefault();
|
|
479
|
+
this.handleStopRequest(electronWindow, () => this.doCloseWindow(electronWindow), StopReason.Close);
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
protected doCloseWindow(electronWindow: BrowserWindow): void {
|
|
484
|
+
this.closeIsConfirmed.add(electronWindow.id);
|
|
485
|
+
electronWindow.close();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
protected async handleStopRequest(electronWindow: BrowserWindow, onSafeCallback: () => unknown, reason: StopReason): Promise<void> {
|
|
489
|
+
// Only confirm close to windows that have loaded our front end.
|
|
490
|
+
const safeToClose = !electronWindow.webContents.getURL().includes(this.globals.THEIA_FRONTEND_HTML_PATH) || await this.checkSafeToStop(electronWindow, reason);
|
|
491
|
+
if (safeToClose) {
|
|
492
|
+
onSafeCallback();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
protected checkSafeToStop(electronWindow: BrowserWindow, reason: StopReason): Promise<boolean> {
|
|
497
|
+
const closeRequest = this.closeRequested++;
|
|
498
|
+
const confirmChannel = `safeToClose-${electronWindow.id}-${closeRequest}`;
|
|
499
|
+
const cancelChannel = `notSafeToClose-${electronWindow.id}-${closeRequest}`;
|
|
500
|
+
return new Promise<boolean>(resolve => {
|
|
501
|
+
electronWindow.webContents.send(CLOSE_REQUESTED_SIGNAL, { confirmChannel, cancelChannel, reason });
|
|
502
|
+
ipcMain.once(confirmChannel, () => resolve(true));
|
|
503
|
+
ipcMain.once(cancelChannel, () => resolve(false));
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
459
507
|
/**
|
|
460
508
|
* Start the NodeJS backend server.
|
|
461
509
|
*
|
|
@@ -541,6 +589,8 @@ export class ElectronMainApplication {
|
|
|
541
589
|
this.restart(sender.id);
|
|
542
590
|
});
|
|
543
591
|
|
|
592
|
+
ipcMain.on(RELOAD_REQUESTED_SIGNAL, event => this.handleReload(event));
|
|
593
|
+
|
|
544
594
|
ipcMain.on(RequestTitleBarStyle, ({ sender }) => {
|
|
545
595
|
sender.send(TitleBarStyleAtStartup, this.didUseNativeWindowFrameOnStart.get(sender.id) ? 'native' : 'custom');
|
|
546
596
|
});
|
|
@@ -578,7 +628,16 @@ export class ElectronMainApplication {
|
|
|
578
628
|
});
|
|
579
629
|
this.restarting = false;
|
|
580
630
|
});
|
|
581
|
-
window.
|
|
631
|
+
this.handleStopRequest(window, () => this.doCloseWindow(window), StopReason.Restart);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
protected async handleReload(event: Electron.IpcMainEvent): Promise<void> {
|
|
635
|
+
const window = BrowserWindow.fromId(event.sender.id);
|
|
636
|
+
this.reload(window);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
protected reload(electronWindow: BrowserWindow): void {
|
|
640
|
+
this.handleStopRequest(electronWindow, () => electronWindow.reload(), StopReason.Reload);
|
|
582
641
|
}
|
|
583
642
|
|
|
584
643
|
protected async startContributions(): Promise<void> {
|