Skip to content

Commit d5cd79f

Browse files
crisbetoalxhub
authored andcommitted
perf(platform-browser): resolve memory leak when using animations with shadow DOM (#47903)
`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. PR Close #47903
1 parent e683298 commit d5cd79f

File tree

1 file changed

+10
-4
lines changed

1 file changed

+10
-4
lines changed

packages/platform-browser/animations/src/animation_renderer.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ export class AnimationRendererFactory implements RendererFactory2 {
5050
if (!hostElement || !type || !type.data || !type.data['animation']) {
5151
let renderer: BaseAnimationRenderer|undefined = this._rendererCache.get(delegate);
5252
if (!renderer) {
53-
renderer = new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, this.engine);
53+
// Ensure that the renderer is removed from the cache on destroy
54+
// since it may contain references to detached DOM nodes.
55+
const onRendererDestroy = () => this._rendererCache.delete(delegate);
56+
renderer =
57+
new BaseAnimationRenderer(EMPTY_NAMESPACE_ID, delegate, this.engine, onRendererDestroy);
5458
// only cache this result when the base renderer is used
5559
this._rendererCache.set(delegate, renderer);
5660
}
@@ -135,7 +139,8 @@ export class AnimationRendererFactory implements RendererFactory2 {
135139

136140
export class BaseAnimationRenderer implements Renderer2 {
137141
constructor(
138-
protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine) {
142+
protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine,
143+
private _onDestroy?: () => void) {
139144
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode!(n) : null;
140145
}
141146

@@ -148,6 +153,7 @@ export class BaseAnimationRenderer implements Renderer2 {
148153
destroy(): void {
149154
this.engine.destroy(this.namespaceId, this.delegate);
150155
this.delegate.destroy();
156+
this._onDestroy?.();
151157
}
152158

153159
createElement(name: string, namespace?: string|null|undefined) {
@@ -237,8 +243,8 @@ export class BaseAnimationRenderer implements Renderer2 {
237243
export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 {
238244
constructor(
239245
public factory: AnimationRendererFactory, namespaceId: string, delegate: Renderer2,
240-
engine: AnimationEngine) {
241-
super(namespaceId, delegate, engine);
246+
engine: AnimationEngine, onDestroy?: () => void) {
247+
super(namespaceId, delegate, engine, onDestroy);
242248
this.namespaceId = namespaceId;
243249
}
244250

0 commit comments

Comments
 (0)