@ticktockbent/charlotte 0.4.2 → 0.5.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.
Files changed (113) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +104 -18
  3. package/dist/browser/browser-manager.d.ts +1 -1
  4. package/dist/browser/browser-manager.d.ts.map +1 -1
  5. package/dist/browser/browser-manager.js +8 -4
  6. package/dist/browser/browser-manager.js.map +1 -1
  7. package/dist/browser/cdp-session.d.ts +9 -1
  8. package/dist/browser/cdp-session.d.ts.map +1 -1
  9. package/dist/browser/cdp-session.js +32 -10
  10. package/dist/browser/cdp-session.js.map +1 -1
  11. package/dist/browser/page-manager.d.ts +16 -0
  12. package/dist/browser/page-manager.d.ts.map +1 -1
  13. package/dist/browser/page-manager.js +97 -29
  14. package/dist/browser/page-manager.js.map +1 -1
  15. package/dist/cli.d.ts +1 -0
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +20 -10
  18. package/dist/cli.js.map +1 -1
  19. package/dist/dev/auditor.d.ts.map +1 -1
  20. package/dist/dev/auditor.js +15 -36
  21. package/dist/dev/auditor.js.map +1 -1
  22. package/dist/dev/dev-mode-state.d.ts.map +1 -1
  23. package/dist/dev/dev-mode-state.js +14 -4
  24. package/dist/dev/dev-mode-state.js.map +1 -1
  25. package/dist/dev/static-server.d.ts.map +1 -1
  26. package/dist/dev/static-server.js +6 -2
  27. package/dist/dev/static-server.js.map +1 -1
  28. package/dist/index.js +9 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/renderer/accessibility-extractor.d.ts +3 -1
  31. package/dist/renderer/accessibility-extractor.d.ts.map +1 -1
  32. package/dist/renderer/accessibility-extractor.js +9 -7
  33. package/dist/renderer/accessibility-extractor.js.map +1 -1
  34. package/dist/renderer/content-extractor.d.ts.map +1 -1
  35. package/dist/renderer/content-extractor.js +7 -1
  36. package/dist/renderer/content-extractor.js.map +1 -1
  37. package/dist/renderer/dom-path.d.ts.map +1 -1
  38. package/dist/renderer/dom-path.js.map +1 -1
  39. package/dist/renderer/element-id-generator.d.ts +4 -1
  40. package/dist/renderer/element-id-generator.d.ts.map +1 -1
  41. package/dist/renderer/element-id-generator.js +12 -1
  42. package/dist/renderer/element-id-generator.js.map +1 -1
  43. package/dist/renderer/frame-discovery.d.ts +23 -0
  44. package/dist/renderer/frame-discovery.d.ts.map +1 -0
  45. package/dist/renderer/frame-discovery.js +107 -0
  46. package/dist/renderer/frame-discovery.js.map +1 -0
  47. package/dist/renderer/interactive-extractor.d.ts +1 -1
  48. package/dist/renderer/interactive-extractor.d.ts.map +1 -1
  49. package/dist/renderer/interactive-extractor.js +9 -9
  50. package/dist/renderer/interactive-extractor.js.map +1 -1
  51. package/dist/renderer/layout-extractor.d.ts +4 -1
  52. package/dist/renderer/layout-extractor.d.ts.map +1 -1
  53. package/dist/renderer/layout-extractor.js +19 -3
  54. package/dist/renderer/layout-extractor.js.map +1 -1
  55. package/dist/renderer/renderer-pipeline.d.ts +26 -1
  56. package/dist/renderer/renderer-pipeline.d.ts.map +1 -1
  57. package/dist/renderer/renderer-pipeline.js +174 -20
  58. package/dist/renderer/renderer-pipeline.js.map +1 -1
  59. package/dist/renderer/structural-tree-extractor.d.ts +7 -0
  60. package/dist/renderer/structural-tree-extractor.d.ts.map +1 -0
  61. package/dist/renderer/structural-tree-extractor.js +245 -0
  62. package/dist/renderer/structural-tree-extractor.js.map +1 -0
  63. package/dist/server.d.ts +2 -0
  64. package/dist/server.d.ts.map +1 -1
  65. package/dist/server.js +3 -4
  66. package/dist/server.js.map +1 -1
  67. package/dist/state/artifact-store.d.ts.map +1 -1
  68. package/dist/state/artifact-store.js +1 -2
  69. package/dist/state/artifact-store.js.map +1 -1
  70. package/dist/state/differ.d.ts.map +1 -1
  71. package/dist/state/differ.js.map +1 -1
  72. package/dist/tools/dev-mode.d.ts.map +1 -1
  73. package/dist/tools/dev-mode.js +4 -16
  74. package/dist/tools/dev-mode.js.map +1 -1
  75. package/dist/tools/dialog.d.ts.map +1 -1
  76. package/dist/tools/dialog.js +2 -4
  77. package/dist/tools/dialog.js.map +1 -1
  78. package/dist/tools/evaluate.d.ts.map +1 -1
  79. package/dist/tools/evaluate.js +1 -4
  80. package/dist/tools/evaluate.js.map +1 -1
  81. package/dist/tools/interaction.d.ts.map +1 -1
  82. package/dist/tools/interaction.js +30 -57
  83. package/dist/tools/interaction.js.map +1 -1
  84. package/dist/tools/meta-tool.d.ts.map +1 -1
  85. package/dist/tools/meta-tool.js.map +1 -1
  86. package/dist/tools/monitoring.d.ts.map +1 -1
  87. package/dist/tools/monitoring.js +33 -15
  88. package/dist/tools/monitoring.js.map +1 -1
  89. package/dist/tools/navigation.d.ts.map +1 -1
  90. package/dist/tools/navigation.js +20 -16
  91. package/dist/tools/navigation.js.map +1 -1
  92. package/dist/tools/observation.d.ts.map +1 -1
  93. package/dist/tools/observation.js +50 -13
  94. package/dist/tools/observation.js.map +1 -1
  95. package/dist/tools/session.d.ts.map +1 -1
  96. package/dist/tools/session.js +35 -24
  97. package/dist/tools/session.js.map +1 -1
  98. package/dist/tools/tool-groups.d.ts.map +1 -1
  99. package/dist/tools/tool-groups.js +5 -21
  100. package/dist/tools/tool-groups.js.map +1 -1
  101. package/dist/tools/tool-helpers.d.ts +43 -5
  102. package/dist/tools/tool-helpers.d.ts.map +1 -1
  103. package/dist/tools/tool-helpers.js +104 -2
  104. package/dist/tools/tool-helpers.js.map +1 -1
  105. package/dist/types/config.d.ts +6 -0
  106. package/dist/types/config.d.ts.map +1 -1
  107. package/dist/types/config.js +2 -0
  108. package/dist/types/config.js.map +1 -1
  109. package/dist/types/page-representation.d.ts +16 -0
  110. package/dist/types/page-representation.d.ts.map +1 -1
  111. package/dist/utils/wait.js +1 -1
  112. package/dist/utils/wait.js.map +1 -1
  113. package/package.json +24 -8
