Commit Graph
7681 Commits
Author SHA1 Message Date
Karl Seguin 2927799cd3 Merge pull request #3508 from lightpanda-io/improve-js-memory-metric
ops: improve v8 memory metric reporting
2026-09-14 14:35:12 +08:00
Karl Seguin 7382efe688 Merge pull request #3501 from lightpanda-io/frameless-documents
webapi: better handling of frameless documents
2026-09-14 14:35:00 +08:00
Karl Seguin 5163c6d385 Merge pull request #3506 from lightpanda-io/silence-test-logs
chore: silence expected error log in MO test
2026-09-14 08:57:19 +08:00
Karl Seguin 19b165d099 Merge pull request #3502 from lightpanda-io/selected-options
chore: cleanup selected / selected options
2026-09-14 08:19:34 +08:00
Karl Seguin 3adacb6900 Merge pull request #3505 from staylor/fix/window-tostringtag
Fix Symbol.toStringTag inheritance for globals and elements
2026-09-14 08:19:09 +08:00
Karl Seguin f2abadbb0e Merge pull request #3484 from lightpanda-io/xhr_cors
XHR changes for CORS WPT tests
2026-09-14 08:18:44 +08:00
Karl Seguin dcd5c796ed ops: improve v8 memory metric reporting
Report the v8 memory every 1 second during tick. The previous model only
reported at four points and would fail to correct capture memory growth between
those points.
2026-09-14 08:15:40 +08:00
Karl Seguin 27b16af770 zig fmt *eyeroll* 2026-09-14 08:05:12 +08:00
Karl Seguin adb2b21ccf chore: silence expected error log in MO test 2026-09-14 08:01:00 +08:00
Muki Kiboigo 22e095370b add unit test for XMLHttpRequest parseMethod 2026-09-14 07:58:57 +08:00
Muki Kiboigo 00c98313c2 use curl no body option for head requests 2026-09-14 07:58:56 +08:00
Muki Kiboigo b3655f5918 shortcut default/text getResponse instead of caching path 2026-09-14 07:58:07 +08:00
Muki Kiboigo 8bbe1907b0 parseMethod for HEAD requests in XHR 2026-09-14 07:58:07 +08:00
Muki Kiboigo 3a7b3f9e1f XHR response can return before done on text/default 2026-09-14 07:58:07 +08:00
Karl Seguin 47eb1b8581 make acquireArena names more consistent with each other 2026-09-14 07:44:21 +08:00
Scott Taylor f8183e2168 js: inherit Symbol.toStringTag from interface prototypes
Web IDL defines Symbol.toStringTag on interface prototype objects, not
instances. Setting it on instance templates left globals and internal
custom/generic element subclasses without a tag, so they reported
[object Object]. Interface prototypes also reported the wrong class string.

Deep-clone helpers that recurse into plain objects consequently traverse
host objects and their cyclic references. Locally, a storefront theme's
store cloner classified custom/generic elements as plain objects and
exhausted the JS heap, terminating the CDP connection.

Set the tag on the member template: interface prototypes for inheritance,
while namespace objects keep their existing own-property behavior.
Test class strings, property descriptors, namespace ownership, and bounded
deep cloning that preserves host-object identity.
2026-09-13 19:27:18 -04:00
Scott Taylor c284fce11a cdp: preserve message storage across reentrant notifications
Runtime.consoleAPICalled serialized object arguments with JSON.stringify,
which can run getters and toJSON callbacks. A callback can log again or
navigate, emitting further CDP events while the outer event is being built.
Resetting send_arena after each send and notification_arena after each
handler then invalidates the outer message's buffers. This produces
use-after-free in debug builds or malformed JSON that disconnects clients.

Scope both arenas to the outermost send or notification handler, including
inspector messages and error paths. Also match Chrome's console argument
representation: objects remain remote handles, primitives carry value,
and non-JSON numbers and bigints use unserializableValue. Console logging
must not invoke object getters or toJSON as a serialization side effect.

