Commit Graph

8347 Commits

Author SHA1 Message Date
Karl Seguin
e3c41d152e Merge pull request #2951 from lightpanda-io/dom-pr-10-selectors-and-encoding
selector/webapi: :link/:lang/:empty semantics, document encoding, for-in enumerability
2026-07-15 16:43:38 +08:00
Karl Seguin
e5257f9bc3 Use node childrenIterator
Introduce node.ownerDocumentIncludingSelf() to remove duplicate logic through
Node.zig.
2026-07-15 16:27:28 +08:00
Francis Bouvier
d357f093c0 webapi: HTMLCollection named properties are not enumerable in for-in
Fixes the failing subtest of WPT /dom/nodes/Element-children.html
("HTMLCollection edge cases 1"): iterating an HTMLCollection with
for-in must yield only the supported indices; the supported names are
[LegacyUnenumerableNamedProperties] and must be skipped, while
Object.getOwnPropertyNames still returns indices + names.

v8 filters for-in through the named query interceptor, which
HTMLCollection did not register (only a descriptor callback), so every
name reported by the enumerator was treated as enumerable.

HTMLCollection now registers a named query reporting DontEnum for
supported names. The query deliberately does not report ReadOnly: v8
also consults it when a [[Set]] walks the prototype chain, and a
read-only property on the prototype would block the shadowing expando
that the spec's ignore-named-props rule requires
(HTMLCollection-as-prototype.html). Writability as observed through
getOwnPropertyDescriptor still comes from the descriptor callback, and
direct assignments to supported names still fail through the definer.

Coverage: /dom/nodes/Element-children.html 1/2 -> 2/2 (fully green);
/dom/collections stays fully green (53/53).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:53:34 +08:00
Francis Bouvier
152f1eb8eb webapi: script-created documents are UTF-8; URLs use the document encoding
Fixes the last failing subtest of WPT
/dom/nodes/DOMImplementation-createHTMLDocument.html ("URL parsing"):
resolving `a.href = "http://example.org/?ä"` on an anchor inside a
createHTMLDocument() document returned "?%E4" instead of "?%C3%A4".

Two spec violations combined:

- Per DOM, documents synthesized by script (createHTMLDocument,
  createDocument, new Document()) have the UTF-8 encoding. We had no
  per-document encoding at all: document.characterSet always reflected
  the frame's charset, so the new document inherited windows-1252 from
  the test page (which uses that encoding deliberately to catch this).
  Document gains a _charset override (same pattern as _content_type),
  set to UTF-8 in DOMImplementation.createHTMLDocument/createDocument
  and the Document constructor.

- Node.resolveURL encoded the query string with the frame's charset.
  Per the URL/HTML specs the query percent-encoding uses the encoding
  of the element's node document, so it now resolves the owning
  document and uses its encoding, falling back to the frame's.

Coverage:
- /dom/nodes/DOMImplementation-createHTMLDocument.html 12/13 -> 13/13
  (fully green)
- /dom/nodes/Document-constructor.html 3/5 -> 4/5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:53:34 +08:00
Francis Bouvier
fd174986cf selector: implement :link, :lang, :empty spec semantics; parser fixes
Fixes the remaining 30 failing subtests of WPT
/dom/nodes/Element-matches.html, in five clusters:

Matching (selector/List.zig):

- :link now matches a and area elements with an href attribute (in a
  headless browser no link is ever visited, so :visited stays
  unmatched and :link covers every hyperlink). :any-link gains the
  missing area case.
- :empty now ignores comment and processing-instruction children, per
  Selectors Level 3 only elements and non-empty text/cdata affect
  emptiness (<p><!-- comment --></p> is :empty, <p> </p> is not).
- :lang() is implemented: the element's language is the nearest
  ancestor-or-self lang attribute, falling back to the UA default (en)
  for elements in a document and to no language at all in detached
  subtrees, which is what the WPT expects for the detached/fragment
  contexts. Matching is ASCII case-insensitive on the exact tag or a
  `-` separated prefix (:lang(en) matches lang="en-AU").

Parsing (selector/Parser.zig):

- An empty selector-list segment ("div," or ",div") is now a parse
  error instead of being silently skipped.
- Unexpected EOF closes open attribute brackets per CSS Syntax:
  '#attr-value [align="center"' parses and matches.
- Attribute selectors accept a namespace component: [*|TiTlE] (any
  namespace) and [|title] (no namespace). Attributes are stored by
  qualified name and almost never namespaced, so both forms match by
  name; [*|*=test] stays invalid.

