Skip to content

Custom element reactions v2#2429

Open
karlseguin wants to merge 5 commits into
mainfrom
custom-element-reactions-v2
Open

Custom element reactions v2#2429
karlseguin wants to merge 5 commits into
mainfrom
custom-element-reactions-v2

Conversation

@karlseguin
Copy link
Copy Markdown
Collaborator

Re-applies #2394 with fixes which was reverted in #2428

Should not be merged until WPT tests can reliably be run against the /custom-elements/ category.

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.
    #1969
    #2172
    #2313
    #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.
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)
Turns a crash into a slightly delayed (incorrect) execution order which has some
basis in the spec.
Flag Range.cloneContents as CEReactions

setAttributeNode triggers single reaction, rather than a remove + add.
@karlseguin karlseguin force-pushed the custom-element-reactions-v2 branch from 58a0ade to 0b6a979 Compare May 18, 2026 06:09
@karlseguin karlseguin marked this pull request as ready for review May 18, 2026 06:09
willmafh pushed a commit to willmafh/browser that referenced this pull request May 20, 2026
…ack)

Removes an assertion that can break with custom element callbacks.
lightpanda-io#2429 does not solve this issue
since it isn't a result of when reactions are executed, but just that they
happen.

(I should note, that I'm not 100% sure the above statement is correct. It's
possible that our CE reactions are (in some cases at least) too micro. Maybe
some operations, like setInnerHtml should operate within a more atomic
frameworks, vs an CE-reaction per internal step. But that's a pretty big change)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant