@theia/core 1.39.0-next.1 → 1.39.0-next.11

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.
Files changed (74) hide show
  1. package/README.md +6 -6
  2. package/lib/browser/dialogs.d.ts +15 -9
  3. package/lib/browser/dialogs.d.ts.map +1 -1
  4. package/lib/browser/dialogs.js +67 -54
  5. package/lib/browser/dialogs.js.map +1 -1
  6. package/lib/browser/saveable.d.ts.map +1 -1
  7. package/lib/browser/saveable.js +4 -2
  8. package/lib/browser/saveable.js.map +1 -1
  9. package/lib/browser/secondary-window-handler.d.ts +2 -2
  10. package/lib/browser/secondary-window-handler.d.ts.map +1 -1
  11. package/lib/browser/secondary-window-handler.js +8 -34
  12. package/lib/browser/secondary-window-handler.js.map +1 -1
  13. package/lib/browser/styling-service.d.ts +5 -2
  14. package/lib/browser/styling-service.d.ts.map +1 -1
  15. package/lib/browser/styling-service.js +15 -5
  16. package/lib/browser/styling-service.js.map +1 -1
  17. package/lib/browser/widgets/widget.d.ts +1 -0
  18. package/lib/browser/widgets/widget.d.ts.map +1 -1
  19. package/lib/browser/widgets/widget.js +7 -3
  20. package/lib/browser/widgets/widget.js.map +1 -1
  21. package/lib/browser/window/default-secondary-window-service.d.ts +5 -3
  22. package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
  23. package/lib/browser/window/default-secondary-window-service.js +62 -18
  24. package/lib/browser/window/default-secondary-window-service.js.map +1 -1
  25. package/lib/browser/window/secondary-window-service.d.ts +3 -1
  26. package/lib/browser/window/secondary-window-service.d.ts.map +1 -1
  27. package/lib/browser/window/secondary-window-service.js.map +1 -1
  28. package/lib/common/message-rpc/rpc-message-encoder.d.ts +3 -3
  29. package/lib/common/message-rpc/rpc-message-encoder.d.ts.map +1 -1
  30. package/lib/common/message-rpc/rpc-message-encoder.js +2 -2
  31. package/lib/common/message-rpc/rpc-message-encoder.js.map +1 -1
  32. package/lib/common/message-rpc/rpc-protocol.d.ts +1 -1
  33. package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -1
  34. package/lib/common/message-rpc/rpc-protocol.js +8 -4
  35. package/lib/common/message-rpc/rpc-protocol.js.map +1 -1
  36. package/lib/common/messaging/proxy-factory.d.ts +6 -6
  37. package/lib/common/messaging/proxy-factory.d.ts.map +1 -1
  38. package/lib/common/messaging/proxy-factory.js +11 -10
  39. package/lib/common/messaging/proxy-factory.js.map +1 -1
  40. package/lib/electron-browser/preload.d.ts.map +1 -1
  41. package/lib/electron-browser/preload.js +20 -15
  42. package/lib/electron-browser/preload.js.map +1 -1
  43. package/lib/electron-browser/window/electron-secondary-window-service.d.ts +3 -1
  44. package/lib/electron-browser/window/electron-secondary-window-service.d.ts.map +1 -1
  45. package/lib/electron-browser/window/electron-secondary-window-service.js +7 -2
  46. package/lib/electron-browser/window/electron-secondary-window-service.js.map +1 -1
  47. package/lib/electron-common/electron-api.d.ts +2 -0
  48. package/lib/electron-common/electron-api.d.ts.map +1 -1
  49. package/lib/electron-common/electron-api.js +2 -1
  50. package/lib/electron-common/electron-api.js.map +1 -1
  51. package/lib/electron-main/electron-api-main.d.ts +1 -0
  52. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  53. package/lib/electron-main/electron-api-main.js +16 -0
  54. package/lib/electron-main/electron-api-main.js.map +1 -1
  55. package/lib/electron-main/theia-electron-window.d.ts +1 -0
  56. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  57. package/lib/electron-main/theia-electron-window.js +32 -0
  58. package/lib/electron-main/theia-electron-window.js.map +1 -1
  59. package/package.json +4 -4
  60. package/src/browser/dialogs.ts +69 -52
  61. package/src/browser/saveable.ts +4 -2
  62. package/src/browser/secondary-window-handler.ts +7 -38
  63. package/src/browser/styling-service.ts +17 -6
  64. package/src/browser/widgets/widget.ts +4 -0
  65. package/src/browser/window/default-secondary-window-service.ts +67 -18
  66. package/src/browser/window/secondary-window-service.ts +4 -1
  67. package/src/common/message-rpc/rpc-message-encoder.ts +4 -4
  68. package/src/common/message-rpc/rpc-protocol.ts +8 -4
  69. package/src/common/messaging/proxy-factory.ts +13 -15
  70. package/src/electron-browser/preload.ts +21 -1
  71. package/src/electron-browser/window/electron-secondary-window-service.ts +8 -2
  72. package/src/electron-common/electron-api.ts +4 -0
  73. package/src/electron-main/electron-api-main.ts +19 -1
  74. package/src/electron-main/theia-electron-window.ts +30 -0