Coverage:
- /dom/nodes/Element-matches.html 639/669 -> 669/669 (fully green)
- /dom/nodes/ParentNode-querySelectors-namespaces.html 0/1 -> 1/1
- /dom/nodes/moveBefore/moveBefore-lang.html 0/1 -> 1/1
- /dom/nodes/moveBefore/Node-moveBefore.html 31/32 -> 32/32

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:53:33 +08:00
Karl Seguin
78fbe2f1f3 Merge pull request #2950 from lightpanda-io/dom-pr-09-event-types-and-realms
webapi: new event interfaces, HTML-namespace naming, realm-correct exceptions
2026-07-15 15:51:52 +08:00
Adrià Arrufat
683b9b412b md_term: render styled borders for fenced code blocks
Adds top and bottom borders around code blocks, displaying the language
tag if present. Also changes horizontal rules to dashed lines to
distinguish them from code block borders.
2026-07-15 09:49:59 +02:00
Karl Seguin
926536fe18 Merge pull request #2918 from lightpanda-io/finalize-more-types
mem: Finalize more (non-dom) types
2026-07-15 15:32:24 +08:00
Karl Seguin
58f5f857d9 Detect Node-types that don't have an asNode()
Don't set null_as_undefined = false..it's the default.
2026-07-15 15:30:49 +08:00
Adrià Arrufat
813cf7f424 agent: clean up terminal rendering and highlighting
- Extract ANSI escape codes to a dedicated `ansi.zig` module.
- Consolidate styled output rendering in `Terminal.zig`.
- Simplify dollar variable highlighting and table measurement.
2026-07-15 09:25:33 +02:00
Adrià Arrufat
8b1686f6fb agent: highlight markdown code blocks as JS
Extracts the JavaScript highlighting logic from `Terminal.zig` into a
reusable `js_highlight.zig` module. Uses this module to syntax-highlight
markdown fenced code blocks in `md_term.zig` using ANSI escape codes.
2026-07-15 09:10:11 +02:00
Karl Seguin
cd7b63cade mem: Finalize more (non-dom) types
Adds finalizers to various types. The two most interesting are ImageData (which
could have a large data field) and HTMLCollection which a page could create
many.

Various Crypto types are also finalized to make sure the key is freed. This is
particularly important for freeing any keys created with EVP_PKEY_new.
2026-07-15 14:51:35 +08:00
Karl Seguin
2edc2eb131 perf: Pre-parse HTML to find and preload scripts
Builds on top of the recently added support for <link rel=preload...> and
<link rel=modulePreload...> to scan the HTML for script tags to preload. I.e.
adds script preloading without actually having any preload hits.

At least for this first pass, I opted for a simple approach which leverages
are fully buffered HTML body and html5ever's tokenizer to prescan the body and
kickoff any script fetching before starting the complete parse.

There are doubtless cases where this will either decrease performance and/or
increase memory usage. E.g. a site with no script gets its html scanned twice
and loading multiple blocking scripts in parallel obvious uses more memory
than loading them sequentially. But for most sites and I think most use-cases,
the impact should range between neutral to significantly faster loads.

This is something most browsers do.
2026-07-15 14:36:23 +08:00
Adrià Arrufat
3083414798 terminal: use standard SGR dim and cyan code blocks
Replaces the 256-color gray escape sequence with standard SGR 2 dim.
Also updates fenced code blocks to render in cyan instead of dim.
2026-07-15 08:02:51 +02:00
Francis Bouvier
945c6d40cb js: create thrown exceptions in the receiver's relevant realm
Fixes the 34 "wrong global" failures in WPT /dom/nodes/Element-matches.html
(and the equivalent checks in the other selector test files): when a DOM
method is called on a node from another realm (the test roots live in an
iframe), the thrown exception must be created in the receiver's relevant
realm, per Web IDL's "create an exception" using the context object's
realm. The harness checks e.constructor === root.ownerDocument.defaultView
.DOMException, which failed because we always built exceptions in the
caller's context.

The creation context of the wrapper can't be used to find that realm:
wrappers are session-level (one JS wrapper per Zig object, shared across
same-origin contexts). Instead, handleError now resolves the receiver
DOM node's document frame: for functions whose receiver is a Node (or a
type with an asNode() cast), errorLocal maps this -> node -> ownerDocument
-> frame, and if that frame differs from the calling one, the error is
constructed inside that frame's v8 context (entered/exited around the
exception creation).

TaggedOpaque.fromJS also learns to reject objects whose internal field
does not hold an aligned TaggedOpaque pointer. Such objects exist: a
custom element whose constructor throws was created from our template
but never mapped to a Zig instance, and its internal field still holds a
v8 tagged value. errorLocal probes the receiver on every error path, so
without the check the @alignCast panics (caught by the unit test suite's
custom_elements constructor.html).

