Follow up to https://github.com/lightpanda-io/browser/pull/3510
Moves the element/node lookups, e.g. `element_class_lists` from Frame to Page.
Elements and nodes can outlive a Frame (it's the reason the identity map lives
on the Page, not the frame). These maps are merely properties on Node/Elements
optimized for a specific usage-pattern (i.e. most Node/Elements don't have these
or they are never materialized from JS). So if a Node/Element can outlive the
Frame, than so too can all of their properties. And, even when an frame is alive
the properties belong to the *Node* or *Element*, NOT the Frame...accessing
those properties across frames should yield the same value / identity.
More mechanically, frame._page => frame.page and all of these lookups lose their
_ prefix. Short summary of _ prefix is:
1 - It's used to deal with Zig not allowing shadowing. This is particularly true
in the WebApis were it happens a bit more often
2 - Early prototype was built as a stand-alone library, and the _ was used to
signal "private" (again, working around Zig). Frame.page shouldn't be
"private" and neither should these lookups (if we aren't going to provide
getter/setters for them).
Cloning reused the synchronous createElement construction path, which
rejects a result with a parent, attributes, or children. A reparenting
constructor left its instance in the source tree while the clone received
an HTMLUnknownElement fallback with different identity.
Queue an upgrade reaction for autonomous clones instead. Their copied
attributes and descendants are present when construction runs, and super()
returns the copied node. A failed upgrade retains that node rather than
substituting a second element. Keep synchronous createElement validation.
Capture initial upgrade reactions before construction and distinguish the
precustomized state so constructor-time DOM mutations do not enqueue
custom-element lifecycle reactions prematurely.
Add Chromium-checked regressions for identity, reparenting, copied state,
attribute reaction order, importNode and failed upgrades.
A custom element constructor can append itself to the source parent
while that parent's children are being cloned. With two trailing
self-appending elements, the live iterator reaches those new instances
and keeps cloning indefinitely, until the watchdog terminates execution.
Snapshot each child list before traversing it. Apply the same handling
to element, shadow root, fragment and document clone paths, retaining
the document path's existing appendChild behavior.
Add a bounded two-sibling regression so a broken traversal fails
without hanging the test process.
The scroll tool (MCP, agent, LP.scrollNode) wrote scrollTop on the exact
node it was given, so a leaf inside an overflow:auto panel stored an
offset on a non-scroller, the panel's own scroll listener never ran, and
the tool reported the requested coordinates as if it had worked. It also
fired a synchronous bubbling scroll on top of the async non-bubbling
scroll/scrollend the setters already schedule.
actions.scroll now resolves the nearest ancestor-or-self scroll
container, falls back to the node itself, and returns the node that
moved plus the read-back position. The tool and LP.scrollNode report
that instead of the request.
The container query moves from user_input.zig onto Element as
scrollContainer(axes), so the wheel path, the tool and WebDriver share
one resolver. WebDriver's wheel scrolled the hit-test element directly
and fired its own bubbling scroll; it now goes through
user_input.wheelScroll like CDP and BiDi wheel.
Window and Element share one ScrollToOpts. Its offsets() helper
normalizes the positional and dictionary forms once, and an omitted axis
in the dictionary form leaves that axis untouched for the window too,
matching browsers, so scrolling the window on one axis no longer resets
the other.
Browsers treat http://localhost, *.localhost, 127.0.0.0/8 and [::1] as
potentially trustworthy, so Secure and prefixed cookies work there over
plain http. We required an https scheme, which broke cookie-auth logins
under Playwright in local development.
Add URL.isPotentiallyTrustworthy (Chromium's net::IsLocalhost rule) and
use it for the cookie prefix gates, the send-path check and cookieStore.
Closes#3477
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.