From d2efca0b046734fa6ac462dd7a25e4e5f9c0a8f0 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sun, 11 Aug 2024 03:46:16 +0100 Subject: [PATCH] fix: prevent numerous transition/animation memory leaks (#12759) * fix: prevent numerous transition/animation memory leaks * address feedback * tweak --- .changeset/silly-masks-exist.md | 5 +++++ .../src/internal/client/dom/elements/transitions.js | 11 ++++++++++- playgrounds/demo/index.html | 3 --- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 .changeset/silly-masks-exist.md diff --git a/.changeset/silly-masks-exist.md b/.changeset/silly-masks-exist.md new file mode 100644 index 0000000000..e1b161719b --- /dev/null +++ b/.changeset/silly-masks-exist.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent numerous transition/animation memory leaks diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index 5d0005b2c1..8777ba706b 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -222,6 +222,8 @@ export function transition(flags, element, get_fn, get_params) { 1, () => { dispatch_event(element, 'introend'); + // Ensure we cancel the animation to prevent leaking + intro?.abort(); intro = current_options = undefined; }, is_both @@ -249,6 +251,8 @@ export function transition(flags, element, get_fn, get_params) { 0, () => { dispatch_event(element, 'outroend'); + // Ensure we cancel the animation to prevent leaking + outro?.abort(); outro = current_options = undefined; fn?.(); }, @@ -322,8 +326,10 @@ function animate(element, options, counterpart, t2, on_finish, on_abort) { // once DOM has been updated... /** @type {Animation} */ var a; + var aborted = false; queue_micro_task(() => { + if (aborted) return; var o = options({ direction: is_intro ? 'in' : 'out' }); a = animate(element, o, counterpart, t2, on_finish, on_abort); }); @@ -331,7 +337,10 @@ function animate(element, options, counterpart, t2, on_finish, on_abort) { // ...but we want to do so without using `async`/`await` everywhere, so // we return a facade that allows everything to remain synchronous return { - abort: () => a.abort(), + abort: () => { + aborted = true; + a?.abort(); + }, deactivate: () => a.deactivate(), reset: () => a.reset(), t: (now) => a.t(now) diff --git a/playgrounds/demo/index.html b/playgrounds/demo/index.html index fae74ccb1e..512b5426a9 100644 --- a/playgrounds/demo/index.html +++ b/playgrounds/demo/index.html @@ -20,9 +20,6 @@ const component = render(App, { target: document.getElementById('root') }); - - // @ts-ignore - window.unmount = () => unmount(component);