Address review: replace the bare 0/1/2 button values in the
mousedown/release switch (Frame.zig) and the CDP button mapping
(input.zig) with named constants so the code self-documents.
Input.dispatchMouseEvent ignored the button and clickCount params, and
mousePressed only fired a click event (never mousedown). Add them:
- mousePressed now fires mousedown carrying the pressed button.
- mouseReleased fires mouseup, then the button-appropriate activation
event: click for the main button, auxclick for the auxiliary button,
and contextmenu for the secondary (right) button.
- a clickCount of 2 additionally fires dblclick.
This unblocks right-click, middle-click and double-click interactions
for Playwright/Puppeteer scripts. Follows the mouse event work in
#2636, #2640 and #2641.
Wire the CDP Input.dispatchMouseEvent "mouseWheel" type to a new
Frame.triggerMouseWheel, which hit-tests the point via elementFromPoint
and dispatches a wheel event (with deltaX/deltaY) on the target. When
the wheel event is not cancelled, it applies the scroll offset and
fires a scroll event, mirroring the wheel handling in WebDriver.zig.
Previously mouseWheel was silently ignored. Follow-up to #2636.
Wire the CDP Input.dispatchMouseEvent "mouseReleased" type to a new
Frame.triggerMouseRelease, which hit-tests the point via
elementFromPoint and dispatches a mouseup event on the target,
mirroring triggerMouseClick / triggerMouseMove and the mouseup
semantics already used by WebDriver.zig.
Previously mouseReleased was silently ignored. Follow-up to #2636.
Populate FileList with real File objects and expose them on
HTMLInputElement:
- input.files returns a live, identity-stable FileList for type=file
(null for other input types)
- input.value returns the spec "C:\fakepath\<name>" string
- required file inputs report value-missing only when empty
Implement CDP DOM.setFileInputFiles: load files from disk into a file
input (MIME sniffed by extension) and fire input + change events.
File backing arenas are reference counted via their Blob proto, so the
owning Frame now acquires/releases them and frees any still held at
teardown, preventing an ArenaPool leak for CDP-set files never read
from JS. A scoped errdefer frees partially-created files when one path
in a multi-file set fails to load.
Wire the CDP Input.dispatchMouseEvent "mouseMoved" type to a new
Frame.triggerMouseMove, which hit-tests the point via elementFromPoint
and dispatches mousemove, mouseover and mouseenter on the target,
mirroring the existing triggerMouseClick and the actions.zig hover()
event semantics.
Previously mouseMoved was silently ignored, so element.hover() over
Playwright/Puppeteer (CDP) fired no events. Addresses the hover gap
reported in #2043.
The intercept state is currently split and hard to keep consistent and even
just reason about. InterceptLayer keeps the `intercepted` count, but CDP's
`BrowserContext` has its intercepted lookup. This isn't a problem per se, but
you BrowserContext.deinit tries to decrement `InterceptLayer.intercepted` which
is only safe if we can guarantee that the two are in sync. Which we can't.
This commit simplifies the upkeep of `InterceptLayer.intercepted` and uses the
Transfer's state on unpark/deinit to decrement it. The CDP layer no longer
cares about / has to maintain the count.
Driven by this crash report:
BrowserContext.deinit.intercepted
---
value: 0
/home/runner/work/browser/browser/src/lightpanda.zig:279:25: 0x2871842 in deinit (lightpanda)
/home/runner/work/browser/browser/src/cdp/CDP.zig:127:18: 0x28c3f45 in deinit (lightpanda)
/home/runner/work/browser/browser/src/Server.zig:186:21: 0x2827997 in handleConnection (lightpanda)
/home/runner/work/_temp/6dc322a8-c74f-4990-9660-4cc6dcfb9352/zig-x86_64-linux-0.15.2/lib/std/Thread.zig:509:13: 0x269c233 in entryFn (lightpanda)
???:?:?: 0x7fce7ccabd57 in ??? (libc.so.6)
Unwind information for `libc.so.6:0x7fce7ccabd57` was not available, trace may be incomplete
on 1.0.0-nightly.6542+94ba0791
Arena reuse/retain can hide UAF issues, often resulting in a crash that is more
symptom than cause (far from where the error actually is). Removing this, lets
us better utilize the DebugAllocator's UAF-detection.
Also, when running WPT tests (-Dwpt_extensions) limit console logging to 100
values (a few tests writer millions of values, which is annoying and just
destroys the terminal).
Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/179
An improvement to https://github.com/lightpanda-io/browser/pull/2515 to prevent
a v8 assertion if we terminate as an inspector dispatch is happening.
The problem is that if we just immediately terminate, we aren't sure what the
worker thread is doing, and, apparently, if we terminate then dispatch a message
to the inspector, we fail an assertion.
With the way the code was, the only safe solution would be to hold a mutex
over the session dispatch, but that could block the network thread.
So instead of terminating from the network thread, we now ask v8 to execute
a callback. This gets executed on the worker thread, which can then terminate
the execution.
The initial version of 2515 delayed the termination from the network thread.
It's possible that solution would "solve" the issue, simply because it's very
unlikely that a worker would be "stuck" for 5 seconds and then get unstuck.
More likely that it exits immediately, or is stuck in an endless loop. But
that would still leave a window where we could terminate in network and then
dispatch in the worker. Less likely, but still possible. Hopefully this new
mechanism eliminates this from being a problem in all circumstances.
Previously, Cookie.Jar.add would only conditionally take over the cookie. The
caller had no way to know whether or not to deinit it. This could result in
a double-free on certain error paths.
Cookie.Jar.add now unconditionally takes ownership of the cookie.
Three changes:
- js.Execution now has a page (instead of calling exec.context.page)
- js.Execution now has a session (instead of calling exec.context.page.session)
- js.Execution.context renamed to .js to be consistent with Frame and WGS
For typically name-less elements, check the "role" attribute to see if we should
fallback to the content.
Improves a handful of WPT tests, e.g.: /accname/name/comp_text_node.html
(It seems impossible to get 100% on these tests without knowing what is and
isn't a block-level element which would require more knowledge in the
StyleManager).
Introduces `Input.getRedactedValue` to mask password values in
LLM-facing dumps (semantic tree, forms, AXNode) instead of
exposing raw values or using ad-hoc checks.
Page.navigate("about:blank") (and blob:) issued against a non-blank tab
routed through Session.initiateRootNavigation, which always allocated a
pending Page. A pending Page is promoted to active only by
frameHeaderDoneCallback when HTTP response headers arrive — but synthetic
navigations ZIGFLAGS= make no HTTP request, so the pending Page was never committed
and the previous document stayed active (window.location.href, document.URL
and Page.getFrameTree all kept reporting the old page).
Route synthetic URLs through the existing immediate-swap path
(replaceRootImmediate) from initiateRootNavigation, mirroring what
processRootQueuedNavigation already does for JS-initiated synthetic
navigations. replaceRootImmediate now takes (frame_id, url, opts) so both
call sites share it.
Fixes#2363
If a worker is in some heavy JS e.g. `for(;;)` it will be stuck forever, even
if the peer closes the CDP connection.
With CDP reads now owned by the network thread, we now correctly detect the
disconnect and simply need to force the worker to shutdown. To achieve this,
on socket close, the CdpLink held by the network is given a terminate_ms (five
seconds from now) and added to a linked list. On every wakeup, the network
thread can check the list + timestamp and, if necessary, call Isolate::Terminate
(which is safe to call on a different thread).
syncRequest's `terminated` early return (added in 88b98e70) exits before request() takes ownership of req.headers, so the curl_slist leaked after a latched disconnect (any nested fetch / importScripts / external stylesheet). Free it in the early return, mirroring request()'s own failure paths.
Also adds a behavioral syncRequest-after-disconnect test. The leak itself isn't assertable here (curl_slist is C-allocated through the tracking allocator, outside the per-test leak check), so it's verified by the ownership contract rather than the suite.
Drives a .disconnect through the worker's inbox and asserts a second tick still returns error.ClientDisconnected once the inbox is empty. Fails against the pre-latch HttpClient (the worker would re-park in perform with no producer to wake it); passes with the latch.
This largely reverts 92607ad765 (captured in PR:
https://github.com/lightpanda-io/browser/pull/2398).
https://github.com/lightpanda-io/browser/pull/2495 introduces protection against
execution arbitrary CDP command during JavaScript callbacks. Claude initially
made the case for keeping the existing code as a safety net, but sycophanted
when I pushed by.
My reason for removing it is that it isn't a low-maintenance guard. It's a flag
that serves a real purpose (ensuring 1 JS script is finished before executing
another one), that has been expended to solve these issues. It needs to be set
(and reverted) at every callsite that makes a blocking call, and it needs to be
checked (recursively across all frames) in any place that can teardown the page/
frame.
Claude called the allowlist "load-bearing in a non-obvious way", but I think
it's purpose built specifically for this case. Extended the comment atop
`allowDuringSyncWait` so that future-selves remember this.
Fold QueryWriter into Writer behind an Opts.filter. Tree mode is unchanged
(filter=null); query mode walks the full subtree (including AX-ignored
nodes per the queryAXTree spec) and emits the flat-match shape. Shared
resolveRole helper handles label-promotion for both paths so the two
can't drift.
Drop the "objectId not yet supported" carve-out: queryAXTree now reuses
dom.getNode, which already resolves nodeId/backendNodeId/objectId.