fix(cdp): unblock puppeteer connect, bound Runtime.evaluate, full TargetInfo (#181)
Three independent gaps that hurt browser-based MCP clients connecting
to obscura's CDP endpoint:
- Runtime.enable returned "No page" when called before a session was
attached. puppeteer-extra's FrameManager.initialize fires it on the
browser-level WebSocket BEFORE any page target exists, so the
standard puppeteer.connect / browser.newPage flow died on connect.
Now reply with {} when no session is attached and let the next
Target.attachToTarget emit executionContextCreated.
- Runtime.evaluate had no per-call timeout. A long synchronous JS
expression held the global V8 lock indefinitely and starved every
other CDP command on the session. Honor the CDP `timeout` parameter
and default to 30s (Chrome's protocolTimeout). Returns a clear error
on expiry so client-side deadlines fire cleanly instead of hanging.
- Target.getTargetInfo for the browser target omitted canAccessOpener.
Strict CDP clients (chromiumoxide) panic when the field is missing.
Fill it in (false for the browser target itself).
Partial fix for #122. The remaining items (networkIdle hard-deadline
on SPAs, /json/list hardcoded data, docs on cheap-vs-queued methods)
need bigger or non-code changes and are deferred.
This commit is contained in:
1 parent
71b9b4fbab
commit
f4f49ebab4
2 files changed
+93
-21
No files matched your search
@@ -224,6 +224,9 @@ pub async fn handle(method: &str, params: &Value, ctx: &mut CdpContext) -> Resul
|
||||
}))
|
||||
}
|
||||
None => {
|
||||
// canAccessOpener is required on every TargetInfo per the
|
||||
// CDP spec. Strict clients (chromiumoxide) panic if it's
|
||||
// missing. The browser target itself has no opener.
|
||||
Ok(json!({
|
||||
"targetInfo": {
|
||||
"targetId": "browser",
|
||||
@@ -231,6 +234,7 @@ pub async fn handle(method: &str, params: &Value, ctx: &mut CdpContext) -> Resul
|
||||
"title": "",
|
||||
"url": "",
|
||||
"attached": true,
|
||||
"canAccessOpener": false,
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -277,4 +281,25 @@ mod tests {
|
||||
.expect_err("unknown methods must surface as errors");
|
||||
assert!(err.contains("Unknown Target method"));
|
||||
}
|
||||
|
||||
/// Regression for #122 item 5: every TargetInfo payload must carry the
|
||||
/// `canAccessOpener` field. The browser-target branch of getTargetInfo
|
||||
/// (no targetId passed → no page) used to omit it; strict CDP clients
|
||||
/// like chromiumoxide panic when the field is missing.
|
||||
#[tokio::test]
|
||||
async fn get_target_info_browser_target_includes_can_access_opener() {
|
||||
let mut ctx = CdpContext::new();
|
||||
// No targetId → falls through to the browser-target branch.
|
||||
let result = handle("getTargetInfo", &json!({}), &mut ctx)
|
||||
.await
|
||||
.expect("getTargetInfo with no targetId must return browser info");
|
||||
|
||||
let info = &result["targetInfo"];
|
||||
assert_eq!(info["type"], "browser", "must be the browser target");
|
||||
assert!(
|
||||
info.get("canAccessOpener").is_some(),
|
||||
"canAccessOpener must be present on every TargetInfo, got: {result}"
|
||||
);
|
||||
assert_eq!(info["canAccessOpener"], false);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user