Test complete nested WebSocket messages, failure recovery, nested legacy
Console notifications, and primitive/object protocol shapes.
2026-09-13 19:27:18 -04:00
Adrià Arrufat db9779a654 Enable CURLOPT_PIPEWAIT on every easy handle
Requests issued to an origin while its first connection is still
handshaking each opened their own socket, up to --http-max-host-open,
because curl only learns from ALPN whether the origin multiplexes. With
pipewait they wait for that answer and share one h2 connection.

Fixture: 12 fetch() calls to a fresh cdnjs (h2) origin, release build.

  new TCP+TLS connections   6 -> 1
  in-page time to last resp ~285 ms -> ~105-135 ms

H1-only origins are unchanged in connection count; their first burst
waits one handshake before fanning out.
2026-09-13 22:09:36 +02:00
Karl Seguin 0d9e5441f5 chore: cleanup selected / selected options
There's been recent work on improve select / options:
- https://github.com/lightpanda-io/browser/pull/3375
- https://github.com/lightpanda-io/browser/pull/3402
- https://github.com/lightpanda-io/browser/pull/3499

One of the main issues is that a select's options "selected" state wasn't always
kept in sync. Select.zig had a boolean flag to mark whether or not a
selectedIndex was explicit set and every read and update would need to dance
around it. Removing the selectedIndex option would not, for example, keep things
in sync.

This removes the flag and keeps the Option._selected in sync, i.e. the sync
happens on write, not on read and is thus naturally recorded in the state of
the Select and its Options.

The write path is more complicated, but the read path is simpler (though the
real win is always being correct).
2026-09-13 14:13:33 +08:00
Scott Taylor 6d454529a5 Fix select value assignment with no matching option
An unmatched select.value assignment must leave every option unselected,
with value == "" and selectedIndex == -1. Reapplying the first-option
fallback on reads instead can keep change-driven select mirroring loops
from converging.

Preserve an explicitly cleared selection until the option list changes.
Restore the single-select default after insertion, removal, moves, and
fragment parsing, without inventing a selection for multiple selects or
listboxes. Select only the first matching option for value assignments.

Cover empty values, duplicate values, grouped options, structural resets,
and bounded select mirroring; compare the new assertions with Chromium.
2026-09-12 23:58:50 -04:00
Scott Taylor 02c4ef8777 test: exception object relocated by GC while its TryCatch is read
Regression page for eff118eb2 (don't copy TryCatch by value): a
MutationObserver callback throws an object whose `message` getter
allocates enough to force several scavenges while `TryCatch.caught` is
between `Exception()` and `StackTrace()`. On a copy of the TryCatch the
relocated exception's old address was read back; with a debug V8 this
failed 7/7 runs before that fix (unknown instance type, or a fault inside
the pointer-compression cage).
2026-09-12 22:32:21 -04:00
Karl Seguin d1ea326b44 webapi: better handling of frameless documents
We've auto-injected `*Frame` into WebApi since forever (used to be called *Page,
but then we split *Page / *Frame, but same same). And it worked wonderfully:
there was always a single *Frame, so the Frame/Context that the JS was being
executed in HAD to be the *Frame that a node belonged to.

But with the addition of iframe and popups, that truth no longer holds. The
*Frame executing the JS (which is the frame that we auto-inject) isn't
necessarily the *Frame that owns a Node.

This is particularly problematic because the *Frame holds a bunch of node/
element data, e.g. `_element_datasets`. So now the DataSet that you get back
depends on the context in which its called..they don't have identity and can
fall out of sync. Some code calls node.ownerFrame() / node.ownerDocument(), but
not all and the hope is to address this throughout the codebase once and for
all.

