fix(#125): DOM mutations not applied after action#125
fix(#125): DOM mutations not applied after action#125lekt9 wants to merge 3 commits intojustrach:mainfrom
Conversation
Request-scoped arenas passed by callers are freed when the HTTP request completes. Buffering raw arena pointers in event_buf.owner caused dangling references when a later har/stop request tried to drain and free those events. push() now dupes the event data into the long-lived event_buf allocator and frees the caller's copy immediately. Adds regression test that simulates the exact failure scenario: events buffered under arena A remain readable and correctly freed after arena A is torn down and arena B reads the buffer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… field fixed - Press action resolves ref to objectId and calls focus() before dispatching Input.dispatchKeyEvent, so key events reach the intended element rather than whatever is currently focused (or nothing) - Key text field is now empty for non-printable keys like Enter/Escape/Tab; only single printable characters (0x20-0x7e) carry text, preventing literal key-name strings from being inserted into focused inputs - Fill action already used nativeInputValueSetter (Object.getOwnPropertyDescriptor) for React/Vue compat; adding tests to cover route param parsing for both actions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After Runtime.callFunctionOn executes a DOM-mutating action (click, dblclick, check, uncheck, fill, type, select, press), the browser queues microtasks and macrotasks for framework state updates (React/Vue reconciliation, MutationObserver callbacks). Returning immediately lets a subsequent /snapshot call read stale DOM. Fix: send Runtime.evaluate with awaitPromise:true and a setTimeout(r, 0)-based Promise after every mutating action. CDP blocks until the promise resolves, which only happens after the current macrotask queue turn drains — ensuring all pending DOM mutations are committed before we respond. Adds regression test verifying the flush JSON payload contains awaitPromise:true and the setTimeout-based Promise expression. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
justrach
left a comment
There was a problem hiding this comment.
Review
This is a solid and needed fix — the microtask flush pattern and native key events are real improvements for SPA compatibility. A few suggestions:
✅ What's good
Input.dispatchKeyEvent(keyDown+keyUp) instead of JSKeyboardEventdispatch — Enter/Escape/Tab now work nativelynativeInputValueSetterbypasses React/Vue controlled input overrides — this was a real pain pointselectnow dispatches bothinput+changeevents for framework compat- Microtask flush with
awaitPromise:trueensures DOM is settled before returning
⚠️ Suggestions
-
Microtask flush depth — The
setTimeout(r, 0)flush drains one macrotask turn. For heavy SPAs (Angular with zone.js, Svelte withtick()), a single turn may not be enough. Consider either:- Two consecutive flushes:
new Promise(r => setTimeout(() => setTimeout(r, 0), 0)) - Or a configurable delay param:
&flush_ms=50for callers who know they need more time
- Two consecutive flushes:
-
HAR flush after every action — The PR adds
flushEventsToHar()after click/fill/press/select/evaluate. This is defensive but adds latency. Consider making it opt-in (&flush_har=true) or only flushing when HAR recording is active (which you partly do with theisRecording()check — good). -
Rebase needed —
client.zighas diverged significantly on main (EventBuffer is nowArrayListUnmanagedwithOwnedEventstruct). Thepush()signature changed. You'll need to rebase against current main.
🔧 Needs rebase
src/cdp/client.zig and src/server/router.zig both have conflicts with main. The router changes (action handling) should rebase cleanly since they're in different regions than our SSRF/bot-detect work.
|
Can you update this PR to build and test against Zig |
Summary
Runtime.callFunctionOndispatches a DOM-mutating action (click, dblclick, check, uncheck, fill, type, select, press), the browser queues microtasks/macrotasks for framework state updates (React/Vue reconciliation, MutationObserver callbacks). The handler was returning immediately, so a subsequent/snapshotcall read stale DOM.Runtime.evaluatewithawaitPromise:trueand asetTimeout(r, 0)-based Promise after every mutating action. CDP blocks until the promise resolves — which only happens after the current macrotask queue turn drains — ensuring all pending DOM mutations are committed before we respond.realisticfill path, and thepresskey-event path.Test plan
action microtask flush params are well-formed JSON (#125)insrc/test/integration.zigverifies the flush JSON payload containsawaitPromise:trueand thesetTimeout-based Promise expressionzig build testexits 0/snapshot— updated DOM should reflect the mutation🤖 Generated with Claude Code