Passes WPT /html/webappapis/atob/base64.html
Two changes
1 - Use the forgiving decoder already in data_url
2 - Coerce input (3 => "3")
The 2nd change was more interesting. These take a js.String.OneByte as an
optimization, which doesn't coerce. To preserve this optimization a union was
used with a `raw: []const u8` fallback (and our bridge always coerces to
a `[]const u8`)
Just a random test I happen to see in WPT, 0/99 -> 99/99:
/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.any.worker.html
This limit isn't about correctness,it's just about making sure a test doesn't
block forever. 2 seconds does seem like plenty of time, but I'm thinking it's a
slow/busy CI. If it still happens after this bump, could mean there's an actual
issue.
The first is with WGS not flushing the deferring layer after its synchronous
importScripts call, see: https://github.com/lightpanda-io/browser/pull/2329#discussion_r3271068955
The second is from the ability of an XHR request to be re-used. This was gated
on a boolean, but the ordering means that the 1st requests' released gets
blocked by the gate, and thus we're always 1 release short. The solution is to
use a counter instead of a boolean.
A fetch() deferred behind a parser-blocking script keeps a DeferredContext
in the DeferringLayer whose forward.ctx points at the Fetch struct, which
lives in a page/session arena. If the transfer completes while deferred,
it is deinited and unlinked from the frame owner, so abortTransfers ->
abortOwner can't reach the now-orphaned context. It lingers in the active
list, and when the page is torn down its Fetch arena is freed; a later
flushFrame (e.g. the next page's parser-blocking script popping) replays
the buffered header callback into the freed Fetch -> use-after-free.
Add DeferringLayer.cancelFrame to drop these orphaned (terminal) contexts
during Frame.abortTransfers. Non-terminal contexts still have a live
transfer that cleans them up through its own callback path, so they are
left alone.
When a CDP client disconnects, through a series of step, we call `Isolate::
TerminateExecution`. The problem is that this only terminates the currently
executing script. If scripts are nested, or another script runs immediately
after the terminated one, the worker keeps running.
I was specifically trying to fix some 100% endless loop in a few WPT tests, e.g
Worker-terminate-forever-during-evaluation.html
The first is the issue described above. We potentially need to guard every entry
into script executing with `if (!env.is_terminated)`. AND/OR we need to bubble
an error when a script _is_ terminated (right now, most JS errors map to
error.JsException which makes this hard). For the specific WPT failures, I opted
for a more targeted fix directly in the Worker. But I think we need to keep an
eye out on other cases where this can happen.
I also discovered another issue, which is not addressed is this commit:
var worker = new Worker("endlessloop.js");
worker.terminate(); <-- never gets called
Given our current architecture, the only solution I can think of is a watcher
thread (or re-using the main/network thread).
A blocking <script src> loads via HttpClient.syncRequest, which spins on
tick(200, .sync_wait). That drain mode only dispatches Fetch-interception
methods (it can't free Page/Frame state mid-parse), so Target.closeTarget /
Target.disposeBrowserContext / Page.close sit undispatched until the blocking
fetch completes — i.e. up to the per-request timeout, per blocking script.
For storefronts with slow/never-completing third-party subresources this
stalls page.close()/context dispose for tens of seconds and collapses CDP
throughput under concurrency (the process stays up; other connections are
unaffected).
Fix: while waiting, peek the inbox; if a teardown/close command (or a
client close/disconnect) is queued, abort the in-flight blocking transfer.
syncRequest then returns the same way it already does on a fetch failure,
the parser unwinds to the next safe .all drain, and the queued command
runs there. No command is dispatched mid-parse, so no UAF.
Repro: 8 workers looping newContext->goto(commit)->page.close against a page
with 15 never-answered subresources went from 0.1 ctx/s (page.close stalling
~30-60s) to ~25 ctx/s; a no-hanging-subresource baseline is unchanged.
Fixes#2646
Wire the CDP Input.dispatchMouseEvent "mouseWheel" type to a new
Frame.triggerMouseWheel, which hit-tests the point via elementFromPoint
and dispatches a wheel event (with deltaX/deltaY) on the target. When
the wheel event is not cancelled, it applies the scroll offset and
fires a scroll event, mirroring the wheel handling in WebDriver.zig.
Previously mouseWheel was silently ignored. Follow-up to #2636.
It has somehow acquired (or always had?) multiple dangling pointers. Luckily
(or worse?) they aren't used anywhere. Remove mime.params (dangles off the
input) and the content_type `.other` values (dangles off the local mime var).
Also, remove double lowerCase.
Cache the xml document so that subsequent calls to getResponseXML return the
same document.
Fix testing.js harness - reject multiple testing.async() calls per block. This
isn't reliable since the runner can temporarily see the count reach 0 before
the next async block starts.
These are needed for apple.com, completely broken without.
Obviously Claude wrote most of this code. The test that it wrote _does_ pass
in FireFox and a reasonable number of WPT *DOMMatrix* test pass.
A relatively small subset of this was needed for Apple, but there was plenty
of low-hanging fruit in the WPT tests.
Also, some of these WPT tests uncovered a name collision in our WPT reporter:
https://github.com/lightpanda-io/wpt/pull/70
Wire the CDP Input.dispatchMouseEvent "mouseReleased" type to a new
Frame.triggerMouseRelease, which hit-tests the point via
elementFromPoint and dispatches a mouseup event on the target,
mirroring triggerMouseClick / triggerMouseMove and the mouseup
semantics already used by WebDriver.zig.
Previously mouseReleased was silently ignored. Follow-up to #2636.
Until now Frame.submitForm performed no constraint validation: requestSubmit()
and interactive submits (Enter, clicking a submit button) fired the `submit`
event and proceeded regardless of control validity — a `<form><input required>`
would submit with the field empty.
Implement the HTML "submit a form element" interactive-validation step: in the
fire_event branch, run form.checkValidity() as a gate before dispatching
`submit`, abort submission when it fails (checkValidity fires `invalid` on the
offending controls), and skip the gate when the form is in the no-validate
state. form.submit() keeps bypassing validation, per spec.
To drive the no-validate state, add the HTMLFormElement.noValidate IDL
attribute reflecting the `novalidate` content attribute, and honor a submitter's
`formnovalidate`. noValidate gates submission only — checkValidity()/
reportValidity() still validate unconditionally.
Populate FileList with real File objects and expose them on
HTMLInputElement:
- input.files returns a live, identity-stable FileList for type=file
(null for other input types)
- input.value returns the spec "C:\fakepath\<name>" string
- required file inputs report value-missing only when empty
Implement CDP DOM.setFileInputFiles: load files from disk into a file
input (MIME sniffed by extension) and fire input + change events.
File backing arenas are reference counted via their Blob proto, so the
owning Frame now acquires/releases them and frees any still held at
teardown, preventing an ArenaPool leak for CDP-set files never read
from JS. A scoped errdefer frees partially-created files when one path
in a multi-file set fails to load.
Return an XMLHttpRequestUpload (inheriting XMLHttpRequestEventTarget) from
the lazily-cached `upload` attribute. Fixes htmx login flows that called
`xhr.upload.addEventListener(...)`.
Adds the overrideMimeType(mime) method.
Wires the final MIME type (override ?? response) into responseXML for
the default response type (""): when the final MIME is text/xml, the
body is lazily parsed into a Document and cached in a new
_response_xml field. Other XML MIME types (application/xml, image/svg+xml)
land in Mime.ContentType.other whose backing slices are unsafe to read
after Mime.parse returns; supporting them is left for a follow-up.
The override does not affect response / responseText today —
responseText charset decoding and the document responseType's
HTML-vs-XML parser choice are noted as follow-ups in the code.
Wire the CDP Input.dispatchMouseEvent "mouseMoved" type to a new
Frame.triggerMouseMove, which hit-tests the point via elementFromPoint
and dispatches mousemove, mouseover and mouseenter on the target,
mirroring the existing triggerMouseClick and the actions.zig hover()
event semantics.
Previously mouseMoved was silently ignored, so element.hover() over
Playwright/Puppeteer (CDP) fired no events. Addresses the hover gap
reported in #2043.
Depends on: https://github.com/lightpanda-io/wpt/pull/69
WPT can send a list of JSON message to the browser in order to simulate user
interaction, e.g.:
{ type: "pointer", actions: [{type: "pointerMove", x, y, origin}, ...] }
While some of these aren't meaningful for us, many are. A lot of these are just:
1 - scroll to an element
2 - mouse down
3 - mouse up
With the main goal of generating trusted events.