Skip to content

Commit 7d2c31e

Browse files
crisbetojosephperrott
authored andcommitted
refactor(layout): use single style tag for dynamically-created media queries (#10946)
1 parent 22524e3 commit 7d2c31e

File tree

2 files changed

+35
-25
lines changed

2 files changed

+35
-25
lines changed

src/cdk/layout/media-matcher.spec.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,24 @@ describe('MediaMatcher', () => {
2828
expect(mediaMatcher.matchMedia('(max-width: 1px)').matches).toBeFalsy();
2929
});
3030

31-
it('adds css rules for provided queries when the platform is webkit, otherwise adds nothing.',
31+
it('should add CSS rules for provided queries when the platform is webkit',
3232
inject([Platform], (platform: Platform) => {
33-
let randomWidth = Math.random();
34-
expect(document.head.textContent).not.toContain(randomWidth);
33+
const randomWidth = `${Math.random()}px`;
34+
35+
expect(getStyleTagByString(randomWidth)).toBeFalsy();
3536
mediaMatcher.matchMedia(`(width: ${randomWidth})`);
3637

3738
if (platform.WEBKIT) {
38-
expect(document.head.textContent).toContain(randomWidth);
39+
expect(getStyleTagByString(randomWidth)).toBeTruthy();
3940
} else {
40-
expect(document.head.textContent).not.toContain(randomWidth);
41+
expect(getStyleTagByString(randomWidth)).toBeFalsy();
42+
}
43+
44+
function getStyleTagByString(str: string): HTMLStyleElement | undefined {
45+
return Array.from(document.head.querySelectorAll('style')).find(tag => {
46+
const rules = tag.sheet ? Array.from((tag.sheet as CSSStyleSheet).cssRules) : [];
47+
return !!rules.find(rule => rule.cssText.includes(str));
48+
});
4149
}
4250
}));
4351
});

src/cdk/layout/media-matcher.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import {Injectable} from '@angular/core';
99
import {Platform} from '@angular/cdk/platform';
1010

11-
/**
12-
* Global registry for all dynamically-created, injected style tags.
13-
*/
14-
const styleElementForWebkitCompatibility: Map<string, HTMLStyleElement> = new Map();
11+
/** Global registry for all dynamically-created, injected media queries. */
12+
const mediaQueriesForWebkitCompatibility: Set<string> = new Set<string>();
13+
14+
/** Style tag that holds all of the dynamically-created media queries. */
15+
let mediaQueryStyleNode: HTMLStyleElement | undefined;
1516

1617
/** A utility for calling matchMedia queries. */
1718
@Injectable({providedIn: 'root'})
@@ -42,27 +43,28 @@ export class MediaMatcher {
4243
}
4344

4445
/**
45-
* For Webkit engines that only trigger the MediaQueryListListener when there is at least one CSS
46-
* selector for the respective media query.
46+
* For Webkit engines that only trigger the MediaQueryListListener when
47+
* there is at least one CSS selector for the respective media query.
4748
*/
4849
function createEmptyStyleRule(query: string) {
49-
if (!styleElementForWebkitCompatibility.has(query)) {
50-
try {
51-
const style = document.createElement('style');
52-
53-
style.setAttribute('type', 'text/css');
54-
if (!style.sheet) {
55-
const cssText = `@media ${query} {.fx-query-test{ }}`;
56-
style.appendChild(document.createTextNode(cssText));
57-
}
50+
if (mediaQueriesForWebkitCompatibility.has(query)) {
51+
return;
52+
}
5853

59-
document.getElementsByTagName('head')[0].appendChild(style);
54+
try {
55+
if (!mediaQueryStyleNode) {
56+
mediaQueryStyleNode = document.createElement('style');
57+
mediaQueryStyleNode.setAttribute('type', 'text/css');
58+
document.head.appendChild(mediaQueryStyleNode);
59+
}
6060

61-
// Store in private global registry
62-
styleElementForWebkitCompatibility.set(query, style);
63-
} catch (e) {
64-
console.error(e);
61+
if (mediaQueryStyleNode.sheet) {
62+
(mediaQueryStyleNode.sheet as CSSStyleSheet)
63+
.insertRule(`@media ${query} {.fx-query-test{ }}`, 0);
64+
mediaQueriesForWebkitCompatibility.add(query);
6565
}
66+
} catch (e) {
67+
console.error(e);
6668
}
6769
}
6870

0 commit comments

Comments
 (0)