dsh-builtin-browser 0.1.23 → 0.3.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.en.md +91 -3
- package/README.md +98 -4
- package/client.js +334 -0
- package/cordis.patch.yml +24 -5
- package/desktop-bridge/install.mjs +98 -0
- package/desktop-bridge/plugin-browser-bridge.js +385 -0
- package/docs/README.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/tool-reference.md +2 -1
- package/lib/browser/runtime.d.ts +18 -1
- package/lib/browser/runtime.js +19 -0
- package/lib/browser/types.d.ts +33 -0
- package/lib/browser-electron/bridge-connection.d.ts +58 -0
- package/lib/browser-electron/bridge-connection.js +133 -0
- package/lib/browser-electron/desktop-bridge-host.d.ts +124 -0
- package/lib/browser-electron/desktop-bridge-host.js +297 -0
- package/lib/browser-electron/entry.js +195 -11
- package/lib/browser-electron/history-store.d.ts +75 -0
- package/lib/browser-electron/history-store.js +162 -0
- package/lib/browser-electron/host-main.js +28 -1
- package/lib/browser-electron/provider.d.ts +122 -1
- package/lib/browser-electron/provider.js +233 -21
- package/lib/browser-electron/remote-host.d.ts +17 -1
- package/lib/browser-electron/remote-host.js +81 -9
- package/lib/browser-electron/settings-store.d.ts +92 -0
- package/lib/browser-electron/settings-store.js +183 -0
- package/lib/browser-electron/system-browser.d.ts +100 -0
- package/lib/browser-electron/system-browser.js +322 -0
- package/lib/browser-electron/virtual-cursor.d.ts +67 -0
- package/lib/browser-electron/virtual-cursor.js +141 -0
- package/lib/index.d.ts +16 -2
- package/lib/index.js +18 -2
- package/lib/tool-browser/index.js +122 -16
- package/package.json +14 -1
- package/src/browser/runtime.ts +22 -0
- package/src/browser/types.ts +31 -0
- package/src/browser-electron/bridge-connection.ts +145 -0
- package/src/browser-electron/desktop-bridge-host.ts +305 -0
- package/src/browser-electron/entry.ts +217 -11
- package/src/browser-electron/history-store.ts +171 -0
- package/src/browser-electron/host-main.ts +26 -1
- package/src/browser-electron/provider.ts +284 -21
- package/src/browser-electron/remote-host.ts +84 -7
- package/src/browser-electron/settings-store.ts +227 -0
- package/src/browser-electron/system-browser.ts +318 -0
- package/src/browser-electron/virtual-cursor.ts +152 -0
- package/src/index.ts +20 -2
- package/src/tool-browser/index.ts +121 -15
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-owned settings document — the switches the browser settings panel
|
|
3
|
+
* exposes, stored next to the browsing history so both follow the same rules:
|
|
4
|
+
* they survive the browser process and DSH restarts.
|
|
5
|
+
*
|
|
6
|
+
* Every field is optional on disk and falls back to a default, so a hand-edited
|
|
7
|
+
* or older file never breaks startup; an unreadable file is treated as "all
|
|
8
|
+
* defaults" rather than an error.
|
|
9
|
+
* @module dsh-browser/browser-electron/settings-store
|
|
10
|
+
*/
|
|
11
|
+
import type { BrowserChannel } from './system-browser.js';
|
|
12
|
+
/** Vision/operation strategy the provider prefers when the model can see. */
|
|
13
|
+
export type VisionStrategy = 'auto' | 'nonVisual';
|
|
14
|
+
/** The full settings document, with every field resolved. */
|
|
15
|
+
export interface BrowserSettings {
|
|
16
|
+
/** Persistent browsing history (visited pages). */
|
|
17
|
+
readonly history: {
|
|
18
|
+
readonly enabled: boolean;
|
|
19
|
+
readonly maxEntries: number;
|
|
20
|
+
readonly maxAgeDays: number;
|
|
21
|
+
};
|
|
22
|
+
/** Login-state retention: cookies and the browser profile. */
|
|
23
|
+
readonly cookies: {
|
|
24
|
+
readonly persist: boolean;
|
|
25
|
+
};
|
|
26
|
+
/** Presentation behaviour. */
|
|
27
|
+
readonly ui: {
|
|
28
|
+
/** Auto-expand the side panel once per task, then leave the user alone. */
|
|
29
|
+
readonly autoExpandOnce: boolean;
|
|
30
|
+
/** Release the browser when its session ends, instead of waiting for a close. */
|
|
31
|
+
readonly closeWithSession: boolean;
|
|
32
|
+
/** Show the synthetic cursor/click feedback while the agent operates. */
|
|
33
|
+
readonly virtualCursor: boolean;
|
|
34
|
+
};
|
|
35
|
+
/** Visual vs non-visual operation strategy. */
|
|
36
|
+
readonly vision: {
|
|
37
|
+
readonly strategy: VisionStrategy;
|
|
38
|
+
};
|
|
39
|
+
/** Which browser binary carries the agent's pages. */
|
|
40
|
+
readonly browser: {
|
|
41
|
+
readonly channel: BrowserChannel;
|
|
42
|
+
};
|
|
43
|
+
/** Whether the agent may read cookies / export login state. */
|
|
44
|
+
readonly credentials: {
|
|
45
|
+
readonly allowRead: boolean;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Defaults for every switch: the documented out-of-the-box behaviour. */
|
|
49
|
+
export declare const DEFAULT_SETTINGS: BrowserSettings;
|
|
50
|
+
/**
|
|
51
|
+
* Absolute path of the settings file, beside the browser profile and the
|
|
52
|
+
* browsing history.
|
|
53
|
+
* @returns the settings file path.
|
|
54
|
+
*/
|
|
55
|
+
export declare function settingsPath(): string;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a raw settings document (already parsed, shape unknown) into the full
|
|
58
|
+
* document, field by field. Unknown keys are dropped: the file is not a place to
|
|
59
|
+
* smuggle configuration.
|
|
60
|
+
* @param raw - the parsed file contents.
|
|
61
|
+
* @returns the resolved settings.
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveSettings(raw: unknown): BrowserSettings;
|
|
64
|
+
/**
|
|
65
|
+
* File-backed settings with an in-memory snapshot. Reads re-stat the file so an
|
|
66
|
+
* edit from another process (or the settings panel writing through a second
|
|
67
|
+
* code path) is picked up without a restart; writes are atomic enough for a
|
|
68
|
+
* single-writer plugin and never throw at the caller.
|
|
69
|
+
*/
|
|
70
|
+
export declare class SettingsStore {
|
|
71
|
+
private readonly file;
|
|
72
|
+
private cached;
|
|
73
|
+
/** Last observed mtime, used to invalidate the cache on external edits. */
|
|
74
|
+
private cachedMtimeMs;
|
|
75
|
+
/**
|
|
76
|
+
* @param file - settings file path (defaults beside the browser profile).
|
|
77
|
+
*/
|
|
78
|
+
constructor(file?: string);
|
|
79
|
+
/** Path this store reads and writes. */
|
|
80
|
+
path(): string;
|
|
81
|
+
/**
|
|
82
|
+
* Current settings, re-read when the file changed on disk.
|
|
83
|
+
* @returns the resolved settings document.
|
|
84
|
+
*/
|
|
85
|
+
get(): BrowserSettings;
|
|
86
|
+
/**
|
|
87
|
+
* Merge a partial patch into the stored document and persist it.
|
|
88
|
+
* @param patch - the fields to change (unknown keys are ignored).
|
|
89
|
+
* @returns the settings after the merge.
|
|
90
|
+
*/
|
|
91
|
+
update(patch: unknown): BrowserSettings;
|
|
92
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-owned settings document — the switches the browser settings panel
|
|
3
|
+
* exposes, stored next to the browsing history so both follow the same rules:
|
|
4
|
+
* they survive the browser process and DSH restarts.
|
|
5
|
+
*
|
|
6
|
+
* Every field is optional on disk and falls back to a default, so a hand-edited
|
|
7
|
+
* or older file never breaks startup; an unreadable file is treated as "all
|
|
8
|
+
* defaults" rather than an error.
|
|
9
|
+
* @module dsh-browser/browser-electron/settings-store
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
/** Defaults for every switch: the documented out-of-the-box behaviour. */
|
|
15
|
+
export const DEFAULT_SETTINGS = {
|
|
16
|
+
history: { enabled: true, maxEntries: 5_000, maxAgeDays: 90 },
|
|
17
|
+
cookies: { persist: true },
|
|
18
|
+
ui: { autoExpandOnce: true, closeWithSession: false, virtualCursor: true },
|
|
19
|
+
vision: { strategy: 'auto' },
|
|
20
|
+
// Bundled by default: it needs nothing installed, and on the desktop the carrier
|
|
21
|
+
// is the sidebar anyway. Choosing a system browser is an explicit opt-in because
|
|
22
|
+
// it starts a real Chrome/Edge with its own profile.
|
|
23
|
+
browser: { channel: 'bundled' },
|
|
24
|
+
credentials: { allowRead: true },
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Absolute path of the settings file, beside the browser profile and the
|
|
28
|
+
* browsing history.
|
|
29
|
+
* @returns the settings file path.
|
|
30
|
+
*/
|
|
31
|
+
export function settingsPath() {
|
|
32
|
+
const home = process.env.DSH_HOME ?? join(homedir(), '.dsh');
|
|
33
|
+
return join(home, 'dsh-builtin-browser-host', 'settings.json');
|
|
34
|
+
}
|
|
35
|
+
/** Coerce one boolean field, keeping the default when absent or mistyped. */
|
|
36
|
+
function bool(value, fallback) {
|
|
37
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Coerce the browser channel, keeping the default when it is not one of the
|
|
41
|
+
* known choices. A hand-edited typo must not silently select a different browser.
|
|
42
|
+
* @param value - the raw value from the settings document.
|
|
43
|
+
* @returns a valid channel.
|
|
44
|
+
*/
|
|
45
|
+
function resolveChannel(value) {
|
|
46
|
+
return value === 'chrome' || value === 'edge' || value === 'auto' || value === 'bundled'
|
|
47
|
+
? value
|
|
48
|
+
: DEFAULT_SETTINGS.browser.channel;
|
|
49
|
+
}
|
|
50
|
+
/** Coerce one positive-integer field, keeping the default when invalid. */
|
|
51
|
+
function count(value, fallback) {
|
|
52
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
53
|
+
}
|
|
54
|
+
/** Read one nested section of an unknown document, or an empty object. */
|
|
55
|
+
function section(source, key) {
|
|
56
|
+
const value = source[key];
|
|
57
|
+
return typeof value === 'object' && value !== null ? value : {};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a raw settings document (already parsed, shape unknown) into the full
|
|
61
|
+
* document, field by field. Unknown keys are dropped: the file is not a place to
|
|
62
|
+
* smuggle configuration.
|
|
63
|
+
* @param raw - the parsed file contents.
|
|
64
|
+
* @returns the resolved settings.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveSettings(raw) {
|
|
67
|
+
const source = typeof raw === 'object' && raw !== null ? raw : {};
|
|
68
|
+
const history = section(source, 'history');
|
|
69
|
+
const cookies = section(source, 'cookies');
|
|
70
|
+
const ui = section(source, 'ui');
|
|
71
|
+
const vision = section(source, 'vision');
|
|
72
|
+
const credentials = section(source, 'credentials');
|
|
73
|
+
const browser = section(source, 'browser');
|
|
74
|
+
return {
|
|
75
|
+
history: {
|
|
76
|
+
enabled: bool(history.enabled, DEFAULT_SETTINGS.history.enabled),
|
|
77
|
+
maxEntries: count(history.maxEntries, DEFAULT_SETTINGS.history.maxEntries),
|
|
78
|
+
maxAgeDays: count(history.maxAgeDays, DEFAULT_SETTINGS.history.maxAgeDays),
|
|
79
|
+
},
|
|
80
|
+
cookies: { persist: bool(cookies.persist, DEFAULT_SETTINGS.cookies.persist) },
|
|
81
|
+
ui: {
|
|
82
|
+
autoExpandOnce: bool(ui.autoExpandOnce, DEFAULT_SETTINGS.ui.autoExpandOnce),
|
|
83
|
+
closeWithSession: bool(ui.closeWithSession, DEFAULT_SETTINGS.ui.closeWithSession),
|
|
84
|
+
virtualCursor: bool(ui.virtualCursor, DEFAULT_SETTINGS.ui.virtualCursor),
|
|
85
|
+
},
|
|
86
|
+
vision: { strategy: vision.strategy === 'nonVisual' ? 'nonVisual' : DEFAULT_SETTINGS.vision.strategy },
|
|
87
|
+
browser: { channel: resolveChannel(browser.channel) },
|
|
88
|
+
credentials: { allowRead: bool(credentials.allowRead, DEFAULT_SETTINGS.credentials.allowRead) },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* File-backed settings with an in-memory snapshot. Reads re-stat the file so an
|
|
93
|
+
* edit from another process (or the settings panel writing through a second
|
|
94
|
+
* code path) is picked up without a restart; writes are atomic enough for a
|
|
95
|
+
* single-writer plugin and never throw at the caller.
|
|
96
|
+
*/
|
|
97
|
+
export class SettingsStore {
|
|
98
|
+
file;
|
|
99
|
+
cached;
|
|
100
|
+
/** Last observed mtime, used to invalidate the cache on external edits. */
|
|
101
|
+
cachedMtimeMs = -1;
|
|
102
|
+
/**
|
|
103
|
+
* @param file - settings file path (defaults beside the browser profile).
|
|
104
|
+
*/
|
|
105
|
+
constructor(file = settingsPath()) {
|
|
106
|
+
this.file = file;
|
|
107
|
+
}
|
|
108
|
+
/** Path this store reads and writes. */
|
|
109
|
+
path() {
|
|
110
|
+
return this.file;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Current settings, re-read when the file changed on disk.
|
|
114
|
+
* @returns the resolved settings document.
|
|
115
|
+
*/
|
|
116
|
+
get() {
|
|
117
|
+
let mtimeMs = -1;
|
|
118
|
+
let text;
|
|
119
|
+
try {
|
|
120
|
+
if (existsSync(this.file)) {
|
|
121
|
+
mtimeMs = statSync(this.file).mtimeMs;
|
|
122
|
+
text = readFileSync(this.file, 'utf8');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Unreadable file: fall through to defaults below.
|
|
127
|
+
mtimeMs = -1;
|
|
128
|
+
text = undefined;
|
|
129
|
+
}
|
|
130
|
+
if (this.cached !== undefined && mtimeMs === this.cachedMtimeMs)
|
|
131
|
+
return this.cached;
|
|
132
|
+
try {
|
|
133
|
+
// A leading BOM is what ordinary Windows editors (Notepad, PowerShell's
|
|
134
|
+
// `Set-Content -Encoding utf8`) leave behind, and `JSON.parse` rejects it.
|
|
135
|
+
// Without this strip, hand-editing the settings file silently discarded every
|
|
136
|
+
// value in it: the document looked correct, and the plugin quietly ran on
|
|
137
|
+
// defaults — including switches the user had just turned off.
|
|
138
|
+
const body = text?.replace(/^\uFEFF/, '');
|
|
139
|
+
this.cached = body === undefined ? DEFAULT_SETTINGS : resolveSettings(JSON.parse(body));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Malformed JSON: behave as defaults rather than fail startup.
|
|
143
|
+
this.cached = DEFAULT_SETTINGS;
|
|
144
|
+
}
|
|
145
|
+
this.cachedMtimeMs = mtimeMs;
|
|
146
|
+
return this.cached;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Merge a partial patch into the stored document and persist it.
|
|
150
|
+
* @param patch - the fields to change (unknown keys are ignored).
|
|
151
|
+
* @returns the settings after the merge.
|
|
152
|
+
*/
|
|
153
|
+
update(patch) {
|
|
154
|
+
const current = this.get();
|
|
155
|
+
const merged = resolveSettings(deepMerge(current, patch));
|
|
156
|
+
try {
|
|
157
|
+
mkdirSync(dirname(this.file), { recursive: true });
|
|
158
|
+
writeFileSync(this.file, `${JSON.stringify(merged, null, 2)}\n`);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Keep the merged value in memory even when the write fails.
|
|
162
|
+
}
|
|
163
|
+
this.cached = merged;
|
|
164
|
+
this.cachedMtimeMs = Date.now();
|
|
165
|
+
return merged;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** One-level-deep merge over the settings sections (patch wins). */
|
|
169
|
+
function deepMerge(base, patch) {
|
|
170
|
+
if (typeof patch !== 'object' || patch === null)
|
|
171
|
+
return base;
|
|
172
|
+
const result = { ...base };
|
|
173
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
174
|
+
const existing = result[key];
|
|
175
|
+
if (typeof value === 'object' && value !== null && typeof existing === 'object' && existing !== null) {
|
|
176
|
+
result[key] = { ...existing, ...value };
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
result[key] = value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drive the browser the user already has (Chrome / Edge / any Chromium) instead of
|
|
3
|
+
* the Electron we ship — the same approach Codex Browser Use takes.
|
|
4
|
+
*
|
|
5
|
+
* WHY
|
|
6
|
+
* The bundled Electron is a private copy with its own profile, so it can never see
|
|
7
|
+
* the logins the user already has in Chrome, and on a managed machine the user may
|
|
8
|
+
* simply prefer their own browser. Launching their browser with a *separate*
|
|
9
|
+
* `--user-data-dir` gives us a real Chromium we can drive over CDP without touching
|
|
10
|
+
* their everyday profile, bookmarks or windows: nothing of theirs is opened, locked
|
|
11
|
+
* or modified, and closing us never closes them.
|
|
12
|
+
*
|
|
13
|
+
* HOW
|
|
14
|
+
* Chromium exposes CDP on `--remote-debugging-port=0`, and writes the port it chose
|
|
15
|
+
* into `<profile>/DevToolsActivePort`. That file is the discovery mechanism — no
|
|
16
|
+
* port guessing, no collisions. From there everything is CDP over one WebSocket:
|
|
17
|
+
* `Target.createTarget` makes a page, `Target.attachToTarget` (flattened) gives a
|
|
18
|
+
* session for it, and the plugin's commands ride that session. Node 22 ships a global
|
|
19
|
+
* `WebSocket`, so this needs no dependency at all.
|
|
20
|
+
*
|
|
21
|
+
* WHAT IT IMPLEMENTS
|
|
22
|
+
* The same `ElectronBrowserViewHost` seam as the self-hosted and sidebar hosts, so
|
|
23
|
+
* the provider, the tools, history, the cursor and teardown rules are unchanged —
|
|
24
|
+
* only the carrier differs.
|
|
25
|
+
* @module dsh-browser/browser-electron/system-browser
|
|
26
|
+
*/
|
|
27
|
+
import type { ElectronBrowserViewHost, ElectronViewHandle } from './provider.js';
|
|
28
|
+
/** Which browser the user asked for. */
|
|
29
|
+
export type BrowserChannel = 'bundled' | 'chrome' | 'edge' | 'auto';
|
|
30
|
+
/** A resolved browser installation. */
|
|
31
|
+
export interface DetectedBrowser {
|
|
32
|
+
/** Which product this is. */
|
|
33
|
+
readonly kind: 'chrome' | 'edge' | 'brave';
|
|
34
|
+
/** Absolute path to the executable. */
|
|
35
|
+
readonly path: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Find an installed Chromium browser.
|
|
39
|
+
*
|
|
40
|
+
* `auto` prefers Chrome, then Edge, then Brave — the order reflects how likely each
|
|
41
|
+
* is to be the browser a user actually chose rather than one Windows shipped.
|
|
42
|
+
* @param channel - the configured choice; `bundled` never resolves to a system browser.
|
|
43
|
+
* @param env - environment lookup, injected so tests need no real machine.
|
|
44
|
+
* @returns the detected browser, or undefined when the choice is unavailable.
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectBrowser(channel: BrowserChannel, env?: NodeJS.ProcessEnv): DetectedBrowser | undefined;
|
|
47
|
+
/** A system browser driven over CDP, presented as a browser view host. */
|
|
48
|
+
export declare class SystemBrowserViewHost implements ElectronBrowserViewHost {
|
|
49
|
+
private readonly child;
|
|
50
|
+
private readonly client;
|
|
51
|
+
readonly kind: DetectedBrowser['kind'];
|
|
52
|
+
private readonly ephemeralDir?;
|
|
53
|
+
private readonly views;
|
|
54
|
+
private disposed;
|
|
55
|
+
/**
|
|
56
|
+
* @param child - the launched browser process.
|
|
57
|
+
* @param client - the CDP connection to it.
|
|
58
|
+
* @param kind - which product this is (for diagnostics).
|
|
59
|
+
* @param ephemeralDir - a throwaway profile to remove on release, when the user
|
|
60
|
+
* has turned persistence off and no login state should outlive the session.
|
|
61
|
+
*/
|
|
62
|
+
private constructor();
|
|
63
|
+
/**
|
|
64
|
+
* @param browser - the detected installation to launch.
|
|
65
|
+
* @param profileDir - a plugin-owned directory; the user's own profile is never touched.
|
|
66
|
+
* @param extraArgs - additional Chromium switches.
|
|
67
|
+
* @param ephemeralDir - a directory to delete when the browser is released, used
|
|
68
|
+
* when persistence is switched off so no login state outlives the session.
|
|
69
|
+
* @returns the host, or undefined when the browser refuses to come up.
|
|
70
|
+
*/
|
|
71
|
+
static launch(browser: DetectedBrowser, profileDir: string, extraArgs?: readonly string[], ephemeralDir?: string): Promise<SystemBrowserViewHost | undefined>;
|
|
72
|
+
/** Whether this host can back views: the CDP connection is live. */
|
|
73
|
+
available(): boolean;
|
|
74
|
+
createView(): ElectronViewHandle;
|
|
75
|
+
destroyView(handle: ElectronViewHandle): void;
|
|
76
|
+
/** Nothing to show: the browser owns its own windows. */
|
|
77
|
+
showView(): void;
|
|
78
|
+
/** Same as {@link showView}. */
|
|
79
|
+
presentView(): Promise<void>;
|
|
80
|
+
/** The browser owns window grouping. */
|
|
81
|
+
groupView(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Bring the browser's window forward.
|
|
84
|
+
*
|
|
85
|
+
* The browser is a separate application, so there is no view to raise; focusing
|
|
86
|
+
* the process is the closest honest equivalent, and failing to do so (a minimized
|
|
87
|
+
* window, a locked session) is never worth an error.
|
|
88
|
+
*/
|
|
89
|
+
focus(): Promise<void>;
|
|
90
|
+
/** The browser reports its own window lifecycle. */
|
|
91
|
+
onUserAction(): void;
|
|
92
|
+
/** The browser reports its own window lifecycle. */
|
|
93
|
+
onViewClosed(): void;
|
|
94
|
+
/** Close the browser we launched. The user's own windows are a different process. */
|
|
95
|
+
dispose(): void;
|
|
96
|
+
/** The flattened session for a view, creating its page on first use. */
|
|
97
|
+
private ensureSession;
|
|
98
|
+
/** sessionId -> targetId, so a destroyed view can close the right page. */
|
|
99
|
+
private readonly sessions;
|
|
100
|
+
}
|