Coverage: /dom/nodes/Element-matches.html 605/669 -> 639/669.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 12:59:53 +08:00
Francis Bouvier
12eb192815 webapi: only HTML-namespace elements are named by their name attribute
Fixes the failing subtest "hasOwnProperty, getOwnPropertyDescriptor,
getOwnPropertyNames" in WPT /dom/nodes/Document-getElementsByTagName.html
(17/18 -> 18/18) and /dom/nodes/Element-getElementsByTagName.html
(18/19 -> 19/19).

Per the DOM spec's supported property names for HTMLCollection, the
name attribute only exposes elements in the HTML namespace (ids expose
any element). The live-collection getByName fallback matched any
element with a name attribute; it now skips non-HTML elements, matching
the enumerator which already had the guard.

Coverage: Document-getElementsByTagName.html 17/18 -> 18/18,
Element-getElementsByTagName.html 18/19 -> 19/19. /dom/collections
stays fully green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 12:59:52 +08:00
Francis Bouvier
7f04ee5f0a webapi: add BeforeUnload/Storage/DeviceMotion/DeviceOrientation/Touch events
Fixes all 21 failing tests in WPT /dom/nodes/Document-createEvent.https.html
(258/279 -> 279/279), Document-createEvent-touchevent.window.html
(0/3 -> 3/3) and, as a side effect, /dom/events/non-cancelable-when-
passive/synthetic-events-cancelable.html (8/12 -> 12/12).

- Five new event interfaces, following the existing patterns:
  BeforeUnloadEvent (returnValue, no constructor per spec), StorageEvent
  (key/oldValue/newValue/url + initStorageEvent; storageArea is always
  null), DeviceMotionEvent and DeviceOrientationEvent (nullable sensor
  values), and TouchEvent (UIEvent subclass with modifier keys and empty
  touch lists — there is no touch input source).
- document.createEvent maps their aliases to real instances instead of
  plain Events, adds "touchevent", and drops the non-spec pluralized
  aliases CustomEvents/FocusEvents/TextEvents, which must throw
  NotSupportedError (the unit test asserting the old behavior is
  updated).
- The "expose legacy touch event APIs" precondition requires
  ontouchstart & co. to exist: the four touch handlers are added to the
  global event handler set, HTMLElement and Document.

Coverage: /dom/nodes/Document-createEvent.https.html 258/279 ->
279/279, Document-createEvent-touchevent.window.html 0/3 -> 3/3,
synthetic-events-cancelable.html 8/12 -> 12/12. No regressions across
/dom/events; 1002/1002 unit tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 12:59:52 +08:00
Karl Seguin
58d8aeb070 Merge pull request #2949 from lightpanda-io/dom-pr-08-idl-and-namespaces
webapi: IDL argument shapes, DOMTokenList reflection, namespace validation
2026-07-15 12:57:55 +08:00
Karl Seguin
872580a708 Move logic out of JsAPI
While we do sometimes write functions directly in the JsAPI bridge, this is
usually limited to things that are clearly JS/v8 bridging related. None of the
new getters fit this (admittedly loose) definition..they're accessors like any
other.
2026-07-15 12:39:10 +08:00
Karl Seguin
a18886b1a9 cleanup: Enhance preload scripts
This fixes a bug with preload script so that they're actually used for
async/defer scripts (previously, only used for blocking scripts).

More importantly, this cleans up the ScriptManager's addFromElement. For example
inline scripts are handled in their own function, which means we aren't weaving
the two modes in a single function. It also allows the inline-script to use
a better-sized arena. The goal for this cleanup is the follow up commit which
will bring a pre-parse step to preload scripts without an explicit <link> hint.
2026-07-15 12:04:26 +08:00
Karl Seguin
8f26f1e4b7 zig fmt 2026-07-15 12:03:46 +08:00
Francis Bouvier
aba796eb36 webapi: implement validate-and-extract; real namespace URIs
Fixes 158 failing tests in WPT
/dom/nodes/DOMImplementation-createDocument.html (276/434 -> 434/434)
and improves Document-createElementNS.html (82/596 -> 197/596),
Document-createElement.html (29/147 -> 34/147),
Document-contentType/createDocument.html (0/1 -> 1/1) and
name-validation.html (0/5 -> 2/5). The remaining failures in those
files need XML/XHTML document loading, which Lightpanda doesn't have.