This is the first in a series of commits meant to fix this long-standing
issue. All it does is change the node.ownerFrame() return value from *Frame to
?*Frame. It's up to each caller to decide how to handle a frameless node, e.g.
clicking a frameless link should not navigate.
2026-09-13 10:31:30 +08:00
Karl Seguin 799dc3e06d Merge pull request #3479 from lightpanda-io/wheel-scrolls-viewport
cdp: wheel events scroll the viewport unless over a scroll container
2026-09-13 09:06:29 +08:00
Karl Seguin cc64255b35 Merge pull request #3497 from lightpanda-io/try-catch-copy
crash: don't copy TryCatch by value
2026-09-13 09:06:06 +08:00
Karl Seguin 9b61b498a9 make css property compare case-insensitive 2026-09-13 08:41:31 +08:00
Karl Seguin ee735143b2 Merge pull request #3474 from lightpanda-io/prune-hidden-walks
Probe own visibility inside walks that prune hidden subtrees
2026-09-13 08:30:26 +08:00
Karl Seguin 128233c4ff Merge pull request #3494 from lightpanda-io/robot-store-leak
mem: fix robot store leak
2026-09-13 08:29:51 +08:00
Karl Seguin eff118eb2e crash: don't copy TryCatch by value
The v8.Handle is initialized / registered with v8 at that address and can't
be copied.
2026-09-13 08:23:57 +08:00
Adrià Arrufat 91cdb52829 cdp: prune accessibility tree children when node is hidden
💘 Generated with Crush

Assisted-by: Crush:gemini-3.8-flash
2026-09-12 14:28:22 +02:00
Adrià Arrufat 55dcad50d1 cdp: wheel scrolls per axis and lets scrollBy schedule the scroll events
Each wheel axis now looks for the nearest ancestor whose inline overflow
along that axis is auto or scroll, and scrolls it through Element.scrollBy,
or the viewport through Window.scrollBy, so the trusted scroll and
scrollend events are scheduled once instead of firing a second, bubbling
scroll inline. The lookup reads the inline style straight from the style
manager instead of building a computed-style object per ancestor.

Both scrollBy implementations saturate the addition, so an oversized delta
from CDP or from a script no longer overflows the i32 position.
2026-09-12 14:12:27 +02:00
Adrià Arrufat 365bc1e1d2 Merge remote-tracking branch 'origin/main' into wheel-scrolls-viewport 2026-09-12 14:07:06 +02:00
Karl Seguin e8aa759390 Merge pull request #3495 from lightpanda-io/HttpClient-Owner-scope
chore: dedupe HttpClient.Owner using new GlobalScope
2026-09-12 16:33:17 +08:00
Karl Seguin 5e849c1bfe logs: demote UrlBlocked errors from err -> warn
Kept a `UrlBlocked` on the main navigation an error, since that seems something
worth raising to the user.
2026-09-12 12:29:54 +08:00
Karl Seguin 35c0a9aeda chore: dedupe HttpClient.Owner using new GlobalScope
https://github.com/lightpanda-io/browser/pull/3447 made better use of the
GlobalScope to simplify various callsites. This changes HttpClient.Owner to
contain the global_scope, rather than copying a handful of scope fields.
2026-09-12 12:09:56 +08:00
Karl Seguin 7a4e2f62bd mem: fix robot store key leak
The RobotStore is shared by all Browsers. While every browser has a single
flight to prevent duplicate requests to the same robots.txt, that's limited to
that specific browser. So, 2 browsers can ask for the same robots.txt and then
put try to store the result. The RobotStore _is_ thread safe, but it's a simple
last-one-wins which overrites the previous record, without freeing either the
key or value.

This replaces the last-write-wins with a first-write-wins, avoiding the leak.
2026-09-12 11:39:27 +08:00
Karl Seguin d812361f2a Merge pull request #3431 from R4m1r0qu41/fix/click-dispatch-full-mouse-sequence
fix(actions): click dispatches full pointer/mouse sequence, not a bare click event
2026-09-12 08:07:50 +08:00
Karl Seguin 702c223cc8 Merge pull request #3493 from lightpanda-io/html5ever-dep-upgrade
deps:  upgrade html5ever
2026-09-12 07:53:26 +08:00
Karl Seguin efcc641440 Merge pull request #3490 from lightpanda-io/xml-crash
skip null nodes when a create callback fails
2026-09-12 07:45:39 +08:00
Karl Seguin 4647dbeee9 deps: upgrade html5ever
Specifically for https://github.com/servo/html5ever/commit/a7588d1a94c7f56c7d6e52a4d32c1d330240e809
which addresses an index overflow we saw in prod.
2026-09-12 07:23:16 +08:00
Adrià Arrufat 14f05408c0 cdp: extract HiddenOptions struct 2026-09-11 15:48:05 +02:00
Adrià Arrufat 33d0a6c6c3 Fold the own-style visibility probe into isHidden as an option
isHiddenSelf duplicated isHidden minus the ancestor walk, on both the
StyleManager and the AX writer. An ancestors flag on the existing options
struct keeps one entry point and states the precondition at the call site.
2026-09-11 15:45:46 +02:00
Karl Seguin 9632a6e9a8 webapi: improve OffscreenCanvas and fetch
1. Add canvas getter to `OffscreenCanvasRenderingContext2D`, and cached
contet to OffscreenCanvas, aligning the OffscreenCanvas* with the non-Offscreens

