The Linux release artifacts and the e2e bench build link with
-Dorderfile=orderfile/lightpanda.ld, but the Dockerfile runs its own
zig build without it, so the published image shipped an unordered binary.
CDP bench (demo/puppeteer/cdp.js, RUNS=100), release builds with and
without the flag, 3 alternating reps each:
tmpfs: VmHWM 28.8MB -> 24.2MB
ext4: VmHWM 38.6MB -> 30.1MB
RssAnon is unchanged (~3.0MB) and avg run duration is within noise, as
expected from a link-order-only change. Cost is ~2s of link time in the
builder stage.
?*Element -> *Element to getAttribute, getOrCreateAttribute, and toAttribute
which never need the option.
Assert that document._page == page.
Text use asCData() instead of constantly calling Factory.protoOf.
Runs microtasks during parsing, rather than waiting for parsing to complete.
We see some sites that setup MutationObserver on the root, early in a document's
code. These then get thousands (4K-10K) of MutationRecords for every added node.
We deliver these as a single batch at the end of parsing. Chrome delivers it
based on some elapsed time calculation (I think).
Both are correct, as far as I can tell. And, I don't really expect this to
change anything. But, because the batch size is fixed:
1 - the inflight metric should be flatter
2 - there could be some small reduction in retained memory (e.g. MO's
`_pending_records` might not grow so much)
3 - `call_arena` doesn't need to dupe such a large array
Related to (1), when we do special builds that log arena usage and eliminating /
reducing this known OK behavior helps remove some noise (Or, put it this way:
I spent some time debugging this, it's more or less nothing, but it still
looks like something that needs fixing, this commit reduces that noise).
Follow up to https://github.com/lightpanda-io/browser/pull/3501.
To recap: many apis use the injected-frame, when, for cross-frame operations,
can be wrong. We've been moving to using the Node's Document's Frame on a case-
by-case basis. The goal with this series of PRs is to more holistically fix it.
The main goal of this commit is to make Node.getDocument fast. Previously,
Node.nodeDocument (renamed to Node.getDocument) and Node.ownerFrame had to walk
up the _parent tree to find the document. Now there's a DocumentRegistry on
the Browser, and every Node has a `_document: u32` index. Thus, Node -> Document
is O(1). This makes it so we can have correctness without a performance cost.
The DocumentRegistry is held on the Browser because of shared CDP nodes. A
follow up should be able to move this to the Page and change the index to u16.
Adding a DocumentRegistry to Browser, and a document: u32 to Document is easy.
The reason this PR is so big is because every `node_factory` and many Factory
methods need to be aware of all of this. You can't create an HTMLDivElement
without the document it belongs to. I incorporated more expected future changes
into the node_factory/factory mechanism so that [hopefully] the next PRs
don't have to touch any of this code (e.g. I removed Frame as a parameter and
added a *Page to every Document so that documents have access to factory/arena/
etc..because a `*Document` might not have a `*Frame`, but it will always have a
`*Page`.
Give every document an index (u32), and store that index in Node.
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.