Skip to content

Commit 7c74674

Browse files
crisbetokirjs
authored andcommitted
fix(core): avoid leaking view data in animations
The animations code currently tracks which views have running leave animations by adding them to a `Set`. This can leak memory if we don't clean something up on time. These changes switch to tracking the views by their ID which doesn't risk retaining the view. Fixes #66255. (cherry picked from commit 80b0fbb)
1 parent 7738eb9 commit 7c74674

File tree

4 files changed

+12
-9
lines changed

4 files changed

+12
-9
lines changed

packages/core/src/animation/longest_animation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,5 @@ function determineLongestAnimationFromElementAnimations(
147147
}
148148
}
149149

150-
export const allLeavingAnimations = new Set<LView>();
150+
/** Tracks the IDs of LViews with leaving animations. */
151+
export const allLeavingAnimations = new Set<number>();

packages/core/src/render3/instructions/animation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
MAX_ANIMATION_TIMEOUT,
1414
} from '../../animation/interfaces';
1515
import {getLView, getCurrentTNode} from '../state';
16-
import {RENDERER, INJECTOR, CONTEXT, LView} from '../interfaces/view';
16+
import {RENDERER, INJECTOR, CONTEXT, LView, ID} from '../interfaces/view';
1717
import {getNativeByTNode} from '../util/view_utils';
1818
import {performanceMarkFeature} from '../../util/performance';
1919
import {Renderer} from '../interfaces/renderer';
@@ -277,7 +277,7 @@ function runLeaveAnimations(
277277

278278
const renderer = lView[RENDERER];
279279
const ngZone = lView[INJECTOR].get(NgZone);
280-
allLeavingAnimations.add(lView);
280+
allLeavingAnimations.add(lView[ID]);
281281
(getLViewLeaveAnimations(lView).get(tNode.index)!.resolvers ??= []).push(resolve);
282282

283283
const activeClasses = getClassListFromValue(value);
@@ -389,7 +389,7 @@ export function ɵɵanimateLeaveListener(value: AnimationFunction): typeof ɵɵa
389389
const tNode = getCurrentTNode()!;
390390
cancelLeavingNodes(tNode, lView);
391391

392-
allLeavingAnimations.add(lView);
392+
allLeavingAnimations.add(lView[ID]);
393393

394394
addAnimationToLView(getLViewLeaveAnimations(lView), tNode, () =>
395395
runLeaveAnimationFunction(lView, tNode, value),

packages/core/src/render3/instructions/control_flow.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
DECLARATION_COMPONENT_VIEW,
2929
HEADER_OFFSET,
3030
HYDRATION,
31+
ID,
3132
INJECTOR,
3233
LView,
3334
TVIEW,
@@ -578,7 +579,7 @@ function clearDetachAnimationList(lContainer: LContainer, index: number): void {
578579
if (lContainer.length <= CONTAINER_HEADER_OFFSET) return;
579580

580581
const indexInContainer = CONTAINER_HEADER_OFFSET + index;
581-
const viewToDetach = lContainer[indexInContainer];
582+
const viewToDetach = lContainer[indexInContainer] as LView;
582583
const animations = viewToDetach
583584
? (viewToDetach[ANIMATIONS] as AnimationLViewData | undefined)
584585
: undefined;
@@ -590,7 +591,7 @@ function clearDetachAnimationList(lContainer: LContainer, index: number): void {
590591
) {
591592
const injector = viewToDetach[INJECTOR];
592593
removeFromAnimationQueue(injector, animations);
593-
allLeavingAnimations.delete(viewToDetach);
594+
allLeavingAnimations.delete(viewToDetach[ID]);
594595
animations.detachedLeaveAnimationFns = undefined;
595596
}
596597
}

packages/core/src/render3/node_manipulation.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import {
7676
TView,
7777
TViewType,
7878
INJECTOR,
79+
ID,
7980
} from './interfaces/view';
8081
import {assertTNodeType} from './node_assert';
8182
import {profiler} from './profiler';
@@ -387,7 +388,7 @@ function runLeaveAnimationsWithCallback(
387388
if (animations == null || animations.leave == undefined || !animations.leave.has(tNode.index))
388389
return callback(false);
389390

390-
if (lView) allLeavingAnimations.add(lView);
391+
if (lView) allLeavingAnimations.add(lView[ID]);
391392

392393
addToAnimationQueue(
393394
injector,
@@ -410,7 +411,7 @@ function runLeaveAnimationsWithCallback(
410411
animations.running = Promise.allSettled(runningAnimations);
411412
runAfterLeaveAnimations(lView!, callback);
412413
} else {
413-
if (lView) allLeavingAnimations.delete(lView);
414+
if (lView) allLeavingAnimations.delete(lView[ID]);
414415
callback(false);
415416
}
416417
},
@@ -423,7 +424,7 @@ function runAfterLeaveAnimations(lView: LView, callback: Function) {
423424
if (runningAnimations) {
424425
runningAnimations.then(() => {
425426
lView[ANIMATIONS]!.running = undefined;
426-
allLeavingAnimations.delete(lView);
427+
allLeavingAnimations.delete(lView[ID]);
427428
callback(true);
428429
});
429430
return;

0 commit comments

Comments
 (0)