fix(cdp): read and validate contextId in Runtime.evaluate / callFunctionOn (#51) (#149)

Fixes #51. `Runtime.evaluate` and `Runtime.callFunctionOn` previously
ignored the `contextId` / `executionContextId` parameter entirely, so
clients targeting an isolated world (e.g. Playwright's locator path,
which calls `Page.createIsolatedWorld` → contextId 100 → evaluates the
UtilityScript against that id) silently fell through to the default
context, breaking the contract advertised via
Runtime.executionContextCreated.

Track the set of advertised execution context ids on CdpContext
(pre-seeded with 1 and 2 — the default-frame contexts emitted by
Runtime.enable and post-navigation) and extend it from
Page.createIsolatedWorld. Both handlers now reject unknown ids with
`Cannot find context with specified id: <id>` — matching the wording
real Chrome returns — and log accepted ids at debug for observability.

Obscura still routes every JS call against the page's single V8 isolate,
so this PR is spec-compliance scaffolding rather than full multi-world
execution. Follow-up work to physically isolate worlds can build on the
same valid_context_ids registry.
This commit is contained in:
Ben Younes authored and GitHub committed 2026-05-14 09:15:19 +00:00
1 parent f3c783f6f5
commit 2ff0f8fe1d
3 files changed
+173 -2

No files matched your search

+21 -1
View File
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use obscura_browser::{BrowserContext, Page};
@@ -25,6 +25,16 @@ pub struct CdpContext {
// back. Stored as plain Strings (not by-page) — for now we only model
// a single page in CdpContext anyway.
pub isolated_worlds: Vec<String>,
// Set of executionContextIds Obscura has emitted via
// Runtime.executionContextCreated. Pre-populated with the default-frame
// contexts (`1`, `2`) that Runtime.enable / Page.navigate emit, then
// extended each time Page.createIsolatedWorld assigns a fresh id.
//
// Runtime.evaluate / Runtime.callFunctionOn consult this set to reject
// requests targeting an unknown context — matching real Chrome's
// "Cannot find context with specified id" CDP error and unblocking the
// Playwright locator path described in issue #51.
pub valid_context_ids: HashSet<i64>,
pub fetch_intercept: FetchInterceptState,
pub intercept_tx: Option<tokio::sync::mpsc::UnboundedSender<InterceptedRequest>>,
}
@@ -53,6 +63,15 @@ impl CdpContext {
stealth,
user_agent,
));
// Pre-seed with the default-frame execution context ids that
// `Runtime.enable` (1) and post-navigation re-emission (2) advertise
// via Runtime.executionContextCreated. Anything else has to be
// registered explicitly (Page.createIsolatedWorld), otherwise
// Runtime.{evaluate,callFunctionOn} should reject it per CDP spec.
let mut valid_context_ids = HashSet::new();
valid_context_ids.insert(1);
valid_context_ids.insert(2);
CdpContext {
pages: Vec::new(),
sessions: HashMap::new(),
@@ -64,6 +83,7 @@ impl CdpContext {
fetch_intercept: FetchInterceptState::new(),
intercept_tx: None,
isolated_worlds: Vec::new(),
valid_context_ids,
}
}