- Document now implements the DOM spec's §1.4 name validation
  productions (valid element/attribute local name, valid namespace
  prefix) and the "validate and extract" algorithm, replacing the
  older ad-hoc element name check: prefixes are validated, and the
  prefix/namespace consistency rules throw NamespaceError (prefix
  without namespace, xml/xmlns mismatches).
- createElementNS and DOMImplementation.createDocument route through
  it. createDocument also now: treats its two first arguments as
  required (namespace via the js.Nullable wrapper, qualifiedName as a
  raw js.Value so [LegacyNullToEmptyString] null maps to "" while
  undefined stringifies), sets the spec's namespace-dependent
  contentType, and registers custom namespace URIs for the root
  element.
- element.namespaceURI now returns the actual URI for namespaces
  outside the built-in set (it returned a lightpanda.io placeholder),
  and Element.clone copies the registration. The unit tests asserting
  the placeholder are updated to the correct values.

Coverage: DOMImplementation-createDocument.html 276/434 -> 434/434 and
the improvements above. No regressions across /dom/nodes; 1002/1002
unit tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 12:02:36 +08:00
Francis Bouvier
4c306d50b7 webapi: DOMImplementation owns its document; validate doctype names
Fixes 81 failing tests in WPT
/dom/nodes/DOMImplementation-createDocumentType.html (1/82 -> 82/82):

- DOMImplementation was an empty singleton-style object with no link to
  its document, so a doctype created through another document's
  implementation (e.g. createHTMLDocument().implementation) reported
  the frame's main document as its ownerDocument. The implementation
  object now stores its associated document and registers created
  doctypes in the frame's node-owner-document map, following the
  createDocumentFragment pattern.
- createDocumentType now validates the qualified name against the
  doctype name production (no ASCII whitespace or '>'), throwing
  InvalidCharacterError.

Coverage: /dom/nodes/DOMImplementation-createDocumentType.html 1/82 ->
82/82.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 11:59:57 +08:00
Francis Bouvier
0a7e24fa2f webapi: DOMTokenList-reflected attributes on area, output, iframe, link
Fixes 4 failing tests in WPT
/dom/lists/DOMTokenList-coverage-for-attributes.html (135/140 ->
139/140): area.relList, output.htmlFor, iframe.sandbox and link.sizes
must be DOMTokenList attributes reflecting their content attributes.

Element gains a generic getTokenList lookup (keyed by element and
attribute) alongside the existing class/rel dedicated ones, and the
four elements expose the accessors following the Anchor/Link relList
pattern (undefined outside the HTML namespace).

The remaining failure needs an SVGAElement interface (relList on SVG
<a> elements), which doesn't exist yet — SVG elements are all generic.

Coverage: /dom/lists/DOMTokenList-coverage-for-attributes.html
135/140 -> 139/140.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 11:59:56 +08:00
Francis Bouvier
0740f7d113 js: required-but-nullable arguments; fix EventTarget/AbortSignal IDL shape
Fixes 6 failing tests in WPT /dom/idlharness.any.worker.html
(211/219 -> 217/219) and the same assertions in the other idlharness
variants:

- addEventListener/removeEventListener had .length 1 instead of 2 and
  accepted being called with a single argument. Per Web IDL their
  callback parameter is required but nullable ("EventListener?"), which
  a plain Zig optional can't express (the bridge treats optionals as
  omittable and stops counting length at the first one). The new
  js.Nullable(T) wrapper marks a required argument that accepts null or
  undefined: omitting it throws a TypeError and it counts towards the
  function's length.
- new AbortSignal() didn't throw: the spec defines no constructor, so
  the JsApi constructor is removed (the interface object now uses the
  illegal-constructor callback; AbortSignal.init stays for internal
  use).
- AbortSignal.any() with no argument didn't throw: the trailing slice
  parameter was treated as variadic and defaulted to empty. The
  parameter is now a required js.Value converted to the signal sequence
  explicitly.

The 2 remaining failures need strict-mode receiver validation on the
flattened worker global and the DOMStringList interface.

Coverage: /dom/idlharness.any.worker.html 211/219 -> 217/219. No
regressions across /dom/events and /dom/abort.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 11:58:44 +08:00
Karl Seguin
7ee249a625 Merge pull request #2948 from lightpanda-io/dom-pr-07-click-activation-passive
webapi: click activation, shadow retargeting, passive-by-default listeners
2026-07-15 11:58:13 +08:00
Karl Seguin
eab33428fb zig fmt 2026-07-15 11:42:18 +08:00
Karl Seguin
09a4092634 use StaticStringMap for lookup 2026-07-15 11:32:39 +08:00
Francis Bouvier
46e9497d35 webapi: scroll-blocking listeners are passive by default on root targets
Fixes the 32 failing tests in WPT /dom/events/passive-by-default.html
(68/100 -> 100/100): per the DOM spec's "default passive value",
touchstart, touchmove, wheel and mousewheel listeners added to the
Window, the Document, or the html or body elements default to
{passive: true} (so their preventDefault is ignored), unless the
options explicitly say {passive: false}.