2. Improve Request's stream support, draining on first use

3. Fix Request leak on error (unlikely to happen, but noticed the ordering was
   wrong.)

1. and 2.were  observed in the scraper logs...#1 happens _a lot_ (a wordpress
plugin).
2026-09-11 21:35:11 +08:00
Adrià Arrufat 1fc9a40d15 Merge branch 'main' into prune-hidden-walks
# Conflicts:
#	src/browser/StyleManager.zig
#	src/browser/webapi/Document.zig
#	src/server/cdp/AXNode.zig
2026-09-11 15:34:07 +02:00
Pierre Tachoire 76a2ff46ed skip null nodes when a create callback fails
xml5ever accepts processing instruction targets that the DOM rejects,
like <?1x?>. createProcessingInstruction then fails and returns null,
but xml5ever still appends the null node, and getNode segfaults.

Make NodeOrText.node optional and skip failed nodes in the append
callbacks. Parser.err is already set, so the XML parse returns a
<parsererror> document.
2026-09-11 12:23:00 +02:00
Karl Seguin b2ca7fb648 mem: free v8's backing store pointer
whenever we get a backingstore's shared pointer, we need to release it
2026-09-11 18:06:06 +08:00
Ramiro_quaiandClaude Opus 5 4844a24878 test(cdp): cover the mousedown preventDefault gate on the CDP path
The agent-script tests only drive actions.click, so Input.dispatchMouseEvent's
own gate had no coverage: deleting the `if (!suppressed)` in triggerMousePress
left the whole suite green.

Press on a preventDefault-ing element and assert focus is kept, then press a
focusable one and assert focus moves, so the test pins the gate rather than an
inert default action. Verified to fail when the gate is removed.

The WebDriver path (performPointerSource) is still uncovered; WebDriver.zig has
no unit tests and is exercised through WPT testdriver, so that one needs a
harness rather than another test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 03:26:34 -06:00
Karl Seguin 9dc7b1bcb2 Merge pull request #3488 from lightpanda-io/isolate_shutdown_notification
v8: call v8__Platform__NotifyIsolateShutdown on isolate shutdown
2026-09-11 17:09:32 +08:00
Adrià Arrufat b141439c83 refactor(input): adopt dispatchCancelable in wheel/keypress/press, trim comments
Route the mouse-wheel, keypress and press paths through the new
EventManager.dispatchCancelable instead of open-coding the acquire-ref /
dispatch / read-defaultPrevented dance three more times. Use
getAttributeInterned for href/tabindex in the focusability helpers to match
their neighbors, and drop WHAT-restating comments.
2026-09-11 10:49:29 +02:00
Adrià Arrufat 0e04a2fe5d Merge remote-tracking branch 'origin/main' into fix/click-dispatch-full-mouse-sequence
# Conflicts:
#	src/browser/frame/user_input.zig
#	src/browser/webapi/Element.zig
2026-09-11 10:39:53 +02:00
Adrià Arrufat 032502db21 refactor(input): share dispatch-cancelable and native-focusable helpers
Extract EventManager.dispatchCancelable so the acquire-ref/dispatch/read-
defaultPrevented pattern lives in one place instead of four copies across
actions.zig, user_input.zig and WebDriver.zig. Share a single
isNativelyFocusable predicate between the mouse-focus rules and moveFocus,
which had the same tag switch verbatim, and fold the SVG-link check into
isSvgLink. Return early from Element.focus when the element is already
active, before the visibility walk, so a click no longer pays a CSS
ancestor walk to re-focus a native control. Collapse the repeated MCP
click-test blocks into a selector loop and give the runtime focus test
active()/expectActive() helpers.
2026-09-11 10:35:37 +02:00