From 7d2c31efaab460053b68e41531abb55d8d4b6779 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 1 May 2018 10:32:17 -0400 Subject: [PATCH] refactor(layout): use single style tag for dynamically-created media queries (#10946) --- src/cdk/layout/media-matcher.spec.ts | 18 ++++++++---- src/cdk/layout/media-matcher.ts | 42 +++++++++++++++------------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/cdk/layout/media-matcher.spec.ts b/src/cdk/layout/media-matcher.spec.ts index cf5c11c4a6e1..94b1ee268350 100644 --- a/src/cdk/layout/media-matcher.spec.ts +++ b/src/cdk/layout/media-matcher.spec.ts @@ -28,16 +28,24 @@ describe('MediaMatcher', () => { expect(mediaMatcher.matchMedia('(max-width: 1px)').matches).toBeFalsy(); }); - it('adds css rules for provided queries when the platform is webkit, otherwise adds nothing.', + it('should add CSS rules for provided queries when the platform is webkit', inject([Platform], (platform: Platform) => { - let randomWidth = Math.random(); - expect(document.head.textContent).not.toContain(randomWidth); + const randomWidth = `${Math.random()}px`; + + expect(getStyleTagByString(randomWidth)).toBeFalsy(); mediaMatcher.matchMedia(`(width: ${randomWidth})`); if (platform.WEBKIT) { - expect(document.head.textContent).toContain(randomWidth); + expect(getStyleTagByString(randomWidth)).toBeTruthy(); } else { - expect(document.head.textContent).not.toContain(randomWidth); + expect(getStyleTagByString(randomWidth)).toBeFalsy(); + } + + function getStyleTagByString(str: string): HTMLStyleElement | undefined { + return Array.from(document.head.querySelectorAll('style')).find(tag => { + const rules = tag.sheet ? Array.from((tag.sheet as CSSStyleSheet).cssRules) : []; + return !!rules.find(rule => rule.cssText.includes(str)); + }); } })); }); diff --git a/src/cdk/layout/media-matcher.ts b/src/cdk/layout/media-matcher.ts index 5af5e0219b2e..8a5b73777f5c 100644 --- a/src/cdk/layout/media-matcher.ts +++ b/src/cdk/layout/media-matcher.ts @@ -8,10 +8,11 @@ import {Injectable} from '@angular/core'; import {Platform} from '@angular/cdk/platform'; -/** - * Global registry for all dynamically-created, injected style tags. - */ -const styleElementForWebkitCompatibility: Map = new Map(); +/** Global registry for all dynamically-created, injected media queries. */ +const mediaQueriesForWebkitCompatibility: Set = new Set(); + +/** Style tag that holds all of the dynamically-created media queries. */ +let mediaQueryStyleNode: HTMLStyleElement | undefined; /** A utility for calling matchMedia queries. */ @Injectable({providedIn: 'root'}) @@ -42,27 +43,28 @@ export class MediaMatcher { } /** - * For Webkit engines that only trigger the MediaQueryListListener when there is at least one CSS - * selector for the respective media query. + * For Webkit engines that only trigger the MediaQueryListListener when + * there is at least one CSS selector for the respective media query. */ function createEmptyStyleRule(query: string) { - if (!styleElementForWebkitCompatibility.has(query)) { - try { - const style = document.createElement('style'); - - style.setAttribute('type', 'text/css'); - if (!style.sheet) { - const cssText = `@media ${query} {.fx-query-test{ }}`; - style.appendChild(document.createTextNode(cssText)); - } + if (mediaQueriesForWebkitCompatibility.has(query)) { + return; + } - document.getElementsByTagName('head')[0].appendChild(style); + try { + if (!mediaQueryStyleNode) { + mediaQueryStyleNode = document.createElement('style'); + mediaQueryStyleNode.setAttribute('type', 'text/css'); + document.head.appendChild(mediaQueryStyleNode); + } - // Store in private global registry - styleElementForWebkitCompatibility.set(query, style); - } catch (e) { - console.error(e); + if (mediaQueryStyleNode.sheet) { + (mediaQueryStyleNode.sheet as CSSStyleSheet) + .insertRule(`@media ${query} {.fx-query-test{ }}`, 0); + mediaQueriesForWebkitCompatibility.add(query); } + } catch (e) { + console.error(e); } }