Skip to content

fix(web): delegated dispatch restores the event's currentTarget and target - #4311

Closed
brenelz wants to merge 1 commit into
solidjs:nextfrom
brenelz:fix/delegated-event-restore
Closed

brenelz wants to merge 1 commit into
solidjs:nextfrom
brenelz:fix/delegated-event-restore

Conversation

@brenelz

@brenelz brenelz commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Delegated dispatch left its overrides on the native event. eventHandler defines an own currentTarget getter on the event and never removes it. It also writes target back as an own property when the walk ends. Since 2.0 delegates at the render root container, every native listener above the root (document, window, body, or any ancestor element) runs after Solid's listener in the same dispatch. Each of those listeners read Solid's last walked node as e.currentTarget instead of its own element, which breaks click-outside handlers, analytics listeners, and third-party scripts.

The walk now runs inside try. A finally block deletes the own target and currentTarget properties, so the prototype getters apply again, even when a handler throws. Nested roots still work, because an outer root defines its own getter when its listener runs.

Left open: dispatching the same Event object a second time still skips delegated handlers, because the $$EVENT_OWNER mark persists on the event. Ignoring a stale mark needs per-container bookkeeping, which measured +131 B minified and failed the size gate, so this PR leaves it out. The two tests for that case are marked test.fails.

Behavior changes:

  • A handler that reads e.currentTarget after an await now gets null, as native DOM does. Before, it got the last node Solid visited.
  • For nested roots inside a shadow root, an outer root's listener now reads the browser's retargeted e.target instead of the inner node.

How did you test this change?

  • packages/web/test/delegated-event-restore.spec.tsx (new) covers:
    • a document listener above the root reading its own currentTarget;
    • a listener on the root's ancestor reading its own currentTarget;
    • re-dispatching to another element reporting the new target;
    • a throwing handler still restoring both properties.
      These 4 tests fail on next 55c7329 and pass with this change. The 2 re-dispatch cases are test.fails.
  • cd packages/web && npx vitest run (default config): 1,450 tests pass.
  • npx vitest run --config vite.config.hydrate.mjs: 554 tests pass. c09-no-phantom failed once during the full run; it passes alone and on unchanged next.
  • npx vitest run --config vite.config.server.mjs: 1,818 tests pass. lone-spread-hydration-key-4240 fails, and it fails the same way on unchanged next with the published rc.18 compiler binary.
  • Size: node scripts/size/size.mjs --no-gate --json against next 55c7329, then node gate.mjs. The change is +36 B minified in scenarios that include the web client, and every scenario passes the gate.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Oct 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b0dffd0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
test-integration Patch
board-example Patch
canvas-example Patch
server-dbmon-example Patch
todos-server-example Patch
@solidjs/compiler Patch
@solidjs/signals Patch
solid-js Patch
@solidjs/universal Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codspeed

codspeed Bot commented Oct 27, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 3 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 196 untouched benchmarks
⏩ 5 skipped benchmarks1


Comparing brenelz:fix/delegated-event-restore (b0dffd0) with next (55c7329)

Open in CodSpeed

Footnotes

  1. 5 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

Copy link
Copy Markdown
Member

Thanks, brenelz. I couldn't push to your fork from here, so this continues in #4325. That PR keeps your commit and drops the try/finally, which brings it from +36 B to +23 B minified. A throwing handler is already reported as an error. Ryan approved that version and a cap raise to fit it. I'll close this PR once #4325 merges.


Generated by Claude Code

Copy link
Copy Markdown
Member

Merged as #4325 (76e9094), which keeps your commit and drops the try/finally. Thanks, brenelz!


Generated by Claude Code

@ryansolid ryansolid closed this Oct 27, 2026
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.

2 participants