Commit Graph
10 Commits
Author SHA1 Message Date
Ben YounesandOra Studio a7b1acfa18 fix(cdp): accept Audits domain as no-op for Puppeteer compatibility (#57) (#80)
Puppeteer's FrameManager.initialize calls Audits.enable on connect.
Without it puppeteer.connect() fails with "Unknown domain: Audits"
before any user code runs. Playwright doesn't enable Audits, which is
why the workaround in #57 sidesteps the issue.

Add Audits to the existing accepted-but-no-op domain list, alongside
Log/Performance/Security/etc. that already swallow Puppeteer's connect
chatter. No new module needed.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:22:24 +02:00
Ben YounesandOra Studio 23753405e5 fix(cdp): implement Target.attachToBrowserTarget for Playwright (#64) (#81)
Playwright (and recent Puppeteer) call Target.attachToBrowserTarget on
connect to obtain a session for the implicit "browser" target. Without
the handler the dispatcher returns "Unknown Target method:
attachToBrowserTarget" and the connect handshake aborts before any
user code runs.

Add a handler that mirrors Target.attachToTarget but for the browser
target: register a "browser-session" → "browser" mapping in
ctx.sessions, emit Target.attachedToTarget with type=browser, and
return the new session id.

Note: this addresses item (1) from #64. The Rocket Loader / Cloudflare
JS execution gap and Page.getLayoutMetrics are tracked separately
(getLayoutMetrics is fixed in PR #79).

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:10:07 +02:00
Ben YounesandOra Studio 3bb628eaf0 fix(cdp): implement Page.getLayoutMetrics for screenshot compatibility (#52) (#79)
Playwright calls Page.getLayoutMetrics before every page.screenshot() to
size the capture. Without it the call fails with "Unknown Page method:
getLayoutMetrics" and screenshot() throws.

Obscura has no visual layout engine, so we return a fixed 1280x720
viewport (Chrome's default) and try to derive the content height from
document.documentElement.scrollHeight via the JS runtime, falling back
to the viewport height when no live page is bound.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
2026-05-03 17:09:44 +02:00
zhangyang-crazy-oneandzhangyang-crazy-one 90ededfc44 fix(cdp): improve AXNode CDP spec compliance (#67)
Addresses follow-up issues from PR #44 bot review:

- Add parentId field to AXNode (walk DOM ancestors to resolve AX parent)
- Omit optional fields when empty (name, value, properties, childIds)
  instead of emitting null/empty arrays, per CDP spec
- Add ax_value_boolean() and ax_value_integer() helpers for consistent
  AXValue construction across all property types
- Use typed helpers throughout compute_properties() instead of inline
  JSON construction

CDP spec reference: Accessibility.AXNode requires parentId as AXNodeId,
and optional fields should be absent when not applicable.

Co-authored-by: zhangyang-crazy-one <[email protected]>
2026-05-03 17:09:19 +02:00
pryceupandClaude Opus 4.7 25b6d58f3e fix: enable request interception when Fetch.enable is called (#50)
Fetch.enable was setting page.intercept_enabled to false and clearing
intercept_block_patterns, which caused should_block_url() to always
return false. As a result, Fetch.requestPaused events were never
triggered and Playwright's page.route() handlers never executed.

- Set intercept_enabled to true (was false)
- Propagate URL patterns to page.intercept_block_patterns (was cleared)

Fixes #49

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-03 17:09:02 +02:00
zhangyang-crazy-one 266afa7c34 fix(cdp): wrap role/name/value as CDP AXValue objects
Per qodo-ai-reviewer feedback on PR #44: Accessibility.getFullAXTree
was emitting AXNode.role as a plain string and name/value as plain
strings or null, instead of CDP-compliant AXValue objects.

Changes:
- Add ax_value_role() and ax_value_string() helpers
- Change compute_name()/compute_value() to return Option<String>
- Wrap role, name, value as AXValue in build_ax_node()

The properties array already used the correct AXValue format.
2026-04-27 20:59:47 +08:00
zhangyang-crazy-one a4591707e3 feat(cdp): implement Accessibility domain (getFullAXTree)
Implements the CDP Accessibility domain with getFullAXTree support,
enabling AI agent tools (agent-browser, Playwright MCP) that rely on
Accessibility.getFullAXTree to work with Obscura.

Changes:
- Add crates/obscura-cdp/src/domains/accessibility.rs (~380 lines)
  - Accessibility.enable: returns {}
  - Accessibility.getFullAXTree: walks DOM tree, produces AXNode array
  - Role mapping: 50+ HTML tags → ARIA roles, respects explicit role attr
  - Name computation: aria-label → aria-labelledby → alt → title → placeholder
  - Properties: focusable, editable, checked, disabled, level, required, multiline
- Update mod.rs: register accessibility module
- Update dispatch.rs: route Accessibility domain to real handler

Tested with example.com (18 nodes), httpbin.org/html (16 nodes),
news.ycombinator.com (1308 nodes). Zero structure errors.

Closes #12
2026-04-27 12:02:38 +08:00
h4ckf0r0day 99e75f1e62 fix: load balancer panics, stealth flag, shadow DOM polyfill
Fixes #1: replace unwrap() panics in run_multi_worker_serve with match on the Result.
Fixes #2: route /json round-robin via worker_port instead of hardcoded port+1.
Fixes #3: return 502 Bad Gateway when a worker is unreachable instead of silently dropping the connection.
Fixes #5: thread --stealth through start_with_options -> CdpContext -> BrowserContext::with_options so the flag actually has an effect in serve mode (was let _ before). Log message now reflects the actual capability based on the stealth cargo feature being compiled in.
Fixes #15: try/catch the parentNode assignment in the attachShadow polyfill so it doesn't throw a TypeError against the read-only Node.parentNode getter (Cloudflare Turnstile triggers this path).
2026-04-25 14:27:52 +02:00
Mike Mooring 9e3e1e4ef7 fix(cdp): five Playwright drop-in compatibility patches
Brings Obscura's CDP server in line with what Playwright's
`chromium.connectOverCDP()` expects. Each fix is small and surgical;
together they make `connect → newContext → newPage → goto → page.title()`
work end-to-end against `https://example.com`.

1. **server.rs — emit pending events before command response.**
   Chromium CDP semantics require side-effect events (e.g.
   `Target.targetCreated` + `Target.attachedToTarget` from
   `Target.createTarget`) to arrive *before* the originating command's
   response. Playwright's wire reader assumes this ordering — it
   resolves the response promise on the response and then synchronously
   looks up the freshly-created session. Reversing the order makes that
   lookup miss and the page hang at `newPage()`.

2. **domains/browser.rs — accept Browser.setWindowBounds as a no-op.**
   Playwright calls `Browser.setWindowBounds` during teardown. Returning
   "Unknown Browser method" causes Playwright to treat the target as
   broken and tear it down mid-test, surfacing as "Frame has been
   detached" on the next `page.title()`.

3. **obscura-browser/page.rs — frame_id == target_id for main frame.**
   Was `format!("{id}.1", id)`. Playwright's frame manager indexes
   frames by frame id and looks up the main frame by target id, so the
   `.1` suffix made every main-frame lookup miss.

4. **domains/page.rs + dispatch.rs — track isolated worlds and re-emit
   them post-navigation.** Playwright registers a utility world via
   `Page.createIsolatedWorld` and expects `Runtime.executionContextCreated`
   for each registered world after every navigation. Without re-emission,
   the utility-world handle goes stale post-`goto` and `page.title()`
   hangs waiting for the world's execution context. Falls back to the
   legacy `__puppeteer_utility_world__24.40.0` name when no worlds have
   been registered, preserving prior Puppeteer support.

5. **obscura-js/runtime.rs — strip trailing semicolons before wrapping.**
   Playwright's utility-script expression is an IIFE ending in `})();`.
   Both `wrap_expression` (CDP `Runtime.evaluate`) and `evaluate_for_cdp`
   (the `meta_code` IIFE) embed the user expression inside `({EXPR})` /
   `return ({EXPR});`. With the trailing `;` left in, this produces
   `({...;})` — a SyntaxError. Parse errors are not catchable, so the
   surrounding try/catch never fires and the call silently returns
   `undefined`, surfacing as `page.title() === ""`.

Verified end-to-end against `https://example.com`:
  [probe] connect OK
  [probe] newContext OK
  [probe] newPage OK
  [probe] goto OK
  [probe] title = Example Domain
2026-04-24 23:27:18 -07:00
h4ckf0r0day 8749725b45 Initial Release 2026-04-13 12:30:28 +02:00