@@ -23,23 +23,31 @@ const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
2323const CSP_MEDIA_ATTR = 'ngCspMedia' ;
2424/**
2525 * Script text used to change the media value of the link tags.
26+ *
27+ * NOTE:
28+ * We do not use `document.querySelectorAll('link').forEach((s) => s.addEventListener('load', ...)`
29+ * because this does not always fire on Chome.
30+ * See: https://github.com/angular/angular-cli/issues/26932 and https://crbug.com/1521256
2631 */
2732const LINK_LOAD_SCRIPT_CONTENT = [
28- `(() => {` ,
29- // Save the `children` in a variable since they're a live DOM node collection.
30- // We iterate over the direct descendants, instead of going through a `querySelectorAll`,
31- // because we know that the tags will be directly inside the `head`.
32- ` const children = document.head.children;` ,
33- // Declare `onLoad` outside the loop to avoid leaking memory.
34- // Can't be an arrow function, because we need `this` to refer to the DOM node.
35- ` function onLoad() {this.media = this.getAttribute('${ CSP_MEDIA_ATTR } ');}` ,
36- // Has to use a plain for loop, because some browsers don't support
37- // `forEach` on `children` which is a `HTMLCollection`.
38- ` for (let i = 0; i < children.length; i++) {` ,
39- ` const child = children[i];` ,
40- ` child.hasAttribute('${ CSP_MEDIA_ATTR } ') && child.addEventListener('load', onLoad);` ,
41- ` }` ,
42- `})();` ,
33+ '(() => {' ,
34+ ` const CSP_MEDIA_ATTR = '${ CSP_MEDIA_ATTR } ';` ,
35+ ' const documentElement = document.documentElement;' ,
36+ ' const listener = (e) => {' ,
37+ ' const target = e.target;' ,
38+ ` if (!target || target.tagName !== 'LINK' || !target.hasAttribute(CSP_MEDIA_ATTR)) {` ,
39+ ' return;' ,
40+ ' }' ,
41+ ' target.media = target.getAttribute(CSP_MEDIA_ATTR);' ,
42+ ' target.removeAttribute(CSP_MEDIA_ATTR);' ,
43+ // Remove onload listener when there are no longer styles that need to be loaded.
44+ ' if (!document.head.querySelector(`link[${CSP_MEDIA_ATTR}]`)) {' ,
45+ ` documentElement.removeEventListener('load', listener);` ,
46+ ' }' ,
47+ ' };' ,
48+ // We use an event with capturing (the true parameter) because load events don't bubble.
49+ ` documentElement.addEventListener('load', listener, true);` ,
50+ '})();' ,
4351] . join ( '\n' ) ;
4452class CrittersExtended extends critters_1 . default {
4553 optionsExtended ;
@@ -105,7 +113,7 @@ class CrittersExtended extends critters_1.default {
105113 // `addEventListener` to apply the media query instead.
106114 link . removeAttribute ( 'onload' ) ;
107115 link . setAttribute ( CSP_MEDIA_ATTR , crittersMedia [ 1 ] ) ;
108- this . conditionallyInsertCspLoadingScript ( document , cspNonce ) ;
116+ this . conditionallyInsertCspLoadingScript ( document , cspNonce , link ) ;
109117 }
110118 // Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
111119 // a way of doing that at the moment so we fall back to doing it any time a `link` tag is
@@ -137,16 +145,16 @@ class CrittersExtended extends critters_1.default {
137145 * Inserts the `script` tag that swaps the critical CSS at runtime,
138146 * if one hasn't been inserted into the document already.
139147 */
140- conditionallyInsertCspLoadingScript ( document , nonce ) {
148+ conditionallyInsertCspLoadingScript ( document , nonce , link ) {
141149 if ( this . addedCspScriptsDocuments . has ( document ) ) {
142150 return ;
143151 }
144152 const script = document . createElement ( 'script' ) ;
145153 script . setAttribute ( 'nonce' , nonce ) ;
146154 script . textContent = LINK_LOAD_SCRIPT_CONTENT ;
147- // Append the script to the head since it needs to
148- // run as early as possible, after the `link` tags.
149- document . head . appendChild ( script ) ;
155+ // Prepend the script to the head since it needs to
156+ // run as early as possible, before the `link` tags.
157+ document . head . insertBefore ( script , link ) ;
150158 this . addedCspScriptsDocuments . add ( document ) ;
151159 }
152160}
0 commit comments