This fixes a bug where only the first text node was being returned, causing fragmented text nodes (e.g. <span>Sub</span><span>mit</span>) to be missing their trailing text.
The introduction of frames means that data is no longer tied to a specific Page
or Context. 255b9a91cc introduced Origins for
v8 values shared across frames of the same origin. The commit highlighted the
lifetime mismatched that we now have with data that can outlive 1 frame. A
specific issue with that commit was the finalizers were still Context-owned.
But like any other piece of data, that isn't right; aside from modules, nothing
should be context-owned.
This commit continues where the last left off and moves finalizers from Context
to Origin. This is done in a separate commit because it introduces significant
changes. Currently, finalizers take a *Page, but that's no longer correct. A
value created in one Page, can outlive the Page. We need another container. I
original thought to use Origin, but that isn't know to CDP/MCP. Instead, I
decide to enhance the Session.
Session is now the owner of the page.arena, the page.factory and the
page.arena_pool. Finalizers are given a *Session which they can use to release
their arena.
Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/153
In some ways this is an extension of
https://github.com/lightpanda-io/browser/pull/1635 but it has more implications
with respect to correctness.
A js.Context wraps a v8::Context. One of the important thing it adds is the
identity_map so that, given a Zig instance we always return the same v8::Object.
But imagine code running in a frame. This frame has its own Context, and thus
its own identity_map. What happens when that frame does:
```js
window.top.frame_loaded = true;
```
From Zig's point of view, `Window.getTop` will return the correct Zig instance.
It will return the *Window references by the "root" page. When that instance is
passed to the bridge, we'll look for the v8::Object in the Context's
`identity_map` but wont' find it. The mapping exists in the root context
`identity_map`, but not within this frame. So we create a new v8::Object and now
our 1 zig instance has N v8::Objects for every page/frame that tries to access
it.
This breaks cross-frame scripting which should work, at least to some degree,
even when frames are on the same origin.
This commit adds a `js.Origin` which contains the `identity_map`, along with our
other `v8::Global` storage. The `Env` now contains a `*js.Origin` lookup,
mapping an origin string (e.g. lightpanda.io:443) to an *Origin. When a Page's
URL is changed, we call `self.js.setOrigin(new_url)` which will then either get
or create an origin from the Env's origin lookup map.
js.Origin is reference counted so that it remains valid so long as at least 1
frame references them.
There's some special handling for null-origins (i.e. about:blank). At the root,
null origins get a distinct/isolated Origin. For a frame, the parent's origin
is used.
Above, we talked about `identity_map`, but a `js.Context` has 8 other fields
to track v8 values, e.g. `global_objects`, `global_functions`,
`global_values_temp`, etc. These all must be shared by frames on the same
origin. So all of these have also been moved to js.Origin. They've also been
merged so that we now have 3 fields: `identity_map`, `globals` and `temps`.
Finally, when the origin of a context is changed, we set the v8::Context's
SecurityToken (to that origin). This is a key part of how v8 allows cross-
context access.
Per DOM spec, appendData(data) is defined as replaceData(length, 0, data).
While the range update would be a no-op (offset=length, count=0), routing
through replaceData ensures consistent code path and spec compliance.
Per DOM spec, all live ranges must have their boundary offsets updated
when CharacterData content changes (insertData, deleteData, replaceData,
splitText) or when nodes are inserted/removed from the tree.
Track live ranges via an intrusive linked list on Page. After each
mutation, iterate and adjust start/end offsets per the spec algorithms.
Also fix Range.deleteContents loop that read _end_offset on each
iteration (now decremented by the range update), and Range.insertNode
that double-incremented _end_offset for non-collapsed ranges.
Route textContent, nodeValue, and data setters through replaceData
so range updates fire consistently.
Fixes 9 WPT test files (all now 100%): Range-mutations-insertData,
deleteData, replaceData, splitText, appendChild, insertBefore,
removeChild, appendData, dataChange (~1330 new passing subtests).
- Remove scale, contain-intrinsic-size, animation-range, text-box-edge
from isTwoValueShorthand: these have asymmetric or 3-value semantics
that make "X X" → "X" collapse incorrect.
- Remove line-height from isLengthProperty: bare 0 is the unitless
number multiplier, not a length (Chrome serializes as "0" not "0px").
- Fix test: background-size "cover cover" is invalid CSS, use "auto auto".
Add missing properties to isLengthProperty (0→0px) and
isTwoValueShorthand (duplicate value collapse) maps based
on WPT test failures in css/css-sizing, css/css-align,
css/css-scroll-snap, css/css-logical, and others.
New length properties: scroll-margin/padding-*, column-width,
column-rule-width, grid-column/row-gap, outline, shape-margin,
offset-distance, translate, animation-range-*, block-step-size,
text-decoration-inset, and *-rule-*-inset (CSS Gaps).
New two-value shorthands: scroll-padding-block/inline,
scroll-snap-align, background-size, border-image-repeat,
mask-repeat/size, contain-intrinsic-size, scale, text-box-edge,
animation-range, grid-gap.
Calling it here ensures that the inspector gets reset on internal page
navigation. We were seeing intermittent segfaults on a problematic WPT tests
(/encoding/legacy-mb-japanese/euc-jp/) which I believe this solves.
(The tests are still broken. Because we don't support form targets, they cause
the root page to reload in a tight cycle, causing a lot of context creation /
destruction, which I thin was the issue. This commit doesn't fix the broken test
but it hopefully fixes the crash).
Also, clear out the Inspector's default_context when the default context is
destroyed. (This was the first thing I did to try to fix the crash, it didn't
work, but I believe it's correct).
- TreeWalker.Full instead of FullExcludeSelf so querying a specific
nodeId evaluates the root element itself
- resolve href to absolute URL via URL.resolve
- isDisabled checks ancestor <fieldset disabled> with legend exemption
- parameter order: allocator before *Page per convention
Allows checking if a direct listener exists, if it doesn't, event creation can
be skipped.
I looked at a couple sites, the benefits of this is small.
Most sites don't seem to trigger that many direct dispatches and when they do,
they seem to have a listener 50-75% of the time.