@web-auto/camo 0.1.26 → 0.2.1
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/LICENSE +21 -21
- package/README.md +586 -586
- package/bin/browser-service.mjs +11 -11
- package/bin/camo.mjs +22 -22
- package/package.json +48 -48
- package/scripts/build.mjs +19 -19
- package/scripts/bump-version.mjs +34 -34
- package/scripts/check-file-size.mjs +80 -80
- package/scripts/file-size-policy.json +12 -2
- package/scripts/install.mjs +76 -76
- package/scripts/release.sh +54 -54
- package/src/autoscript/action-providers/index.mjs +6 -6
- package/src/autoscript/impact-engine.mjs +78 -78
- package/src/autoscript/runtime.mjs +1017 -1017
- package/src/autoscript/schema.mjs +376 -376
- package/src/cli.mjs +405 -405
- package/src/commands/attach.mjs +141 -141
- package/src/commands/autoscript.mjs +1011 -1011
- package/src/commands/browser.mjs +1255 -1257
- package/src/commands/container.mjs +401 -401
- package/src/commands/cookies.mjs +69 -69
- package/src/commands/create.mjs +98 -98
- package/src/commands/devtools.mjs +349 -349
- package/src/commands/events.mjs +152 -152
- package/src/commands/highlight-mode.mjs +24 -24
- package/src/commands/init.mjs +68 -68
- package/src/commands/lifecycle.mjs +275 -275
- package/src/commands/mouse.mjs +45 -45
- package/src/commands/profile.mjs +46 -46
- package/src/commands/record.mjs +115 -115
- package/src/commands/system.mjs +14 -14
- package/src/commands/window.mjs +123 -123
- package/src/container/change-notifier.mjs +362 -362
- package/src/container/element-filter.mjs +143 -143
- package/src/container/index.mjs +3 -3
- package/src/container/runtime-core/checkpoint.mjs +209 -209
- package/src/container/runtime-core/index.mjs +21 -21
- package/src/container/runtime-core/operations/index.mjs +774 -774
- package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
- package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
- package/src/container/runtime-core/operations/viewport.mjs +189 -189
- package/src/container/runtime-core/search.mjs +190 -190
- package/src/container/runtime-core/subscription.mjs +224 -224
- package/src/container/runtime-core/utils.mjs +94 -94
- package/src/container/runtime-core/validation.mjs +127 -184
- package/src/container/runtime-core.mjs +1 -1
- package/src/container/subscription-registry.mjs +459 -459
- package/src/core/actions.mjs +561 -561
- package/src/core/browser.mjs +266 -266
- package/src/core/index.mjs +52 -52
- package/src/core/utils.mjs +91 -91
- package/src/events/daemon-entry.mjs +33 -33
- package/src/events/daemon.mjs +80 -80
- package/src/events/progress-log.mjs +109 -109
- package/src/events/ws-server.mjs +239 -239
- package/src/lib/client.mjs +200 -200
- package/src/lifecycle/cleanup.mjs +83 -83
- package/src/lifecycle/lock.mjs +126 -126
- package/src/lifecycle/session-registry.mjs +279 -279
- package/src/lifecycle/session-view.mjs +76 -76
- package/src/lifecycle/session-watchdog.mjs +281 -281
- package/src/services/browser-service/index.js +671 -674
- package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
- package/src/services/browser-service/internal/BrowserSession.js +325 -336
- package/src/services/browser-service/internal/ElementRegistry.js +60 -60
- package/src/services/browser-service/internal/ProfileLock.js +84 -84
- package/src/services/browser-service/internal/SessionManager.js +184 -184
- package/src/services/browser-service/internal/SessionManager.test.js +39 -39
- package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
- package/src/services/browser-service/internal/browser-session/input-ops.js +222 -219
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
- package/src/services/browser-service/internal/browser-session/logging.js +46 -46
- package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
- package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
- package/src/services/browser-service/internal/browser-session/page-management.js +302 -336
- package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
- package/src/services/browser-service/internal/browser-session/recording.js +198 -198
- package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
- package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
- package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
- package/src/services/browser-service/internal/browser-session/types.js +14 -14
- package/src/services/browser-service/internal/browser-session/utils.js +95 -95
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
- package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
- package/src/services/browser-service/internal/container-matcher.js +851 -851
- package/src/services/browser-service/internal/container-registry.js +182 -182
- package/src/services/browser-service/internal/engine-manager.js +259 -259
- package/src/services/browser-service/internal/fingerprint.js +203 -203
- package/src/services/browser-service/internal/heartbeat.js +137 -137
- package/src/services/browser-service/internal/logging.js +46 -46
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
- package/src/services/browser-service/internal/pageRuntime.js +28 -28
- package/src/services/browser-service/internal/runtimeInjector.js +31 -31
- package/src/services/browser-service/internal/service-process-logger.js +140 -140
- package/src/services/browser-service/internal/state-bus.js +45 -45
- package/src/services/browser-service/internal/storage-paths.js +42 -42
- package/src/services/browser-service/internal/ws-server.js +1194 -1194
- package/src/services/browser-service/internal/ws-server.test.js +58 -58
- package/src/services/browser-service/server.mjs +6 -6
- package/src/services/controller/cli-bridge.js +93 -93
- package/src/services/controller/container-index.js +50 -50
- package/src/services/controller/container-storage.js +36 -36
- package/src/services/controller/controller-actions.js +207 -207
- package/src/services/controller/controller.js +1138 -1138
- package/src/services/controller/selectors.js +54 -54
- package/src/services/controller/transport.js +125 -125
- package/src/utils/args.mjs +26 -26
- package/src/utils/browser-service.mjs +544 -544
- package/src/utils/command-log.mjs +64 -64
- package/src/utils/config.mjs +214 -214
- package/src/utils/fingerprint.mjs +181 -181
- package/src/utils/help.mjs +216 -216
- package/src/utils/js-policy.mjs +13 -13
- package/src/utils/ws-client.mjs +30 -30
- package/src/container/runtime-core/operations/tab-pool.mjs.bak +0 -762
- package/src/container/runtime-core/operations/tab-pool.mjs.syntax-error +0 -762
- package/src/services/browser-service/index.js.bak +0 -671
|
@@ -1,336 +1,302 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { ensurePageRuntime } from '../pageRuntime.js';
|
|
3
|
-
import { resolveNavigationWaitUntil, normalizeUrl
|
|
4
|
-
export class BrowserSessionPageManagement {
|
|
5
|
-
deps;
|
|
6
|
-
trackedPages = [];
|
|
7
|
-
trackedPageListeners = new WeakSet();
|
|
8
|
-
trackedPageState = new WeakMap();
|
|
9
|
-
static NEW_PAGE_FORCE_ALIVE_MS = 15_000;
|
|
10
|
-
static ACTIVE_PAGE_FORCE_ALIVE_MS = 5_000;
|
|
11
|
-
constructor(deps) {
|
|
12
|
-
this.deps = deps;
|
|
13
|
-
}
|
|
14
|
-
safeIsClosed(page) {
|
|
15
|
-
if (!page)
|
|
16
|
-
return true;
|
|
17
|
-
try {
|
|
18
|
-
return page.isClosed();
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
markTrackedPage(page, forceAliveMs = 0) {
|
|
25
|
-
if (!page)
|
|
26
|
-
return null;
|
|
27
|
-
const prev = this.trackedPageState.get(page);
|
|
28
|
-
const next = {
|
|
29
|
-
closed: false,
|
|
30
|
-
forceAliveUntil: Math.max(Number(prev?.forceAliveUntil || 0), forceAliveMs > 0 ? Date.now() + forceAliveMs : 0),
|
|
31
|
-
};
|
|
32
|
-
this.trackedPageState.set(page, next);
|
|
33
|
-
return page;
|
|
34
|
-
}
|
|
35
|
-
isTrackedPageAlive(page) {
|
|
36
|
-
if (!page)
|
|
37
|
-
return false;
|
|
38
|
-
const state = this.trackedPageState.get(page);
|
|
39
|
-
if (state?.closed === true)
|
|
40
|
-
return false;
|
|
41
|
-
if (!this.safeIsClosed(page))
|
|
42
|
-
return true;
|
|
43
|
-
return Number(state?.forceAliveUntil || 0) > Date.now();
|
|
44
|
-
}
|
|
45
|
-
rememberPage(page, options = {}) {
|
|
46
|
-
if (!page)
|
|
47
|
-
return null;
|
|
48
|
-
this.markTrackedPage(page, Math.max(0, Number(options.forceAliveMs || 0) || 0));
|
|
49
|
-
if (this.safeIsClosed(page) && !this.isTrackedPageAlive(page))
|
|
50
|
-
return null;
|
|
51
|
-
if (!this.trackedPages.includes(page)) {
|
|
52
|
-
this.trackedPages.push(page);
|
|
53
|
-
}
|
|
54
|
-
if (typeof page.on === 'function' && !this.trackedPageListeners.has(page)) {
|
|
55
|
-
page.on('close', () => {
|
|
56
|
-
this.trackedPageState.set(page, {
|
|
57
|
-
closed: true,
|
|
58
|
-
forceAliveUntil: 0,
|
|
59
|
-
});
|
|
60
|
-
this.trackedPages = this.trackedPages.filter((item) => item !== page && !item.isClosed());
|
|
61
|
-
});
|
|
62
|
-
this.trackedPageListeners.add(page);
|
|
63
|
-
}
|
|
64
|
-
return page;
|
|
65
|
-
}
|
|
66
|
-
collectPages(ctx) {
|
|
67
|
-
const active = this.deps.getActivePage();
|
|
68
|
-
if (active) {
|
|
69
|
-
this.rememberPage(active, {
|
|
70
|
-
forceAliveMs: BrowserSessionPageManagement.ACTIVE_PAGE_FORCE_ALIVE_MS,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
const merged = [...ctx.pages(), ...this.trackedPages, ...(active ? [active] : [])];
|
|
74
|
-
const seen = new Set();
|
|
75
|
-
const pages = [];
|
|
76
|
-
for (const page of merged) {
|
|
77
|
-
const tracked = this.trackedPages.includes(page) || page === active;
|
|
78
|
-
const alive = tracked ? this.isTrackedPageAlive(page) : !this.safeIsClosed(page);
|
|
79
|
-
if (!page || !alive || seen.has(page))
|
|
80
|
-
continue;
|
|
81
|
-
seen.add(page);
|
|
82
|
-
pages.push(page);
|
|
83
|
-
this.rememberPage(page);
|
|
84
|
-
}
|
|
85
|
-
this.trackedPages = this.trackedPages.filter((page) => page && this.isTrackedPageAlive(page));
|
|
86
|
-
return pages;
|
|
87
|
-
}
|
|
88
|
-
async openPageViaContext(ctx, beforeCount) {
|
|
89
|
-
try {
|
|
90
|
-
const page = this.rememberPage(await ctx.newPage(), {
|
|
91
|
-
forceAliveMs: BrowserSessionPageManagement.NEW_PAGE_FORCE_ALIVE_MS,
|
|
92
|
-
});
|
|
93
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 1500 }).catch(() => null);
|
|
94
|
-
const after = this.collectPages(ctx).length;
|
|
95
|
-
if (after > beforeCount) {
|
|
96
|
-
return page;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
// Fall through to shortcut-based creation below.
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
async openPageViaShortcut(ctx, opener, shortcut, beforeCount) {
|
|
105
|
-
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
106
|
-
const waitPage = ctx.waitForEvent('page', { timeout: 1200 }).catch(() => null);
|
|
107
|
-
await opener.keyboard.press(shortcut).catch(() => null);
|
|
108
|
-
const page = this.rememberPage(await waitPage, {
|
|
109
|
-
forceAliveMs: BrowserSessionPageManagement.NEW_PAGE_FORCE_ALIVE_MS,
|
|
110
|
-
});
|
|
111
|
-
const pagesNow = this.collectPages(ctx);
|
|
112
|
-
const after = pagesNow.length;
|
|
113
|
-
if (page && after > beforeCount)
|
|
114
|
-
return page;
|
|
115
|
-
if (!page && after > beforeCount) {
|
|
116
|
-
return pagesNow[pagesNow.length - 1] || null;
|
|
117
|
-
}
|
|
118
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
119
|
-
}
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
tryOsNewTabShortcut() {
|
|
123
|
-
if (this.deps.isHeadless())
|
|
124
|
-
return false;
|
|
125
|
-
if (process.platform === 'darwin') {
|
|
126
|
-
const res = spawnSync('osascript', ['-e', 'tell application "System Events" to keystroke "t" using command down'], { windowsHide: true });
|
|
127
|
-
return res.status === 0;
|
|
128
|
-
}
|
|
129
|
-
if (process.platform === 'win32') {
|
|
130
|
-
const script = 'Add-Type -AssemblyName System.Windows.Forms; $ws = New-Object -ComObject WScript.Shell; $ws.SendKeys("^t");';
|
|
131
|
-
const res = spawnSync('powershell', ['-NoProfile', '-Command', script], { windowsHide: true });
|
|
132
|
-
return res.status === 0;
|
|
133
|
-
}
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
async ensurePrimaryPage() {
|
|
137
|
-
const ctx = this.deps.ensureContext();
|
|
138
|
-
const existing = this.deps.getActivePage();
|
|
139
|
-
if (existing) {
|
|
140
|
-
this.rememberPage(existing);
|
|
141
|
-
try {
|
|
142
|
-
await this.deps.ensurePageViewport(existing);
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
/* ignore */
|
|
146
|
-
}
|
|
147
|
-
return existing;
|
|
148
|
-
}
|
|
149
|
-
const page = this.rememberPage(await ctx.newPage(), {
|
|
150
|
-
forceAliveMs: BrowserSessionPageManagement.NEW_PAGE_FORCE_ALIVE_MS,
|
|
151
|
-
});
|
|
152
|
-
this.deps.setActivePage(page);
|
|
153
|
-
this.deps.setupPageHooks(page);
|
|
154
|
-
try {
|
|
155
|
-
await this.deps.ensurePageViewport(page);
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
/* ignore */
|
|
159
|
-
}
|
|
160
|
-
return page;
|
|
161
|
-
}
|
|
162
|
-
async ensurePage(url) {
|
|
163
|
-
let page = await this.ensurePrimaryPage();
|
|
164
|
-
if (url) {
|
|
165
|
-
const current = this.deps.getCurrentUrl() || page.url();
|
|
166
|
-
if (!current || normalizeUrl(current) !== normalizeUrl(url)) {
|
|
167
|
-
await page.goto(url, { waitUntil: resolveNavigationWaitUntil() });
|
|
168
|
-
await ensurePageRuntime(page);
|
|
169
|
-
this.deps.recordLastKnownUrl(url);
|
|
170
|
-
page = await this.ensurePrimaryPage();
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return page;
|
|
174
|
-
}
|
|
175
|
-
listPages() {
|
|
176
|
-
const ctx = this.deps.ensureContext();
|
|
177
|
-
const pages = this.collectPages(ctx);
|
|
178
|
-
const active = this.deps.getActivePage();
|
|
179
|
-
return pages.map((p, index) => ({
|
|
180
|
-
index,
|
|
181
|
-
url: p.url(),
|
|
182
|
-
active: active === p,
|
|
183
|
-
}));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (!page || after <= before) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
const
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
const active = this.deps.getActivePage();
|
|
305
|
-
const requested = typeof index === 'number' && Number.isFinite(index) ? index : null;
|
|
306
|
-
const closedIndex = requested !== null ? requested : Math.max(0, pages.findIndex((p) => p === active));
|
|
307
|
-
if (closedIndex < 0 || closedIndex >= pages.length) {
|
|
308
|
-
throw new Error(`invalid_page_index: ${index}`);
|
|
309
|
-
}
|
|
310
|
-
const page = pages[closedIndex];
|
|
311
|
-
this.trackedPageState.set(page, { closed: true, forceAliveUntil: 0 });
|
|
312
|
-
await page.close().catch(() => { });
|
|
313
|
-
this.trackedPages = this.trackedPages.filter((item) => item !== page && !item.isClosed());
|
|
314
|
-
const remaining = this.collectPages(ctx);
|
|
315
|
-
const nextIndex = remaining.length === 0 ? -1 : Math.min(Math.max(0, closedIndex - 1), remaining.length - 1);
|
|
316
|
-
if (nextIndex >= 0) {
|
|
317
|
-
const nextPage = remaining[nextIndex];
|
|
318
|
-
this.deps.setActivePage(nextPage);
|
|
319
|
-
if (!shouldSkipBringToFront()) {
|
|
320
|
-
try {
|
|
321
|
-
await nextPage.bringToFront();
|
|
322
|
-
}
|
|
323
|
-
catch {
|
|
324
|
-
/* ignore */
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
await ensurePageRuntime(nextPage, true).catch(() => { });
|
|
328
|
-
this.deps.recordLastKnownUrl(nextPage.url());
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
this.deps.setActivePage(undefined);
|
|
332
|
-
}
|
|
333
|
-
return { closedIndex, activeIndex: nextIndex, total: remaining.length };
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
//# sourceMappingURL=page-management.js.map
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { ensurePageRuntime } from '../pageRuntime.js';
|
|
3
|
+
import { resolveNavigationWaitUntil, normalizeUrl } from './utils.js';
|
|
4
|
+
export class BrowserSessionPageManagement {
|
|
5
|
+
deps;
|
|
6
|
+
trackedPages = [];
|
|
7
|
+
trackedPageListeners = new WeakSet();
|
|
8
|
+
trackedPageState = new WeakMap();
|
|
9
|
+
static NEW_PAGE_FORCE_ALIVE_MS = 15_000;
|
|
10
|
+
static ACTIVE_PAGE_FORCE_ALIVE_MS = 5_000;
|
|
11
|
+
constructor(deps) {
|
|
12
|
+
this.deps = deps;
|
|
13
|
+
}
|
|
14
|
+
safeIsClosed(page) {
|
|
15
|
+
if (!page)
|
|
16
|
+
return true;
|
|
17
|
+
try {
|
|
18
|
+
return page.isClosed();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
markTrackedPage(page, forceAliveMs = 0) {
|
|
25
|
+
if (!page)
|
|
26
|
+
return null;
|
|
27
|
+
const prev = this.trackedPageState.get(page);
|
|
28
|
+
const next = {
|
|
29
|
+
closed: false,
|
|
30
|
+
forceAliveUntil: Math.max(Number(prev?.forceAliveUntil || 0), forceAliveMs > 0 ? Date.now() + forceAliveMs : 0),
|
|
31
|
+
};
|
|
32
|
+
this.trackedPageState.set(page, next);
|
|
33
|
+
return page;
|
|
34
|
+
}
|
|
35
|
+
isTrackedPageAlive(page) {
|
|
36
|
+
if (!page)
|
|
37
|
+
return false;
|
|
38
|
+
const state = this.trackedPageState.get(page);
|
|
39
|
+
if (state?.closed === true)
|
|
40
|
+
return false;
|
|
41
|
+
if (!this.safeIsClosed(page))
|
|
42
|
+
return true;
|
|
43
|
+
return Number(state?.forceAliveUntil || 0) > Date.now();
|
|
44
|
+
}
|
|
45
|
+
rememberPage(page, options = {}) {
|
|
46
|
+
if (!page)
|
|
47
|
+
return null;
|
|
48
|
+
this.markTrackedPage(page, Math.max(0, Number(options.forceAliveMs || 0) || 0));
|
|
49
|
+
if (this.safeIsClosed(page) && !this.isTrackedPageAlive(page))
|
|
50
|
+
return null;
|
|
51
|
+
if (!this.trackedPages.includes(page)) {
|
|
52
|
+
this.trackedPages.push(page);
|
|
53
|
+
}
|
|
54
|
+
if (typeof page.on === 'function' && !this.trackedPageListeners.has(page)) {
|
|
55
|
+
page.on('close', () => {
|
|
56
|
+
this.trackedPageState.set(page, {
|
|
57
|
+
closed: true,
|
|
58
|
+
forceAliveUntil: 0,
|
|
59
|
+
});
|
|
60
|
+
this.trackedPages = this.trackedPages.filter((item) => item !== page && !item.isClosed());
|
|
61
|
+
});
|
|
62
|
+
this.trackedPageListeners.add(page);
|
|
63
|
+
}
|
|
64
|
+
return page;
|
|
65
|
+
}
|
|
66
|
+
collectPages(ctx) {
|
|
67
|
+
const active = this.deps.getActivePage();
|
|
68
|
+
if (active) {
|
|
69
|
+
this.rememberPage(active, {
|
|
70
|
+
forceAliveMs: BrowserSessionPageManagement.ACTIVE_PAGE_FORCE_ALIVE_MS,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const merged = [...ctx.pages(), ...this.trackedPages, ...(active ? [active] : [])];
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
const pages = [];
|
|
76
|
+
for (const page of merged) {
|
|
77
|
+
const tracked = this.trackedPages.includes(page) || page === active;
|
|
78
|
+
const alive = tracked ? this.isTrackedPageAlive(page) : !this.safeIsClosed(page);
|
|
79
|
+
if (!page || !alive || seen.has(page))
|
|
80
|
+
continue;
|
|
81
|
+
seen.add(page);
|
|
82
|
+
pages.push(page);
|
|
83
|
+
this.rememberPage(page);
|
|
84
|
+
}
|
|
85
|
+
this.trackedPages = this.trackedPages.filter((page) => page && this.isTrackedPageAlive(page));
|
|
86
|
+
return pages;
|
|
87
|
+
}
|
|
88
|
+
async openPageViaContext(ctx, beforeCount) {
|
|
89
|
+
try {
|
|
90
|
+
const page = this.rememberPage(await ctx.newPage(), {
|
|
91
|
+
forceAliveMs: BrowserSessionPageManagement.NEW_PAGE_FORCE_ALIVE_MS,
|
|
92
|
+
});
|
|
93
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 1500 }).catch(() => null);
|
|
94
|
+
const after = this.collectPages(ctx).length;
|
|
95
|
+
if (after > beforeCount) {
|
|
96
|
+
return page;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Fall through to shortcut-based creation below.
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
async openPageViaShortcut(ctx, opener, shortcut, beforeCount) {
|
|
105
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
106
|
+
const waitPage = ctx.waitForEvent('page', { timeout: 1200 }).catch(() => null);
|
|
107
|
+
await opener.keyboard.press(shortcut).catch(() => null);
|
|
108
|
+
const page = this.rememberPage(await waitPage, {
|
|
109
|
+
forceAliveMs: BrowserSessionPageManagement.NEW_PAGE_FORCE_ALIVE_MS,
|
|
110
|
+
});
|
|
111
|
+
const pagesNow = this.collectPages(ctx);
|
|
112
|
+
const after = pagesNow.length;
|
|
113
|
+
if (page && after > beforeCount)
|
|
114
|
+
return page;
|
|
115
|
+
if (!page && after > beforeCount) {
|
|
116
|
+
return pagesNow[pagesNow.length - 1] || null;
|
|
117
|
+
}
|
|
118
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
tryOsNewTabShortcut() {
|
|
123
|
+
if (this.deps.isHeadless())
|
|
124
|
+
return false;
|
|
125
|
+
if (process.platform === 'darwin') {
|
|
126
|
+
const res = spawnSync('osascript', ['-e', 'tell application "System Events" to keystroke "t" using command down'], { windowsHide: true });
|
|
127
|
+
return res.status === 0;
|
|
128
|
+
}
|
|
129
|
+
if (process.platform === 'win32') {
|
|
130
|
+
const script = 'Add-Type -AssemblyName System.Windows.Forms; $ws = New-Object -ComObject WScript.Shell; $ws.SendKeys("^t");';
|
|
131
|
+
const res = spawnSync('powershell', ['-NoProfile', '-Command', script], { windowsHide: true });
|
|
132
|
+
return res.status === 0;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
async ensurePrimaryPage() {
|
|
137
|
+
const ctx = this.deps.ensureContext();
|
|
138
|
+
const existing = this.deps.getActivePage();
|
|
139
|
+
if (existing) {
|
|
140
|
+
this.rememberPage(existing);
|
|
141
|
+
try {
|
|
142
|
+
await this.deps.ensurePageViewport(existing);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
/* ignore */
|
|
146
|
+
}
|
|
147
|
+
return existing;
|
|
148
|
+
}
|
|
149
|
+
const page = this.rememberPage(await ctx.newPage(), {
|
|
150
|
+
forceAliveMs: BrowserSessionPageManagement.NEW_PAGE_FORCE_ALIVE_MS,
|
|
151
|
+
});
|
|
152
|
+
this.deps.setActivePage(page);
|
|
153
|
+
this.deps.setupPageHooks(page);
|
|
154
|
+
try {
|
|
155
|
+
await this.deps.ensurePageViewport(page);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* ignore */
|
|
159
|
+
}
|
|
160
|
+
return page;
|
|
161
|
+
}
|
|
162
|
+
async ensurePage(url) {
|
|
163
|
+
let page = await this.ensurePrimaryPage();
|
|
164
|
+
if (url) {
|
|
165
|
+
const current = this.deps.getCurrentUrl() || page.url();
|
|
166
|
+
if (!current || normalizeUrl(current) !== normalizeUrl(url)) {
|
|
167
|
+
await page.goto(url, { waitUntil: resolveNavigationWaitUntil() });
|
|
168
|
+
await ensurePageRuntime(page);
|
|
169
|
+
this.deps.recordLastKnownUrl(url);
|
|
170
|
+
page = await this.ensurePrimaryPage();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return page;
|
|
174
|
+
}
|
|
175
|
+
listPages() {
|
|
176
|
+
const ctx = this.deps.ensureContext();
|
|
177
|
+
const pages = this.collectPages(ctx);
|
|
178
|
+
const active = this.deps.getActivePage();
|
|
179
|
+
return pages.map((p, index) => ({
|
|
180
|
+
index,
|
|
181
|
+
url: p.url(),
|
|
182
|
+
active: active === p,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
async newPage(url, options = {}) {
|
|
186
|
+
const ctx = this.deps.ensureContext();
|
|
187
|
+
const isMac = process.platform === 'darwin';
|
|
188
|
+
const shortcut = isMac ? 'Meta+t' : 'Control+t';
|
|
189
|
+
let page = null;
|
|
190
|
+
const opener = this.deps.getActivePage() || ctx.pages()[0];
|
|
191
|
+
if (!opener)
|
|
192
|
+
throw new Error('no_opener_page');
|
|
193
|
+
const before = this.collectPages(ctx).length;
|
|
194
|
+
if (!options?.strictShortcut) {
|
|
195
|
+
page = await this.openPageViaContext(ctx, before);
|
|
196
|
+
}
|
|
197
|
+
if (!page) {
|
|
198
|
+
page = await this.openPageViaShortcut(ctx, opener, shortcut, before);
|
|
199
|
+
}
|
|
200
|
+
let after = this.collectPages(ctx).length;
|
|
201
|
+
if (!page || after <= before) {
|
|
202
|
+
const waitPage = ctx.waitForEvent('page', { timeout: 1200 }).catch(() => null);
|
|
203
|
+
const osShortcutOk = this.tryOsNewTabShortcut();
|
|
204
|
+
if (osShortcutOk) {
|
|
205
|
+
page = this.rememberPage(await waitPage, {
|
|
206
|
+
forceAliveMs: BrowserSessionPageManagement.NEW_PAGE_FORCE_ALIVE_MS,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const pagesNow = this.collectPages(ctx);
|
|
210
|
+
after = pagesNow.length;
|
|
211
|
+
if (!page && after > before) {
|
|
212
|
+
page = pagesNow[pagesNow.length - 1] || null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!page || after <= before) {
|
|
216
|
+
if (!options?.strictShortcut) {
|
|
217
|
+
page = await this.openPageViaContext(ctx, before);
|
|
218
|
+
after = this.collectPages(ctx).length;
|
|
219
|
+
if (!page && after > before) {
|
|
220
|
+
const pagesNow = this.collectPages(ctx);
|
|
221
|
+
page = pagesNow[pagesNow.length - 1] || null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!page || after <= before) {
|
|
226
|
+
throw new Error('new_tab_failed');
|
|
227
|
+
}
|
|
228
|
+
this.deps.setupPageHooks(page);
|
|
229
|
+
this.rememberPage(page, {
|
|
230
|
+
forceAliveMs: BrowserSessionPageManagement.NEW_PAGE_FORCE_ALIVE_MS,
|
|
231
|
+
});
|
|
232
|
+
this.deps.setActivePage(page);
|
|
233
|
+
try {
|
|
234
|
+
await this.deps.ensurePageViewport(page);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
/* ignore */
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
await this.deps.maybeCenterPage(page, { width: 1920, height: 1080 });
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
/* ignore */
|
|
244
|
+
}
|
|
245
|
+
if (url) {
|
|
246
|
+
await page.goto(url, { waitUntil: resolveNavigationWaitUntil() });
|
|
247
|
+
await ensurePageRuntime(page);
|
|
248
|
+
this.deps.recordLastKnownUrl(url);
|
|
249
|
+
}
|
|
250
|
+
const pages = this.collectPages(ctx);
|
|
251
|
+
return { index: Math.max(0, pages.indexOf(page)), url: page.url() };
|
|
252
|
+
}
|
|
253
|
+
async switchPage(index) {
|
|
254
|
+
const ctx = this.deps.ensureContext();
|
|
255
|
+
const pages = this.collectPages(ctx);
|
|
256
|
+
const idx = Number(index);
|
|
257
|
+
if (!Number.isFinite(idx) || idx < 0 || idx >= pages.length) {
|
|
258
|
+
throw new Error(`invalid_page_index: ${index}`);
|
|
259
|
+
}
|
|
260
|
+
const page = pages[idx];
|
|
261
|
+
this.deps.setActivePage(page);
|
|
262
|
+
try {
|
|
263
|
+
await this.deps.ensurePageViewport(page);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
/* ignore */
|
|
267
|
+
}
|
|
268
|
+
await ensurePageRuntime(page, true).catch(() => { });
|
|
269
|
+
this.deps.recordLastKnownUrl(page.url());
|
|
270
|
+
return { index: idx, url: page.url() };
|
|
271
|
+
}
|
|
272
|
+
async closePage(index) {
|
|
273
|
+
const ctx = this.deps.ensureContext();
|
|
274
|
+
const pages = this.collectPages(ctx);
|
|
275
|
+
if (pages.length === 0) {
|
|
276
|
+
return { closedIndex: -1, activeIndex: -1, total: 0 };
|
|
277
|
+
}
|
|
278
|
+
const active = this.deps.getActivePage();
|
|
279
|
+
const requested = typeof index === 'number' && Number.isFinite(index) ? index : null;
|
|
280
|
+
const closedIndex = requested !== null ? requested : Math.max(0, pages.findIndex((p) => p === active));
|
|
281
|
+
if (closedIndex < 0 || closedIndex >= pages.length) {
|
|
282
|
+
throw new Error(`invalid_page_index: ${index}`);
|
|
283
|
+
}
|
|
284
|
+
const page = pages[closedIndex];
|
|
285
|
+
this.trackedPageState.set(page, { closed: true, forceAliveUntil: 0 });
|
|
286
|
+
await page.close().catch(() => { });
|
|
287
|
+
this.trackedPages = this.trackedPages.filter((item) => item !== page && !item.isClosed());
|
|
288
|
+
const remaining = this.collectPages(ctx);
|
|
289
|
+
const nextIndex = remaining.length === 0 ? -1 : Math.min(Math.max(0, closedIndex - 1), remaining.length - 1);
|
|
290
|
+
if (nextIndex >= 0) {
|
|
291
|
+
const nextPage = remaining[nextIndex];
|
|
292
|
+
this.deps.setActivePage(nextPage);
|
|
293
|
+
await ensurePageRuntime(nextPage, true).catch(() => { });
|
|
294
|
+
this.deps.recordLastKnownUrl(nextPage.url());
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
this.deps.setActivePage(undefined);
|
|
298
|
+
}
|
|
299
|
+
return { closedIndex, activeIndex: nextIndex, total: remaining.length };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=page-management.js.map
|