Fixes 24 failing tests in WPT /dom/events/Body-FrameSet-Event-
Handlers.html (24/48 -> 48/48).
Per the HTML spec, the onblur, onerror, onfocus, onload, onresize and
onscroll event handlers of body and frameset elements are aliases for
the Window's handlers (the "window-reflecting body element event
handler set"), and setting the corresponding content attribute on those
elements must activate the Window's handler too. Two groups of
failures:
- "Set ...": assigning a non-callable (e.g. "") to the IDL attribute
threw "invalid argument" instead of storing null, because the setters
took ?js.Function.Global. Per [LegacyTreatNonObjectAsNull], the
Body/FrameSet setters now use Window.FunctionSetter (function or
anything-else-means-null), like the existing Window handler setters.
- "Forward ... to Window": only body.onload was forwarded. Window now
stores onblur/onfocus/onresize/onscroll handlers too (and exposes the
matching accessors, which it lacked entirely), and Body/FrameSet
implement getters/setters forwarding all six handlers to the Window
of the current frame. Setting or removing one of the six content
attributes (at parse time via Build.complete or at runtime via the
attributeChange/attributeRemove build hooks) compiles the handler
body and stores it on the Window, so window.onX === element.onX.
Coverage: /dom/events/Body-FrameSet-Event-Handlers.html 24/48 -> 48/48.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes 2 failing tests in WPT /dom/events/AddEventListenerOptions-
signal.any.html (and its .worker variant): "Passing null as the signal
should throw" (with a valid and with a null listener).
The AddEventListenerOptions dictionary declares `signal` as a
non-nullable AbortSignal, so per Web IDL an explicit null (or any
non-AbortSignal value) must be rejected with a TypeError during the
dictionary conversion — even when the listener argument is null. The
bridge collapsed both a missing member and an explicit null into a Zig
null, silently ignoring the signal.
EventTarget.addEventListener now receives the signal member as a raw
js.Value: an absent or undefined member means "no signal", anything
else is converted to an AbortSignal (throwing a TypeError when it
isn't one), and the conversion runs before the null-callback early
return to match the spec's argument-conversion ordering.
Coverage: /dom/events/AddEventListenerOptions-signal.any.html
9/11 -> 11/11, /dom/events/AddEventListenerOptions-signal.any.worker.html
9/11 -> 11/11.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The applied review suggestions switched the getNames/getIndexes
enumerator callbacks of HTMLCollection, NamedNodeMap and DOMStringMap
from *const js.Execution to the injected *Frame, but only partially:
signatures and bodies disagreed and the build broke. Complete the
conversion — the bridge resolves *Frame from the context, so the manual
exec.js.global switch goes away, and the arenas/JS locals are reached
through the frame.
Co-Authored-By: Karl Seguin <karlseguin@users.noreply.github.com>
The Caller.deleteIndex and Caller.deleteNamedIndex helpers are invoked
by the bridge for definer interceptors too, since definers share the
deleter's (self, index/name) -> bool calling shape. Rename them (and
their private counterparts) to deleteOrDefineIndex and
deleteOrDefineNamedIndex so the name reflects both uses.
No behavior change.
Co-Authored-By: Karl Seguin <karlseguin@users.noreply.github.com>
Fixes the remaining WPT /dom/collections failures around setting,
defining and describing HTMLCollection properties:
- HTMLCollection-own-props.html: assigning to a supported named property
or to any array index must fail (TypeError in strict mode), while
expandos for unsupported names must keep working.
- HTMLCollection-supported-property-indices.html: indexed property
descriptors must be {enumerable: true, configurable: true, writable:
false}, Object.defineProperty on any array index must throw, and
deleting a supported index must fail.
- HTMLCollection-supported-property-names.html: named property
descriptors must be {enumerable: false, configurable: true, writable:
false} ([LegacyUnenumerableNamedProperties]) and defineProperty on a
supported name must throw.
Bridge changes, mirroring the existing interceptor callbacks:
- bridge.Indexed gains a definer callback; bridge.NamedIndexed gains
definer and descriptor callbacks, exposed through new indexedFull and
namedIndexedFull builders so existing indexed/indexedReadWrite/
namedIndexed call sites are untouched. Definer and descriptor
handlers reuse the deleter (name -> bool) and getter (name -> value)
calling shapes. The stale void-returning definer/descriptor typedefs
in the v8 binding are bridged with @ptrCast; v8 actually reads the
returned u32 as v8::Intercepted.
- Caller's query helpers (shared queryReturn) now accept, besides the
existing bool shape, query callbacks returning the
v8.PropertyAttribute bits directly (e.g. ReadOnly), with
error.NotHandled falling through to the ordinary lookup.
HTMLCollection registers:
- an indexed setter and definer that always fail (no indexed setter in
Web IDL), and an indexed query reporting ReadOnly for supported
indices;
- a named definer that fails for supported names, and a named
*descriptor* (not query) reporting {writable: false, enumerable:
false, configurable: true}. Using the descriptor callback instead of
query is deliberate: the ordinary [[Set]] path does not consult it, so
assigning through a derived object (HTMLCollection-as-prototype.html)
still creates an expando on the receiver, as the spec's
ignore-named-props rule requires, while direct assignments fail via
the definer.
Coverage: /dom/collections is now fully green (53/53):
- HTMLCollection-own-props.html 4/8 -> 8/8
- HTMLCollection-supported-property-indices.html 4/7 -> 7/7
- HTMLCollection-supported-property-names.html 5/6 -> 6/6
- HTMLCollection-as-prototype.html stays 2/2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the Object.getOwnPropertyNames related failures in WPT
/dom/collections:
- HTMLCollection-live-mutations.window.html: "ownPropertyNames" case
- HTMLCollection-supported-property-names.html: 3 getOwnPropertyNames cases
- namednodemap-supported-property-names.html: all 3 cases
- domstringmap-supported-property-names.html: all 5 cases
Object.getOwnPropertyNames returned no interceptor-backed properties
because these collections registered no enumerator callbacks.
Using the bridge's existing indexed and named enumerator support:
- HTMLCollection registers an indexed enumerator (the supported
indices) and a named enumerator implementing the spec's supported
property names: for each element in tree order, its non-empty id and,
for HTML elements, its non-empty name attribute, without duplicates.
- NamedNodeMap registers indexed and named enumerators (attribute
qualified names in order, via List.getNames).
- DOMStringMap registers a named enumerator (camel-cased data-*
attribute names). The previously unused kebabToCamel helper is fixed
to follow the spec conversion: only a '-' followed by an ASCII
lowercase letter is folded to an uppercase letter, a bare "data-"
attribute maps to the empty name, and a trailing '-' is preserved.
(As originally written, this commit also added named enumerator
interceptor support to the js bridge; main gained equivalent support
independently, so the rebase keeps main's bridge API.)
Coverage:
- /dom/collections/HTMLCollection-live-mutations.window.html 4/5 -> 5/5
- /dom/collections/HTMLCollection-supported-property-names.html 2/6 -> 5/6
- /dom/collections/namednodemap-supported-property-names.html 0/3 -> 3/3
- /dom/collections/domstringmap-supported-property-names.html 0/5 -> 5/5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes 2 failing tests in WPT /dom/collections/HTMLCollection-delete.html
("Strict id" and "Strict name"): deleting a supported indexed or named
property of an HTMLCollection must fail (and throw a TypeError in strict
mode), since those properties are backed by the live collection.
HTMLCollection registered no deleter interceptors, so `delete c[0]` and
`delete c.name` fell through to the ordinary [[Delete]] which succeeded
on the (non-own) property.
HTMLCollection now registers an indexed and a named deleter (via the
bridge's indexedReadWrite/namedIndexed deleter callbacks) that return
false for supported properties - which v8 turns into a TypeError in
strict mode - and error.NotHandled for unsupported ones, leaving those
to the ordinary path.
(As originally written, this commit also added indexed setter/deleter
interceptor support to the js bridge; main gained equivalent support
independently, so the rebase keeps main's bridge API.)
Coverage: /dom/collections/HTMLCollection-delete.html 2/4 -> 4/4.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the failing test in WPT /dom/abort/abort-signal-timeout.html:
"Signal returned by AbortSignal.timeout() is not aborted after frame
detach".
The scheduler task registered by AbortSignal.timeout() kept running
after the iframe that created the signal was removed from the document,
so the signal aborted and the abort event fired. Per the DOM spec, the
abort is queued as a global task on the signal's relevant global, and
such tasks must not run once the global's document stops being fully
active.
The timeout callback now walks the signal's frame and its ancestors and
skips the abort when a hosting iframe element is no longer connected.
Coverage: /dom/abort/abort-signal-timeout.html 0/1 -> 1/1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes 1 failing test in WPT /dom/abort/event.any.html (and its .worker
variant): "AbortController abort(null) should set signal.reason".
abort() reason parameters were typed ?js.Value.Global, and the JS->Zig
conversion collapses an explicit JS null into a missing argument, so
controller.abort(null) fell back to the default "AbortError"
DOMException. Per the DOM spec, the abort reason defaults to a new
AbortError only when the reason is not given (undefined); an explicit
null must be stored as-is.
The parameters are now ?js.Value, which preserves the missing vs
explicit-null distinction, and the new AbortSignal.reasonFromJs helper
only falls back to the default for a missing or undefined reason.
Coverage: /dom/abort/event.any.worker.html 15/16 -> 16/16,
/dom/abort/event.any.html 15/16 -> 16/16.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes 3 failing tests in WPT /dom/abort/abort-signal-any.any.html (and
its .worker variant):
- "Dependent signals are aborted correctly for reentrant aborts": when a
source signal aborted a second source from within an abort listener,
the shared dependent signal dispatched its abort event twice. Per the
DOM spec, only dependent signals that are not already aborted are
appended to the list of signals to dispatch. Dependend.markAborted now
reports whether the dependent was newly marked, and abort() only
dispatches for those.
- "Dependent signals should use the same DOMException instance" (already
aborted source / source aborted later): Reason.dom held a DOMException
by value, so every .reason access wrapped a fresh JS object and the
identity check source.reason === dependent.reason failed. Reason.dom
is now a *DOMException allocated once at abort time; the JS bridge
identity-maps the pointer so the source and all dependents expose the
very same DOMException instance.
Coverage: /dom/abort/abort-signal-any.any.worker.html 11/14 -> 14/14,
/dom/abort/abort-signal-any.any.html 11/14 -> 14/14.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The EventSource API only requires a simple line-based parser. It's pretty
straightforward. It's RC'd by v8 and itself, but also by scheduled tasks (e.g.
to reconnect).
The bigger change is to the HttpClient to support streaming requests. This
changes to things:
1 - A transfer doesn't only deliver() once
2 - A transfer can be in the multi while delivering
Neither of these are surprising, but they both add complexity.
There's 1 WPT test that returns a 8GB value content-length (testing failures)
and the u32 limit fails to parse it and makes it as though no content-length
header exists.
Adds a /metrics endpoint in serve mode. Can be disabled via --disable-metrics.
In fetch, dumps the metrics if --metrics is specified (defaults to false).
None of the metrics being collected are on a hot path, so they're just always
collected using atomic operations (i.e. no `if (!enabled) return;`). Because
the operations are cheap and infrequent enough not to matter.
The one place I want to add more metrics is in HttpClient (bytes, status,
counts, ...) but after the pending HttpClient-related PR is merged.
./lightpanda fetch --log-level fatal --metrics "https://lightpanda.io/"
```
build_info{version="1.0.0-dev.7837+70493ce35"} 1
cdp_connections_total 0
cdp_connection_limit_total 0
cdp_active_connections 0
cdp_commands_total 0
cdp_unknown_commands_total 0
js_heap_limits_total 0
script_errors_total 0
arena_hit_total{size="tiny"} 908
arena_hit_total{size="small"} 35
arena_hit_total{size="medium"} 0
arena_hit_total{size="large"} 27
arena_miss_total{size="tiny"} 193
arena_miss_total{size="small"} 31
arena_miss_total{size="medium"} 5
arena_miss_total{size="large"} 23
navigate_total{type="page"} 1
navigate_total{type="iframe"} 0
navigate_total{type="popup"} 0
js_heap_size_bytes_bucket{le="4194304"} 0
js_heap_size_bytes_bucket{le="8388608"} 0
js_heap_size_bytes_bucket{le="16777216"} 1
js_heap_size_bytes_bucket{le="33554432"} 1
js_heap_size_bytes_bucket{le="67108864"} 1
js_heap_size_bytes_bucket{le="134217728"} 1
js_heap_size_bytes_bucket{le="268435456"} 1
js_heap_size_bytes_bucket{le="536870912"} 1
js_heap_size_bytes_bucket{le="+Inf"} 1
js_heap_size_bytes_sum 11223040
js_heap_size_bytes_count 1
```
In a custom element, when this.innerHTML = '....' is called, we need to be
careful to prevent endless recursion. The html5ever callback used to determine
the context element should not invoke the custom-element constructor, else we'll
enter an endless loop.
This also fixes an ungating problem added with the new HttpClient when a
waitForImport can block forever.
Both issues were see on a WooCommerce site - though the HttpClient is only
due to an earlier HttpClient refactor.