addEventListener's options dictionary now keeps `passive` optional so
an absent or undefined member can fall back to the default passive
value computed from the event type and target; an explicit boolean
keeps its meaning.

Coverage: /dom/events/passive-by-default.html 68/100 -> 100/100. No
regressions across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:51:11 +08:00
Francis Bouvier
2c64c7fdbe webapi: real clicks on disabled form controls dispatch nothing
Fixes the "Real clicks on disabled elements must not dispatch events"
subtest of WPT /dom/events/Event-dispatch-on-disabled-elements.html
(verified in isolation): a testdriver click on a disabled
button/input/select/textarea must not fire any pointer or mouse events,
while a click on the re-enabled element must. WebDriver.click now
checks the disabled state like HTMLElement.click does.

The file's overall score is unchanged (4/9) because its four CSS
transition/animation subtests hang awaiting animation events (no CSS
animation engine in the renderless browser) and, being sequential
promise_tests, also mask this subtest's result in the full run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:51:11 +08:00
Francis Bouvier
6587778c12 webapi: spec-conformant click activation target selection
Fixes 3 failing tests in WPT /dom/events/Event-dispatch-click.html
(29/33 -> 32/33) and both tests in /dom/events/no-focus-events-at-
clicking-editable-content-in-link.html (1/2 -> 2/2).

- Only a MouseEvent click is an activation event.
- The activation target is the nearest inclusive ancestor with
  activation behavior (ancestors only for bubbling events), shared by
  ActivationState and handleClick via findClickActivationTarget.
- Clicks on editable content don't activate enclosing links; mousedown
  focuses the editing host (CDP and WebDriver paths).
- Interactive submission of a disconnected form does nothing; the
  programmatic form.submit() stays exempt per HTML 'cannot navigate'.

The remaining Event-dispatch-click.html failure needs javascript: URL
navigation.

Coverage: Event-dispatch-click.html 29/33 -> 32/33,
no-focus-events-at-clicking-editable-content-in-link.html 1/2 -> 2/2.
No regressions across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:51:11 +08:00
Francis Bouvier
f72cdf33cf webapi: retarget relatedTarget and reset shadow-tree targets pre-dispatch
Fixes 5 failing tests across WPT /dom/events/relatedTarget.window.html
(0/6 -> 3/6) and /dom/events/shadow-relatedTarget.html (0/2 -> 2/2).
The remaining 3 relatedTarget.window.html subtests also fail in Chrome
stable (verified on wpt.fyi), so they are out of scope.

Per the DOM dispatch algorithm:

- The event's relatedTarget is retargeted against the dispatch target
  when dispatch starts (Event gains relatedTargetPtr() to reach the
  MouseEvent/FocusEvent storage generically), so listeners and
  post-dispatch reads observe e.g. the shadow host instead of a node
  inside a shadow tree.
- The "clear targets" decision (reset target and relatedTarget to null
  when they would expose nodes whose root is a shadow root) is now
  computed when the event path is built — on the pre-dispatch tree —
  instead of after dispatch, so listeners moving nodes out of the
  shadow tree during dispatch can't defeat it, and it now also applies
  to relatedTarget, not just target.

Coverage: /dom/events/relatedTarget.window.html 0/6 -> 3/6 (Chrome
parity), /dom/events/shadow-relatedTarget.html 0/2 -> 2/2. No
regressions across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:51:10 +08:00
Karl Seguin
64b4276cfa Merge pull request #2947 from lightpanda-io/dom-pr-06-window-event-createevent
webapi: window.event rules, Window/Document handlers, createEvent, timeStamp
2026-07-15 10:49:57 +08:00
Karl Seguin
54ae8800b2 Use Window frame to set click listener, not invocation frame
Same with document

Use existing `rootIsShadowRoot` helper in EventManager
2026-07-15 10:24:22 +08:00
Francis Bouvier
40fa69ecf6 webapi: Event.timeStamp relative to the performance time origin
Fixes all 4 failing tests in WPT
/dom/events/Event-timestamp-high-resolution.html: event.timeStamp
returned a raw monotonic-clock value (milliseconds since boot) instead
of a DOMHighResTimeStamp sharing performance.now()'s time origin.

- Events now record their creation time with the same (already
  coarsened) clock Performance uses, and the timeStamp getter reports
  it in milliseconds relative to the time origin.