@@ -23,6 +23,7 @@ import { Emitter, Event } from '../event';
23
23
  import { Channel } from '../message-rpc/channel';
24
24
  import { RequestHandler, RpcProtocol } from '../message-rpc/rpc-protocol';
25
25
  import { ConnectionHandler } from './handler';
26
+ import { Deferred } from '../promise-util';
26
27
 
27
28
  export type JsonRpcServer<Client> = Disposable & {
28
29
  /**
@@ -55,11 +56,11 @@ export class JsonRpcConnectionHandler<T extends object> implements ConnectionHan
55
56
  }
56
57
  }
57
58
  /**
58
- * Factory for creating a new {@link RpcConnection} for a given chanel and {@link RequestHandler}.
59
+ * Factory for creating a new {@link RpcProtocol} for a given chanel and {@link RequestHandler}.
59
60
  */
60
- export type RpcConnectionFactory = (channel: Channel, requestHandler: RequestHandler) => RpcProtocol;
61
+ export type RpcProtocolFactory = (channel: Channel, requestHandler: RequestHandler) => RpcProtocol;
61
62
 
62
- const defaultRPCConnectionFactory: RpcConnectionFactory = (channel, requestHandler) => new RpcProtocol(channel, requestHandler);
63
+ const defaultRpcProtocolFactory: RpcProtocolFactory = (channel, requestHandler) => new RpcProtocol(channel, requestHandler);
63
64
 
64
65
  /**
65
66
  * Factory for JSON-RPC proxy objects.
@@ -109,8 +110,7 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
109
110
  protected readonly onDidOpenConnectionEmitter = new Emitter<void>();
110
111
  protected readonly onDidCloseConnectionEmitter = new Emitter<void>();
111
112
 
112
- protected connectionPromiseResolve: (connection: RpcProtocol) => void;
113
- protected connectionPromise: Promise<RpcProtocol>;
113
+ protected rpcDeferred: Deferred<RpcProtocol>;
114
114
 
115
115
  /**
116
116
  * Build a new JsonRpcProxyFactory.
@@ -118,16 +118,14 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
118
118
  * @param target - The object to expose to JSON-RPC methods calls. If this
119
119
  * is omitted, the proxy won't be able to handle requests, only send them.
120
120
  */
121
- constructor(public target?: any, protected rpcConnectionFactory = defaultRPCConnectionFactory) {
121
+ constructor(public target?: any, protected rpcProtocolFactory = defaultRpcProtocolFactory) {
122
122
  this.waitForConnection();
123
123
  }
124
124
 
125
125
  protected waitForConnection(): void {
126
- this.connectionPromise = new Promise(resolve =>
127
- this.connectionPromiseResolve = resolve
128
- );
129
- this.connectionPromise.then(connection => {
130
- connection.channel.onClose(() => {
126
+ this.rpcDeferred = new Deferred<RpcProtocol>();
127
+ this.rpcDeferred.promise.then(protocol => {
128
+ protocol.channel.onClose(() => {
131
129
  this.onDidCloseConnectionEmitter.fire(undefined);
132
130
  // Wait for connection in case the backend reconnects
133
131
  this.waitForConnection();
@@ -143,10 +141,10 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
143
141
  * response.
144
142
  */
145
143
  listen(channel: Channel): void {
146
- const connection = this.rpcConnectionFactory(channel, (meth, args) => this.onRequest(meth, ...args));
147
- connection.onNotification(event => this.onNotification(event.method, ...event.args));
144
+ const protocol = this.rpcProtocolFactory(channel, (meth, args) => this.onRequest(meth, ...args));
145
+ protocol.onNotification(event => this.onNotification(event.method, ...event.args));
148
146
 
149
- this.connectionPromiseResolve(connection);
147
+ this.rpcDeferred.resolve(protocol);
150
148
  }
151
149
 
152
150
  /**
@@ -249,7 +247,7 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
249
247
  return (...args: any[]) => {
250
248
  const method = p.toString();
251
249
  const capturedError = new Error(`Request '${method}' failed`);
252
- return this.connectionPromise.then(connection =>
250
+ return this.rpcDeferred.promise.then(connection =>
253
251
  new Promise<void>((resolve, reject) => {
254
252
  try {
255
253
  if (isNotify) {
@@ -13,6 +13,7 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  //
16
+ import { IpcRendererEvent } from '@theia/electron/shared/electron';
16
17
  import { Disposable } from '../common/disposable';
17
18
  import { StopReason } from '../common/frontend-application-state';
18
19
  import { NativeKeyboardLayout } from '../common/keyboard/keyboard-layout-provider';
@@ -24,7 +25,7 @@ import {
24
25
  CHANNEL_ON_WINDOW_EVENT, CHANNEL_GET_ZOOM_LEVEL, CHANNEL_SET_ZOOM_LEVEL, CHANNEL_IS_FULL_SCREENABLE, CHANNEL_TOGGLE_FULL_SCREEN,
25
26
  CHANNEL_IS_FULL_SCREEN, CHANNEL_SET_MENU_BAR_VISIBLE, CHANNEL_REQUEST_CLOSE, CHANNEL_SET_TITLE_STYLE, CHANNEL_RESTART,
26
27
  CHANNEL_REQUEST_RELOAD, CHANNEL_APP_STATE_CHANGED, CHANNEL_SHOW_ITEM_IN_FOLDER, CHANNEL_READ_CLIPBOARD, CHANNEL_WRITE_CLIPBOARD,
27
- CHANNEL_KEYBOARD_LAYOUT_CHANGED, CHANNEL_IPC_CONNECTION, InternalMenuDto
28
+ CHANNEL_KEYBOARD_LAYOUT_CHANGED, CHANNEL_IPC_CONNECTION, InternalMenuDto, CHANNEL_REQUEST_SECONDARY_CLOSE
28
29
  } from '../electron-common/electron-api';
29
30
 
30
31
  // eslint-disable-next-line import/no-extraneous-dependencies
@@ -138,6 +139,25 @@ const api: TheiaCoreAPI = {
138
139
  });
139
140
  },
140
141
 
142
+ setSecondaryWindowCloseRequestHandler(windowName: string, handler: () => Promise<boolean>): void {
143
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
+ const listener: (event: IpcRendererEvent, ...args: any[]) => void = async (event, name, confirmChannel, cancelChannel) => {
145
+ if (name === windowName) {
146
+ try {
147
+ if (await handler()) {
148
+ event.sender.send(confirmChannel);
149
+ ipcRenderer.removeListener(CHANNEL_REQUEST_SECONDARY_CLOSE, listener);
150
+ return;
151
+ };
152
+ } catch (e) {
153
+ console.warn('exception in close handler ', e);
154
+ }
155
+ event.sender.send(cancelChannel);
156
+ }
157
+ };
158
+ ipcRenderer.on(CHANNEL_REQUEST_SECONDARY_CLOSE, listener);
159
+ },
160
+
141
161
  toggleDevTools: function (): void {
142
162
  ipcRenderer.send(CHANNEL_TOGGLE_DEVTOOLS);
143
163
  },
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { injectable } from 'inversify';
18
18
  import { DefaultSecondaryWindowService } from '../../browser/window/default-secondary-window-service';
19
+ import { ApplicationShell, ExtractableWidget } from 'src/browser';
19
20
 
20
21
  @injectable()
21
22
  export class ElectronSecondaryWindowService extends DefaultSecondaryWindowService {
@@ -23,11 +24,16 @@ export class ElectronSecondaryWindowService extends DefaultSecondaryWindowServic
23
24
  window.electronTheiaCore.focusWindow(win.name);
24
25
  }
25
26
 
26
- protected override doCreateSecondaryWindow(onClose?: (closedWin: Window) => void): Window | undefined {
27
- const w = super.doCreateSecondaryWindow(onClose);
27
+ protected override doCreateSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined {
28
+ const w = super.doCreateSecondaryWindow(widget, shell);
28
29
  if (w) {
29
30
  window.electronTheiaCore.setMenuBarVisible(false, w.name);
31
+ window.electronTheiaCore.setSecondaryWindowCloseRequestHandler(w.name, () => this.canClose(widget, shell));
30
32
  }
31
33
  return w;
32
34
  }
35
+ private async canClose(widget: ExtractableWidget, shell: ApplicationShell): Promise<boolean> {
36
+ await shell.closeWidget(widget.id, undefined);
37
+ return widget.isDisposed;
38
+ }
33
39
  }
@@ -64,6 +64,8 @@ export interface TheiaCoreAPI {
64
64
  onWindowEvent(event: WindowEvent, handler: () => void): Disposable;
65
65
  setCloseRequestHandler(handler: (reason: StopReason) => Promise<boolean>): void;
66
66
 
67
+ setSecondaryWindowCloseRequestHandler(windowName: string, handler: () => Promise<boolean>): void;
68
+
67
69
  toggleDevTools(): void;
68
70
  getZoomLevel(): Promise<number>;
69
71
  setZoomLevel(desired: number): void;
@@ -121,6 +123,8 @@ export const CHANNEL_IS_FULL_SCREENABLE = 'IsFullScreenable';
121
123
  export const CHANNEL_IS_FULL_SCREEN = 'IsFullScreen';
122
124
  export const CHANNEL_TOGGLE_FULL_SCREEN = 'ToggleFullScreen';
123
125
 
126
+ export const CHANNEL_REQUEST_SECONDARY_CLOSE = 'RequestSecondaryClose';
127
+
124
128
  export const CHANNEL_REQUEST_CLOSE = 'RequestClose';
125
129
  export const CHANNEL_REQUEST_RELOAD = 'RequestReload';
126
130
  export const CHANNEL_RESTART = 'Restart';
@@ -49,7 +49,8 @@ import {
49
49
  InternalMenuDto,
50
50
  CHANNEL_SET_MENU_BAR_VISIBLE,
51
51
  CHANNEL_TOGGLE_FULL_SCREEN,
52
- CHANNEL_IS_MAXIMIZED
52
+ CHANNEL_IS_MAXIMIZED,
53
+ CHANNEL_REQUEST_SECONDARY_CLOSE
53
54
  } from '../electron-common/electron-api';
54
55
  import { ElectronMainApplication, ElectronMainApplicationContribution } from './electron-main-application';
55
56
  import { Disposable, DisposableCollection, isOSX, MaybePromise } from '../common';
@@ -267,6 +268,23 @@ export namespace TheiaRendererAPI {
267
268
  }).finally(() => disposables.dispose());
268
269
  }
269
270
 
271
+ export function requestSecondaryClose(mainWindow: WebContents, secondaryWindow: WebContents): Promise<boolean> {
272
+ const channelNr = nextReplyChannel++;
273
+ const confirmChannel = `confirm-${channelNr}`;
274
+ const cancelChannel = `cancel-${channelNr}`;
275
+ const disposables = new DisposableCollection();
276
+
277
+ return new Promise<boolean>(resolve => {
278
+ mainWindow.send(CHANNEL_REQUEST_SECONDARY_CLOSE, secondaryWindow.mainFrame.name, confirmChannel, cancelChannel);
279
+ createDisposableListener(ipcMain, confirmChannel, e => {
280
+ resolve(true);
281
+ }, disposables);
282
+ createDisposableListener(ipcMain, cancelChannel, e => {
283
+ resolve(false);
284
+ }, disposables);
285
+ }).finally(() => disposables.dispose());
286
+ }
287
+
270
288
  export function onRequestReload(wc: WebContents, handler: () => void): Disposable {
271
289
  return createWindowListener(wc, CHANNEL_REQUEST_RELOAD, handler);
272
290
  }
@@ -44,6 +44,12 @@ export const TheiaBrowserWindowOptions = Symbol('TheiaBrowserWindowOptions');
44
44
  export const WindowApplicationConfig = Symbol('WindowApplicationConfig');
45
45
  export type WindowApplicationConfig = FrontendApplicationConfig;
46
46
 
47
+ enum ClosingState {
48
+ initial,
49
+ inProgress,
50
+ readyToClose
51
+ }
52
+
47
53
  @injectable()
48
54
  export class TheiaElectronWindow {
49
55
  @inject(TheiaBrowserWindowOptions) protected readonly options: TheiaBrowserWindowOptions;
@@ -75,8 +81,32 @@ export class TheiaElectronWindow {
75
81
  this.attachCloseListeners();
76
82
  this.trackApplicationState();
77
83
  this.attachReloadListener();
84
+ this.attachSecondaryWindowListener();
78
85
  }
79
86
 
87
+ protected attachSecondaryWindowListener(): void {
88
+ createDisposableListener(this._window.webContents, 'did-create-window', (newWindow: BrowserWindow) => {
89
+ let closingState = ClosingState.initial;
90
+ newWindow.on('close', event => {
91
+ if (closingState === ClosingState.initial) {
92
+ closingState = ClosingState.inProgress;
93
+ event.preventDefault();
94
+ TheiaRendererAPI.requestSecondaryClose(this._window.webContents, newWindow.webContents).then(shouldClose => {
95
+ if (shouldClose) {
96
+ closingState = ClosingState.readyToClose;
97
+ newWindow.close();
98
+ } else {
99
+ closingState = ClosingState.initial;
100
+ }
101
+ });
102
+ } else if (closingState === ClosingState.inProgress) {
103
+ // When the extracted widget is disposed programmatically, a dispose listener on it will try to close the window.
104
+ // if we dispose the widget because of closing the window, we'll get a recursive call to window.close()
105
+ event.preventDefault();
106
+ }
107
+ });
108
+ });
109
+ }
80
110
  /**
81
111
  * Only show the window when the content is ready.
82
112
  */