@theia/core 1.76.0-next.6 → 1.76.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.
- package/README.md +8 -8
- package/i18n/nls.cs.json +187 -49
- package/i18n/nls.de.json +187 -49
- package/i18n/nls.es.json +187 -49
- package/i18n/nls.fr.json +187 -49
- package/i18n/nls.hu.json +187 -49
- package/i18n/nls.it.json +187 -49
- package/i18n/nls.ja.json +187 -49
- package/i18n/nls.json +187 -49
- package/i18n/nls.ko.json +187 -49
- package/i18n/nls.pl.json +187 -49
- package/i18n/nls.pt-br.json +187 -49
- package/i18n/nls.ru.json +187 -49
- package/i18n/nls.tr.json +187 -49
- package/i18n/nls.zh-cn.json +187 -49
- package/i18n/nls.zh-tw.json +187 -49
- package/lib/browser/catalog.json +175 -95
- package/lib/browser/dialogs.d.ts +2 -0
- package/lib/browser/dialogs.d.ts.map +1 -1
- package/lib/browser/dialogs.js +9 -5
- package/lib/browser/dialogs.js.map +1 -1
- package/lib/browser/frontend-application-module.d.ts.map +1 -1
- package/lib/browser/frontend-application-module.js +3 -0
- package/lib/browser/frontend-application-module.js.map +1 -1
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +1 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/resource-context-key.d.ts +25 -0
- package/lib/browser/resource-context-key.d.ts.map +1 -1
- package/lib/browser/resource-context-key.js +41 -16
- package/lib/browser/resource-context-key.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts +7 -5
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +13 -12
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +11 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +38 -3
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +2 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +4 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js +123 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +12 -4
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js +2 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js.map +1 -1
- package/lib/browser/tree/tree-widget.d.ts +20 -2
- package/lib/browser/tree/tree-widget.d.ts.map +1 -1
- package/lib/browser/tree/tree-widget.js +31 -13
- package/lib/browser/tree/tree-widget.js.map +1 -1
- package/lib/browser/tree/tree-widget.spec.d.ts +2 -0
- package/lib/browser/tree/tree-widget.spec.d.ts.map +1 -0
- package/lib/browser/tree/tree-widget.spec.js +192 -0
- package/lib/browser/tree/tree-widget.spec.js.map +1 -0
- package/lib/browser/view-container.d.ts.map +1 -1
- package/lib/browser/view-container.js +2 -2
- package/lib/browser/view-container.js.map +1 -1
- package/lib/browser/widget-context-key-contribution.d.ts +17 -0
- package/lib/browser/widget-context-key-contribution.d.ts.map +1 -0
- package/lib/browser/widget-context-key-contribution.js +20 -0
- package/lib/browser/widget-context-key-contribution.js.map +1 -0
- package/lib/browser/window/browser-window-module.d.ts.map +1 -1
- package/lib/browser/window/browser-window-module.js +3 -0
- package/lib/browser/window/browser-window-module.js.map +1 -1
- package/lib/browser/window/window-launch-args.d.ts +29 -0
- package/lib/browser/window/window-launch-args.d.ts.map +1 -0
- package/lib/browser/window/window-launch-args.js +39 -0
- package/lib/browser/window/window-launch-args.js.map +1 -0
- package/lib/browser-only/frontend-only-application-module.d.ts.map +1 -1
- package/lib/browser-only/frontend-only-application-module.js +5 -2
- package/lib/browser-only/frontend-only-application-module.js.map +1 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts +3 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts.map +1 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.js +8 -7
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.js.map +1 -1
- package/lib/common/index.d.ts +2 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +2 -0
- package/lib/common/index.js.map +1 -1
- package/lib/common/launch-arguments.d.ts +32 -0
- package/lib/common/launch-arguments.d.ts.map +1 -0
- package/lib/common/launch-arguments.js +44 -0
- package/lib/common/launch-arguments.js.map +1 -0
- package/lib/common/remote-cli-args-contribution.d.ts +22 -0
- package/lib/common/remote-cli-args-contribution.d.ts.map +1 -0
- package/lib/common/remote-cli-args-contribution.js +20 -0
- package/lib/common/remote-cli-args-contribution.js.map +1 -0
- package/lib/electron-browser/messaging/electron-ipc-connection-source.d.ts +6 -3
- package/lib/electron-browser/messaging/electron-ipc-connection-source.d.ts.map +1 -1
- package/lib/electron-browser/messaging/electron-ipc-connection-source.js +4 -3
- package/lib/electron-browser/messaging/electron-ipc-connection-source.js.map +1 -1
- package/lib/electron-browser/preload.d.ts.map +1 -1
- package/lib/electron-browser/preload.js +3 -1
- package/lib/electron-browser/preload.js.map +1 -1
- package/lib/electron-browser/window/electron-window-launch-args.d.ts +14 -0
- package/lib/electron-browser/window/electron-window-launch-args.d.ts.map +1 -0
- package/lib/electron-browser/window/electron-window-launch-args.js +41 -0
- package/lib/electron-browser/window/electron-window-launch-args.js.map +1 -0
- package/lib/electron-browser/window/electron-window-launch-args.spec.d.ts +2 -0
- package/lib/electron-browser/window/electron-window-launch-args.spec.d.ts.map +1 -0
- package/lib/electron-browser/window/electron-window-launch-args.spec.js +43 -0
- package/lib/electron-browser/window/electron-window-launch-args.spec.js.map +1 -0
- package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
- package/lib/electron-browser/window/electron-window-module.js +7 -0
- package/lib/electron-browser/window/electron-window-module.js.map +1 -1
- package/lib/electron-common/electron-api.d.ts +10 -0
- package/lib/electron-common/electron-api.d.ts.map +1 -1
- package/lib/electron-common/electron-api.js.map +1 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +7 -1
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/electron-main/electron-main-application-module.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application-module.js +2 -0
- package/lib/electron-main/electron-main-application-module.js.map +1 -1
- package/lib/electron-main/electron-main-application.d.ts +57 -2
- package/lib/electron-main/electron-main-application.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application.js +68 -8
- package/lib/electron-main/electron-main-application.js.map +1 -1
- package/lib/electron-main/launch-args-store.d.ts +45 -0
- package/lib/electron-main/launch-args-store.d.ts.map +1 -0
- package/lib/electron-main/launch-args-store.js +76 -0
- package/lib/electron-main/launch-args-store.js.map +1 -0
- package/lib/electron-main/launch-args-store.spec.d.ts +2 -0
- package/lib/electron-main/launch-args-store.spec.d.ts.map +1 -0
- package/lib/electron-main/launch-args-store.spec.js +53 -0
- package/lib/electron-main/launch-args-store.spec.js.map +1 -0
- package/lib/electron-main/launch-argv-parser.d.ts +19 -0
- package/lib/electron-main/launch-argv-parser.d.ts.map +1 -0
- package/lib/electron-main/launch-argv-parser.js +71 -0
- package/lib/electron-main/launch-argv-parser.js.map +1 -0
- package/lib/electron-main/launch-argv-parser.spec.d.ts +2 -0
- package/lib/electron-main/launch-argv-parser.spec.d.ts.map +1 -0
- package/lib/electron-main/launch-argv-parser.spec.js +62 -0
- package/lib/electron-main/launch-argv-parser.spec.js.map +1 -0
- package/lib/electron-main/theia-electron-window.d.ts +2 -0
- package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
- package/lib/electron-main/theia-electron-window.js +12 -0
- package/lib/electron-main/theia-electron-window.js.map +1 -1
- package/package.json +11 -11
- package/src/browser/dialogs.ts +10 -5
- package/src/browser/frontend-application-module.ts +5 -1
- package/src/browser/index.ts +1 -0
- package/src/browser/resource-context-key.ts +61 -16
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +13 -12
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +36 -4
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.ts +150 -3
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +6 -3
- package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +13 -5
- package/src/browser/style/tree.css +7 -0
- package/src/browser/tree/tree-widget.spec.tsx +245 -0
- package/src/browser/tree/tree-widget.tsx +36 -12
- package/src/browser/view-container.ts +3 -3
- package/src/browser/widget-context-key-contribution.ts +34 -0
- package/src/browser/window/browser-window-module.ts +3 -0
- package/src/browser/window/window-launch-args.ts +53 -0
- package/src/browser-only/frontend-only-application-module.ts +5 -2
- package/src/browser-only/messaging/frontend-only-service-connection-provider.ts +3 -1
- package/src/common/i18n/nls.metadata.json +5246 -1106
- package/src/common/index.ts +2 -0
- package/src/common/launch-arguments.ts +61 -0
- package/src/common/remote-cli-args-contribution.ts +39 -0
- package/src/electron-browser/messaging/electron-ipc-connection-source.ts +6 -3
- package/src/electron-browser/preload.ts +3 -1
- package/src/electron-browser/window/electron-window-launch-args.spec.ts +45 -0
- package/src/electron-browser/window/electron-window-launch-args.ts +38 -0
- package/src/electron-browser/window/electron-window-module.ts +7 -0
- package/src/electron-common/electron-api.ts +10 -0
- package/src/electron-main/electron-api-main.ts +7 -1
- package/src/electron-main/electron-main-application-module.ts +2 -0
- package/src/electron-main/electron-main-application.ts +101 -8
- package/src/electron-main/launch-args-store.spec.ts +59 -0
- package/src/electron-main/launch-args-store.ts +73 -0
- package/src/electron-main/launch-argv-parser.spec.ts +70 -0
- package/src/electron-main/launch-argv-parser.ts +66 -0
- package/src/electron-main/theia-electron-window.ts +9 -0
package/src/common/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export * from './core-preferences';
|
|
|
24
24
|
export * from './disposable';
|
|
25
25
|
export * from './event';
|
|
26
26
|
export * from './inversify-utils';
|
|
27
|
+
export * from './launch-arguments';
|
|
27
28
|
export * from './listener';
|
|
28
29
|
export * from './logger';
|
|
29
30
|
export * from './logger-sanitizer';
|
|
@@ -44,6 +45,7 @@ export * from './progress-service';
|
|
|
44
45
|
export * from './progress-service-protocol';
|
|
45
46
|
export * from './quick-pick-service';
|
|
46
47
|
export * from './reference';
|
|
48
|
+
export * from './remote-cli-args-contribution';
|
|
47
49
|
export * from './resource';
|
|
48
50
|
export * from './selection';
|
|
49
51
|
export * from './selection-service';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH 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
|
+
/**
|
|
18
|
+
* The CLI options of a launch, already parsed. A window opened for a *forwarded* launch (see the
|
|
19
|
+
* `second-instance` handling in `ElectronMainApplication`) receives this rather than a raw `argv`:
|
|
20
|
+
* the command line is parsed once, in the Electron main process, so that no consumer has to
|
|
21
|
+
* re-implement the argument grammar and the frontend never sees an unparsed command line.
|
|
22
|
+
*
|
|
23
|
+
* @experimental
|
|
24
|
+
*/
|
|
25
|
+
export interface LaunchArguments {
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The values of each (possibly repeated) `--name value` / `--name=value` option, in the order
|
|
29
|
+
* they appeared on the command line.
|
|
30
|
+
*/
|
|
31
|
+
readonly values: Readonly<Record<string, readonly string[]>>;
|
|
32
|
+
|
|
33
|
+
/** The options that were explicitly negated, i.e. `--no-name` or `--name=false`. */
|
|
34
|
+
readonly negated: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @experimental */
|
|
38
|
+
export namespace LaunchArguments {
|
|
39
|
+
|
|
40
|
+
/** A launch that carried no options. */
|
|
41
|
+
export const EMPTY: LaunchArguments = { values: {}, negated: [] };
|
|
42
|
+
|
|
43
|
+
/** Every value of a (possibly repeated) option, or an empty array when the option is absent. */
|
|
44
|
+
export function values(args: LaunchArguments, name: string): readonly string[] {
|
|
45
|
+
return args.values[name] ?? [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The last value of an option, or `undefined` when the option is absent. Last wins, so that a
|
|
50
|
+
* repeated option behaves like it does on a normal command line.
|
|
51
|
+
*/
|
|
52
|
+
export function lastValue(args: LaunchArguments, name: string): string | undefined {
|
|
53
|
+
const all = values(args, name);
|
|
54
|
+
return all.length > 0 ? all[all.length - 1] : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Whether a boolean option was explicitly disabled, i.e. `--no-name` or `--name=false`. */
|
|
58
|
+
export function isNegated(args: LaunchArguments, name: string): boolean {
|
|
59
|
+
return args.negated.includes(name);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH 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 { MaybePromise } from './types';
|
|
18
|
+
|
|
19
|
+
export const RemoteCliArgsContribution = Symbol('RemoteCliArgsContribution');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Frontend contribution that provides additional CLI arguments to pass to a remote backend
|
|
23
|
+
* started for the *current* window (e.g. when attaching to a dev container).
|
|
24
|
+
*
|
|
25
|
+
* This lets per-window CLI options (such as `--session-preference`) that were forwarded to a
|
|
26
|
+
* window be re-applied on the remote backend. Unlike the node-side `RemoteCliContribution#enhanceArgs`,
|
|
27
|
+
* which reads process-global state on the shared backend, this contribution runs in the renderer and
|
|
28
|
+
* therefore has access to the current window's context.
|
|
29
|
+
*
|
|
30
|
+
* @experimental
|
|
31
|
+
*/
|
|
32
|
+
export interface RemoteCliArgsContribution {
|
|
33
|
+
/**
|
|
34
|
+
* Returns the extra CLI arguments to append when starting a remote backend for the current
|
|
35
|
+
* window. Implementations should return an empty array when they have nothing to contribute
|
|
36
|
+
* (e.g. for a regular cold-start window whose args are already applied by the shared backend).
|
|
37
|
+
*/
|
|
38
|
+
getRemoteCliArgs(): MaybePromise<string[]>;
|
|
39
|
+
}
|
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { injectable, interfaces } from 'inversify';
|
|
18
|
-
import { RpcProxy } from '../../common/messaging';
|
|
19
|
-
import { AbstractChannel, Channel
|
|
18
|
+
import { RpcProxy } from '../../common/messaging/proxy-factory';
|
|
19
|
+
import { AbstractChannel, Channel } from '../../common/message-rpc/channel';
|
|
20
|
+
import { WriteBuffer } from '../../common/message-rpc/message-buffer';
|
|
21
|
+
import { Emitter, Event } from '../../common/event';
|
|
22
|
+
import { MaybePromise } from '../../common/types';
|
|
20
23
|
import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '../../common/message-rpc/uint8-array-message-buffer';
|
|
21
24
|
import { ServiceConnectionProvider } from '../../browser/messaging/service-connection-provider';
|
|
22
25
|
import { ConnectionSource } from '../../browser/messaging/connection-source';
|
|
23
|
-
import { FrontendApplicationContribution } from '../../browser';
|
|
26
|
+
import { FrontendApplicationContribution } from '../../browser/frontend-application-contribution';
|
|
24
27
|
|
|
25
28
|
export interface ElectronIpcOptions {
|
|
26
29
|
}
|
|
@@ -269,7 +269,9 @@ export function preload(): void {
|
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
});
|
|
272
|
-
|
|
272
|
+
// Synchronous, so the metadata (including the parsed options of a forwarded launch) is in place
|
|
273
|
+
// before any frontend code runs.
|
|
274
|
+
api.WindowMetadata = ipcRenderer.sendSync(CHANNEL_WC_METADATA);
|
|
273
275
|
|
|
274
276
|
contextBridge.exposeInMainWorld('electronTheiaCore', api);
|
|
275
277
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH 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 { LaunchArguments } from '../../common/launch-arguments';
|
|
19
|
+
import { ElectronWindowLaunchArgs } from './electron-window-launch-args';
|
|
20
|
+
|
|
21
|
+
class TestElectronWindowLaunchArgs extends ElectronWindowLaunchArgs {
|
|
22
|
+
constructor(protected readonly metadata: { webcontentId: string, launchArgs?: LaunchArguments }) {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override getLaunchArgs(): LaunchArguments | undefined {
|
|
27
|
+
return this.metadata.launchArgs;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('ElectronWindowLaunchArgs', () => {
|
|
32
|
+
|
|
33
|
+
it('returns undefined for a cold-start window (main attached no options)', () => {
|
|
34
|
+
const service = new TestElectronWindowLaunchArgs({ webcontentId: '1' });
|
|
35
|
+
expect(service.getLaunchArgs()).to.equal(undefined);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns the options attached to the window metadata', () => {
|
|
39
|
+
const launchArgs: LaunchArguments = { values: { 'attach-container': ['B'], 'session-preference': ['foo=1'] }, negated: [] };
|
|
40
|
+
const service = new TestElectronWindowLaunchArgs({ webcontentId: '1', launchArgs });
|
|
41
|
+
const args = service.getLaunchArgs()!;
|
|
42
|
+
expect(LaunchArguments.lastValue(args, 'attach-container')).to.equal('B');
|
|
43
|
+
expect(LaunchArguments.values(args, 'session-preference')).to.deep.equal(['foo=1']);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH 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 { injectable } from 'inversify';
|
|
18
|
+
import { LaunchArguments } from '../../common/launch-arguments';
|
|
19
|
+
import { WindowLaunchArgs } from '../../browser/window/window-launch-args';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Electron implementation of {@link WindowLaunchArgs}. Reads the options the main process attached
|
|
23
|
+
* to this window's metadata, which the preload script fetches synchronously before any frontend code
|
|
24
|
+
* runs, and which main keys by the IPC sender rather than by any value from the URL (see
|
|
25
|
+
* `LaunchArgsStore`).
|
|
26
|
+
*
|
|
27
|
+
* @experimental
|
|
28
|
+
*/
|
|
29
|
+
@injectable()
|
|
30
|
+
export class ElectronWindowLaunchArgs implements WindowLaunchArgs {
|
|
31
|
+
|
|
32
|
+
getLaunchArgs(): LaunchArguments | undefined {
|
|
33
|
+
// `undefined` for a cold-start window, letting callers fall back to the shared backend, and
|
|
34
|
+
// the stored options for a forwarded window (kept for as long as the window shows the
|
|
35
|
+
// frontend the launch opened, so a plain reload still sees them).
|
|
36
|
+
return window.electronTheiaCore.WindowMetadata.launchArgs;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -33,6 +33,8 @@ import { ElectronUriHandlerContribution } from '../electron-uri-handler';
|
|
|
33
33
|
import { bindRootContributionProvider } from '../../common';
|
|
34
34
|
import { WindowTitleContribution } from '../../browser/window/window-title-service';
|
|
35
35
|
import { WindowFocusService } from '../../browser/window/window-focus-service';
|
|
36
|
+
import { WindowLaunchArgs } from '../../browser/window/window-launch-args';
|
|
37
|
+
import { ElectronWindowLaunchArgs } from './electron-window-launch-args';
|
|
36
38
|
import { WindowZoomStatusBarItem } from './window-zoom-status-bar-item';
|
|
37
39
|
import '../../../src/electron-browser/style/window-zoom-action-bar.css';
|
|
38
40
|
|
|
@@ -47,6 +49,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
|
47
49
|
bind(FrontendApplicationContribution).toService(ElectronUriHandlerContribution);
|
|
48
50
|
bind(ClipboardService).to(ElectronClipboardService).inSingletonScope();
|
|
49
51
|
rebind(FrontendApplicationStateService).to(ElectronFrontendApplicationStateService).inSingletonScope();
|
|
52
|
+
// `browser-window-module` (which binds `WindowLaunchArgs`) is not loaded in Electron, since the
|
|
53
|
+
// `window` extension provides `electron-window-module` as its `frontendElectron` replacement.
|
|
54
|
+
// Bind here directly, mirroring how `WindowService` is bound in each of the two modules.
|
|
55
|
+
bind(ElectronWindowLaunchArgs).toSelf().inSingletonScope();
|
|
56
|
+
bind(WindowLaunchArgs).toService(ElectronWindowLaunchArgs);
|
|
50
57
|
bind(SecondaryWindowService).to(ElectronSecondaryWindowService).inSingletonScope();
|
|
51
58
|
bind(ExternalAppOpenHandler).toSelf().inSingletonScope();
|
|
52
59
|
bind(OpenHandler).toService(ExternalAppOpenHandler);
|
|
@@ -18,6 +18,7 @@ import { NativeKeyboardLayout } from '../common/keyboard/keyboard-layout-provide
|
|
|
18
18
|
import { Disposable } from '../common';
|
|
19
19
|
import { FrontendApplicationState, StopReason } from '../common/frontend-application-state';
|
|
20
20
|
import { ThemeMode } from '../common/theme';
|
|
21
|
+
import { LaunchArguments } from '../common/launch-arguments';
|
|
21
22
|
|
|
22
23
|
export type MenuRole = ('undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'about' | 'services' | 'hide' | 'hideOthers' | 'unhide' | 'quit');
|
|
23
24
|
|
|
@@ -44,6 +45,15 @@ export type WindowEvent = 'maximize' | 'unmaximize' | 'focus';
|
|
|
44
45
|
export interface TheiaCoreAPI {
|
|
45
46
|
WindowMetadata: {
|
|
46
47
|
webcontentId: string;
|
|
48
|
+
/**
|
|
49
|
+
* The parsed CLI options of a *forwarded* launch (see the `second-instance` handling in
|
|
50
|
+
* `ElectronMainApplication`), or `undefined` for a cold-start window. Provided synchronously
|
|
51
|
+
* by the preload script, so a frontend contribution can act on it from the first paint.
|
|
52
|
+
* The options never ride on the window URL; see `LaunchArgsStore` for the rationale.
|
|
53
|
+
*
|
|
54
|
+
* @experimental
|
|
55
|
+
*/
|
|
56
|
+
launchArgs?: LaunchArguments;
|
|
47
57
|
}
|
|
48
58
|
getSecurityToken: () => string;
|
|
49
59
|
attachSecurityToken: (endpoint: string) => Promise<void>;
|
|
@@ -76,7 +76,13 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
|
|
|
76
76
|
|
|
77
77
|
onStart(application: ElectronMainApplication): MaybePromise<void> {
|
|
78
78
|
ipcMain.on(CHANNEL_WC_METADATA, event => {
|
|
79
|
-
|
|
79
|
+
// Answered synchronously, so the window has its metadata — including the parsed options
|
|
80
|
+
// of a forwarded launch — before any frontend code runs. The window is identified by the
|
|
81
|
+
// IPC sender, so it can only ever read its own.
|
|
82
|
+
event.returnValue = {
|
|
83
|
+
webcontentId: event.sender.id.toString(),
|
|
84
|
+
launchArgs: application.getLaunchArgs(event.sender.id)
|
|
85
|
+
};
|
|
80
86
|
});
|
|
81
87
|
|
|
82
88
|
// electron security token
|
|
@@ -23,6 +23,7 @@ import { RpcConnectionHandler } from '../common/messaging/proxy-factory';
|
|
|
23
23
|
import { ElectronSecurityToken } from '../electron-common/electron-token';
|
|
24
24
|
import { ElectronMainWindowService, electronMainWindowServicePath } from '../electron-common/electron-main-window-service';
|
|
25
25
|
import { ElectronMainApplication, ElectronMainApplicationContribution, ElectronMainProcessArgv } from './electron-main-application';
|
|
26
|
+
import { LaunchArgsStore } from './launch-args-store';
|
|
26
27
|
import { ElectronMainWindowServiceImpl } from './electron-main-window-service-impl';
|
|
27
28
|
import { TheiaBrowserWindowOptions, TheiaElectronWindow, TheiaElectronWindowFactory, WindowApplicationConfig } from './theia-electron-window';
|
|
28
29
|
import { TheiaMainApi } from './electron-api-main';
|
|
@@ -37,6 +38,7 @@ const electronSecurityToken: ElectronSecurityToken = { value: generateUuid() };
|
|
|
37
38
|
|
|
38
39
|
export default new ContainerModule(bind => {
|
|
39
40
|
bind(Stopwatch).toConstantValue(new SimpleStopwatch('electron main', () => performance.now()));
|
|
41
|
+
bind(LaunchArgsStore).toSelf().inSingletonScope();
|
|
40
42
|
bind(ElectronMainApplication).toSelf().inSingletonScope();
|
|
41
43
|
bind(ElectronMessagingContribution).toSelf().inSingletonScope();
|
|
42
44
|
bind(ElectronMainApplicationContribution).toService(ElectronMessagingContribution);
|
|
@@ -37,6 +37,9 @@ import { ElectronSecurityToken } from '../electron-common/electron-token';
|
|
|
37
37
|
import Storage = require('electron-store');
|
|
38
38
|
import { CancellationTokenSource, Disposable, DisposableCollection, Path, isOSX, isWindows } from '../common';
|
|
39
39
|
import { DEFAULT_WINDOW_HASH, WindowSearchParams } from '../common/window';
|
|
40
|
+
import { LaunchArguments } from '../common/launch-arguments';
|
|
41
|
+
import { LaunchArgvParser } from './launch-argv-parser';
|
|
42
|
+
import { LaunchArgsStore } from './launch-args-store';
|
|
40
43
|
import { TheiaBrowserWindowOptions, TheiaElectronWindow, TheiaElectronWindowFactory } from './theia-electron-window';
|
|
41
44
|
import { ElectronMainApplicationGlobals } from './electron-main-constants';
|
|
42
45
|
import { createDisposableListener } from './event-utils';
|
|
@@ -69,6 +72,25 @@ export interface ElectronMainCommandOptions {
|
|
|
69
72
|
* If the app is already running but user relaunches it, `secondInstance` is true.
|
|
70
73
|
*/
|
|
71
74
|
readonly secondInstance: boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The parsed CLI options of a forwarded launch. Set for `second-instance` launches so that
|
|
78
|
+
* per-window options (e.g. `--attach-container`, `--session-preference`) are carried to the
|
|
79
|
+
* newly created window rather than being dropped.
|
|
80
|
+
*
|
|
81
|
+
* @experimental
|
|
82
|
+
*/
|
|
83
|
+
readonly launchArgs?: LaunchArguments;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Whether an {@link ElectronMainApplicationContribution} has *claimed* this launch via its
|
|
87
|
+
* `claimsWindow` hook (e.g. a `--attach-container` launch claimed by `@theia/dev-container`).
|
|
88
|
+
* When set, an empty window is opened instead of restoring the last workspace, since the window
|
|
89
|
+
* is about to be replaced by whatever the claiming contribution attaches to.
|
|
90
|
+
*
|
|
91
|
+
* @experimental
|
|
92
|
+
*/
|
|
93
|
+
readonly claimed?: boolean;
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
/**
|
|
@@ -103,6 +125,17 @@ export interface ElectronMainApplicationContribution {
|
|
|
103
125
|
* The application is stopping. Contributions must perform only synchronous operations.
|
|
104
126
|
*/
|
|
105
127
|
onStop?(application: ElectronMainApplication): void;
|
|
128
|
+
/**
|
|
129
|
+
* Whether this contribution *claims* responsibility for a window opened for the given launch,
|
|
130
|
+
* e.g. a `--attach-container` launch claimed by `@theia/dev-container`. A claimed launch opens
|
|
131
|
+
* an empty window rather than restoring the last workspace, since the window is about to be
|
|
132
|
+
* replaced by whatever the claiming contribution attaches to. Core itself stays agnostic of the
|
|
133
|
+
* concrete CLI options. The frontend receives the same options (see `LaunchArgsStore`), so the
|
|
134
|
+
* corresponding frontend contribution can show a placeholder from the first paint.
|
|
135
|
+
*
|
|
136
|
+
* @experimental
|
|
137
|
+
*/
|
|
138
|
+
claimsWindow?(args: LaunchArguments): MaybePromise<boolean>;
|
|
106
139
|
}
|
|
107
140
|
|
|
108
141
|
// Extracted and modified the functionality from `yargs@15.4.0-beta.0`.
|
|
@@ -177,6 +210,9 @@ export class ElectronMainApplication {
|
|
|
177
210
|
@inject(Stopwatch)
|
|
178
211
|
protected readonly stopwatch: Stopwatch;
|
|
179
212
|
|
|
213
|
+
@inject(LaunchArgsStore)
|
|
214
|
+
protected readonly launchArgsStore: LaunchArgsStore;
|
|
215
|
+
|
|
180
216
|
protected isPortable = this.makePortable();
|
|
181
217
|
|
|
182
218
|
protected readonly electronStore = new Storage<{
|
|
@@ -252,10 +288,12 @@ export class ElectronMainApplication {
|
|
|
252
288
|
() => this.startContributions());
|
|
253
289
|
startupMeasurement.info('Startup sequence completed');
|
|
254
290
|
|
|
291
|
+
const launchArgs = LaunchArgvParser.parse(argv);
|
|
255
292
|
this.handleMainCommand({
|
|
256
293
|
file: args.file,
|
|
257
294
|
cwd: process.cwd(),
|
|
258
|
-
secondInstance: false
|
|
295
|
+
secondInstance: false,
|
|
296
|
+
claimed: await this.isWindowClaimed(launchArgs)
|
|
259
297
|
});
|
|
260
298
|
},
|
|
261
299
|
).parse();
|
|
@@ -445,6 +483,7 @@ export class ElectronMainApplication {
|
|
|
445
483
|
this.activeWindowStack.splice(stackIndex, 1);
|
|
446
484
|
}
|
|
447
485
|
this.windows.delete(id);
|
|
486
|
+
this.launchArgsStore.delete(id);
|
|
448
487
|
});
|
|
449
488
|
electronWindow.window.on('maximize', () => TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'maximize'));
|
|
450
489
|
electronWindow.window.on('unmaximize', () => TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'unmaximize'));
|
|
@@ -524,20 +563,35 @@ export class ElectronMainApplication {
|
|
|
524
563
|
}
|
|
525
564
|
}
|
|
526
565
|
|
|
527
|
-
async openDefaultWindow(params?: WindowSearchParams): Promise<BrowserWindow> {
|
|
566
|
+
async openDefaultWindow(params?: WindowSearchParams, launchArgs?: LaunchArguments): Promise<BrowserWindow> {
|
|
528
567
|
const options = this.getDefaultTheiaWindowOptions();
|
|
529
568
|
const [uri, electronWindow] = await Promise.all([this.createWindowUri(params), this.reuseOrCreateWindow(options)]);
|
|
569
|
+
this.stashLaunchArgs(electronWindow, launchArgs);
|
|
530
570
|
electronWindow.loadURL(uri.withFragment(DEFAULT_WINDOW_HASH).toString(true));
|
|
531
571
|
return electronWindow;
|
|
532
572
|
}
|
|
533
573
|
|
|
534
|
-
protected async openWindowWithWorkspace(workspacePath: string): Promise<BrowserWindow> {
|
|
574
|
+
protected async openWindowWithWorkspace(workspacePath: string, launchArgs?: LaunchArguments): Promise<BrowserWindow> {
|
|
535
575
|
const options = await this.getLastWindowOptions();
|
|
536
576
|
const [uri, electronWindow] = await Promise.all([this.createWindowUri(), this.reuseOrCreateWindow(options)]);
|
|
577
|
+
this.stashLaunchArgs(electronWindow, launchArgs);
|
|
537
578
|
electronWindow.loadURL(uri.withFragment(encodeURI(workspacePath)).toString(true));
|
|
538
579
|
return electronWindow;
|
|
539
580
|
}
|
|
540
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Associates the parsed options of a forwarded launch with the target window before it loads its
|
|
584
|
+
* URL, so that the preload script cannot ask for them before they are stored. No-op for a
|
|
585
|
+
* cold-start launch, which carries none.
|
|
586
|
+
*
|
|
587
|
+
* @experimental
|
|
588
|
+
*/
|
|
589
|
+
protected stashLaunchArgs(window: BrowserWindow, launchArgs?: LaunchArguments): void {
|
|
590
|
+
if (launchArgs) {
|
|
591
|
+
this.launchArgsStore.store(window.webContents.id, launchArgs);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
541
595
|
protected async reuseOrCreateWindow(asyncOptions: MaybePromise<TheiaBrowserWindowOptions>): Promise<BrowserWindow> {
|
|
542
596
|
if (!this.initialWindow) {
|
|
543
597
|
return this.createWindow(asyncOptions);
|
|
@@ -556,6 +610,14 @@ export class ElectronMainApplication {
|
|
|
556
610
|
}
|
|
557
611
|
|
|
558
612
|
protected async handleMainCommand(options: ElectronMainCommandOptions): Promise<void> {
|
|
613
|
+
// A claimed launch (e.g. a CLI attach) opens an empty window rather than restoring the last
|
|
614
|
+
// workspace: the local workbench would only be discarded when the window reloads into the
|
|
615
|
+
// claimed target, so loading a real workspace behind the placeholder wastes work and can
|
|
616
|
+
// trigger side effects.
|
|
617
|
+
if (options.claimed) {
|
|
618
|
+
await this.openDefaultWindow(undefined, options.launchArgs);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
559
621
|
let workspacePath: string | undefined;
|
|
560
622
|
if (options.file) {
|
|
561
623
|
try {
|
|
@@ -565,14 +627,41 @@ export class ElectronMainApplication {
|
|
|
565
627
|
}
|
|
566
628
|
}
|
|
567
629
|
if (workspacePath !== undefined) {
|
|
568
|
-
await this.openWindowWithWorkspace(workspacePath);
|
|
630
|
+
await this.openWindowWithWorkspace(workspacePath, options.launchArgs);
|
|
569
631
|
} else {
|
|
570
632
|
if (options.secondInstance === false) {
|
|
571
|
-
await this.openWindowWithWorkspace(''); // restore previous workspace.
|
|
633
|
+
await this.openWindowWithWorkspace('', options.launchArgs); // restore previous workspace.
|
|
572
634
|
} else if (options.file === undefined) {
|
|
573
|
-
await this.openDefaultWindow();
|
|
635
|
+
await this.openDefaultWindow(undefined, options.launchArgs);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Returns the parsed launch options stored for the window with the given `webContents` id, or
|
|
642
|
+
* `undefined` for a cold-start window. Read from the per-window metadata channel, where the
|
|
643
|
+
* caller is identified by the IPC sender, so a window can only ever see its own options.
|
|
644
|
+
*
|
|
645
|
+
* @experimental
|
|
646
|
+
*/
|
|
647
|
+
getLaunchArgs(windowId: number): LaunchArguments | undefined {
|
|
648
|
+
return this.launchArgsStore.get(windowId);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Whether any {@link ElectronMainApplicationContribution} claims responsibility for a window
|
|
653
|
+
* opened for the given launch (see its `claimsWindow` hook). Core stays agnostic of the concrete
|
|
654
|
+
* CLI options; e.g. `@theia/dev-container` claims `--attach-container` launches.
|
|
655
|
+
*
|
|
656
|
+
* @experimental
|
|
657
|
+
*/
|
|
658
|
+
protected async isWindowClaimed(args: LaunchArguments): Promise<boolean> {
|
|
659
|
+
for (const contribution of this.contributions.getContributions()) {
|
|
660
|
+
if (await contribution.claimsWindow?.(args)) {
|
|
661
|
+
return true;
|
|
574
662
|
}
|
|
575
663
|
}
|
|
664
|
+
return false;
|
|
576
665
|
}
|
|
577
666
|
|
|
578
667
|
async openUrl(url: string): Promise<void> {
|
|
@@ -833,16 +922,20 @@ export class ElectronMainApplication {
|
|
|
833
922
|
if (originalArgv.includes('--open-url')) {
|
|
834
923
|
this.openUrl(originalArgv[originalArgv.length - 1]);
|
|
835
924
|
} else {
|
|
836
|
-
|
|
925
|
+
const argv = this.processArgv.getProcessArgvWithoutBin(originalArgv);
|
|
926
|
+
createYargs(argv, cwd)
|
|
837
927
|
.help(false)
|
|
838
928
|
.command('$0 [file]', false,
|
|
839
929
|
cmd => cmd
|
|
840
930
|
.positional('file', { type: 'string' }),
|
|
841
931
|
async args => {
|
|
932
|
+
const launchArgs = LaunchArgvParser.parse(argv);
|
|
842
933
|
await this.handleMainCommand({
|
|
843
934
|
file: args.file,
|
|
844
935
|
cwd: cwd,
|
|
845
|
-
secondInstance: true
|
|
936
|
+
secondInstance: true,
|
|
937
|
+
launchArgs,
|
|
938
|
+
claimed: await this.isWindowClaimed(launchArgs)
|
|
846
939
|
});
|
|
847
940
|
},
|
|
848
941
|
).parse();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH 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 { LaunchArguments } from '../common/launch-arguments';
|
|
19
|
+
import { LaunchArgsStore } from './launch-args-store';
|
|
20
|
+
|
|
21
|
+
const args = (name: string, value: string): LaunchArguments => ({ values: { [name]: [value] }, negated: [] });
|
|
22
|
+
|
|
23
|
+
describe('LaunchArgsStore', () => {
|
|
24
|
+
|
|
25
|
+
it('returns the stored argv for its window id', () => {
|
|
26
|
+
const store = new LaunchArgsStore();
|
|
27
|
+
const launchArgs: LaunchArguments = { values: { 'attach-container': ['B'], 'session-preference': ['foo=1'] }, negated: [] };
|
|
28
|
+
store.store(1, launchArgs);
|
|
29
|
+
expect(store.get(1)).to.deep.equal(launchArgs);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('keeps the argv for the window lifetime, so a reload still sees it', () => {
|
|
33
|
+
const store = new LaunchArgsStore();
|
|
34
|
+
store.store(1, args('attach-container', 'B'));
|
|
35
|
+
expect(store.get(1)).to.deep.equal(args('attach-container', 'B'));
|
|
36
|
+
// a second read (e.g. after a reload) still returns the options
|
|
37
|
+
expect(store.get(1)).to.deep.equal(args('attach-container', 'B'));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('returns undefined for an unknown (cold-start) window id', () => {
|
|
41
|
+
const store = new LaunchArgsStore();
|
|
42
|
+
expect(store.get(42)).to.equal(undefined);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('drops the argv once the window is deleted', () => {
|
|
46
|
+
const store = new LaunchArgsStore();
|
|
47
|
+
store.store(1, args('attach-container', 'A'));
|
|
48
|
+
store.delete(1);
|
|
49
|
+
expect(store.get(1)).to.equal(undefined);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('keeps entries independent per window id', () => {
|
|
53
|
+
const store = new LaunchArgsStore();
|
|
54
|
+
store.store(1, args('attach-container', 'A'));
|
|
55
|
+
store.store(2, args('attach-container', 'B'));
|
|
56
|
+
expect(store.get(2)).to.deep.equal(args('attach-container', 'B'));
|
|
57
|
+
expect(store.get(1)).to.deep.equal(args('attach-container', 'A'));
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH 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 { injectable } from 'inversify';
|
|
18
|
+
import { LaunchArguments } from '../common/launch-arguments';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Holds the parsed CLI options of *forwarded* launches (see the `second-instance` handling in
|
|
22
|
+
* `ElectronMainApplication`) in the trusted Electron main process, keyed by the `webContents` id of
|
|
23
|
+
* the window that was opened for them.
|
|
24
|
+
*
|
|
25
|
+
* ### Why the arguments never travel on the window URL
|
|
26
|
+
*
|
|
27
|
+
* The URL is the one channel in the system that carries no trust: in a browser deployment it is
|
|
28
|
+
* attacker-writable, so putting per-window options such as `--session-preference` there would let a
|
|
29
|
+
* crafted link inject settings (some of which persist to the User scope). The arguments therefore
|
|
30
|
+
* stay here in the main process and are handed to the window over the per-window metadata channel
|
|
31
|
+
* (`CHANNEL_WC_METADATA`), where main identifies the calling window by `event.sender.id` rather than
|
|
32
|
+
* by any value from the URL. This is the single place that documents that rationale; other files
|
|
33
|
+
* point here.
|
|
34
|
+
*
|
|
35
|
+
* The entry is not consumed on first read, so that a plain `Reload Window`, which re-runs the
|
|
36
|
+
* renderer against the same window, still observes the arguments. It is removed when the window is
|
|
37
|
+
* closed, and when the window is redirected to a *different* frontend (e.g. into a dev container
|
|
38
|
+
* after a CLI attach), since the arguments describe the launch rather than whatever the window is
|
|
39
|
+
* navigated to afterwards. See `TheiaElectronWindow.reload`.
|
|
40
|
+
*
|
|
41
|
+
* @experimental
|
|
42
|
+
*/
|
|
43
|
+
@injectable()
|
|
44
|
+
export class LaunchArgsStore {
|
|
45
|
+
|
|
46
|
+
protected readonly argsByWindowId = new Map<number, LaunchArguments>();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Associates the parsed options of a forwarded launch with the window identified by `windowId` (its
|
|
50
|
+
* `webContents` id). Must be called before the window loads its URL, so that the preload script
|
|
51
|
+
* cannot ask for the arguments before they are stored.
|
|
52
|
+
*/
|
|
53
|
+
store(windowId: number, args: LaunchArguments): void {
|
|
54
|
+
this.argsByWindowId.set(windowId, args);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns the parsed launch options for the window identified by `windowId`, or `undefined`
|
|
59
|
+
* for a window that was not opened by a forwarded launch (a cold-start window), or one whose
|
|
60
|
+
* arguments were dropped. Repeatable, so a plain reload still sees the arguments.
|
|
61
|
+
*/
|
|
62
|
+
get(windowId: number): LaunchArguments | undefined {
|
|
63
|
+
return this.argsByWindowId.get(windowId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Drops the arguments stored for `windowId`. Called when the window is closed, and when it is
|
|
68
|
+
* redirected to a different frontend.
|
|
69
|
+
*/
|
|
70
|
+
delete(windowId: number): void {
|
|
71
|
+
this.argsByWindowId.delete(windowId);
|
|
72
|
+
}
|
|
73
|
+
}
|