diff --git a/.changeset/spotty-ducks-refuse.md b/.changeset/spotty-ducks-refuse.md new file mode 100644 index 0000000000..6faba7e89f --- /dev/null +++ b/.changeset/spotty-ducks-refuse.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: release `last_propagated_event` after event propagation settles so it no longer retains the last event's target subtree diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 45e042a8c6..068c64b9ab 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -159,12 +159,15 @@ export function delegate(events) { } // used to store the reference to the currently propagated event -// to prevent garbage collection between microtasks in Firefox +// to prevent garbage collection between microtasks in Firefox (<= 141) // If the event object is GCed too early, the expando __root property // set on the event object is lost, causing the event delegation // to process the event twice let last_propagated_event = null; +// whether a task is already queued to clear `last_propagated_event` +let last_propagated_event_clear_scheduled = false; + /** * @this {EventTarget} * @param {Event} event @@ -179,6 +182,21 @@ export function handle_event_propagation(event) { last_propagated_event = event; + // The reference is only needed while the event can still reach another + // delegated root, i.e. during the current (synchronous) dispatch and its + // microtask checkpoints. Clearing it in a later task preserves the + // Firefox workaround while making sure the slot doesn't retain the last + // event forever — through `event.target` it would otherwise keep the + // entire detached subtree of whatever the user last clicked in alive + // until the next delegated event happens to arrive. + if (!last_propagated_event_clear_scheduled) { + last_propagated_event_clear_scheduled = true; + setTimeout(() => { + last_propagated_event_clear_scheduled = false; + last_propagated_event = null; + }); + } + // composedPath contains list of nodes the event has propagated through. // We check `event_symbol` to skip all nodes below it in case this is a // parent of the `event_symbol` node, which indicates that there's nested