fix(eval): replMode only for sync let/const decls, keep awaitPromise for async (#38)

replMode and awaitPromise are mutually exclusive in Chrome — under replMode a
returned promise serialises to {} instead of being awaited, which broke every
fetch/async eval (e2e_domain_filter, e2e_headers, e2e_react_tree all regressed).
Enable replMode only for synchronous scripts that declare a top-level let/const
(the #38 case); promise-returning scripts keep awaitPromise — restoring the
pre-#38 await behaviour while still fixing the let-redeclaration collision.
This commit is contained in:
leeguooooo
2026-06-17 02:08:11 +09:00
parent 0296bc7a88
commit c47601bd7b
+17 -9
View File
@@ -1208,13 +1208,21 @@ impl BrowserManager {
pub async fn evaluate(&self, script: &str, _args: Option<Value>) -> Result<Value, String> { pub async fn evaluate(&self, script: &str, _args: Option<Value>) -> Result<Value, String> {
let session_id = self.active_session_id()?.to_string(); let session_id = self.active_session_id()?.to_string();
// `replMode: true` matches the DevTools console: top-level `let`/`const` // `replMode: true` lets successive `eval`s re-declare top-level
// can be re-declared across successive `eval`s instead of throwing // `let`/`const` instead of throwing "Identifier 'x' has already been
// "Identifier 'x' has already been declared" (issue #38 — independent // declared" (issue #38 — independent `eval` steps in a test suite collided
// `eval` steps in a test suite collided in the page's shared lexical // in the page's shared lexical scope). BUT replMode and `awaitPromise` are
// scope), and top-level `await` is allowed. Completion-value and // mutually exclusive in Chrome: under replMode a returned promise is NOT
// main-world semantics are unchanged. Built as raw params so the other // awaited (it serialises to `{}`), which breaks `fetch(...).then(...)` and
// ~28 EvaluateParams literals don't all need a new field. // every other async eval. So enable replMode ONLY for synchronous scripts
// that declare a top-level `let`/`const`; promise-returning scripts keep
// `awaitPromise` (no replMode) — exactly the pre-#38 behaviour.
let mentions_async = script.contains("await")
|| script.contains(".then(")
|| script.contains("fetch(")
|| script.contains("Promise");
let declares = script.contains("let ") || script.contains("const ");
let repl_mode = declares && !mentions_async;
let result: EvaluateResult = self let result: EvaluateResult = self
.client .client
.send_command_typed( .send_command_typed(
@@ -1222,8 +1230,8 @@ impl BrowserManager {
&json!({ &json!({
"expression": script, "expression": script,
"returnByValue": true, "returnByValue": true,
"awaitPromise": true, "awaitPromise": !repl_mode,
"replMode": true, "replMode": repl_mode,
}), }),
Some(&session_id), Some(&session_id),
) )