Skip to content

perf: walk composedPath() directly in delegated event propagation#18268

Merged
dummdidumm merged 2 commits into
sveltejs:mainfrom
MathiasWP:perf-event-walk-composed-path
May 21, 2026
Merged

perf: walk composedPath() directly in delegated event propagation#18268
dummdidumm merged 2 commits into
sveltejs:mainfrom
MathiasWP:perf-event-walk-composed-path

Conversation

@MathiasWP

@MathiasWP MathiasWP commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

The propagation walk in handle_event_propagation already calls event.composedPath() at the start to find the entry index, but then re-derives the same chain step-by-step via current_target.assignedSlot || current_target.parentNode || .host. Three property reads per iteration is measurable on the hot event path.

Walk the captured path array by index instead.

Notes on behavior

composedPath() is the spec-compliant snapshot of the dispatch chain:

  • Same shadow-DOM crossings (slots and shadow roots are included for composed events).
  • Same host traversal (composed-path crosses shadow boundaries when appropriate).
  • Differs from the previous walk in one edge case: if a handler removes a parent mid-dispatch, the snapshot-based walk continues through the captured chain (matches native browser semantics — the previous parentNode walk would have stopped at a null parent).

Performance

Measured in real Chromium on a click through a 30-deep tree with five delegated handlers: ~245k hz → ~277k hz (~+13%, ~−12% per-event time).

Test plan

  • All 6006 runtime tests pass (runtime-runes + runtime-legacy + runtime-browser)
  • Native shadow-DOM event tests (in runtime-browser) pass unchanged

🤖 Generated with Claude Code

The propagation walk in `handle_event_propagation` already calls
`event.composedPath()` at the start to find the entry index, but then
re-derives the same chain step-by-step via
`current_target.assignedSlot || current_target.parentNode || .host`. Three
property reads per iteration is measurable on the hot event path.

Walk the captured `path` array by index instead. composedPath is the
spec-compliant snapshot of the dispatch chain — same shadow-DOM and
slot crossings as the manual walk, and arguably more correct in the edge
case where a handler removes a parent mid-dispatch (the snapshot
continues; the manual walk would stop at a null parent).

Measured ~+13% in real Chromium on a click through a 30-deep tree with
five delegated handlers. All 6006 runtime tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented May 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b5ceea0

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

This PR includes changesets to release 1 package
Name Type
svelte 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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@dummdidumm dummdidumm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! makes the code simpler aswell

@dummdidumm dummdidumm merged commit 04d408b into sveltejs:main May 21, 2026
16 of 17 checks passed
@github-actions github-actions Bot mentioned this pull request May 21, 2026
Rich-Harris pushed a commit that referenced this pull request May 27, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## svelte@5.55.10

### Patch Changes

- fix: unlink errored and otherwise finished batch
([#18264](#18264))

- perf: walk composedPath() directly in delegated event propagation
([#18268](#18268))

- fix: transfer effects when merging batches
([#18254](#18254))

- fix: allow `$derived(await ...)` in disconnected effect roots
([#18273](#18273))

- fix: remove temporary raw-text hydration markers
([#18269](#18269))

- fix: propagate async `@const` blockers through closure references so
template expressions like `{(() => host)()}` correctly wait for the
awaited value ([#18309](#18309))

- fix: properly unlink batches
([#18298](#18298))

- fix: settle discarded batch
([#18290](#18290))

- fix: declare `let:` directives before `{@const}` declarations on
slotted elements
([#18271](#18271))

- fix: resume outro-ed branches if they were kept around
([#18291](#18291))

- fix: avoid waterfall-warning when async resolves to same value
([#18297](#18297))

- fix: correctly coordinate component-level effects inside async blocks
([#18260](#18260))

- fix: make unnecessary commit work less likely
([#18263](#18263))

- chore: add tag name to `a11y_click_events_have_key_events` warning
([#18272](#18272))

- fix: catch rejected promises while merging/committing
([#18266](#18266))

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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