Skip to content

Prototype fix for emberjs#19344: single native listener per event (invoker pattern) - #38

Open
johanrd wants to merge 1 commit into
todo-test-gh19344from
prototype-fix-gh19344
Open

Prototype fix for emberjs#19344: single native listener per event (invoker pattern)#38
johanrd wants to merge 1 commit into
todo-test-gh19344from
prototype-fix-gh19344

Conversation

@johanrd

@johanrd johanrd commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Prototype for emberjs#19344, stacked on the failing-test branch (emberjs#21530).

All {{on}} handlers for the same (element, event, capture/passive bucket) share one native listener dispatching to a snapshot of the handler list — sibling handlers run in a single callback frame, so a render flush scheduled by one handler cannot interleave at a microtask checkpoint and tear down the rest (Vue's invoker architecture). once is per-handler; counters track logical registration.

Scope: covers the reported same-element case. A cross-element variant exists (a handler's flush destroying an ancestor's {{on}} before the event bubbles up to it); fixing that requires deferring teardown past the dispatch — i.e. deciding whether handlers may fire after their component is destroyed — which needs maintainer input and is deliberately not attempted here.

Precedent — every mainstream framework funnels its handlers through a single native callback per dispatch; the surfaces that don't are exactly where this bug class lives:

architecture source
React one native listener per event at the root, synthesized propagation, post-dispatch flush DOMPluginEventSystem.js#L432-L435
Vue 3 per-element invoker (el[veiKey]), handler swaps mutate invoker.value, stale-event guard via e._vts <= invoker.attached events.ts#L43-L61, #L112-L116
Solid document delegation, handlers as node[$$event] props client.js#L90-L96
Svelte 5 root delegation — and the same bug class, open, at the native/delegated boundary: sveltejs/svelte#18070 events.js#L173-L177
Angular per-listener change detection by default; opt-in rAF event coalescing exists precisely for multi-handler events ng_zone.ts#L310-L328

…voker

All {{on}} handlers for the same (element, event, capture/passive bucket)
now register with a single native listener that dispatches to a snapshot
of the handler list. Sibling handlers therefore run in one callback frame:
a render flush scheduled by one handler cannot run at a microtask
checkpoint and tear down the others before they are invoked.

Same architecture as Vue's event invoker. once= is implemented
per-handler; adds/removes counters track logical handler registration,
matching previous test expectations. Flips the GH#19344 repro from @todo
to a passing @test. Full suite: 9437 tests, 0 failures.
@johanrd
johanrd force-pushed the prototype-fix-gh19344 branch from 07b24ab to 7a84253 Compare July 28, 2026 09:29
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