@theia/core 1.38.0 → 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 (129) hide show
  1. package/README.md +7 -7
  2. package/lib/browser/about-dialog.d.ts +2 -1
  3. package/lib/browser/about-dialog.d.ts.map +1 -1
  4. package/lib/browser/about-dialog.js +5 -2
  5. package/lib/browser/about-dialog.js.map +1 -1
  6. package/lib/browser/dialogs.d.ts +15 -9
  7. package/lib/browser/dialogs.d.ts.map +1 -1
  8. package/lib/browser/dialogs.js +67 -54
  9. package/lib/browser/dialogs.js.map +1 -1
  10. package/lib/browser/frontend-application-bindings.js +1 -1
  11. package/lib/browser/frontend-application-bindings.js.map +1 -1
  12. package/lib/browser/frontend-application-module.js +1 -1
  13. package/lib/browser/frontend-application-module.js.map +1 -1
  14. package/lib/browser/keyboard/browser-keyboard-layout-provider.d.ts +2 -1
  15. package/lib/browser/keyboard/browser-keyboard-layout-provider.d.ts.map +1 -1
  16. package/lib/browser/keyboard/browser-keyboard-layout-provider.js +6 -3
  17. package/lib/browser/keyboard/browser-keyboard-layout-provider.js.map +1 -1
  18. package/lib/browser/preferences/injectable-preference-proxy.d.ts +1 -1
  19. package/lib/browser/preferences/injectable-preference-proxy.d.ts.map +1 -1
  20. package/lib/browser/preferences/injectable-preference-proxy.js +5 -4
  21. package/lib/browser/preferences/injectable-preference-proxy.js.map +1 -1
  22. package/lib/browser/saveable.d.ts.map +1 -1
  23. package/lib/browser/saveable.js +4 -2
  24. package/lib/browser/saveable.js.map +1 -1
  25. package/lib/browser/secondary-window-handler.d.ts +2 -2
  26. package/lib/browser/secondary-window-handler.d.ts.map +1 -1
  27. package/lib/browser/secondary-window-handler.js +8 -34
  28. package/lib/browser/secondary-window-handler.js.map +1 -1
  29. package/lib/browser/styling-service.d.ts +5 -2
  30. package/lib/browser/styling-service.d.ts.map +1 -1
  31. package/lib/browser/styling-service.js +15 -5
  32. package/lib/browser/styling-service.js.map +1 -1
  33. package/lib/browser/widgets/widget.d.ts +1 -0
  34. package/lib/browser/widgets/widget.d.ts.map +1 -1
  35. package/lib/browser/widgets/widget.js +7 -3
  36. package/lib/browser/widgets/widget.js.map +1 -1
  37. package/lib/browser/window/default-secondary-window-service.d.ts +5 -3
  38. package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
  39. package/lib/browser/window/default-secondary-window-service.js +62 -18
  40. package/lib/browser/window/default-secondary-window-service.js.map +1 -1
  41. package/lib/browser/window/secondary-window-service.d.ts +3 -1
  42. package/lib/browser/window/secondary-window-service.d.ts.map +1 -1
  43. package/lib/browser/window/secondary-window-service.js.map +1 -1
  44. package/lib/common/logger.d.ts +3 -6
  45. package/lib/common/logger.d.ts.map +1 -1
  46. package/lib/common/logger.js +29 -22
  47. package/lib/common/logger.js.map +1 -1
  48. package/lib/common/message-rpc/rpc-message-encoder.d.ts +3 -3
  49. package/lib/common/message-rpc/rpc-message-encoder.d.ts.map +1 -1
  50. package/lib/common/message-rpc/rpc-message-encoder.js +2 -2
  51. package/lib/common/message-rpc/rpc-message-encoder.js.map +1 -1
  52. package/lib/common/message-rpc/rpc-protocol.d.ts +1 -1
  53. package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -1
  54. package/lib/common/message-rpc/rpc-protocol.js +8 -4
  55. package/lib/common/message-rpc/rpc-protocol.js.map +1 -1
  56. package/lib/common/messaging/proxy-factory.d.ts +6 -6
  57. package/lib/common/messaging/proxy-factory.d.ts.map +1 -1
  58. package/lib/common/messaging/proxy-factory.js +15 -10
  59. package/lib/common/messaging/proxy-factory.js.map +1 -1
  60. package/lib/common/performance/stopwatch.d.ts +1 -1
  61. package/lib/common/performance/stopwatch.d.ts.map +1 -1
  62. package/lib/common/performance/stopwatch.js.map +1 -1
  63. package/lib/electron-browser/keyboard/electron-keyboard-layout-change-notifier.d.ts +1 -1
  64. package/lib/electron-browser/keyboard/electron-keyboard-layout-change-notifier.d.ts.map +1 -1
  65. package/lib/electron-browser/keyboard/electron-keyboard-layout-change-notifier.js +2 -2
  66. package/lib/electron-browser/keyboard/electron-keyboard-layout-change-notifier.js.map +1 -1
  67. package/lib/electron-browser/menu/electron-context-menu-renderer.d.ts +2 -1
  68. package/lib/electron-browser/menu/electron-context-menu-renderer.d.ts.map +1 -1
  69. package/lib/electron-browser/menu/electron-context-menu-renderer.js +5 -2
  70. package/lib/electron-browser/menu/electron-context-menu-renderer.js.map +1 -1
  71. package/lib/electron-browser/preload.d.ts.map +1 -1
  72. package/lib/electron-browser/preload.js +21 -16
  73. package/lib/electron-browser/preload.js.map +1 -1
  74. package/lib/electron-browser/window/electron-secondary-window-service.d.ts +3 -1
  75. package/lib/electron-browser/window/electron-secondary-window-service.d.ts.map +1 -1
  76. package/lib/electron-browser/window/electron-secondary-window-service.js +7 -2
  77. package/lib/electron-browser/window/electron-secondary-window-service.js.map +1 -1
  78. package/lib/electron-common/electron-api.d.ts +3 -1
  79. package/lib/electron-common/electron-api.d.ts.map +1 -1
  80. package/lib/electron-common/electron-api.js +2 -1
  81. package/lib/electron-common/electron-api.js.map +1 -1
  82. package/lib/electron-main/electron-api-main.d.ts +1 -0
  83. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  84. package/lib/electron-main/electron-api-main.js +19 -1
  85. package/lib/electron-main/electron-api-main.js.map +1 -1
  86. package/lib/electron-main/theia-electron-window.d.ts +1 -0
  87. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  88. package/lib/electron-main/theia-electron-window.js +32 -0
  89. package/lib/electron-main/theia-electron-window.js.map +1 -1
  90. package/lib/electron-node/token/electron-token-validator.d.ts +1 -1
  91. package/lib/electron-node/token/electron-token-validator.d.ts.map +1 -1
  92. package/lib/electron-node/token/electron-token-validator.js +2 -2
  93. package/lib/electron-node/token/electron-token-validator.js.map +1 -1
  94. package/lib/node/backend-application.d.ts +1 -0
  95. package/lib/node/backend-application.d.ts.map +1 -1
  96. package/lib/node/backend-application.js +5 -2
  97. package/lib/node/backend-application.js.map +1 -1
  98. package/lib/node/hosting/backend-application-hosts.d.ts +1 -1
  99. package/lib/node/hosting/backend-application-hosts.d.ts.map +1 -1
  100. package/lib/node/hosting/backend-application-hosts.js +2 -2
  101. package/lib/node/hosting/backend-application-hosts.js.map +1 -1
  102. package/package.json +5 -5
  103. package/src/browser/about-dialog.tsx +5 -1
  104. package/src/browser/dialogs.ts +69 -52
  105. package/src/browser/frontend-application-bindings.ts +1 -1
  106. package/src/browser/frontend-application-module.ts +2 -2
  107. package/src/browser/keyboard/browser-keyboard-layout-provider.ts +5 -1
  108. package/src/browser/preferences/injectable-preference-proxy.ts +5 -4
  109. package/src/browser/saveable.ts +4 -2
  110. package/src/browser/secondary-window-handler.ts +7 -38
  111. package/src/browser/styling-service.ts +17 -6
  112. package/src/browser/widgets/widget.ts +4 -0
  113. package/src/browser/window/default-secondary-window-service.ts +67 -18
  114. package/src/browser/window/secondary-window-service.ts +4 -1
  115. package/src/common/logger.ts +12 -14
  116. package/src/common/message-rpc/rpc-message-encoder.ts +4 -4
  117. package/src/common/message-rpc/rpc-protocol.ts +8 -4
  118. package/src/common/messaging/proxy-factory.ts +17 -15
  119. package/src/common/performance/stopwatch.ts +1 -1
  120. package/src/electron-browser/keyboard/electron-keyboard-layout-change-notifier.ts +1 -1
  121. package/src/electron-browser/menu/electron-context-menu-renderer.ts +5 -1
  122. package/src/electron-browser/preload.ts +22 -2
  123. package/src/electron-browser/window/electron-secondary-window-service.ts +8 -2
  124. package/src/electron-common/electron-api.ts +5 -1
  125. package/src/electron-main/electron-api-main.ts +22 -2
  126. package/src/electron-main/theia-electron-window.ts +30 -0
  127. package/src/electron-node/token/electron-token-validator.ts +1 -1
  128. package/src/node/backend-application.ts +4 -0
  129. package/src/node/hosting/backend-application-hosts.ts +1 -1
