@theia/core 1.71.0-next.8 → 1.71.0

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 (257) hide show
  1. package/README.md +19 -19
  2. package/i18n/nls.cs.json +97 -51
  3. package/i18n/nls.de.json +97 -51
  4. package/i18n/nls.es.json +97 -51
  5. package/i18n/nls.fr.json +97 -51
  6. package/i18n/nls.hu.json +97 -51
  7. package/i18n/nls.it.json +97 -51
  8. package/i18n/nls.ja.json +97 -51
  9. package/i18n/nls.json +101 -55
  10. package/i18n/nls.ko.json +97 -51
  11. package/i18n/nls.pl.json +97 -51
  12. package/i18n/nls.pt-br.json +97 -51
  13. package/i18n/nls.ru.json +97 -51
  14. package/i18n/nls.tr.json +97 -51
  15. package/i18n/nls.zh-cn.json +97 -51
  16. package/i18n/nls.zh-tw.json +97 -51
  17. package/lib/browser/catalog.json +169 -10
  18. package/lib/browser/common-frontend-contribution.js +2 -2
  19. package/lib/browser/common-frontend-contribution.js.map +1 -1
  20. package/lib/browser/components/card.d.ts.map +1 -1
  21. package/lib/browser/components/card.js +11 -3
  22. package/lib/browser/components/card.js.map +1 -1
  23. package/lib/browser/connection-status-service.js +1 -1
  24. package/lib/browser/connection-status-service.js.map +1 -1
  25. package/lib/browser/frontend-application-contribution.d.ts +2 -2
  26. package/lib/browser/frontend-application-contribution.d.ts.map +1 -1
  27. package/lib/browser/frontend-application.d.ts +2 -0
  28. package/lib/browser/frontend-application.d.ts.map +1 -1
  29. package/lib/browser/frontend-application.js +15 -6
  30. package/lib/browser/frontend-application.js.map +1 -1
  31. package/lib/browser/keyboard/index.d.ts +1 -0
  32. package/lib/browser/keyboard/index.d.ts.map +1 -1
  33. package/lib/browser/keyboard/index.js +1 -0
  34. package/lib/browser/keyboard/index.js.map +1 -1
  35. package/lib/browser/keyboard/keyboard-utils.d.ts +17 -0
  36. package/lib/browser/keyboard/keyboard-utils.d.ts.map +1 -0
  37. package/lib/browser/keyboard/keyboard-utils.js +40 -0
  38. package/lib/browser/keyboard/keyboard-utils.js.map +1 -0
  39. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  40. package/lib/browser/menu/browser-menu-plugin.js +8 -1
  41. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  42. package/lib/browser/messaging/connection-source.d.ts +2 -1
  43. package/lib/browser/messaging/connection-source.d.ts.map +1 -1
  44. package/lib/browser/messaging/connection-source.js.map +1 -1
  45. package/lib/browser/messaging/messaging-frontend-module.d.ts.map +1 -1
  46. package/lib/browser/messaging/messaging-frontend-module.js +3 -0
  47. package/lib/browser/messaging/messaging-frontend-module.js.map +1 -1
  48. package/lib/browser/messaging/service-connection-provider.d.ts +2 -2
  49. package/lib/browser/messaging/service-connection-provider.d.ts.map +1 -1
  50. package/lib/browser/messaging/service-connection-provider.js +2 -2
  51. package/lib/browser/messaging/service-connection-provider.js.map +1 -1
  52. package/lib/browser/messaging/ws-connection-provider.d.ts +1 -1
  53. package/lib/browser/messaging/ws-connection-provider.d.ts.map +1 -1
  54. package/lib/browser/messaging/ws-connection-provider.js +3 -3
  55. package/lib/browser/messaging/ws-connection-provider.js.map +1 -1
  56. package/lib/browser/messaging/ws-connection-source.d.ts +4 -2
  57. package/lib/browser/messaging/ws-connection-source.d.ts.map +1 -1
  58. package/lib/browser/messaging/ws-connection-source.js +15 -10
  59. package/lib/browser/messaging/ws-connection-source.js.map +1 -1
  60. package/lib/browser/performance/frontend-stopwatch.js +1 -1
  61. package/lib/browser/performance/frontend-stopwatch.js.map +1 -1
  62. package/lib/browser/preload/i18n-preload-contribution.d.ts +1 -1
  63. package/lib/browser/preload/i18n-preload-contribution.d.ts.map +1 -1
  64. package/lib/browser/preload/i18n-preload-contribution.js +2 -2
  65. package/lib/browser/preload/i18n-preload-contribution.js.map +1 -1
  66. package/lib/browser/preload/os-preload-contribution.d.ts +1 -1
  67. package/lib/browser/preload/os-preload-contribution.d.ts.map +1 -1
  68. package/lib/browser/preload/os-preload-contribution.js +6 -6
  69. package/lib/browser/preload/os-preload-contribution.js.map +1 -1
  70. package/lib/browser/saveable-service.d.ts +9 -2
  71. package/lib/browser/saveable-service.d.ts.map +1 -1
  72. package/lib/browser/saveable-service.js +34 -25
  73. package/lib/browser/saveable-service.js.map +1 -1
  74. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +1 -0
  75. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
  76. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +10 -1
  77. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  78. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +2 -0
  79. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
  80. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js +11 -2
  81. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js.map +1 -1
  82. package/lib/browser/window/browser-window-module.d.ts.map +1 -1
  83. package/lib/browser/window/browser-window-module.js +2 -0
  84. package/lib/browser/window/browser-window-module.js.map +1 -1
  85. package/lib/browser/window/default-secondary-window-service.d.ts +2 -0
  86. package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
  87. package/lib/browser/window/default-secondary-window-service.js +7 -0
  88. package/lib/browser/window/default-secondary-window-service.js.map +1 -1
  89. package/lib/browser/window/window-focus-service.d.ts +71 -0
  90. package/lib/browser/window/window-focus-service.d.ts.map +1 -0
  91. package/lib/browser/window/window-focus-service.js +173 -0
  92. package/lib/browser/window/window-focus-service.js.map +1 -0
  93. package/lib/common/event.d.ts +16 -0
  94. package/lib/common/event.d.ts.map +1 -1
  95. package/lib/common/event.js +20 -2
  96. package/lib/common/event.js.map +1 -1
  97. package/lib/common/event.spec.js +63 -0
  98. package/lib/common/event.spec.js.map +1 -1
  99. package/lib/common/glob.d.ts +2 -0
  100. package/lib/common/glob.d.ts.map +1 -1
  101. package/lib/common/glob.js +8 -7
  102. package/lib/common/glob.js.map +1 -1
  103. package/lib/common/message-rpc/channel.d.ts +1 -1
  104. package/lib/common/message-rpc/channel.d.ts.map +1 -1
  105. package/lib/common/message-rpc/channel.js +20 -12
  106. package/lib/common/message-rpc/channel.js.map +1 -1
  107. package/lib/common/message-rpc/channel.spec.d.ts.map +1 -1
  108. package/lib/common/message-rpc/channel.spec.js +94 -0
  109. package/lib/common/message-rpc/channel.spec.js.map +1 -1
  110. package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -1
  111. package/lib/common/message-rpc/rpc-protocol.js +13 -3
  112. package/lib/common/message-rpc/rpc-protocol.js.map +1 -1
  113. package/lib/common/message-rpc/uint8-array-message-buffer.d.ts.map +1 -1
  114. package/lib/common/message-rpc/uint8-array-message-buffer.js +1 -1
  115. package/lib/common/message-rpc/uint8-array-message-buffer.js.map +1 -1
  116. package/lib/common/messaging/index.d.ts +1 -0
  117. package/lib/common/messaging/index.d.ts.map +1 -1
  118. package/lib/common/messaging/index.js +1 -0
  119. package/lib/common/messaging/index.js.map +1 -1
  120. package/lib/common/messaging/socket-write-buffer.d.ts +4 -3
  121. package/lib/common/messaging/socket-write-buffer.d.ts.map +1 -1
  122. package/lib/common/messaging/socket-write-buffer.js +14 -4
  123. package/lib/common/messaging/socket-write-buffer.js.map +1 -1
  124. package/lib/common/performance/index.d.ts +1 -0
  125. package/lib/common/performance/index.d.ts.map +1 -1
  126. package/lib/common/performance/index.js +1 -0
  127. package/lib/common/performance/index.js.map +1 -1
  128. package/lib/common/performance/simple-stopwatch.d.ts +18 -0
  129. package/lib/common/performance/simple-stopwatch.d.ts.map +1 -0
  130. package/lib/common/performance/simple-stopwatch.js +80 -0
  131. package/lib/common/performance/simple-stopwatch.js.map +1 -0
  132. package/lib/common/performance/stopwatch.d.ts +41 -0
  133. package/lib/common/performance/stopwatch.d.ts.map +1 -1
  134. package/lib/common/performance/stopwatch.js +89 -3
  135. package/lib/common/performance/stopwatch.js.map +1 -1
  136. package/lib/common/performance/stopwatch.spec.d.ts +2 -0
  137. package/lib/common/performance/stopwatch.spec.d.ts.map +1 -0
  138. package/lib/common/performance/stopwatch.spec.js +256 -0
  139. package/lib/common/performance/stopwatch.spec.js.map +1 -0
  140. package/lib/common/preferences/index.d.ts +1 -0
  141. package/lib/common/preferences/index.d.ts.map +1 -1
  142. package/lib/common/preferences/index.js +1 -0
  143. package/lib/common/preferences/index.js.map +1 -1
  144. package/lib/common/preferences/preference-utils.d.ts +6 -0
  145. package/lib/common/preferences/preference-utils.d.ts.map +1 -0
  146. package/lib/common/preferences/preference-utils.js +29 -0
  147. package/lib/common/preferences/preference-utils.js.map +1 -0
  148. package/lib/common/resource.d.ts +2 -0
  149. package/lib/common/resource.d.ts.map +1 -1
  150. package/lib/common/resource.js +7 -3
  151. package/lib/common/resource.js.map +1 -1
  152. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  153. package/lib/electron-browser/menu/electron-main-menu-factory.js +5 -1
  154. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  155. package/lib/electron-browser/messaging/electron-messaging-frontend-module.d.ts.map +1 -1
  156. package/lib/electron-browser/messaging/electron-messaging-frontend-module.js +3 -0
  157. package/lib/electron-browser/messaging/electron-messaging-frontend-module.js.map +1 -1
  158. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  159. package/lib/electron-browser/window/electron-window-module.js +2 -0
  160. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  161. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  162. package/lib/electron-main/electron-api-main.js +4 -2
  163. package/lib/electron-main/electron-api-main.js.map +1 -1
  164. package/lib/electron-main/electron-main-application-module.d.ts.map +1 -1
  165. package/lib/electron-main/electron-main-application-module.js +3 -0
  166. package/lib/electron-main/electron-main-application-module.js.map +1 -1
  167. package/lib/electron-main/electron-main-application.d.ts +2 -0
  168. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  169. package/lib/electron-main/electron-main-application.js +14 -5
  170. package/lib/electron-main/electron-main-application.js.map +1 -1
  171. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  172. package/lib/electron-main/theia-electron-window.js +3 -0
  173. package/lib/electron-main/theia-electron-window.js.map +1 -1
  174. package/lib/node/backend-application.d.ts +2 -0
  175. package/lib/node/backend-application.d.ts.map +1 -1
  176. package/lib/node/backend-application.js +17 -5
  177. package/lib/node/backend-application.js.map +1 -1
  178. package/lib/node/messaging/default-messaging-service.d.ts.map +1 -1
  179. package/lib/node/messaging/default-messaging-service.js +1 -0
  180. package/lib/node/messaging/default-messaging-service.js.map +1 -1
  181. package/lib/node/messaging/index.d.ts +1 -0
  182. package/lib/node/messaging/index.d.ts.map +1 -1
  183. package/lib/node/messaging/index.js +1 -0
  184. package/lib/node/messaging/index.js.map +1 -1
  185. package/lib/node/messaging/messaging-backend-module.d.ts.map +1 -1
  186. package/lib/node/messaging/messaging-backend-module.js +4 -0
  187. package/lib/node/messaging/messaging-backend-module.js.map +1 -1
  188. package/lib/node/messaging/test/default-messaging-service.spec.d.ts +2 -0
  189. package/lib/node/messaging/test/default-messaging-service.spec.d.ts.map +1 -0
  190. package/lib/node/messaging/test/default-messaging-service.spec.js +81 -0
  191. package/lib/node/messaging/test/default-messaging-service.spec.js.map +1 -0
  192. package/lib/node/messaging/websocket-frontend-connection-service.d.ts +9 -5
  193. package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
  194. package/lib/node/messaging/websocket-frontend-connection-service.js +21 -5
  195. package/lib/node/messaging/websocket-frontend-connection-service.js.map +1 -1
  196. package/lib/node/performance/node-stopwatch.js +1 -1
  197. package/lib/node/performance/node-stopwatch.js.map +1 -1
  198. package/lib/node/process-utils.d.ts.map +1 -1
  199. package/lib/node/process-utils.js +9 -1
  200. package/lib/node/process-utils.js.map +1 -1
  201. package/package.json +34 -34
  202. package/src/browser/common-frontend-contribution.ts +2 -2
  203. package/src/browser/components/card.tsx +13 -2
  204. package/src/browser/connection-status-service.ts +1 -1
  205. package/src/browser/frontend-application-contribution.ts +2 -2
  206. package/src/browser/frontend-application.ts +26 -17
  207. package/src/browser/keyboard/index.ts +1 -0
  208. package/src/browser/keyboard/keyboard-utils.ts +37 -0
  209. package/src/browser/menu/browser-menu-plugin.ts +8 -1
  210. package/src/browser/messaging/connection-source.ts +2 -1
  211. package/src/browser/messaging/messaging-frontend-module.ts +3 -0
  212. package/src/browser/messaging/service-connection-provider.ts +2 -2
  213. package/src/browser/messaging/ws-connection-provider.ts +1 -1
  214. package/src/browser/messaging/ws-connection-source.ts +7 -4
  215. package/src/browser/performance/frontend-stopwatch.ts +1 -1
  216. package/src/browser/preload/i18n-preload-contribution.ts +1 -1
  217. package/src/browser/preload/os-preload-contribution.ts +1 -1
  218. package/src/browser/saveable-service.ts +34 -27
  219. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +14 -1
  220. package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +13 -2
  221. package/src/browser/style/card.css +4 -2
  222. package/src/browser/style/hover-service.css +7 -0
  223. package/src/browser/window/browser-window-module.ts +2 -0
  224. package/src/browser/window/default-secondary-window-service.ts +6 -0
  225. package/src/browser/window/window-focus-service.ts +187 -0
  226. package/src/common/event.spec.ts +80 -0
  227. package/src/common/event.ts +31 -2
  228. package/src/common/glob.ts +2 -2
  229. package/src/common/i18n/nls.metadata.json +4254 -1058
  230. package/src/common/message-rpc/channel.spec.ts +116 -0
  231. package/src/common/message-rpc/channel.ts +15 -11
  232. package/src/common/message-rpc/rpc-protocol.ts +12 -3
  233. package/src/common/message-rpc/uint8-array-message-buffer.ts +1 -1
  234. package/src/common/messaging/index.ts +1 -0
  235. package/src/common/messaging/socket-write-buffer.ts +10 -4
  236. package/src/common/performance/index.ts +1 -0
  237. package/src/common/performance/simple-stopwatch.ts +91 -0
  238. package/src/common/performance/stopwatch.spec.ts +321 -0
  239. package/src/common/performance/stopwatch.ts +103 -2
  240. package/src/common/preferences/index.ts +1 -0
  241. package/src/common/preferences/preference-utils.ts +28 -0
  242. package/src/common/resource.ts +8 -2
  243. package/src/electron-browser/menu/electron-main-menu-factory.ts +5 -1
  244. package/src/electron-browser/messaging/electron-messaging-frontend-module.ts +3 -0
  245. package/src/electron-browser/window/electron-window-module.ts +2 -0
  246. package/src/electron-main/electron-api-main.ts +4 -2
  247. package/src/electron-main/electron-main-application-module.ts +3 -0
  248. package/src/electron-main/electron-main-application.ts +21 -5
  249. package/src/electron-main/theia-electron-window.ts +3 -0
  250. package/src/node/backend-application.ts +27 -10
  251. package/src/node/messaging/default-messaging-service.ts +1 -0
  252. package/src/node/messaging/index.ts +1 -0
  253. package/src/node/messaging/messaging-backend-module.ts +5 -1
  254. package/src/node/messaging/test/default-messaging-service.spec.ts +85 -0
  255. package/src/node/messaging/websocket-frontend-connection-service.ts +20 -7
  256. package/src/node/performance/node-stopwatch.ts +1 -1
  257. package/src/node/process-utils.ts +9 -1
