Skip to content

Commit d78e646

Browse files
committed
refactor(platform-browser): avoid duplicate styles when re-adding a host in SharedStylesHost
`SharedStylesHost` has a known bug where removing hosts does not remove styles from the DOM and fixing that would be a breaking change. However, this leads to a separate bug where removing a style down to zero usages and then adding an extra one, could duplicate styles on the page. This commit ensures styles are only appended if they aren't already present on the parent node from a previously leaked style.
1 parent 337da99 commit d78e646

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,18 @@ export class SharedStylesHost implements ɵSharedStylesHost, OnDestroy {
219219
if (existingUsage === 0) {
220220
// Add existing styles to new host
221221
for (const [style, {elements}] of this.inline) {
222-
elements.push(this.addElement(hostNode, createStyleElement(style, this.doc)));
222+
// `removeHost` currently does not actually remove styles when usage drops to zero.
223+
// Therefore removing a host to zero and then re-adding to one, could cause Angular
224+
// to duplicate the styles on the page. This check makes sure we don't add the styles
225+
// more than once.
226+
if (!elements.some((e) => e.parentNode === hostNode)) {
227+
elements.push(this.addElement(hostNode, createStyleElement(style, this.doc)));
228+
}
223229
}
224230
for (const [url, {elements}] of this.external) {
225-
elements.push(this.addElement(hostNode, createLinkElement(url, this.doc)));
231+
if (!elements.some((e) => e.parentNode === hostNode)) {
232+
elements.push(this.addElement(hostNode, createLinkElement(url, this.doc)));
233+
}
226234
}
227235
}
228236

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ describe('SharedStylesHost', () => {
5454
expect(someHost.innerHTML).not.toContain('<style>c {};</style>');
5555
});
5656

57+
it('should not duplicate styles when removing *all hosts* and then re-adding a pre-existing host', () => {
58+
ssh.addHost(someHost);
59+
ssh.addStyles(['a {};']);
60+
expect(someHost.innerHTML).toEqual('<style>a {};</style>');
61+
62+
// Reproduce undesirable, but known leak.
63+
ssh.removeHost(someHost);
64+
expect(someHost.innerHTML).toEqual('<style>a {};</style>');
65+
66+
// Should not duplicate.
67+
ssh.addHost(someHost);
68+
expect(someHost.innerHTML).toEqual('<style>a {};</style>');
69+
});
70+
5771
it('should add styles only once to hosts', () => {
5872
ssh.addStyles(['a {};']);
5973
ssh.addHost(someHost);
@@ -129,6 +143,20 @@ describe('SharedStylesHost', () => {
129143
expect(someHost.innerHTML).not.toContain('<link rel="stylesheet" href="component-3.css">');
130144
});
131145

146+
it('should not duplicate styles when removing and re-adding a host', () => {
147+
ssh.addStyles([], ['component-1.css']);
148+
ssh.addHost(someHost);
149+
expect(someHost.innerHTML).toEqual('<link rel="stylesheet" href="component-1.css">');
150+
151+
// Reproduce undesirable, but known leak.
152+
ssh.removeHost(someHost);
153+
expect(someHost.innerHTML).toEqual('<link rel="stylesheet" href="component-1.css">');
154+
155+
// Should not duplicate
156+
ssh.addHost(someHost);
157+
expect(someHost.innerHTML).toEqual('<link rel="stylesheet" href="component-1.css">');
158+
});
159+
132160
it('should add styles only once to hosts', () => {
133161
ssh.addStyles([], ['component-1.css']);
134162
ssh.addHost(someHost);

0 commit comments

Comments
 (0)