Skip to content

Commit f66c9ae

Browse files
JoostKalxhub
authored andcommitted
fix(platform-browser): prevent memory leak of style nodes if shadow DOM encapsulation is used (angular#42005)
Prior to this change, any inserted `<style>` nodes into shadow dom trees would be retained in memory, even after the shadow dom tree has been removed. This commit fixes the memory leak by tracking the inserted `<style>` nodes per host element, such that removal of the host element also releases the style nodes. Fixes angular#36655 PR Close angular#42005
1 parent 2d9a9aa commit f66c9ae

File tree

5 files changed

+39
-9
lines changed

5 files changed

+39
-9
lines changed

packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,9 @@
14301430
{
14311431
"name": "removeListItem"
14321432
},
1433+
{
1434+
"name": "removeStyle"
1435+
},
14331436
{
14341437
"name": "renderComponent"
14351438
},

packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,9 @@
13941394
{
13951395
"name": "removeListItem"
13961396
},
1397+
{
1398+
"name": "removeStyle"
1399+
},
13971400
{
13981401
"name": "renderComponent"
13991402
},

packages/core/test/bundling/router/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,6 +1808,9 @@
18081808
{
18091809
"name": "removeFromArray"
18101810
},
1811+
{
1812+
"name": "removeStyle"
1813+
},
18111814
{
18121815
"name": "renderComponent"
18131816
},

packages/platform-browser/src/dom/shared_styles_host.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,35 +34,47 @@ export class SharedStylesHost {
3434

3535
@Injectable()
3636
export class DomSharedStylesHost extends SharedStylesHost implements OnDestroy {
37-
private _hostNodes = new Set<Node>();
38-
private _styleNodes = new Set<Node>();
37+
// Maps all registered host nodes to a list of style nodes that have been added to the host node.
38+
private _hostNodes = new Map<Node, Node[]>();
39+
3940
constructor(@Inject(DOCUMENT) private _doc: any) {
4041
super();
41-
this._hostNodes.add(_doc.head);
42+
this._hostNodes.set(_doc.head, []);
4243
}
4344

44-
private _addStylesToHost(styles: Set<string>, host: Node): void {
45+
private _addStylesToHost(styles: Set<string>, host: Node, styleNodes: Node[]): void {
4546
styles.forEach((style: string) => {
4647
const styleEl = this._doc.createElement('style');
4748
styleEl.textContent = style;
48-
this._styleNodes.add(host.appendChild(styleEl));
49+
styleNodes.push(host.appendChild(styleEl));
4950
});
5051
}
5152

5253
addHost(hostNode: Node): void {
53-
this._addStylesToHost(this._stylesSet, hostNode);
54-
this._hostNodes.add(hostNode);
54+
const styleNodes: Node[] = [];
55+
this._addStylesToHost(this._stylesSet, hostNode, styleNodes);
56+
this._hostNodes.set(hostNode, styleNodes);
5557
}
5658

5759
removeHost(hostNode: Node): void {
60+
const styleNodes = this._hostNodes.get(hostNode);
61+
if (styleNodes) {
62+
styleNodes.forEach(removeStyle);
63+
}
5864
this._hostNodes.delete(hostNode);
5965
}
6066

6167
onStylesAdded(additions: Set<string>): void {
62-
this._hostNodes.forEach(hostNode => this._addStylesToHost(additions, hostNode));
68+
this._hostNodes.forEach((styleNodes, hostNode) => {
69+
this._addStylesToHost(additions, hostNode, styleNodes);
70+
});
6371
}
6472

6573
ngOnDestroy(): void {
66-
this._styleNodes.forEach(styleNode => getDOM().remove(styleNode));
74+
this._hostNodes.forEach(styleNodes => styleNodes.forEach(removeStyle));
6775
}
6876
}
77+
78+
function removeStyle(styleNode: Node): void {
79+
getDOM().remove(styleNode);
80+
}

packages/platform-browser/test/dom/shared_styles_host_spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
4747
expect(doc.head).toHaveText('a {};b {};');
4848
});
4949

50+
it('should remove style nodes when the host is removed', () => {
51+
ssh.addStyles(['a {};']);
52+
ssh.addHost(someHost);
53+
expect(someHost.innerHTML).toEqual('<style>a {};</style>');
54+
55+
ssh.removeHost(someHost);
56+
expect(someHost.innerHTML).toEqual('');
57+
});
58+
5059
it('should remove style nodes on destroy', () => {
5160
ssh.addStyles(['a {};']);
5261
ssh.addHost(someHost);

0 commit comments

Comments
 (0)