@@ -30,6 +30,7 @@ import URI from '../common/uri';
30
30
  import { FileUri } from '../common/file-uri';
31
31
  import { Deferred, timeout } from '../common/promise-util';
32
32
  import { MaybePromise } from '../common/types';
33
+ import { Stopwatch } from '../common/performance';
33
34
  import { ContributionProvider } from '../common/contribution-provider';
34
35
  import { ElectronSecurityTokenService } from './electron-security-token-service';
35
36
  import { ElectronSecurityToken } from '../electron-common/electron-token';
@@ -49,6 +50,8 @@ export { ElectronMainApplicationGlobals };
49
50
 
50
51
  const createYargs: (argv?: string[], cwd?: string) => Argv = require('yargs/yargs');
51
52
 
53
+ const ELECTRON_TIMER_WARNING_THRESHOLD = 50;
54
+
52
55
  /**
53
56
  * Options passed to the main/default command handler.
54
57
  */
@@ -171,6 +174,9 @@ export class ElectronMainApplication {
171
174
  @inject(TheiaElectronWindowFactory)
172
175
  protected readonly windowFactory: TheiaElectronWindowFactory;
173
176
 
177
+ @inject(Stopwatch)
178
+ protected readonly stopwatch: Stopwatch;
179
+
174
180
  protected isPortable = this.makePortable();
175
181
 
176
182
  protected readonly electronStore = new Storage<{
@@ -229,15 +235,19 @@ export class ElectronMainApplication {
229
235
  await fs.mkdir(args.electronUserData, { recursive: true });
230
236
  app.setPath('userData', args.electronUserData);
231
237
  }
238
+ const startupMeasurement = this.stopwatch.start('electron-main-startup');
232
239
  this.useNativeWindowFrame = this.getTitleBarStyle(config) === 'native';
233
240
  this._config = config;
234
241
  this.hookApplicationEvents();
235
242
  this.showInitialWindow(argv.includes('--open-url') ? argv[argv.length - 1] : undefined);
236
- const port = await this.startBackend();
243
+ const port = await this.stopwatch.startAsync('electron-main-start-backend', 'Starting backend', () => this.startBackend());
237
244
  this._backendPort.resolve(port);
238
245
  await app.whenReady();
239
- await this.attachElectronSecurityToken(port);
240
- await this.startContributions();
246
+ await this.stopwatch.startAsync('electron-main-security-token', 'Attaching security token',
247
+ () => this.attachElectronSecurityToken(port));
248
+ await this.stopwatch.startAsync('electron-main-start-contributions', 'Starting contributions',
249
+ () => this.startContributions());
250
+ startupMeasurement.info('Startup sequence completed');
241
251
 
242
252
  this.handleMainCommand({
243
253
  file: args.file,
@@ -920,8 +930,14 @@ export class ElectronMainApplication {
920
930
  protected async startContributions(): Promise<void> {
921
931
  const promises = [];
922
932
  for (const contribution of this.contributions.getContributions()) {
923
- if (contribution.onStart) {
924
- promises.push(contribution.onStart(this));
933
+ const onStart = contribution.onStart;
934
+ if (onStart) {
935
+ promises.push(this.stopwatch.startAsync(
936
+ `${contribution.constructor.name}.onStart`,
937
+ `${contribution.constructor.name}.onStart`,
938
+ () => onStart.call(contribution, this),
939
+ { thresholdMillis: ELECTRON_TIMER_WARNING_THRESHOLD }
940
+ ));
925
941
  }
926
942
  }
927
943
  await Promise.all(promises);
@@ -140,6 +140,9 @@ export class TheiaElectronWindow {
140
140
 
141
141
  protected async doCloseWindow(): Promise<void> {
142
142
  this.closeIsConfirmed = true;
143
+ // Hide the window immediately so the user perceives an instant close.
144
+ // This is done after veto checks have passed to ensure save dialogs remain visible.
145
+ this._window.hide();
143
146
  await TheiaRendererAPI.sendAboutToClose(this._window.webContents);
144
147
  this._window.close();
145
148
  }
@@ -22,7 +22,7 @@ import * as express from 'express';
22
22
  import * as yargs from 'yargs';
23
23
  import * as fs from 'fs-extra';
24
24
  import { inject, named, injectable, postConstruct } from 'inversify';
25
- import { ContributionProvider, MaybePromise, Stopwatch } from '../common';
25
+ import { ContributionProvider, LogLevel, MaybePromise, MeasurementContext, Stopwatch } from '../common';
26
26
  import { CliContribution } from './cli';
27
27
  import { Deferred } from '../common/promise-util';
28
28
  import { environment } from '../common/index';
@@ -164,6 +164,8 @@ export class BackendApplication {
164
164
 
165
165
  private _configured: Promise<void>;
166
166
 
167
+ private settlementContext?: MeasurementContext<BackendApplicationContribution>;
168
+
167
169
  constructor(
168
170
  @inject(ContributionProvider) @named(BackendApplicationContribution)
169
171
  protected readonly contributionsProvider: ContributionProvider<BackendApplicationContribution>,
@@ -195,9 +197,8 @@ export class BackendApplication {
195
197
  await Promise.all(this.contributionsProvider.getContributions().map(async contribution => {
196
198
  if (contribution.initialize) {
197
199
  try {
198
- await this.measure(contribution.constructor.name + '.initialize',
199
- () => contribution.initialize!()
200
- );
200
+ await this.measureContribution(contribution, 'initialize',
201
+ () => contribution.initialize!());
201
202
  } catch (error) {
202
203
  console.error('Could not initialize contribution', error);
203
204
  }
@@ -211,6 +212,7 @@ export class BackendApplication {
211
212
 
212
213
  @postConstruct()
213
214
  protected init(): void {
215
+ this.settlementContext = new MeasurementContext(this.stopwatch, 'Backend', TIMER_WARNING_THRESHOLD);
214
216
  this._configured = this.configure();
215
217
  }
216
218
 
@@ -232,7 +234,8 @@ export class BackendApplication {
232
234
  await Promise.all(this.contributionsProvider.getContributions().map(async contribution => {
233
235
  if (contribution.configure) {
234
236
  try {
235
- await contribution.configure!(this.app);
237
+ await this.measureContribution(contribution, 'configure',
238
+ () => contribution.configure!(this.app));
236
239
  } catch (error) {
237
240
  console.error('Could not configure contribution', error);
238
241
  }
@@ -246,6 +249,8 @@ export class BackendApplication {
246
249
  }
247
250
 
248
251
  async start(port?: number, hostname?: string): Promise<http.Server | https.Server> {
252
+ const startupMeasurement = this.stopwatch.start('backend-startup');
253
+
249
254
  hostname ??= this.cliParams.hostname;
250
255
  port ??= this.cliParams.port;
251
256
 
@@ -307,15 +312,17 @@ export class BackendApplication {
307
312
  for (const contribution of this.contributionsProvider.getContributions()) {
308
313
  if (contribution.onStart) {
309
314
  try {
310
- await this.measure(contribution.constructor.name + '.onStart',
311
- () => contribution.onStart!(server)
312
- );
315
+ await this.measureContribution(contribution, 'onStart',
316
+ () => contribution.onStart!(server));
313
317
  } catch (error) {
314
318
  console.error('Could not start contribution', error);
315
319
  }
316
320
  }
317
321
  }
318
- return this.stopwatch.startAsync('server', 'Finished starting backend application', () => deferred.promise);
322
+ await deferred.promise;
323
+ startupMeasurement.info('Backend application startup sequence completed (async work may still be pending)');
324
+ this.settlementContext?.armAllSettled();
325
+ return server;
319
326
  }
320
327
 
321
328
  protected getHttpUrl({ address, port, family }: AddressInfo, ssl?: boolean): string {
@@ -358,8 +365,18 @@ export class BackendApplication {
358
365
  next();
359
366
  }
360
367
 
368
+ protected async measureContribution<T>(contribution: BackendApplicationContribution, hook: string, fn: () => MaybePromise<T>): Promise<T> {
369
+ let innerResult: MaybePromise<T>;
370
+ this.settlementContext?.ensureEntry(contribution);
371
+ const result = await this.measure(contribution.constructor.name + '.' + hook,
372
+ () => (innerResult = fn())
373
+ );
374
+ this.settlementContext?.trackSettlement(contribution, innerResult!);
375
+ return result;
376
+ }
377
+
361
378
  protected async measure<T>(name: string, fn: () => MaybePromise<T>): Promise<T> {
362
- return this.stopwatch.startAsync(name, `Backend ${name}`, fn, { thresholdMillis: TIMER_WARNING_THRESHOLD });
379
+ return this.stopwatch.startAsync(name, `Backend ${name}`, fn, { thresholdMillis: TIMER_WARNING_THRESHOLD, defaultLogLevel: LogLevel.DEBUG });
363
380
  }
364
381
 
365
382
  protected handleUncaughtError(error: Error): void {
@@ -71,6 +71,7 @@ export class DefaultMessagingService implements MessagingService, BackendApplica
71
71
  protected createMainChannelContainer(socket: Channel): Container {
72
72
  const connectionContainer: Container = this.container.createChild() as Container;
73
73
  connectionContainer.bind(MainChannel).toConstantValue(socket);
74
+ socket.onClose(() => connectionContainer.unbindAllAsync().catch(e => console.error(e)));
74
75
  return connectionContainer;
75
76
  }
76
77
 
@@ -17,3 +17,4 @@
17
17
  export * from './messaging-service';
18
18
  export * from './ipc-connection-provider';
19
19
  export * from './ipc-channel';
20
+ export * from './websocket-frontend-connection-service';
@@ -24,8 +24,9 @@ import { MessagingListener, MessagingListenerContribution } from './messaging-li
24
24
  import { FrontendConnectionService } from './frontend-connection-service';
25
25
  import { BackendApplicationContribution } from '../backend-application';
26
26
  import { connectionCloseServicePath } from '../../common/messaging/connection-management';
27
- import { WebsocketFrontendConnectionService } from './websocket-frontend-connection-service';
27
+ import { ReconnectableSocketChannel, WebsocketFrontendConnectionService } from './websocket-frontend-connection-service';
28
28
  import { WebsocketEndpoint } from './websocket-endpoint';
29
+ import { SocketWriteBuffer } from '../../common/messaging/socket-write-buffer';
29
30
 
30
31
  export const messagingBackendModule = new ContainerModule(bind => {
31
32
  bindRootContributionProvider(bind, ConnectionContainerModule);
@@ -36,6 +37,9 @@ export const messagingBackendModule = new ContainerModule(bind => {
36
37
  bind(MessagingContainer).toDynamicValue(({ container }) => container).inSingletonScope();
37
38
  bind(WebsocketEndpoint).toSelf().inSingletonScope();
38
39
  bind(BackendApplicationContribution).toService(WebsocketEndpoint);
40
+ // Transient: each connection gets its own private buffer and channel instances.
41
+ bind(SocketWriteBuffer).toSelf();
42
+ bind(ReconnectableSocketChannel).toSelf();
39
43
  bind(WebsocketFrontendConnectionService).toSelf().inSingletonScope();
40
44
  bind(FrontendConnectionService).toService(WebsocketFrontendConnectionService);
41
45
  bind(MessagingListener).toSelf().inSingletonScope();
@@ -0,0 +1,85 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { expect } from 'chai';
18
+ import { Container, injectable, preDestroy } from 'inversify';
19
+ import { ConnectionHandler, bindContributionProvider, servicesPath } from '../../../common';
20
+ import { BasicChannel, Channel } from '../../../common/message-rpc/channel';
21
+ import { Uint8ArrayWriteBuffer } from '../../../common/message-rpc/uint8-array-message-buffer';
22
+ import { ConnectionContainerModule } from '../connection-container-module';
23
+ import { DefaultMessagingService, MessagingContainer } from '../default-messaging-service';
24
+ import { FrontendConnectionService } from '../frontend-connection-service';
25
+ import { MessagingService } from '../messaging-service';
26
+
27
+ describe('DefaultMessagingService', () => {
28
+
29
+ describe('when a frontend connection closes', () => {
30
+
31
+ it('disposes the connection-scoped child container, invoking @preDestroy on bound singleton services', async () => {
32
+ let canaryDisposed = false;
33
+
34
+ @injectable()
35
+ class CanaryConnectionHandler implements ConnectionHandler {
36
+ readonly path = 'canary';
37
+ onConnection(_channel: Channel): void { /* not relevant for this test */ }
38
+
39
+ @preDestroy()
40
+ protected onPreDestroy(): void {
41
+ canaryDisposed = true;
42
+ }
43
+ }
44
+
45
+ const canaryModule = ConnectionContainerModule.create(({ bind }) => {
46
+ bind(CanaryConnectionHandler).toSelf().inSingletonScope();
47
+ bind(ConnectionHandler).toService(CanaryConnectionHandler);
48
+ });
49
+
50
+ const container = new Container();
51
+ container.bind(MessagingContainer).toConstantValue(container);
52
+ container.bind(DefaultMessagingService).toSelf().inSingletonScope();
53
+ container.bind(ConnectionContainerModule).toConstantValue(canaryModule);
54
+ bindContributionProvider(container, ConnectionContainerModule);
55
+ bindContributionProvider(container, MessagingService.Contribution);
56
+
57
+ let serviceHandler: ((params: MessagingService.PathParams, mainChannel: Channel) => void) | undefined;
58
+ const frontendConnectionService: FrontendConnectionService = {
59
+ registerConnectionHandler(path, callback): void {
60
+ if (path === servicesPath) {
61
+ serviceHandler = callback;
62
+ }
63
+ }
64
+ };
65
+ container.bind(FrontendConnectionService).toConstantValue(frontendConnectionService);
66
+
67
+ const messagingService = container.get(DefaultMessagingService);
68
+ messagingService.initialize();
69
+
70
+ expect(serviceHandler, 'connection handler not registered on the services path').to.not.be.undefined;
71
+
72
+ const mainChannel = new BasicChannel(() => new Uint8ArrayWriteBuffer());
73
+ serviceHandler!({}, mainChannel);
74
+
75
+ expect(canaryDisposed, 'canary should not be disposed before the channel is closed').to.be.false;
76
+
77
+ mainChannel.onCloseEmitter.fire({ reason: 'frontend connection closed' });
78
+ await new Promise<void>(resolve => setImmediate(resolve));
79
+
80
+ expect(canaryDisposed, 'canary @preDestroy was not invoked').to.be.true;
81
+ });
82
+
83
+ });
84
+
85
+ });
@@ -15,9 +15,9 @@
15
15
 
16
16
  import { Channel, WriteBuffer } from '../../common/message-rpc';
17
17
  import { MessagingService } from './messaging-service';
18
- import { inject, injectable } from 'inversify';
18
+ import { inject, injectable, interfaces } from 'inversify';
19
19
  import { Socket } from 'socket.io';
20
- import { ConnectionHandlers } from './default-messaging-service';
20
+ import { ConnectionHandlers, MessagingContainer } from './default-messaging-service';
21
21
  import { SocketWriteBuffer } from '../../common/messaging/socket-write-buffer';
22
22
  import { FrontendConnectionService } from './frontend-connection-service';
23
23
  import { AbstractChannel } from '../../common/message-rpc/channel';
@@ -33,6 +33,9 @@ export class WebsocketFrontendConnectionService implements FrontendConnectionSer
33
33
  @inject(WebsocketEndpoint)
34
34
  protected readonly websocketServer: WebsocketEndpoint;
35
35
 
36
+ @inject(MessagingContainer)
37
+ protected readonly container: interfaces.Container;
38
+
36
39
  protected readonly wsHandlers = new ConnectionHandlers();
37
40
  protected readonly connectionsByFrontend = new Map<string, ReconnectableSocketChannel>();
38
41
  protected readonly closeTimeouts = new Map<string, NodeJS.Timeout>();
@@ -89,12 +92,13 @@ export class WebsocketFrontendConnectionService implements FrontendConnectionSer
89
92
  this.closeTimeouts.delete(frontEndId);
90
93
 
91
94
  connection.onCloseEmitter.fire({ reason });
95
+ connection.drainBuffer();
92
96
  connection.close();
93
97
  }
94
98
 
95
99
  protected createConnection(socket: Socket, frontEndId: string): ReconnectableSocketChannel {
96
100
  console.info(`creating connection for ${frontEndId}`);
97
- const channel = new ReconnectableSocketChannel();
101
+ const channel = this.container.get(ReconnectableSocketChannel);
98
102
  channel.connect(socket);
99
103
 
100
104
  this.connectionsByFrontend.set(frontEndId, channel);
@@ -136,12 +140,17 @@ export class WebsocketFrontendConnectionService implements FrontendConnectionSer
136
140
  }
137
141
  }
138
142
 
139
- class ReconnectableSocketChannel extends AbstractChannel {
140
- private socket: Socket | undefined;
141
- private socketBuffer = new SocketWriteBuffer();
142
- private disposables = new DisposableCollection();
143
+ @injectable()
144
+ export class ReconnectableSocketChannel extends AbstractChannel {
145
+ protected socket: Socket | undefined;
146
+
147
+ @inject(SocketWriteBuffer)
148
+ protected socketBuffer: SocketWriteBuffer;
149
+
150
+ protected disposables = new DisposableCollection();
143
151
 
144
152
  connect(socket: Socket): void {
153
+ this.disposables.dispose();
145
154
  this.disposables = new DisposableCollection();
146
155
  this.socket = socket;
147
156
  const errorHandler = (err: Error) => {
@@ -171,6 +180,10 @@ class ReconnectableSocketChannel extends AbstractChannel {
171
180
  this.socket = undefined;
172
181
  }
173
182
 
183
+ drainBuffer(): void {
184
+ this.socketBuffer.drain();
185
+ }
186
+
174
187
  override getWriteBuffer(): WriteBuffer {
175
188
  const writeBuffer = new Uint8ArrayWriteBuffer();
176
189
  writeBuffer.onCommit(data => {
@@ -23,7 +23,7 @@ export class NodeStopwatch extends Stopwatch {
23
23
 
24
24
  constructor() {
25
25
  super({
26
- owner: 'backend',
26
+ owner: 'backend process',
27
27
  now: () => performance.now(),
28
28
  });
29
29
  }
@@ -32,7 +32,15 @@ export class ProcessUtils {
32
32
  }
33
33
 
34
34
  protected winTerminateProcessTree(ppid: number): void {
35
- this.spawnSync('taskkill.exe', ['/f', '/t', '/pid', ppid.toString(10)]);
35
+ const result = cp.spawnSync('taskkill.exe', ['/f', '/t', '/pid', ppid.toString(10)], { encoding: 'utf8' });
36
+ if (result.error) {
37
+ throw result.error;
38
+ }
39
+ // taskkill may exit with a non-zero code when some child processes have already exited.
40
+ // This is expected during shutdown — log but don't throw.
41
+ if (result.status !== 0) {
42
+ console.warn(`taskkill.exe exited with ${result.status} for PID ${ppid}. Output:\n${JSON.stringify(result.output)}`);
43
+ }
36
44
  }
37
45
 
38
46
  protected unixTerminateProcessTree(ppid: number): void {