- Per spec the origin is the *event's relevant global*'s: the JS Event
  constructor captures the creating realm's origin so cross-realm reads
  (Event-timestamp-cross-realm-getter.html) stay correct; events
  without a captured origin fall back to the accessing realm's, which
  is the creating realm in all other paths.

Coverage: /dom/events/Event-timestamp-high-resolution.html 0/4 -> 4/4,
Event-timestamp-cross-realm-getter.html stays 1/1. No regressions
across /dom/events; Event-dispatch-single-activation-behavior.html now
runs (0/0 crash -> 3/132).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:39:16 +08:00
Francis Bouvier
ad9e1d8dc6 webapi: uninitialized-event dispatch throws; more createEvent aliases
Fixes the 20 failing tests in WPT /dom/events/EventTarget-
dispatchEvent.html (5/25 -> 25/25), all of the form "If the event's
initialized flag is not set, an InvalidStateError must be thrown".

Per the DOM spec, an event created via document.createEvent has its
initialized flag unset until one of the legacy init*Event methods runs,
and dispatchEvent must throw an InvalidStateError for it. Lightpanda
dispatched such events happily. Event now carries an _initialized flag:
true by default (constructor-created and internal events), cleared by
document.createEvent, and set by initEvent, initCustomEvent,
initUIEvent, initMouseEvent, initKeyboardEvent, initCompositionEvent
and initTextEvent. EventTarget.dispatchEvent rejects uninitialized
events.

document.createEvent also gains the spec-required aliases it was
missing: DragEvent, HashChangeEvent and SVGEvents, plus
BeforeUnloadEvent, DeviceMotionEvent, DeviceOrientationEvent and
StorageEvent mapped to a plain Event (logged as not_implemented) until
those interfaces exist.

Coverage: /dom/events/EventTarget-dispatchEvent.html 5/25 -> 25/25. No
regressions across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:39:16 +08:00
Francis Bouvier
a0418aed5c webapi: support onclick property handler on Window and Document
Fixes the failing test in WPT /dom/events/handler-count.html?window and
?document (1/2 -> 2/2 each): setting `window.onclick` or
`document.onclick` did nothing, because neither interface exposed the
accessor — the assignment created an inert expando and the handler
never fired for clicks bubbling up from the page.

Both now store the handler in the frame's attribute-listener map (the
same mechanism as element and ShadowRoot property handlers), which the
dispatch propagation path already consults for any event target.
Non-callable values clear the handler per [LegacyTreatNonObjectAsNull].

Coverage: /dom/events/handler-count.html?window 1/2 -> 2/2,
/dom/events/handler-count.html?document 1/2 -> 2/2. No regressions
across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:39:16 +08:00
Francis Bouvier
86f9059f3f webapi: window.event shadow-tree rule; property handlers on direct dispatch
Fixes the 3 failing tests in WPT /dom/events/event-global.html
(5/8 -> 8/8):

- "window.event is undefined if the target is in a shadow tree" (and
  the window.onerror variant): per the DOM invoke algorithm, the
  window's current event is only set while invoking listeners whose
  invocation target is not in a shadow tree. window.event was set once
  for the whole dispatch; it is now set per invocation target (in
  dispatchPhase and around inline handler calls), left undefined for
  targets whose root is a shadow root.
- "window.event is set to the current event, which is the event passed
  to dispatch (2)": script-dispatched events on non-node targets only
  ran addEventListener listeners. Property event handlers now fire too:
  XMLHttpRequestEventTarget resolves its on* fields by event type, and
  the Window's handler fields are consulted both when an event
  propagates to the window (getInlineHandler) and when one is
  dispatched directly on it.

Coverage: /dom/events/event-global.html 5/8 -> 8/8. No regressions
across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:39:15 +08:00
Karl Seguin
6df58c2318 Merge pull request #2946 from lightpanda-io/dom-pr-05-event-interfaces
webapi: event interface conformance (isTrusted, constructor defaults, handleEvent)
2026-07-15 09:38:40 +08:00
Karl Seguin
d42e8b4d81 Improve unforgeable detection
Collect types with unforgeable upfront, so that detection in the child is only
O(N) over the very small set of types with an unforgeable property.

Remove duplicate comments.
2026-07-15 09:14:29 +08:00
Francis Bouvier
169d3b8314 webapi: report TypeError for non-callable handleEvent listeners
Fixes the 2 remaining failures in WPT
/dom/events/EventListener-handleEvent.html (4/6 -> 6/6): "throws if
`handleEvent` is falsy/truthy and not callable".