package/CHANGELOG.md CHANGED
@@ -4,6 +4,49 @@ All notable changes to Charlotte will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.1] - 2026-03-14
8
+
9
+ ### Added
10
+
11
+ - **Popup and target="_blank" tab capture** — Clicks on `target="_blank"` links and `window.open()` calls were silently lost because PageManager had no `popup` event handler. New tabs are now auto-captured via `page.on("popup")`, auto-cleaned when pages close themselves, and surfaced as `opened_tabs` in tool responses using single-consumption semantics. Fixes #103, #98.
12
+ - **Contributor issue templates** — Bug report, feature request, and tool request templates added to the repository. Community links added to README. (#102)
13
+
14
+ ### Changed
15
+
16
+ - Renamed AXIOM to ASM across Charlotte site (#100).
17
+ - Bumped hono dependency (#99).
18
+
19
+ ## [0.5.0] - 2026-03-09
20
+
21
+ ### Added
22
+
23
+ - **Iframe content extraction** — Child frames are now discovered and their content (interactive elements, content summaries, full text) is merged into the parent page representation. Configurable depth limit (default 3). Iframe interactive elements are included in the `interactive` array and `interactive_summary`. Closes #23.
24
+ - **Structural tree view** — `charlotte:observe` now accepts a `view` parameter with `"tree"` and `"tree-labeled"` modes that render the page as a hierarchical tree with indentation, replacing the flat JSON representation. Tree-labeled mode annotates interactive elements with their IDs for direct use.
25
+ - **File output for large responses** — `charlotte:observe` and `charlotte:screenshot` accept an `output_file` parameter to write results to disk instead of returning inline, reducing token consumption for large pages. Relative paths resolve against `output_dir` (configurable via `charlotte:configure` or `--output-dir` CLI flag). Closes GAP-13, #16.
26
+ - **Screenshot artifact management** — `charlotte:screenshots` (list), `charlotte:screenshot_get` (retrieve), `charlotte:screenshot_delete` (remove) tools for managing persistent screenshot files. `charlotte:screenshot` gains a `save` parameter for persistence.
27
+ - **Code quality tooling** — ESLint, Prettier, and coverage configuration added to the project.
28
+
29
+ ### Fixed
30
+
31
+ - **`wait_for` JS evaluation** — Replaced `new Function("return " + expr)` with CDP `Runtime.evaluate`, fixing multi-statement JS conditions that silently returned `undefined` due to ASI. Now consistent with `charlotte:evaluate`. Fixes #73.
32
+ - **Browser reconnection race** — `getBrowser()` now calls `ensureConnected()` to auto-recover instead of throwing immediately. `ensureConnected()` verifies browser health after awaiting concurrent launches. Fixes #83.
33
+ - **Renderer pipeline resilience** — Malformed AX properties no longer crash accessibility extraction (#86). Content extraction skips failed nodes instead of aborting (#79). Recursive frame traversal catches errors per-frame (#74). Batch layout extraction uses `Promise.allSettled()` for partial failure tolerance (#77).
34
+ - **Event listener cleanup** — `closeTab()` now explicitly removes all page event listeners before `page.close()` to prevent memory leaks across tab cycles. Fixes #89.
35
+ - **Dialog handler error handling** — Dialog event handler wrapped in try/catch to prevent unhandled promise rejections when dialog is already dismissed. Fixes #75.
36
+ - **Dev mode shutdown resilience** — `DevModeState.stopAll()` catches errors per substep so a file watcher or static server failure doesn't prevent browser cleanup. Fixes #80.
37
+ - **Form field matching null guard** — `resolveId()` null return no longer produces false-positive form field matches. Fixes #76.
38
+ - **Landmark ID collision** — Main-frame landmarks now pass explicit `"main"` frameId for consistent hash input, preventing rare cross-frame ID collisions. Fixes #82.
39
+ - **CLI argument parsing** — `--output-dir=`, `--profile=`, and `--tools=` flags now use `substring(indexOf("=") + 1)` instead of `split("=")[1]`, preserving paths containing `=`. Fixes #70.
40
+ - **Zod bounds validation** — Added `.min()`/`.max()` constraints to `quality` (1-100), viewport `width`/`height` (>=1), and key `delay` (>=0). Fixes #81.
41
+ - **Test cleanup** — File output integration test no longer leaks artifact store temp directory. Fixes #71.
42
+ - **File output security** — Path traversal prevention, mkdir-before-validation fix, and CLI `output-dir` initialization hardened. Fixes security issues from #16 review.
43
+
44
+ ### Changed
45
+
46
+ - README rewritten with problem-first opening, expanded MCP client setup configs (Cursor, Windsurf, VS Code, Cline, Amp), and updated tool counts.
47
+ - npm package description and keywords updated for discoverability.
48
+ - Site meta descriptions updated to lead with token-efficiency comparison.
49
+
7
50
  ## [0.4.2] - 2026-03-06
8
51
 
9
52
  ### Added
package/README.md CHANGED
@@ -2,17 +2,23 @@
2
2
 
3
3
  **The Web, Readable.**
4
4
 
5
- Charlotte is an MCP server that renders web pages into structured, agent-readable representations using headless Chromium. It exposes the browser's semantic understanding — accessibility tree, layout geometry, interactive elements — to AI agents via [Model Context Protocol](https://modelcontextprotocol.io/) tools, enabling navigation, observation, and interaction without vision models or brittle selectors.
5
+ Your AI agent spends 60,000 tokens just to look at a web page. Charlotte does it in 336.
6
+
7
+ Charlotte is an MCP server that gives AI agents structured, token-efficient access to the web.
8
+ Instead of dumping the full accessibility tree on every call, Charlotte returns only what
9
+ the agent needs: a compact page summary on arrival, targeted queries for specific elements,
10
+ and full detail only when explicitly requested. The result is 25-182x less data per page
11
+ compared to [Playwright MCP](https://github.com/anthropics/playwright-mcp), saving thousands of dollars across production workloads.
6
12
 
7
13
  ## Why Charlotte?
8
14
 
9
15
  Most browser MCP servers dump the entire accessibility tree on every call — a flat text blob that can exceed a million characters on content-heavy pages. Agents pay for all of it whether they need it or not.
10
16
 
11
- Charlotte takes a different approach. It decomposes each page into a typed, structured representation — landmarks, headings, interactive elements, forms, content summaries — and lets agents control how much they receive with three detail levels. When an agent navigates to a new page, it gets a compact orientation (336 characters for Hacker News) instead of the full element dump (61,000+ characters). When it needs specifics, it asks for them.
17
+ Charlotte decomposes each page into a typed, structured representation — landmarks, headings, interactive elements, forms, content summaries — and lets agents control how much they receive with three detail levels. When an agent navigates to a new page, it gets a compact orientation (336 characters for Hacker News) instead of the full element dump (61,000+ characters). When it needs specifics, it asks for them.
12
18
 
13
19
  ### Benchmarks
14
20
 
15
- Charlotte v0.4.2 vs Playwright MCP, measured by characters returned per tool call on real websites:
21
+ Charlotte v0.5.1 vs Playwright MCP, measured by characters returned per tool call on real websites:
16
22
 
17
23
  **Navigation** (first contact with a page):
18
24
 
@@ -33,7 +39,7 @@ Charlotte's `navigate` returns minimal detail by default — landmarks, headings
33
39
  | browse (default) | 23 | ~3,900 | **~47%** |
34
40
  | core | 7 | 1,677 | **~77%** |
35
41
 
36
- Tool definitions are sent on every API round-trip. With the default `browse` profile, Charlotte carries ~47% less definition overhead than loading all 42 tools. Over a 20-call browsing session, that's **~38% fewer total tokens**. See the [profile benchmark report](docs/charlotte-profile-benchmark-report.md) for full results.
42
+ Tool definitions are sent on every API round-trip. With the default `browse` profile, Charlotte carries ~47% less definition overhead than loading all tools. Over a 20-call browsing session, that's **~38% fewer total tokens**. See the [profile benchmark report](docs/charlotte-profile-benchmark-report.md) for full results.
37
43
 
38
44
  **The workflow difference:** Playwright agents receive 61K+ characters every time they look at Hacker News, whether they're reading headlines or looking for a login button. Charlotte agents get 336 characters on arrival, call `find({ type: "link", text: "login" })` to get exactly what they need, and never pay for the rest.
39
45
 
@@ -63,7 +69,7 @@ Agents receive landmarks, headings, interactive elements with typed metadata, bo
63
69
 
64
70
  **Navigation** — `navigate`, `back`, `forward`, `reload`
65
71
 
66
- **Observation** — `observe` (3 detail levels), `find` (spatial + semantic search, CSS selector mode), `screenshot`, `diff` (structural comparison against snapshots)
72
+ **Observation** — `observe` (3 detail levels, structural tree view), `find` (spatial + semantic search, CSS selector mode), `screenshot` (with persistent artifact management), `screenshots`, `screenshot_get`, `screenshot_delete`, `diff` (structural comparison against snapshots)
67
73
 
68
74
  **Interaction** — `click`, `click_at` (coordinate-based), `type`, `select`, `toggle`, `submit`, `scroll`, `hover`, `drag`, `key` (single/sequence with element targeting), `wait_for` (async condition polling), `upload` (file input), `dialog` (accept/dismiss JS dialogs)
69
75
 
@@ -77,7 +83,7 @@ Agents receive landmarks, headings, interactive elements with typed metadata, bo
77
83
 
78
84
  ## Tool Profiles
79
85
 
80
- Charlotte ships 42 tools, but most workflows only need a subset. Startup profiles control which tools load into the agent's context, reducing definition overhead by up to 77%.
86
+ Charlotte ships 42 tools (41 registered + the `charlotte:tools` meta-tool), but most workflows only need a subset. Startup profiles control which tools load into the agent's context, reducing definition overhead by up to 77%.
81
87
 
82
88
  ```bash
83
89
  charlotte --profile browse # 23 tools (default) — navigate, observe, interact, tabs
@@ -150,7 +156,9 @@ npm start
150
156
 
151
157
  ### MCP Client Configuration
152
158
 
153
- Add Charlotte to your MCP client configuration. For Claude Code, create `.mcp.json` in your project root:
159
+ #### Claude Code
160
+
161
+ Create `.mcp.json` in your project root:
154
162
 
155
163
  ```json
156
164
  {
@@ -165,7 +173,85 @@ Add Charlotte to your MCP client configuration. For Claude Code, create `.mcp.js
165
173
  }
166
174
  ```
167
175
 
168
- For Claude Desktop, add to `claude_desktop_config.json`:
176
+ #### Claude Desktop
177
+
178
+ Add to `claude_desktop_config.json`:
179
+
180
+ ```json
181
+ {
182
+ "mcpServers": {
183
+ "charlotte": {
184
+ "command": "npx",
185
+ "args": ["@ticktockbent/charlotte"]
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
191
+ #### Cursor
192
+
193
+ Add to `.cursor/mcp.json`:
194
+
195
+ ```json
196
+ {
197
+ "mcpServers": {
198
+ "charlotte": {
199
+ "command": "npx",
200
+ "args": ["@ticktockbent/charlotte"]
201
+ }
202
+ }
203
+ }
204
+ ```
205
+
206
+ #### Windsurf
207
+
208
+ Add to `~/.codeium/windsurf/mcp_config.json`:
209
+
210
+ ```json
211
+ {
212
+ "mcpServers": {
213
+ "charlotte": {
214
+ "command": "npx",
215
+ "args": ["@ticktockbent/charlotte"]
216
+ }
217
+ }
218
+ }
219
+ ```
220
+
221
+ #### VS Code (Copilot)
222
+
223
+ Add to `.vscode/mcp.json`:
224
+
225
+ ```json
226
+ {
227
+ "servers": {
228
+ "charlotte": {
229
+ "type": "stdio",
230
+ "command": "npx",
231
+ "args": ["@ticktockbent/charlotte"]
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ #### Cline
238
+
239
+ Add to Cline MCP settings (via the Cline sidebar > MCP Servers > Configure):
240
+
241
+ ```json
242
+ {
243
+ "mcpServers": {
244
+ "charlotte": {
245
+ "command": "npx",
246
+ "args": ["@ticktockbent/charlotte"]
247
+ }
248
+ }
249
+ }
250
+ ```
251
+
252
+ #### Amp
253
+
254
+ Add to `~/.amp/settings.json`:
169
255
 
170
256
  ```json
171
257
  {
@@ -347,20 +433,18 @@ All tools go through `renderActivePage()` which handles snapshots, reload events
347
433
 
348
434
  ## Sandbox
349
435
 
350
- Charlotte includes a test website in `tests/sandbox/` that exercises all 42 tools without touching the public internet. Serve it locally with:
436
+ Charlotte includes a test website in `tests/sandbox/` that exercises all tools without touching the public internet. Serve it locally with:
351
437
 
352
438
  ```
353
439
  dev_serve({ path: "tests/sandbox" })
354
440
  ```
355
441
 
356
- Four pages cover navigation, forms, interactive elements, delayed content, scroll containers, and more. See [docs/sandbox.md](docs/sandbox.md) for the full page reference and a tool-by-tool exercise checklist.
442
+ Five pages cover navigation, forms, interactive elements, popups, delayed content, scroll containers, and more. See [docs/sandbox.md](docs/sandbox.md) for the full page reference and a tool-by-tool exercise checklist.
357
443
 
358
444
  ## Known Issues
359
445
 
360
446
  **Tool naming convention** — Charlotte uses `:` as a namespace separator in tool names (e.g., `charlotte:navigate`, `charlotte:observe`). MCP SDK v1.26.0+ logs validation warnings for this character, as the emerging [SEP standard](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/986) restricts tool names to `[A-Za-z0-9_.-]`. This does not affect functionality — all tools work correctly — but produces stderr warnings on server startup. Will be addressed in a future release to comply with the SEP standard.
361
447
 
362
- **Iframe content not captured** — Charlotte reads the main frame's accessibility tree only. Content inside iframes (same-origin or cross-origin) is not included in the page representation. See the Roadmap for planned iframe support.
363
-
364
448
  **Shadow DOM** — Open shadow DOM works transparently. Chromium's accessibility tree pierces open shadow boundaries, so web components (e.g., GitHub's `<relative-time>`, `<tool-tip>`) render their content into Charlotte's representation without special handling. Closed shadow roots are opaque to the accessibility tree and will not be captured.
365
449
 
366
450
  ## Roadmap
@@ -379,20 +463,14 @@ Four pages cover navigation, forms, interactive elements, delayed content, scrol
379
463
 
380
464
  **Configuration File** — Support a `--config` CLI argument to load settings from a JSON file, simplifying repeatable setups and CI/CD integration.
381
465
 
382
- **File Output** — Add an optional `filename` parameter to `screenshot`, `observe`, and future monitoring tools so large responses can be written to disk instead of returned inline, reducing token consumption.
383
-
384
466
  **Full Device Emulation** — Extend `charlotte:viewport` to accept named devices (e.g., "iPhone 15") and configure user agent, touch support, and device pixel ratio via CDP, not just viewport dimensions.
385
467
 
386
468
  ### Feature Roadmap
387
469
 
388
- **Screenshot Artifacts** — Save screenshots as persistent file artifacts rather than only returning inline data, enabling agents to reference and manage captured images across sessions.
389
-
390
470
  **Video Recording** — Record interactions as video, capturing the full sequence of agent-driven navigation and manipulation for debugging, documentation, and review.
391
471
 
392
472
  **ARM64 Docker Images** — Add `linux/arm64` platform support to the Docker publish workflow for native performance on Apple Silicon Macs and ARM servers.
393
473
 
394
- **Iframe Content Extraction** — Traverse child frames via CDP to include iframe content in the page representation. Currently, Charlotte only reads the main frame's accessibility tree; same-origin and cross-origin iframe content is invisible.
395
-
396
474
  See [docs/playwright-mcp-gap-analysis.md](docs/playwright-mcp-gap-analysis.md) for the full gap analysis against Playwright MCP, including lower-priority items (vision tools, testing/verification, tracing, transport, security) and areas where Charlotte has advantages.
397
475
 
398
476
  ## Full Specification
@@ -403,6 +481,14 @@ See [docs/CHARLOTTE_SPEC.md](docs/CHARLOTTE_SPEC.md) for the complete specificat
403
481
 
404
482
  [MIT](LICENSE)
405
483
 
484
+ ## Community
485
+
486
+ - Open a [bug report](https://github.com/TickTockBent/charlotte/issues/new?template=bug_report.md) for reproducible defects, regressions, or MCP-client-specific problems.
487
+ - Open a [feature request](https://github.com/TickTockBent/charlotte/issues/new?template=feature_request.md) for workflow improvements or new capabilities.
488
+ - Open a [tool request](https://github.com/TickTockBent/charlotte/issues/new?template=tool_request.md) if you want to propose a new tool, parameter surface, or profile placement.
489
+ - Browse [open issues](https://github.com/TickTockBent/charlotte/issues) to find current work and discussion.
490
+ - Check the planned [good first issue filter](https://github.com/TickTockBent/charlotte/issues?q=is%3Aopen+label%3A%22good+first+issue%22) as maintainers tag starter-friendly tasks.
491
+
406
492
  ## Contributing
407
493
 
408
494
  See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
@@ -7,7 +7,7 @@ export declare class BrowserManager {
7
7
  private doLaunch;
8
8
  ensureConnected(): Promise<void>;
9
9
  close(): Promise<void>;
10
- getBrowser(): Browser;
10
+ getBrowser(): Promise<Browser>;
11
11
  newPage(): Promise<Page>;
12
12
  isConnected(): boolean;
13
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"browser-manager.d.ts","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAInF,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,SAAS,CAA8B;IAEzC,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;YAetC,QAAQ;IAchB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,UAAU,IAAI,OAAO;IAUf,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,WAAW,IAAI,OAAO;CAGvB"}
1
+ {"version":3,"file":"browser-manager.d.ts","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAInF,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,SAAS,CAA8B;IAEzC,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;YAetC,QAAQ;IAchB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAW9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B,WAAW,IAAI,OAAO;CAGvB"}
@@ -36,6 +36,10 @@ export class BrowserManager {
36
36
  // Prevent concurrent relaunch attempts
37
37
  if (this.launching) {
38
38
  await this.launching;
39
+ // Verify the concurrent launch actually succeeded
40
+ if (!this.browser || !this.browser.connected) {
41
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Browser launch failed during concurrent reconnection attempt.");
42
+ }
39
43
  return;
40
44
  }
41
45
  logger.info("Browser not connected, relaunching");
@@ -54,15 +58,15 @@ export class BrowserManager {
54
58
  this.browser = null;
55
59
  }
56
60
  }
57
- getBrowser() {
61
+ async getBrowser() {
62
+ await this.ensureConnected();
58
63
  if (!this.browser || !this.browser.connected) {
59
- throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Browser is not connected. Call ensureConnected() first.");
64
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Browser is not connected after ensureConnected().");
60
65
  }
61
66
  return this.browser;
62
67
  }
63
68
  async newPage() {
64
- await this.ensureConnected();
65
- const browser = this.getBrowser();
69
+ const browser = await this.getBrowser();
66
70
  return browser.newPage();
67
71
  }
68
72
  isConnected() {
@@ -1 +1 @@
1
- {"version":3,"file":"browser-manager.js","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAO,SAA0D,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExE,MAAM,OAAO,cAAc;IACjB,OAAO,GAAmB,IAAI,CAAC;IAC/B,aAAa,GAAkB,EAAE,CAAC;IAClC,SAAS,GAAyB,IAAI,CAAC;IAE/C,KAAK,CAAC,MAAM,CAAC,OAAuB;QAClC,IAAI,CAAC,aAAa,GAAG;YACnB,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE;gBACJ,cAAc;gBACd,0BAA0B;gBAC1B,eAAe;gBACf,yBAAyB;aAC1B;YACD,GAAG,OAAO;SACX,CAAC;QAEF,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAE1D,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;YACnC,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;YAClD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;YAC/B,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,GAAG;SACjC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,uCAAuC;QACvC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,CAAC,SAAS,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC;QACvB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAChC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,UAAU;QACR,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC7C,MAAM,IAAI,cAAc,CACtB,kBAAkB,CAAC,aAAa,EAChC,yDAAyD,CAC1D,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;IACzD,CAAC;CACF"}
1
+ {"version":3,"file":"browser-manager.js","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,15 @@
1
- import type { Page, CDPSession } from "puppeteer";
1
+ import type { Page, CDPSession, Frame } from "puppeteer";
2
2
  export declare class CDPSessionManager {
3
3
  private sessions;
4
+ private frameSessions;
4
5
  getSession(page: Page): Promise<CDPSession>;
6
+ /**
7
+ * Get or create a CDP session for a child frame.
8
+ * Uses the frame's own client for out-of-process (cross-origin) frames.
9
+ */
10
+ getFrameSession(frame: Frame): Promise<CDPSession>;
11
+ /** Extract the CDP frame ID from a Puppeteer Frame. */
12
+ getFrameId(frame: Frame): string;
5
13
  private enableDomains;
6
14
  }
7
15
  //# sourceMappingURL=cdp-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cdp-session.d.ts","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAWlD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAA4C;IAEtD,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;YAanC,aAAa;CAW5B"}
1
+ {"version":3,"file":"cdp-session.d.ts","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAQzD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAA4C;IAC5D,OAAO,CAAC,aAAa,CAAiC;IAEhD,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IAajD;;;OAGG;IACG,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;IAiBxD,uDAAuD;IACvD,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM;YAMlB,aAAa;CAc5B"}
@@ -1,13 +1,10 @@
1
1
  import { logger } from "../utils/logger.js";
2
- const REQUIRED_DOMAINS = [
3
- "Accessibility",
4
- "DOM",
5
- "CSS",
6
- "Page",
7
- "Network",
8
- ];
2
+ const REQUIRED_DOMAINS = ["Accessibility", "DOM", "CSS", "Page", "Network"];
3
+ /** Domains needed for iframe frame sessions (subset — no Page/Network). */
4
+ const FRAME_DOMAINS = ["Accessibility", "DOM", "CSS"];
9
5
  export class CDPSessionManager {
10
6
  sessions = new WeakMap();
7
+ frameSessions = new Map();
11
8
  async getSession(page) {
12
9
  const existing = this.sessions.get(page);
13
10
  if (existing) {
@@ -15,12 +12,37 @@ export class CDPSessionManager {
15
12
  }
16
13
  logger.debug("Creating new CDP session");
17
14
  const session = await page.createCDPSession();
18
- await this.enableDomains(session);
15
+ await this.enableDomains(session, REQUIRED_DOMAINS);
19
16
  this.sessions.set(page, session);
20
17
  return session;
21
18
  }
22
- async enableDomains(session) {
23
- for (const domain of REQUIRED_DOMAINS) {
19
+ /**
20
+ * Get or create a CDP session for a child frame.
21
+ * Uses the frame's own client for out-of-process (cross-origin) frames.
22
+ */
23
+ async getFrameSession(frame) {
24
+ const frameId = this.getFrameId(frame);
25
+ const existing = this.frameSessions.get(frameId);
26
+ if (existing) {
27
+ return existing;
28
+ }
29
+ logger.debug("Creating CDP session for frame", { frameId, url: frame.url() });
30
+ // CdpFrame exposes .client which is the CDP session for that frame's target.
31
+ // This is a Puppeteer internal (tested with puppeteer 24.x). If Puppeteer
32
+ // changes the internal API, this will need updating.
33
+ const session = frame.client;
34
+ await this.enableDomains(session, FRAME_DOMAINS);
35
+ this.frameSessions.set(frameId, session);
36
+ return session;
37
+ }
38
+ /** Extract the CDP frame ID from a Puppeteer Frame. */
39
+ getFrameId(frame) {
40
+ // Puppeteer Frame exposes _id as the CDP frame ID.
41
+ // This is a Puppeteer internal (tested with puppeteer 24.x).
42
+ return frame._id;
43
+ }
44
+ async enableDomains(session, domains) {
45
+ for (const domain of domains) {
24
46
  try {
25
47
  await session.send(`${domain}.enable`);
26
48
  logger.debug(`Enabled CDP domain: ${domain}`);
@@ -1 +1 @@
1
- {"version":3,"file":"cdp-session.js","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,gBAAgB,GAAG;IACvB,eAAe;IACf,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;CACD,CAAC;AAEX,MAAM,OAAO,iBAAiB;IACpB,QAAQ,GAA8B,IAAI,OAAO,EAAE,CAAC;IAE5D,KAAK,CAAC,UAAU,CAAC,IAAU;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9C,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,OAAmB;QAC7C,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,SAAgB,CAAC,CAAC;gBAC9C,MAAM,CAAC,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;YAChD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,8CAA8C;gBAC9C,MAAM,CAAC,KAAK,CAAC,gCAAgC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"cdp-session.js","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,gBAAgB,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAU,CAAC;AAErF,2EAA2E;AAC3E,MAAM,aAAa,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAE/D,MAAM,OAAO,iBAAiB;IACpB,QAAQ,GAA8B,IAAI,OAAO,EAAE,CAAC;IACpD,aAAa,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEtD,KAAK,CAAC,UAAU,CAAC,IAAU;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9C,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QACpD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CAAC,KAAY;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC9E,6EAA6E;QAC7E,0EAA0E;QAC1E,qDAAqD;QACrD,MAAM,OAAO,GAAI,KAAa,CAAC,MAAoB,CAAC;QACpD,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QACjD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,uDAAuD;IACvD,UAAU,CAAC,KAAY;QACrB,mDAAmD;QACnD,6DAA6D;QAC7D,OAAQ,KAAa,CAAC,GAAa,CAAC;IACtC,CAAC;IAEO,KAAK,CAAC,aAAa,CACzB,OAAmB,EACnB,OAA0B;QAE1B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,SAAgB,CAAC,CAAC;gBAC9C,MAAM,CAAC,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;YAChD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,8CAA8C;gBAC9C,MAAM,CAAC,KAAK,CAAC,gCAAgC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -25,7 +25,23 @@ export declare class PageManager {
25
25
  private pages;
26
26
  private activeTabId;
27
27
  private config;
28
+ /** Tab IDs of pages opened by popups since the last drain. */
29
+ private newTabQueue;
28
30
  constructor(config?: CharlotteConfig);
31
+ /**
32
+ * Wire up event listeners on a managed page: console, network, dialog,
33
+ * framenavigated, popup, and close. Shared by openTab() and the popup handler.
34
+ */
35
+ private wirePageListeners;
36
+ /**
37
+ * Register a popup page as a managed tab. Called by the popup event handler.
38
+ */
39
+ private registerPopupPage;
40
+ /**
41
+ * Drain the new-tab queue. Returns tab IDs of pages opened by popups since
42
+ * the last call, then clears the queue (single-consumption semantics).
43
+ */
44
+ consumeNewTabs(): string[];
29
45
  openTab(browserManager: BrowserManager, url?: string): Promise<string>;
30
46
  switchTab(tabId: string): Promise<Page>;
31
47
  closeTab(tabId: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"page-manager.d.ts","sourceRoot":"","sources":["../../src/browser/page-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI1D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAAkB;gBAEpB,MAAM,CAAC,EAAE,eAAe;IAK9B,OAAO,CAAC,cAAc,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuFtE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBtC,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAapC,aAAa,IAAI,IAAI;IAmBrB,cAAc,IAAI,MAAM;IAUxB,+EAA+E;IAC/E,gBAAgB,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAS1D,uFAAuF;IACvF,gBAAgB,IAAI,KAAK,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IASF,iEAAiE;IACjE,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE;IAUpD,wDAAwD;IACxD,kBAAkB,IAAI,cAAc,EAAE;IAOtC,oBAAoB,IAAI,IAAI;IAQ5B,oBAAoB,IAAI,IAAI;IAQ5B,WAAW,IAAI,IAAI;IASnB,oBAAoB,IAAI,aAAa,GAAG,IAAI;IAM5C,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAMjC,kBAAkB,IAAI,IAAI;IAS1B,QAAQ,IAAI,OAAO;CAGpB"}
1
+ {"version":3,"file":"page-manager.d.ts","sourceRoot":"","sources":["../../src/browser/page-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI1D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAAkB;IAChC,8DAA8D;IAC9D,OAAO,CAAC,WAAW,CAAgB;gBAEvB,MAAM,CAAC,EAAE,eAAe;IAKpC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA6FzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkBzB;;;OAGG;IACH,cAAc,IAAI,MAAM,EAAE;IAOpB,OAAO,CAAC,cAAc,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BtE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBtC,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAapC,aAAa,IAAI,IAAI;IAmBrB,cAAc,IAAI,MAAM;IAOxB,+EAA+E;IAC/E,gBAAgB,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAS1D,uFAAuF;IACvF,gBAAgB,IAAI,KAAK,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IASF,iEAAiE;IACjE,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE;IAUpD,wDAAwD;IACxD,kBAAkB,IAAI,cAAc,EAAE;IAOtC,oBAAoB,IAAI,IAAI;IAQ5B,oBAAoB,IAAI,IAAI;IAQ5B,WAAW,IAAI,IAAI;IASnB,oBAAoB,IAAI,aAAa,GAAG,IAAI;IAM5C,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAMjC,kBAAkB,IAAI,IAAI;IAS1B,QAAQ,IAAI,OAAO;CAGpB"}
@@ -11,21 +11,18 @@ export class PageManager {
11
11
  pages = new Map();
12
12
  activeTabId = null;
13
13
  config;
14
+ /** Tab IDs of pages opened by popups since the last drain. */
15
+ newTabQueue = [];
14
16
  constructor(config) {
15
17
  // Accept optional config; callers without config get a permissive default
16
18
  this.config = config ?? createDefaultConfig();
17
19
  }
18
- async openTab(browserManager, url) {
19
- const page = await browserManager.newPage();
20
- const tabId = generateTabId();
21
- const managedPage = {
22
- id: tabId,
23
- page,
24
- consoleMessages: [],
25
- networkRequests: [],
26
- pendingDialog: null,
27
- pendingDialogInfo: null,
28
- };
20
+ /**
21
+ * Wire up event listeners on a managed page: console, network, dialog,
22
+ * framenavigated, popup, and close. Shared by openTab() and the popup handler.
23
+ */
24
+ wirePageListeners(managedPage) {
25
+ const { page, id: tabId } = managedPage;
29
26
  // Collect all console messages
30
27
  page.on("console", (msg) => {
31
28
  if (managedPage.consoleMessages.length >= MAX_CONSOLE_MESSAGES) {
@@ -53,26 +50,32 @@ export class PageManager {
53
50
  });
54
51
  // Handle JavaScript dialogs (alert, confirm, prompt, beforeunload)
55
52
  page.on("dialog", async (dialog) => {
56
- const dialogType = dialog.type();
57
- const autoDismiss = this.config.dialogAutoDismiss;
58
- logger.info("Dialog appeared", { tabId, type: dialogType, message: dialog.message() });
59
- // Auto-dismiss logic
60
- if (autoDismiss === "accept_all" || (autoDismiss === "accept_alerts" && dialogType === "alert")) {
61
- await dialog.accept();
62
- return;
53
+ try {
54
+ const dialogType = dialog.type();
55
+ const autoDismiss = this.config.dialogAutoDismiss;
56
+ logger.info("Dialog appeared", { tabId, type: dialogType, message: dialog.message() });
57
+ // Auto-dismiss logic
58
+ if (autoDismiss === "accept_all" ||
59
+ (autoDismiss === "accept_alerts" && dialogType === "alert")) {
60
+ await dialog.accept();
61
+ return;
62
+ }
63
+ if (autoDismiss === "dismiss_all") {
64
+ await dialog.dismiss();
65
+ return;
66
+ }
67
+ // Queue for manual handling
68
+ managedPage.pendingDialog = dialog;
69
+ managedPage.pendingDialogInfo = {
70
+ type: dialogType,
71
+ message: dialog.message(),
72
+ ...(dialogType === "prompt" ? { default_value: dialog.defaultValue() } : {}),
73
+ timestamp: new Date().toISOString(),
74
+ };
63
75
  }
64
- if (autoDismiss === "dismiss_all") {
65
- await dialog.dismiss();
66
- return;
76
+ catch (error) {
77
+ logger.warn("Dialog handler failed", { tabId, error });
67
78
  }
68
- // Queue for manual handling
69
- managedPage.pendingDialog = dialog;
70
- managedPage.pendingDialogInfo = {
71
- type: dialogType,
72
- message: dialog.message(),
73
- ...(dialogType === "prompt" ? { default_value: dialog.defaultValue() } : {}),
74
- timestamp: new Date().toISOString(),
75
- };
76
79
  });
77
80
  // Clear stale dialog references on main-frame navigation only.
78
81
  // Subframe navigations (iframes, ads, embeds) must not wipe dialog state.
@@ -82,6 +85,65 @@ export class PageManager {
82
85
  managedPage.pendingDialogInfo = null;
83
86
  }
84
87
  });
88
+ // Capture popups (target="_blank" links, window.open()) as managed tabs
89
+ page.on("popup", (popupPage) => {
90
+ if (popupPage) {
91
+ this.registerPopupPage(popupPage);
92
+ }
93
+ });
94
+ // Auto-clean when a page closes itself (window.close(), site-initiated)
95
+ page.on("close", () => {
96
+ if (this.pages.has(tabId)) {
97
+ this.pages.delete(tabId);
98
+ logger.info(`Tab ${tabId} closed by page`);
99
+ if (this.activeTabId === tabId) {
100
+ const remaining = this.pages.keys().next();
101
+ this.activeTabId = remaining.done ? null : remaining.value;
102
+ }
103
+ }
104
+ });
105
+ }
106
+ /**
107
+ * Register a popup page as a managed tab. Called by the popup event handler.
108
+ */
109
+ registerPopupPage(popupPage) {
110
+ const popupTabId = generateTabId();
111
+ const managedPopup = {
112
+ id: popupTabId,
113
+ page: popupPage,
114
+ consoleMessages: [],
115
+ networkRequests: [],
116
+ pendingDialog: null,
117
+ pendingDialogInfo: null,
118
+ };
119
+ this.wirePageListeners(managedPopup);
120
+ this.pages.set(popupTabId, managedPopup);
121
+ this.newTabQueue.push(popupTabId);
122
+ logger.info(`Captured popup as ${popupTabId}`, { url: popupPage.url() });
123
+ }
124
+ /**
125
+ * Drain the new-tab queue. Returns tab IDs of pages opened by popups since
126
+ * the last call, then clears the queue (single-consumption semantics).
127
+ */
128
+ consumeNewTabs() {
129
+ if (this.newTabQueue.length === 0)
130
+ return [];
131
+ const tabs = [...this.newTabQueue];
132
+ this.newTabQueue = [];
133
+ return tabs;
134
+ }
135
+ async openTab(browserManager, url) {
136
+ const page = await browserManager.newPage();
137
+ const tabId = generateTabId();
138
+ const managedPage = {
139
+ id: tabId,
140
+ page,
141
+ consoleMessages: [],
142
+ networkRequests: [],
143
+ pendingDialog: null,
144
+ pendingDialogInfo: null,
145
+ };
146
+ this.wirePageListeners(managedPage);
85
147
  this.pages.set(tabId, managedPage);
86
148
  this.activeTabId = tabId;
87
149
  if (url) {
@@ -104,6 +166,12 @@ export class PageManager {
104
166
  if (!managedPage) {
105
167
  throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, `Tab '${tabId}' not found`);
106
168
  }
169
+ managedPage.page.removeAllListeners("console");
170
+ managedPage.page.removeAllListeners("response");
171
+ managedPage.page.removeAllListeners("dialog");
172
+ managedPage.page.removeAllListeners("framenavigated");
173
+ managedPage.page.removeAllListeners("popup");
174
+ managedPage.page.removeAllListeners("close");
107
175
  await managedPage.page.close();
108
176
  this.pages.delete(tabId);
109
177
  if (this.activeTabId === tabId) {