From 9ecada4bee4db89895a71a28931b4eef546c2429 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 28 Oct 2022 11:29:07 +0200 Subject: [PATCH] perf(platform-browser): resolve memory leak when using animations with shadow DOM `AnimationRendererFactory` maintains a map between a renderer delegate and the animations renderer it corresponds to, but the renderers are never removed from the map. This leads to memory leaks when used with the `ShadowDom` view encapsulation, because the specific renderer keeps a references to its shadow root which in turn references all the elements in the view. These changes resolve the leak by clearing the reference when the animations renderer is destroyed. Fixes #47892. --- .../animations/src/animation_renderer.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts index d7997ce0dd00b..8f4166f7bcbcc 100644 --- a/packages/platform-browser/animations/src/animation_renderer.ts +++ b/packages/platform-browser/animations/src/animation_renderer.ts @@ -50,7 +50,11 @@ export class AnimationRendererFactory implements RendererFactory2 { if (!hostElement || !type || !type.data || !type.data['animation']) { let renderer: BaseAnimationRenderer|undefined = this._rendererCache.get(delegate); if (!renderer) { - renderer = new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, this.engine); + // Ensure that the renderer is removed from the cache on destroy + // since it may contain references to detached DOM nodes. + const onRendererDestroy = () => this._rendererCache.delete(delegate); + renderer = + new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, this.engine, onRendererDestroy); // only cache this result when the base renderer is used this._rendererCache.set(delegate, renderer); } @@ -135,7 +139,8 @@ export class AnimationRendererFactory implements RendererFactory2 { export class BaseAnimationRenderer implements Renderer2 { constructor( - protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine) { + protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine, + private _onDestroy?: () => void) { this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode!(n) : null; } @@ -148,6 +153,7 @@ export class BaseAnimationRenderer implements Renderer2 { destroy(): void { this.engine.destroy(this.namespaceId, this.delegate); this.delegate.destroy(); + this._onDestroy?.(); } createElement(name: string, namespace?: string|null|undefined) { @@ -237,8 +243,8 @@ export class BaseAnimationRenderer implements Renderer2 { export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 { constructor( public factory: AnimationRendererFactory, namespaceId: string, delegate: Renderer2, - engine: AnimationEngine) { - super(namespaceId, delegate, engine); + engine: AnimationEngine, onDestroy?: () => void) { + super(namespaceId, delegate, engine, onDestroy); this.namespaceId = namespaceId; }