Per the Web IDL callback interface invocation steps, when an object
event listener's handleEvent member is not callable, invoking the
listener reports a TypeError to the global (window.onerror / an "error"
event). Lightpanda silently skipped such listeners.

Listener.run's object branch now performs the Get(handleEvent)
explicitly: a throwing getter is reported like a throwing listener, a
non-callable value reports a TypeError, and only a callable one is
invoked (with exceptions reported as before).

Coverage: /dom/events/EventListener-handleEvent.html 4/6 -> 6/6. No
regressions across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:39:51 +08:00
Francis Bouvier
5ebc61907e webapi: accept arbitrary button/location values; legacy keyboard init members
Fixes the 3 remaining failures in WPT
/dom/events/Event-subclasses-constructors.html (46/49 -> 49/49): the
"argument with non-default values" cases for MouseEvent, WheelEvent and
KeyboardEvent.

- MouseEvent.button is a WebIDL short: any value (e.g. 40) is allowed.
  It was stored as the MouseButton enum and construction threw a
  TypeError for values outside the named buttons. The field is now a
  raw i16 (the enum remains for the named constants); same fix in
  WheelEvent, DragEvent and PointerEvent, and in initMouseEvent.
- KeyboardEvent.location is an unsigned long with the same problem
  (Location enum); now stored as a raw u32.
- KeyboardEvent supports the legacy KeyboardEventInit members charCode
  and keyCode: synthetic events report the initialized values (still 0
  by default), while trusted events keep deriving them from the key.
  UIEvent.which now returns keyCode for keyboard events, and clamps
  negative mouse buttons instead of overflowing.

Coverage: /dom/events/Event-subclasses-constructors.html 46/49 ->
49/49. No regressions across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:39:51 +08:00
Francis Bouvier
b309640ab0 webapi: fix UIEvent.view default, initCustomEvent guard, KeyboardEvent flags
Fixes 4 failing tests in WPT /dom/events/Event-init-while-dispatching.html
(1/5 -> 5/5), and as a side effect most of
Event-subclasses-constructors.html (16/49 -> 46/49). Three distinct
bugs, all about synthetic (constructor-created) events not following
the spec's dictionary defaults:

- UIEvent.view defaulted to the window: the constructor stored
  `opts.view orelse frame.window` and the getter fell back to the
  window too, so `new UIEvent("x").view` was never null. Per
  UIEventInit, view defaults to null. The window fallback is kept for
  *trusted* (browser-generated) UI events only, which do target the
  window, so internal dispatch call sites stay unchanged.
- initCustomEvent was missing the "short-circuit while dispatching"
  guard that initEvent and the other legacy initializers have.
- KeyboardEvent's constructor unconditionally forced bubbles,
  cancelable and composed to true (correct only for trusted key events
  per UI Events); synthetic KeyboardEvents now follow the EventInit
  defaults.

The unit tests asserting the old view-defaults-to-window behavior are
updated to expect null.

Coverage: /dom/events/Event-init-while-dispatching.html 1/5 -> 5/5,
/dom/events/Event-subclasses-constructors.html 16/49 -> 46/49. No
regressions across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:39:51 +08:00
Francis Bouvier
0b2cfc35f4 js: support [LegacyUnforgeable] accessors; make Event.isTrusted one
Fixes the failing test in WPT /dom/events/Event-isTrusted.any.html (and
its .worker variant): Object.getOwnPropertyDescriptor(event,
"isTrusted") returned undefined because the accessor lived on
Event.prototype. Per Web IDL, isTrusted is [LegacyUnforgeable]: an own,
non-configurable accessor property of every Event instance, with the
same getter function shared between instances.

- bridge.accessor gains an `unforgeable` option. Such accessors are
  installed on the class's v8 instance template instead of the
  prototype template, so every instance gets an own accessor backed by
  one getter function per context.
- Since v8's Inherit only chains prototype templates, attachClass now
  walks the ancestor chain (via the _proto fields) and re-installs
  ancestors' unforgeable accessors on the subclass's instance template,
  so MouseEvent instances & co. get their own isTrusted as well. The
  accessor installation code is extracted into attachAccessorProperty
  for reuse.
- Event.isTrusted is declared unforgeable and non-deletable.