@@ -121,7 +121,7 @@ import {
121
121
  BreadcrumbsService,
122
122
  DefaultBreadcrumbRenderer,
123
123
  } from './breadcrumbs';
124
- import { RendererHost } from './widgets';
124
+ import { DockPanel, RendererHost } from './widgets';
125
125
  import { TooltipService, TooltipServiceImpl } from './tooltip-service';
126
126
  import { BackendRequestService, RequestService, REQUEST_SERVICE_PATH } from '@theia/request';
127
127
  import { bindFrontendStopwatch, bindBackendStopwatch } from './performance';
@@ -190,7 +190,7 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
190
190
  const hoverService = container.get(HoverService);
191
191
  return new TabBarRenderer(contextMenuRenderer, tabBarDecoratorService, iconThemeService, selectionService, commandService, corePreferences, hoverService);
192
192
  });
193
- bind(TheiaDockPanel.Factory).toFactory(({ container }) => options => {
193
+ bind(TheiaDockPanel.Factory).toFactory(({ container }) => (options?: DockPanel.IOptions) => {
194
194
  const corePreferences = container.get<CorePreferences>(CorePreferences);
195
195
  return new TheiaDockPanel(options, corePreferences);
196
196
  });
@@ -59,7 +59,11 @@ export class BrowserKeyboardLayoutProvider implements KeyboardLayoutProvider, Ke
59
59
  }
60
60
 
61
61
  @postConstruct()
62
- protected async initialize(): Promise<void> {
62
+ protected init(): void {
63
+ this.doInit();
64
+ }
65
+
66
+ protected async doInit(): Promise<void> {
63
67
  await this.loadState();
64
68
  const keyboard = (navigator as NavigatorExtension).keyboard;
65
69
  if (keyboard && keyboard.addEventListener) {
@@ -48,7 +48,7 @@ export class InjectablePreferenceProxy<T extends Record<string, JSONValue>> impl
48
48
 
49
49
  @inject(PreferenceProxyOptions) protected readonly options: PreferenceProxyOptions;
50
50
  @inject(PreferenceService) protected readonly preferences: PreferenceService;
51
- @inject(PreferenceProxySchema) protected readonly promisedSchema: PreferenceSchema | Promise<PreferenceSchema>;
51
+ @inject(PreferenceProxySchema) protected readonly promisedSchema: () => PreferenceSchema | Promise<PreferenceSchema>;
52
52
  @inject(PreferenceProxyFactory) protected readonly factory: PreferenceProxyFactory;
53
53
  protected toDispose = new DisposableCollection();
54
54
  protected _onPreferenceChangedEmitter: Emitter<PreferenceChangeEvent<T>> | undefined;
@@ -95,10 +95,11 @@ export class InjectablePreferenceProxy<T extends Record<string, JSONValue>> impl
95
95
 
96
96
  @postConstruct()
97
97
  protected init(): void {
98
- if (this.promisedSchema instanceof Promise) {
99
- this.promisedSchema.then(schema => this.schema = schema);
98
+ const schema = this.promisedSchema();
99
+ if (schema instanceof Promise) {
100
+ schema.then(resolvedSchema => this.schema = resolvedSchema);
100
101
  } else {
101
- this.schema = this.promisedSchema;
102
+ this.schema = schema;
102
103
  }
103
104
  }
104
105
 
@@ -287,14 +287,16 @@ export class ShouldSaveDialog extends AbstractDialog<boolean> {
287
287
  constructor(widget: Widget) {
288
288
  super({
289
289
  title: nls.localizeByDefault('Do you want to save the changes you made to {0}?', widget.title.label || widget.title.caption)
290
+ }, {
291
+ node: widget.node.ownerDocument.createElement('div')
290
292
  });
291
293
 
292
- const messageNode = document.createElement('div');
294
+ const messageNode = this.node.ownerDocument.createElement('div');
293
295
  messageNode.textContent = nls.localizeByDefault("Your changes will be lost if you don't save them.");
294
296
  messageNode.setAttribute('style', 'flex: 1 100%; padding-bottom: calc(var(--theia-ui-padding)*3);');
295
297
  this.contentNode.appendChild(messageNode);
296
- this.dontSaveButton = this.appendDontSaveButton();
297
298
  this.appendCloseButton();
299
+ this.dontSaveButton = this.appendDontSaveButton();
298
300
  this.appendAcceptButton(nls.localizeByDefault('Save'));
299
301
  }
300
302
 
@@ -23,6 +23,7 @@ import { Emitter } from '../common/event';
23
23
  import { SecondaryWindowService } from './window/secondary-window-service';
24
24
  import { KeybindingRegistry } from './keybinding';
25
25
  import { ColorApplicationContribution } from './color-application-contribution';
26
+ import { StylingService } from './styling-service';
26
27
 
27
28
  /** Widget to be contained directly in a secondary window. */
28
29
  class SecondaryWindowRootWidget extends Widget {
@@ -50,8 +51,6 @@ class SecondaryWindowRootWidget extends Widget {
50
51
  */
51
52
  @injectable()
52
53
  export class SecondaryWindowHandler {
53
- /** List of currently open secondary windows. Window references should be removed once the window is closed. */
54
- protected readonly secondaryWindows: Window[] = [];
55
54
  /** List of widgets in secondary windows. */
56
55
  protected readonly _widgets: ExtractableWidget[] = [];
57
56
 
@@ -63,6 +62,9 @@ export class SecondaryWindowHandler {
63
62
  @inject(ColorApplicationContribution)
64
63
  protected colorAppContribution: ColorApplicationContribution;
65
64
 
65
+ @inject(StylingService)
66
+ protected stylingService: StylingService;
67
+
66
68
  protected readonly onDidAddWidgetEmitter = new Emitter<Widget>();
67
69
  /** Subscribe to get notified when a widget is added to this handler, i.e. the widget was moved to an secondary window . */
68
70
  readonly onDidAddWidget = this.onDidAddWidgetEmitter.event;
@@ -95,33 +97,6 @@ export class SecondaryWindowHandler {
95
97
  return;
96
98
  }
97
99
  this.applicationShell = shell;
98
-
99
- // Set up messaging with secondary windows
100
- window.addEventListener('message', (event: MessageEvent) => {
101
- console.trace('Message on main window', event);
102
- if (event.data.fromSecondary) {
103
- console.trace('Message comes from secondary window');
104
- return;
105
- }
106
- if (event.data.fromMain) {
107
- console.trace('Message has mainWindow marker, therefore ignore it');
108
- return;
109
- }
110
-
111
- // Filter setImmediate messages. Do not forward because these come in with very high frequency.
112
- // They are not needed in secondary windows because these messages are just a work around
113
- // to make setImmediate work in the main window: https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
114
- if (typeof event.data === 'string' && event.data.startsWith('setImmediate')) {
115
- return;
116
- }
117
-
118
- console.trace('Delegate main window message to secondary windows', event);
119
- this.secondaryWindows.forEach(secondaryWindow => {
120
- if (!secondaryWindow.window.closed) {
121
- secondaryWindow.window.postMessage({ ...event.data, fromMain: true }, '*');
122
- }
123
- });
124
- });
125
100
  }
126
101
 
127
102
  /**
@@ -139,21 +114,13 @@ export class SecondaryWindowHandler {
139
114
  return;
140
115
  }
141
116
 
142
- const newWindow = this.secondaryWindowService.createSecondaryWindow(closed => {
143
- this.applicationShell.closeWidget(widget.id);
144
- const extIndex = this.secondaryWindows.indexOf(closed);
145
- if (extIndex > -1) {
146
- this.secondaryWindows.splice(extIndex, 1);
147
- }
148
- });
117
+ const newWindow = this.secondaryWindowService.createSecondaryWindow(widget, this.applicationShell);
149
118
 
150
119
  if (!newWindow) {
151
120
  this.messageService.error('The widget could not be moved to a secondary window because the window creation failed. Please make sure to allow popups.');
152
121
  return;
153
122
  }
154
123
 
155
- this.secondaryWindows.push(newWindow);
156
-
157
124
  const mainWindowTitle = document.title;
158
125
  newWindow.onload = () => {
159
126
  this.keybindings.registerEventListeners(newWindow);
@@ -168,6 +135,7 @@ export class SecondaryWindowHandler {
168
135
  return;
169
136
  }
170
137
  const unregisterWithColorContribution = this.colorAppContribution.registerWindow(newWindow);
138
+ const unregisterWithStylingService = this.stylingService.registerWindow(newWindow);
171
139
 
172
140
  widget.secondaryWindow = newWindow;
173
141
  const rootWidget = new SecondaryWindowRootWidget();
@@ -182,6 +150,7 @@ export class SecondaryWindowHandler {
182
150
  // Close the window if the widget is disposed, e.g. by a command closing all widgets.
183
151
  widget.disposed.connect(() => {
184
152
  unregisterWithColorContribution.dispose();
153
+ unregisterWithStylingService.dispose();
185
154
  this.removeWidget(widget);
186
155
  if (!newWindow.closed) {
187
156
  newWindow.close();
@@ -21,6 +21,7 @@ import { ColorRegistry } from './color-registry';
21
21
  import { DecorationStyle } from './decoration-style';
22
22
  import { FrontendApplicationContribution } from './frontend-application';
23
23
  import { ThemeService } from './theming';
24
+ import { Disposable } from '../common';
24
25
 
25
26
  export const StylingParticipant = Symbol('StylingParticipant');
26
27
 
@@ -40,8 +41,7 @@ export interface CssStyleCollector {
40
41
 
41
42
  @injectable()
42
43
  export class StylingService implements FrontendApplicationContribution {
43
-
44
- protected cssElement = DecorationStyle.createStyleElement('contributedColorTheme');
44
+ protected cssElements = new Map<Window, HTMLStyleElement>();
45
45
 
46
46
  @inject(ThemeService)
47
47
  protected readonly themeService: ThemeService;
@@ -53,11 +53,22 @@ export class StylingService implements FrontendApplicationContribution {
53
53
  protected readonly themingParticipants: ContributionProvider<StylingParticipant>;
54
54
 
55
55
  onStart(): void {
56
- this.applyStyling(this.themeService.getCurrentTheme());
57
- this.themeService.onDidColorThemeChange(e => this.applyStyling(e.newTheme));
56
+ this.registerWindow(window);
57
+ this.themeService.onDidColorThemeChange(e => this.applyStylingToWindows(e.newTheme));
58
+ }
59
+
60
+ registerWindow(win: Window): Disposable {
61
+ const cssElement = DecorationStyle.createStyleElement('contributedColorTheme', win.document.head);
62
+ this.cssElements.set(win, cssElement);
63
+ this.applyStyling(this.themeService.getCurrentTheme(), cssElement);
64
+ return Disposable.create(() => this.cssElements.delete(win));
65
+ }
66
+
67
+ protected applyStylingToWindows(theme: Theme): void {
68
+ this.cssElements.forEach(cssElement => this.applyStyling(theme, cssElement));
58
69
  }
59
70
 
60
- protected applyStyling(theme: Theme): void {
71
+ protected applyStyling(theme: Theme, cssElement: HTMLStyleElement): void {
61
72
  const rules: string[] = [];
62
73
  const colorTheme: ColorTheme = {
63
74
  type: theme.type,
@@ -71,6 +82,6 @@ export class StylingService implements FrontendApplicationContribution {
71
82
  themingParticipant.registerThemeStyle(colorTheme, styleCollector);
72
83
  }
73
84
  const fullCss = rules.join('\n');
74
- this.cssElement.innerText = fullCss;
85
+ cssElement.innerText = fullCss;
75
86
  }
76
87
  }
@@ -115,6 +115,10 @@ export class BaseWidget extends Widget {
115
115
  protected scrollBar?: PerfectScrollbar;
116
116
  protected scrollOptions?: PerfectScrollbar.Options;
117
117
 
118
+ constructor(options?: Widget.IOptions) {
119
+ super(options);
120
+ }
121
+
118
122
  override dispose(): void {
119
123
  if (this.isDisposed) {
120
124
  return;
@@ -16,6 +16,9 @@
16
16
  import { inject, injectable, postConstruct } from 'inversify';
17
17
  import { SecondaryWindowService } from './secondary-window-service';
18
18
  import { WindowService } from './window-service';
19
+ import { ExtractableWidget } from '../widgets';
20
+ import { ApplicationShell } from '../shell';
21
+ import { Saveable } from '../saveable';
19
22
 
20
23
  @injectable()
21
24
  export class DefaultSecondaryWindowService implements SecondaryWindowService {
@@ -37,6 +40,33 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
37
40
 
38
41
  @postConstruct()
39
42
  init(): void {
43
+ // Set up messaging with secondary windows
44
+ window.addEventListener('message', (event: MessageEvent) => {
45
+ console.trace('Message on main window', event);
46
+ if (event.data.fromSecondary) {
47
+ console.trace('Message comes from secondary window');
48
+ return;
49
+ }
50
+ if (event.data.fromMain) {
51
+ console.trace('Message has mainWindow marker, therefore ignore it');
52
+ return;
53
+ }
54
+
55
+ // Filter setImmediate messages. Do not forward because these come in with very high frequency.
56
+ // They are not needed in secondary windows because these messages are just a work around
57
+ // to make setImmediate work in the main window: https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
58
+ if (typeof event.data === 'string' && event.data.startsWith('setImmediate')) {
59
+ return;
60
+ }
61
+
62
+ console.trace('Delegate main window message to secondary windows', event);
63
+ this.secondaryWindows.forEach(secondaryWindow => {
64
+ if (!secondaryWindow.window.closed) {
65
+ secondaryWindow.window.postMessage({ ...event.data, fromMain: true }, '*');
66
+ }
67
+ });
68
+ });
69
+
40
70
  // Close all open windows when the main window is closed.
41
71
  this.windowService.onUnload(() => {
42
72
  // Iterate backwards because calling window.close might remove the window from the array
@@ -46,33 +76,52 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
46
76
  });
47
77
  }
48
78
 
49
- createSecondaryWindow(onClose?: (closedWin: Window) => void): Window | undefined {
50
- const win = this.doCreateSecondaryWindow(onClose);
79
+ createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined {
80
+ const win = this.doCreateSecondaryWindow(widget, shell);
51
81
  if (win) {
52
82
  this.secondaryWindows.push(win);
83
+ win.addEventListener('close', () => {
84
+ const extIndex = this.secondaryWindows.indexOf(win);
85
+ if (extIndex > -1) {
86
+ this.secondaryWindows.splice(extIndex, 1);
87
+ };
88
+ });
53
89
  }
54
90
  return win;
55
91
  }
56
92
 
57
- protected doCreateSecondaryWindow(onClose?: (closedWin: Window) => void): Window | undefined {
58
- const win = window.open(DefaultSecondaryWindowService.SECONDARY_WINDOW_URL, this.nextWindowId(), 'popup');
59
- if (win) {
60
- // Add the unload listener after the dom content was loaded because otherwise the unload listener is called already on open in some browsers (e.g. Chrome).
61
- win.addEventListener('DOMContentLoaded', () => {
62
- win.addEventListener('unload', () => {
63
- this.handleWindowClosed(win, onClose);
64
- });
65
- });
93
+ protected findWindow<T>(windowName: string): Window | undefined {
94
+ for (const w of this.secondaryWindows) {
95
+ if (w.name === windowName) {
96
+ return w;
97
+ }
66
98
  }
67
- return win ?? undefined;
99
+ return undefined;
68
100
  }
69
101
 
70
- protected handleWindowClosed(win: Window, onClose?: (closedWin: Window) => void): void {
71
- const extIndex = this.secondaryWindows.indexOf(win);
72
- if (extIndex > -1) {
73
- this.secondaryWindows.splice(extIndex, 1);
74
- };
75
- onClose?.(win);
102
+ protected doCreateSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined {
103
+ const newWindow = window.open(DefaultSecondaryWindowService.SECONDARY_WINDOW_URL, this.nextWindowId(), 'popup') ?? undefined;
104
+ if (newWindow) {
105
+ newWindow.addEventListener('DOMContentLoaded', () => {
106
+ newWindow.addEventListener('beforeunload', evt => {
107
+ const saveable = Saveable.get(widget);
108
+ const wouldLoseState = !!saveable && saveable.dirty && saveable.autoSave === 'off';
109
+ if (wouldLoseState) {
110
+ evt.returnValue = '';
111
+ evt.preventDefault();
112
+ return 'non-empty';
113
+ }
114
+ }, { capture: true });
115
+
116
+ newWindow.addEventListener('close', () => {
117
+ const saveable = Saveable.get(widget);
118
+ shell.closeWidget(widget.id, {
119
+ save: !!saveable && saveable.dirty && saveable.autoSave !== 'off'
120
+ });
121
+ });
122
+ });
123
+ }
124
+ return newWindow;
76
125
  }
77
126
 
78
127
  focus(win: Window): void {
@@ -14,6 +14,9 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
+ import { ApplicationShell } from '../shell';
18
+ import { ExtractableWidget } from '../widgets';
19
+
17
20
  export const SecondaryWindowService = Symbol('SecondaryWindowService');
18
21
 
19
22
  /**
@@ -29,7 +32,7 @@ export interface SecondaryWindowService {
29
32
  * @param onClose optional callback that is invoked when the secondary window is closed
30
33
  * @returns the created window or `undefined` if it could not be created
31
34
  */
32
- createSecondaryWindow(onClose?: (win: Window) => void): Window | undefined;
35
+ createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): Window | undefined;
33
36
 
34
37
  /** Handles focussing the given secondary window in the browser and on Electron. */
35
38
  focus(win: Window): void;
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { inject, injectable } from 'inversify';
17
+ import { inject, injectable, postConstruct } from 'inversify';
18
18
  import { LoggerWatcher } from './logger-watcher';
19
19
  import { ILoggerServer, LogLevel, ConsoleLogger, rootLoggerName } from './logger-protocol';
20
20
 
@@ -235,30 +235,28 @@ export class Logger implements ILogger {
235
235
  /* A promise resolved when the logger has been created by the backend. */
236
236
  protected created: Promise<void>;
237
237
 
238
- /**
239
- * Build a new Logger.
240
- */
241
- constructor(
242
- @inject(ILoggerServer) protected readonly server: ILoggerServer,
243
- @inject(LoggerWatcher) protected readonly loggerWatcher: LoggerWatcher,
244
- @inject(LoggerFactory) protected readonly factory: LoggerFactory,
245
- @inject(LoggerName) protected name: string) {
238
+ @inject(ILoggerServer) protected readonly server: ILoggerServer;
239
+ @inject(LoggerWatcher) protected readonly loggerWatcher: LoggerWatcher;
240
+ @inject(LoggerFactory) protected readonly factory: LoggerFactory;
241
+ @inject(LoggerName) protected name: string;
246
242
 
247
- if (name !== rootLoggerName) {
243
+ @postConstruct()
244
+ protected init(): void {
245
+ if (this.name !== rootLoggerName) {
248
246
  /* Creating a child logger. */
249
- this.created = server.child(name);
247
+ this.created = this.server.child(this.name);
250
248
  } else {
251
249
  /* Creating the root logger (it already exists at startup). */
252
250
  this.created = Promise.resolve();
253
251
  }
254
252
 
255
253
  /* Fetch the log level so it's cached in the frontend. */
256
- this._logLevel = this.created.then(_ => this.server.getLogLevel(name));
254
+ this._logLevel = this.created.then(_ => this.server.getLogLevel(this.name));
257
255
 
258
256
  /* Update the log level if it changes in the backend. */
259
- loggerWatcher.onLogLevelChanged(event => {
257
+ this.loggerWatcher.onLogLevelChanged(event => {
260
258
  this.created.then(() => {
261
- if (event.loggerName === name) {
259
+ if (event.loggerName === this.name) {
262
260
  this._logLevel = Promise.resolve(event.newLogLevel);
263
261
  }
264
262
  });
@@ -51,7 +51,7 @@ export interface RequestMessage {
51
51
 
52
52
  export interface NotificationMessage {
53
53
  type: RpcMessageType.Notification;
54
- id: number;
54
+ id?: number;
55
55
  method: string;
56
56
  args: any[];
57
57
  }
@@ -111,7 +111,7 @@ export interface RpcMessageDecoder {
111
111
  export interface RpcMessageEncoder {
112
112
  cancel(buf: WriteBuffer, requestId: number): void;
113
113
 
114
- notification(buf: WriteBuffer, requestId: number, method: string, args: any[]): void
114
+ notification(buf: WriteBuffer, method: string, args: any[], id?: number): void
115
115
 
116
116
  request(buf: WriteBuffer, requestId: number, method: string, args: any[]): void
117
117
 
@@ -130,8 +130,8 @@ export class MsgPackMessageEncoder implements RpcMessageEncoder {
130
130
  cancel(buf: WriteBuffer, requestId: number): void {
131
131
  this.encode<CancelMessage>(buf, { type: RpcMessageType.Cancel, id: requestId });
132
132
  }
133
- notification(buf: WriteBuffer, requestId: number, method: string, args: any[]): void {
134
- this.encode<NotificationMessage>(buf, { type: RpcMessageType.Notification, id: requestId, method, args });
133
+ notification(buf: WriteBuffer, method: string, args: any[], id?: number): void {
134
+ this.encode<NotificationMessage>(buf, { type: RpcMessageType.Notification, method, args, id });
135
135
  }
136
136
  request(buf: WriteBuffer, requestId: number, method: string, args: any[]): void {
137
137
  this.encode<RequestMessage>(buf, { type: RpcMessageType.Request, id: requestId, method, args });
@@ -77,7 +77,11 @@ export class RpcProtocol {
77
77
  this.encoder = options.encoder ?? new MsgPackMessageEncoder();
78
78
  this.decoder = options.decoder ?? new MsgPackMessageDecoder();
79
79
  this.toDispose.push(this.onNotificationEmitter);
80
- channel.onClose(() => this.toDispose.dispose());
80
+ channel.onClose(event => {
81
+ this.pendingRequests.forEach(pending => pending.reject(new Error(event.reason)));
82
+ this.pendingRequests.clear();
83
+ this.toDispose.dispose();
84
+ });
81
85
  this.toDispose.push(channel.onMessage(readBuffer => this.handleMessage(this.decoder.parse(readBuffer()))));
82
86
  this.mode = options.mode ?? 'default';
83
87
 
@@ -98,7 +102,7 @@ export class RpcProtocol {
98
102
  return;
99
103
  }
100
104
  case RpcMessageType.Notification: {
101
- this.handleNotify(message.id, message.method, message.args);
105
+ this.handleNotify(message.method, message.args, message.id);
102
106
  return;
103
107
  }
104
108
  }
@@ -179,7 +183,7 @@ export class RpcProtocol {
179
183
  }
180
184
 
181
185
  const output = this.channel.getWriteBuffer();
182
- this.encoder.notification(output, this.nextMessageId++, method, args);
186
+ this.encoder.notification(output, method, args, this.nextMessageId++);
183
187
  output.commit();
184
188
  }
185
189
 
@@ -226,7 +230,7 @@ export class RpcProtocol {
226
230
  }
227
231
  }
228
232
 
229
- protected async handleNotify(id: number, method: string, args: any[]): Promise<void> {
233
+ protected async handleNotify(method: string, args: any[], id?: number): Promise<void> {
230
234
  if (this.toDispose.disposed) {
231
235
  return;
232
236
  }
@@ -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
  /**
@@ -241,11 +239,15 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
241
239
  if (p === 'onDidCloseConnection') {
242
240
  return this.onDidCloseConnectionEmitter.event;
243
241
  }
242
+ if (p === 'then') {
243
+ // Prevent inversify from identifying this proxy as a promise object.
244
+ return undefined;
245
+ }
244
246
  const isNotify = this.isNotification(p);
245
247
  return (...args: any[]) => {
246
248
  const method = p.toString();
247
249
  const capturedError = new Error(`Request '${method}' failed`);
248
- return this.connectionPromise.then(connection =>
250
+ return this.rpcDeferred.promise.then(connection =>
249
251
  new Promise<void>((resolve, reject) => {
250
252
  try {
251
253
  if (isNotify) {
@@ -50,7 +50,7 @@ export abstract class Stopwatch {
50
50
  @inject(ILogger)
51
51
  protected readonly logger: ILogger;
52
52
 
53
- protected constructor(protected readonly defaultLogOptions: LogOptions) {
53
+ constructor(protected readonly defaultLogOptions: LogOptions) {
54
54
  if (!defaultLogOptions.defaultLogLevel) {
55
55
  defaultLogOptions.defaultLogLevel = DEFAULT_LOG_LEVEL;
56
56
  }
@@ -32,7 +32,7 @@ export class ElectronKeyboardLayoutChangeNotifier implements KeyboardLayoutChang
32
32
  }
33
33
 
34
34
  @postConstruct()
35
- protected initialize(): void {
35
+ protected init(): void {
36
36
  window.electronTheiaCore.onKeyboardLayoutChanged((newLayout: NativeKeyboardLayout) => this.nativeLayoutChanged.fire(newLayout));
37
37
  }
38
38
 
@@ -90,7 +90,11 @@ export class ElectronContextMenuRenderer extends BrowserContextMenuRenderer {
90
90
  }
91
91
 
92
92
  @postConstruct()
93
- protected async init(): Promise<void> {
93
+ protected init(): void {
94
+ this.doInit();
95
+ }
96
+
97
+ protected async doInit(): Promise<void> {
94
98
  this.useNativeStyle = await window.electronTheiaCore.getTitleBarStyleAtStartup() === 'native';
95
99
  }
96
100