Skip to content

fix(browser-utils): Stop leaking DOM instrumentation listeners on mismatched removals - #24727

Open
Lms24 wants to merge 1 commit into
developfrom
fix/dom-instrumentation-listener-leak
Open

Lms24 wants to merge 1 commit into
developfrom
fix/dom-instrumentation-listener-leak

Conversation

@Lms24

@Lms24 Lms24 commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

This PR fixes two (related) problems in our instrumentDOM event listener instrumentation:

TIL about event listener capture modes.

  1. We didn't differentiate between addEventListener(fn, {capture: true}) and addEventListener(fn, {capture: false}) calls, causing leakage of our event listeners when event listeners were removed with different options.

    => Fixed by checking the capture option, registering our own listeners in the same capture config and keeping the capture option on the meta object of the event target so that we can then remove it in the correct capture config

  2. More generally fixes an issue with refCount where e.g. calling removeEventListener with a callback that was never added via addEventListener: Browsers just ignore this call but our refCount was decremented anyway.

    => Fixed by replacing the general ref count with two sets of callbacks (for both capture modes) and only removing our listener if all user-set listeners were removed

Fixes #24702

supersedes #24725
supersedes #24723

…matched removals

instrumentDOM refcounted add/removeEventListener calls without regard to
listener identity or capture phase, so no-op removals (e.g. Radix
DismissableLayer removing a bubble-phase listener that was added in capture
phase) decremented the count and our handler was removed with the wrong
capture flag, leaking it on every cycle.

Track listeners per capture phase in sets so only removals that the browser
would actually honor count, and always detach our handler with the capture
flag it was attached with.

Fixes #24702

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Lms24 Lms24 self-assigned this Sep 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 29.3 kB +0.22% +62 B 🔺
@sentry/browser - with treeshaking flags 27.58 kB +0.27% +74 B 🔺
@sentry/browser - with treeshaking flags tracing without tracing 27.47 kB +0.26% +71 B 🔺
@sentry/browser (incl. Tracing) 51.23 kB +0.16% +81 B 🔺
@sentry/browser (incl. Tracing + Span Streaming) 51.25 kB +0.15% +75 B 🔺
@sentry/browser (incl. Tracing, Profiling) 54.24 kB +0.12% +62 B 🔺
@sentry/browser (incl. Tracing, Replay) 90.83 kB +0.08% +66 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.92 kB +0.08% +60 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 95.52 kB +0.07% +59 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 108.5 kB +0.09% +91 B 🔺
@sentry/browser (incl. Feedback) 46.82 kB +0.12% +56 B 🔺
@sentry/browser (incl. sendFeedback) 34.36 kB +0.19% +62 B 🔺
@sentry/browser (incl. FeedbackAsync) 39.48 kB +0.16% +63 B 🔺
@sentry/browser (incl. Metrics) 30.32 kB +0.23% +67 B 🔺
@sentry/browser (incl. Logs) 30.58 kB +0.22% +66 B 🔺
@sentry/browser (incl. Metrics & Logs) 31.25 kB +0.22% +67 B 🔺
@sentry/react 31.06 kB +0.2% +61 B 🔺
@sentry/react (incl. Tracing) 53.53 kB +0.15% +79 B 🔺
@sentry/vue 36.79 kB +0.16% +58 B 🔺
@sentry/vue (incl. Tracing) 53.77 kB +0.14% +74 B 🔺
@sentry/svelte 29.33 kB +0.24% +68 B 🔺
CDN Bundle 31.01 kB +0.25% +75 B 🔺
CDN Bundle (incl. Tracing) 51.76 kB +0.14% +69 B 🔺
CDN Bundle (incl. Logs, Metrics) 33.26 kB +0.18% +57 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 53.73 kB +0.14% +70 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 73.98 kB +0.09% +63 B 🔺
CDN Bundle (incl. Tracing, Replay) 89.34 kB +0.08% +64 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 91.31 kB +0.08% +67 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 95.52 kB +0.08% +72 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 97.49 kB +0.08% +77 B 🔺
CDN Bundle - uncompressed 91.65 kB +0.27% +243 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 154.01 kB +0.16% +243 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 98.22 kB +0.25% +243 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 159.97 kB +0.16% +243 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 227.79 kB +0.11% +243 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 273.75 kB +0.09% +243 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 279.68 kB +0.09% +243 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 287.45 kB +0.09% +243 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 293.38 kB +0.09% +243 B 🔺
@sentry/nextjs (client) 55.84 kB +0.12% +64 B 🔺
@sentry/sveltekit (client) 51.67 kB +0.15% +74 B 🔺
@sentry/core/server 39.95 kB - -
@sentry/core/browser 13.63 kB - -
@sentry/node 133.9 kB +0.01% +5 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 82.43 kB - -
@sentry/node - without tracing 90.46 kB +0.02% +15 B 🔺
@sentry/node - without channel injection 112.42 kB +0.02% +12 B 🔺
@sentry/aws-serverless 98.72 kB +0.01% +4 B 🔺
@sentry/cloudflare (withSentry) - minified 206.49 kB - -
@sentry/cloudflare (withSentry) 513.73 kB - -

View base workflow run

@Lms24

Lms24 commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

bugbot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 171dec6. Configure here.

@Lms24
Lms24 marked this pull request as ready for review September 25, 2026 09:17
@Lms24
Lms24 requested a review from a team as a code owner September 25, 2026 09:17
@Lms24
Lms24 requested review from logaretm and msonnb and removed request for a team September 25, 2026 09:17
Comment on lines +91 to +92
captureListeners: new Set(),
bubbleListeners: new Set(),

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.

m: i think this creates another subtle leakage: listeners that are added with { once: true } or aborted by passing a { signal } are automatically cleaned up by the browser and don't go through removeEventListener, thus remaining in these sets indefinitely.

Can we track if either of these are set, then remove the listener from the set if either the signal aborts or the { once: true } listener has fired? WDYT?

This branch has not been deployed

No deployments
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.

instrumentDOM leaks a document click handler when the refcount hits zero on a removal with different capture options (e.g. Radix DismissableLayer)

2 participants