Coverage: /dom/events/Event-isTrusted.any.html 0/1 -> 1/1,
/dom/events/Event-isTrusted.any.worker.html 0/1 -> 1/1. No regressions
across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:39:50 +08:00
Karl Seguin
2623be4493 Merge pull request #2945 from lightpanda-io/dom-pr-04-dispatch-and-onerror
webapi: dispatch passes, trusted clicks, listener exceptions to onerror
2026-07-15 08:39:04 +08:00
Karl Seguin
b392cbd95a use childIterator to iterate child nodes 2026-07-15 08:04:05 +08:00
Francis Bouvier
f1bdf9baa6 webapi: report listener exceptions to window.onerror
Fixes both failing tests in WPT /dom/events/Event-dispatch-throwing.html
("Throwing in event listener with a single/multiple listeners"): an
exception thrown by an event listener must be reported to the global —
firing window.onerror and an "error" event — while the dispatch
continues with the remaining listeners. Lightpanda only logged the
exception internally, so window.onerror never fired.

- js.Function gains callWithThisRethrow, built on the existing
  callRethrow machinery (_tryCallWithThis with .rethrow = true): a
  thrown JS exception is surfaced as error.TryCatchRethrow past the
  internal TryCatch so an enclosing TryCatch of the caller can observe
  the exception value. js.TryCatch gains exceptionValue() returning
  the raw caught value.
- Listener.run wraps function and handleEvent callbacks in a TryCatch
  and routes the caught exception value to Window.reportError (the
  existing "report the exception" implementation, which invokes
  onerror with the 5-argument form and dispatches the error event).
  This merges with the pre-existing _listeners_did_throw tracking on
  Event (used by IndexedDB): the reporting paths also set the flag.
  Worker globals are left as-is for now.
- Window.reportError now implements the spec's "in error reporting
  mode" guard: an exception thrown while reporting one (e.g. by a
  throwing "error" listener) is not reported again, which would recurse
  without bound.

Coverage: /dom/events/Event-dispatch-throwing.html 0/2 -> 2/2. No
regressions across /dom/events (verified against a baseline build).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:59:29 +08:00
Francis Bouvier
cb70a911c7 webapi: add WebDriver.click for trusted testdriver clicks
Fixes 1 failing test in WPT /dom/events/Event-dispatch-redispatch.html:
"Redispatching mouseup event whose default action dispatches a click
event".

The WPT testdriver-vendor shim implemented test_driver.click() with
HTMLElement.click(), which dispatches a single untrusted click event
and no press/release, so the test saw no trusted mouseup at all and an
untrusted click. window.webdriver (the -Dwpt_extensions WebAPI) now
exposes click(element): a full trusted primary-button sequence
(pointerdown, mousedown, pointerup, mouseup, click), dispatched
synchronously so the events are observable when the testdriver promise
resolves. The shim routes test_driver.click through it, like
action_sequence already routes through webdriver.actionSequence.

Note this test (like everything testdriver-based) needs the browser
built with -Dwpt_extensions=true, otherwise window.webdriver is
undefined.

Coverage: /dom/events/Event-dispatch-redispatch.html 3/4 -> 4/4 (with a
wpt_extensions build).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:59:29 +08:00
Francis Bouvier
b59400c836 webapi: invoke event target separately for capture and bubble passes
Fixes the failing test in WPT /dom/events/Event-dispatch-handlers-
changed.html ("Dispatch additional events inside an event listener").

At the target, listeners were run in a single pass mixing capture and
bubble listeners, with one snapshot of the listener list. Per the DOM
dispatch algorithm, the target participates in both the capturing and
the bubbling iterations of the event path, and each invoke clones the
listener list separately: a bubble listener added by one of the
target's capture listeners must run during the bubbling pass. The
single pass used one snapshot, so such a listener never ran.

The at-target step now dispatches twice — capture listeners first, then
non-capture listeners — re-fetching the listener list (and taking a new
sentinel snapshot) for the second pass, and honoring stopPropagation /
stopImmediatePropagation between the two.

Coverage: /dom/events/Event-dispatch-handlers-changed.html 0/1 -> 1/1,
/dom/events/EventTarget-dispatchEvent.html 4/25 -> 5/25. No regressions
across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:59:28 +08:00
Francis Bouvier
27ba864697 webapi: implement cloneNode for Document nodes
Fixes 1 failing test each in WPT /dom/events/Event-dispatch-bubbles-
false.html and Event-dispatch-bubbles-true.html ("In
window.document.cloneNode(true)"): document.cloneNode() threw
NotSupportedError because the .document case of Node.cloneNode was
unimplemented.

Per the DOM spec cloning steps for Document, a new document of the same
type (HTML vs XML) is created with the same URL, and a deep clone
copies the document's children (doctype and document element subtree)
into it, reusing the existing per-child cloning path.

Coverage: /dom/events/Event-dispatch-bubbles-false.html 4/5 -> 5/5,
/dom/events/Event-dispatch-bubbles-true.html 4/5 -> 5/5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:59:28 +08:00