fix(ScrollArea): cancel ScrollAreaThumb RAF loop and clean up Presence listeners on unmount#2744
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughTwo ChangesMemory Leak Fixes: onUnmounted Cleanup
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
…e listeners on unmount - ScrollAreaThumb: call removeUnlinkedScrollListenerRef to cancel the requestAnimationFrame loop on unmount, preventing a perpetual RAF loop that retains DOM references after component destruction - ScrollAreaThumb: remove duplicate viewport event listener removal - Presence: explicitly remove animation event listeners from the DOM node when the host component unmounts, preventing leaked listeners on teleported nodes Closes unovue#2420
97784e4 to
4cc6a96
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/core/src/ScrollArea/ScrollAreaThumb.vue (1)
66-66: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPre-existing: Consider defensive check for viewport existence.
The non-null assertion (
!) assumesviewport.valueis defined at unmount. While unlikely givenrootContext.viewportis injected from the parent, a defensive check would be safer:- viewport.value!.removeEventListener('scroll', handleScroll) + viewport.value?.removeEventListener('scroll', handleScroll)This is a pre-existing pattern, not introduced by this PR.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/ScrollArea/ScrollAreaThumb.vue` at line 66, In the unmount lifecycle hook where `viewport.value!.removeEventListener('scroll', handleScroll)` is called, replace the non-null assertion with a defensive check to verify viewport.value exists before attempting to remove the event listener. Use a conditional statement or optional chaining to safely access viewport.value and only proceed with the removeEventListener call if the value is defined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/core/src/ScrollArea/ScrollAreaThumb.vue`:
- Line 66: In the unmount lifecycle hook where
`viewport.value!.removeEventListener('scroll', handleScroll)` is called, replace
the non-null assertion with a defensive check to verify viewport.value exists
before attempting to remove the event listener. Use a conditional statement or
optional chaining to safely access viewport.value and only proceed with the
removeEventListener call if the value is defined.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f85c8d05-070c-418e-ae28-d7de8d00a58e
📒 Files selected for processing (2)
packages/core/src/Presence/usePresence.tspackages/core/src/ScrollArea/ScrollAreaThumb.vue
Description
Fixes #2420 — Memory leak from detached DOM nodes in Popover/Tooltip/ScrollArea after route navigation.
Root Causes
ScrollAreaThumb RAF loop never cancelled — addUnlinkedScrollListener starts a perpetual requestAnimationFrame loop that polls scroll position. The returned cancellation function was stored in a ref but onUnmounted only removed the scroll event listener — it never called the cancellation function. The RAF loop continued running forever, retaining DOM references to the viewport element.
Presence listeners not cleaned up on unmount — usePresence attaches animationstart/animationcancel/animationend listeners to the DOM node. These were removed only when the node ref changed, but in onUnmounted they were never explicitly cleaned up. With teleported content, these leaked listeners can prevent garbage collection.
Fixes
Testing
Summary by CodeRabbit