fix: prevent memory leak in ScriptGoogleMapsAdvancedMarkerElement#649
fix: prevent memory leak in ScriptGoogleMapsAdvancedMarkerElement#649
Conversation
Track unmounted state so async marker creation is aborted if the
component unmounts before `importLibrary('marker')` resolves.
Previously the marker would be created and attached to the map
with no owner to clean it up.
Resolves #646
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
📝 WalkthroughWalkthroughThis change addresses a memory leak in the ScriptGoogleMapsAdvancedMarkerElement component. A ref-based unmount flag ( Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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 |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue (1)
87-100:⚠️ Potential issue | 🟠 MajorAlign unmount condition with mount to prevent missed detachment on early unmounts.
The mount path checks
markerClustererContext?.markerClusterer.value(both context AND initialized value), but unmount only checksmarkerClustererContext(context alone). If a marker mounts while the clusterer context exists but its.valueis still undefined (async initialization window), mount uses theelsebranch to setmap = mapContext.map.value. However, on unmount, the condition enters theifbranch, attempts optionalremoveMarker, and skips theelsethat would clearmap = null. This leaves the marker attached to the map, undoing the leak fix.Proposed fix
- if (markerClustererContext) { + if (markerClustererContext?.markerClusterer.value) { markerClustererContext.markerClusterer.value?.removeMarker(advancedMarkerElement.value) } else { advancedMarkerElement.value.map = null }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue` around lines 87 - 100, The unmount branch uses markerClustererContext alone which can cause skipping the map-clearing path if markerClustererContext exists but markerClustererContext.markerClusterer.value is still undefined; update the unmount condition to match the mount check (use markerClustererContext?.markerClusterer.value) so you call markerClustererContext.markerClusterer.value?.removeMarker(advancedMarkerElement.value) only when the clusterer instance exists and otherwise always run advancedMarkerElement.value.map = null after clearing listeners (reference isUnmounted, advancedMarkerElement, mapContext.mapsApi.value.event.clearInstanceListeners, markerClustererContext?.markerClusterer.value, and advancedMarkerElement.value.map = null).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue`:
- Around line 87-100: The unmount branch uses markerClustererContext alone which
can cause skipping the map-clearing path if markerClustererContext exists but
markerClustererContext.markerClusterer.value is still undefined; update the
unmount condition to match the mount check (use
markerClustererContext?.markerClusterer.value) so you call
markerClustererContext.markerClusterer.value?.removeMarker(advancedMarkerElement.value)
only when the clusterer instance exists and otherwise always run
advancedMarkerElement.value.map = null after clearing listeners (reference
isUnmounted, advancedMarkerElement,
mapContext.mapsApi.value.event.clearInstanceListeners,
markerClustererContext?.markerClusterer.value, and
advancedMarkerElement.value.map = null).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 641bbd47-76f1-4fce-9161-df2f76340fef
📒 Files selected for processing (1)
src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue
The previous fix (#649) only handled one race condition — unmounting during `await importLibrary`. The actual leak was caused by orphaned Vue watchers: watchers created after an `await` lose component instance context and are never auto-stopped on unmount, retaining the entire reactive scope. - Create `useGoogleMapsResource` composable encoding lifecycle safety - Move all options watchers to synchronous setup scope (Vue auto-stops) - Add unmount guards to all async resource creation callbacks - Null all resource refs on unmount to release Google Maps objects - Convert `let` variables to `shallowRef` for proper GC - Make parent `onBeforeUnmount` synchronous (Vue doesn't await async hooks) - Clear `libraries` and `queryToLatLngCache` on parent unmount - Extract injection keys to shared `injectionKeys.ts` Closes #646
🔗 Linked issue
❓ Type of change
📚 Description
Track unmounted state so async marker creation is aborted if the component unmounts before
importLibrary('marker')resolves. Previously the marker would be created and attached to the map with no owner to clean it up.Resolves #646