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.
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.
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.
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.
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).
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.
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).
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.
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.
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.
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.
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.
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).
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.
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>
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.
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.