Fixes WPT crash /html/semantics/forms/form-submission-0/form-submission-algorithm.html
This fixes two separate possible recursive entries in the form submission
process. Both fixes are spec-accurate (hence the WPT test that specifically
tries it).
In short, the issues are the submit -> submit -> repeat and
formdata -> submit -> formdata -> submit -> repeat.
So two guards are added to track "I'm submitting" and "I'm building the form
data".
A v8 Finalizer can fire at any point, including after the underlying Zig object
has been freed. Even after the corresponding Frame and Page have been freed.
To deal with this, v8 Finalizers are tied to a special
FinalizerCallback.Identity (fci) which tracks whether the underlying Page still
exists (fci.done == true). This ensures that, if v8 calls an finalizer much
later, we don't UAF. All we have to do is cleanup the fci itself and exit -
everything else is already gone.
The problem is, we tied the fci's to the session, but the v8 Finalizer can
outlive the Session (which is meaningful to v8). We made this work by trying
to force the isolate to reclaim memory on session deinit. The problem with this
is that it still isn't guaranteed to work (though, practically speaking, if
this _has_ resulted in any UAF, it's been rare). PLUS it puts load on the v8
worker threads that can compound over short-lived sessions.
The fix is to move the fci to the browser. This is safe because Browser ==
Env == Isolate, so when the browser is torn down, the isolate is torn down, and
at THAT point, we're sure no Finalizers can fire.
With this change, the call to `memoryPressureNotification(.critical)` in
Session.deinit has been removed.
Previously, the logic for data/blob URLs were spread out at every http_client
caller. fetch/XHR/ScriptManager all had their own "if this is a data url, ..."
logic.
This causes two issues.
1 - Duplication, particularly as we try to cover more edge cases that need to
be handled in all places.
2 - Correctness because data/blob URLs are still URLs and still need to be
"fetched" (from memory). They should still fire with the same timing as any
other URL. That means that for fetch/XHR, they should fire asynchronously
(i.e. on the next tick). And for ScriptManager they should fire depending
on the type of script (normal/defer/async).
This PR relies on the infrastructure added to:
https://github.com/lightpanda-io/browser/pull/2506 in order to fulfilled a
synthetic response on the next tick.
Frame.navigate is excluded from this refactor. For one, about:blank must be
special-cased and run synchronously (one of the few places where this is
strictly required) and even blob URLs are a bit different: the blob URL list
is the parent frame, not self, and there's more we need to do (set origin).
Potentially there _is_ some improvement here, but it's both less significant
and less simple.
Implement `bodyUsed` getter for both types. With this implemented, correctly
reject requests when `bodyUsed == true`.
Expand Response to use BodyInit (Request was already using it).
Expand BodyInit to discriminate between a TypedArray/BufferArray and a string,
which allows us to capture the correct content type.
Add Request.priority and in Request/Response body getters, strip BOM as needed.
This fixes two things. First, it better tracks the calling context and the
target context, so that if a parent frame does a document.write within a child
frame, any JavaScript is executed in the child frame's context (previously, it
would be executed in the parent's context).
Second, it ensures that, on document.write, we force-execute any pending
scripts. This is related to https://github.com/lightpanda-io/browser/pull/2542
but applies specifically to document.write.
Currently, any inline deferred script, e.g:
<script type=module>
...
</script>
That happens AFTER load, never executes. Unclear how serious an issue this is,
but it _does_ cause problems for some WPT tests which use document.write to
inject a <script type="module">...</script> block after an iframe is loaded.
For typically name-less elements, check the "role" attribute to see if we should
fallback to the content.
Improves a handful of WPT tests, e.g.: /accname/name/comp_text_node.html
(It seems impossible to get 100% on these tests without knowing what is and
isn't a block-level element which would require more knowledge in the
StyleManager).
popAndInvoke captured the reactions queue as a slice at loop entry. If
firing a reaction triggered a nested scope whose enqueue grew the
underlying ArrayList, the captured slice became dangling and the next
iteration read freed memory. Switch to indexed iteration so the queue
pointer is re-read each step. Repros via wpt/custom-elements/
enqueue-custom-element-callback-reactions-inside-another-callback.html.
The rest of the diff adds .ce_reactions = true to bridge declarations
that were missing it per WebIDL [CEReactions] *and* whose Zig impl
actually performs a DOM/attribute mutation (verified by reading each
setter). Without the flag, the algorithm's enqueue path hits
assertScopeActive and panics. Runtime-state-only setters (Input.value,
Input.checked, Select.value, Option.selected, Media.muted/volume, etc.)
are deliberately left untagged.
Covered:
- CustomElementRegistry.define, .upgrade
- HTMLDocument: body, title, dir
- Document: open, close
- HTMLElement base: insertAdjacentHTML, dir, hidden, lang, tabIndex,
title, innerText
- Range: insertNode, deleteContents, extractContents,
surroundContents, createContextualFragment
- Selection: deleteFromDocument
- HTMLOptionsCollection: add, remove
- DOMStringMap (dataset): namedIndexed setter/deleter — required adding
.ce_reactions to NamedIndexed.Opts in bridge.zig
- ~28 HTMLxxxElement files: every settable attribute that resolves to
setAttributeSafe/removeAttribute (Anchor, Button, Canvas, Data,
Details, Dialog, FieldSet, Form, IFrame, Image, Input, Label, Link,
LI, Media, Meta, OL, OptGroup, Option, Quote, Script, Select, Slot,
Style, TableCell, Template, TextArea, Time, Track, Video)
While this PR touches a lot of files, and isn't trivial, many of the changes
are either:
1 - removing guards added in previous PRs, e.g.
https://github.com/lightpanda-io/browser/pull/1969https://github.com/lightpanda-io/browser/pull/2172https://github.com/lightpanda-io/browser/pull/2313https://github.com/lightpanda-io/browser/pull/2366
2 - Adding the `.ce_reactions = true` flag to various WebAPIs
CustomElements have callbacks, e.g. connectedCallback. Also, many WebAPI calls
are implemented as a series of mutations, e.g. appendChild = remove from current
+ append to new.
These two things interact in an important way: when should callbacks execute?
Before this PR, we were invoking callbacks at each individual step. This is
(a) technically wrong and (b) breaks a lot of assumptions (the reason the above
4 PRs were needed to fix bugs).
This PR adds a `_ce_reactions` queue to the frame. And, instead of invoking
callbacks, we "enqueue" the reaction. At various boundaries, a scope is created
the DOM manipulation is done, and then we pop the scope, invoking all queued
reactions.
Page.navigate("about:blank") (and blob:) issued against a non-blank tab
routed through Session.initiateRootNavigation, which always allocated a
pending Page. A pending Page is promoted to active only by
frameHeaderDoneCallback when HTTP response headers arrive — but synthetic
navigations ZIGFLAGS= make no HTTP request, so the pending Page was never committed
and the previous document stayed active (window.location.href, document.URL
and Page.getFrameTree all kept reporting the old page).
Route synthetic URLs through the existing immediate-swap path
(replaceRootImmediate) from initiateRootNavigation, mirroring what
processRootQueuedNavigation already does for JS-initiated synthetic
navigations. replaceRootImmediate now takes (frame_id, url, opts) so both
call sites share it.
Fixes#2363
The prebuilt archive already lives at .lp-cache/prebuilt-v8/<version>.a;
the v8/libc_v8.a symlink was redundant indirection. build.zig has no
implicit lookup, so the Makefile passes -Dprebuilt_v8_path explicitly
anyway. Point it straight at the cached file, dropping the symlink step,
the v8/ dir, and its .gitignore entry. Keep the version-keyed filename so
bumping the V8 pin fetches a new file instead of clobbering.