@@ -21,23 +21,34 @@ const CSP_MEDIA_ATTR = 'ngCspMedia';
2121
2222/**
2323 * Script text used to change the media value of the link tags.
24+ *
25+ * NOTE:
26+ * We do not use `document.querySelectorAll('link').forEach((s) => s.addEventListener('load', ...)`
27+ * because this does not always fire on Chome.
28+ * See: https://github.com/angular/angular-cli/issues/26932 and https://crbug.com/1521256
2429 */
2530const LINK_LOAD_SCRIPT_CONTENT = [
26- `(() => {` ,
27- // Save the `children` in a variable since they're a live DOM node collection.
28- // We iterate over the direct descendants, instead of going through a `querySelectorAll`,
29- // because we know that the tags will be directly inside the `head`.
30- ` const children = document.head.children;` ,
31- // Declare `onLoad` outside the loop to avoid leaking memory.
32- // Can't be an arrow function, because we need `this` to refer to the DOM node.
33- ` function onLoad() {this.media = this.getAttribute('${ CSP_MEDIA_ATTR } ');}` ,
34- // Has to use a plain for loop, because some browsers don't support
35- // `forEach` on `children` which is a `HTMLCollection`.
36- ` for (let i = 0; i < children.length; i++) {` ,
37- ` const child = children[i];` ,
38- ` child.hasAttribute('${ CSP_MEDIA_ATTR } ') && child.addEventListener('load', onLoad);` ,
39- ` }` ,
40- `})();` ,
31+ '(() => {' ,
32+ ` const CSP_MEDIA_ATTR = '${ CSP_MEDIA_ATTR } ';` ,
33+ ' const documentElement = document.documentElement;' ,
34+ ' const listener = (e) => {' ,
35+ ' const target = e.target;' ,
36+ ` if (!target || target.tagName !== 'LINK' || !target.hasAttribute(CSP_MEDIA_ATTR)) {` ,
37+ ' return;' ,
38+ ' }' ,
39+
40+ ' target.media = target.getAttribute(CSP_MEDIA_ATTR);' ,
41+ ' target.removeAttribute(CSP_MEDIA_ATTR);' ,
42+
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+
49+ // We use an event with capturing (the true parameter) because load events don't bubble.
50+ ` documentElement.addEventListener('load', listener, true);` ,
51+ '})();' ,
4152] . join ( '\n' ) ;
4253
4354export interface InlineCriticalCssProcessOptions {
@@ -57,6 +68,7 @@ interface PartialHTMLElement {
5768 hasAttribute ( name : string ) : boolean ;
5869 removeAttribute ( name : string ) : void ;
5970 appendChild ( child : PartialHTMLElement ) : void ;
71+ insertBefore ( newNode : PartialHTMLElement , referenceNode ?: PartialHTMLElement ) : void ;
6072 remove ( ) : void ;
6173 name : string ;
6274 textContent : string ;
@@ -155,7 +167,7 @@ class CrittersExtended extends Critters {
155167 // `addEventListener` to apply the media query instead.
156168 link . removeAttribute ( 'onload' ) ;
157169 link . setAttribute ( CSP_MEDIA_ATTR , crittersMedia [ 1 ] ) ;
158- this . conditionallyInsertCspLoadingScript ( document , cspNonce ) ;
170+ this . conditionallyInsertCspLoadingScript ( document , cspNonce , link ) ;
159171 }
160172
161173 // Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
@@ -195,17 +207,21 @@ class CrittersExtended extends Critters {
195207 * Inserts the `script` tag that swaps the critical CSS at runtime,
196208 * if one hasn't been inserted into the document already.
197209 */
198- private conditionallyInsertCspLoadingScript ( document : PartialDocument , nonce : string ) : void {
210+ private conditionallyInsertCspLoadingScript (
211+ document : PartialDocument ,
212+ nonce : string ,
213+ link : PartialHTMLElement ,
214+ ) : void {
199215 if ( this . addedCspScriptsDocuments . has ( document ) ) {
200216 return ;
201217 }
202218
203219 const script = document . createElement ( 'script' ) ;
204220 script . setAttribute ( 'nonce' , nonce ) ;
205221 script . textContent = LINK_LOAD_SCRIPT_CONTENT ;
206- // Append the script to the head since it needs to
207- // run as early as possible, after the `link` tags.
208- document . head . appendChild ( script ) ;
222+ // Prepend the script to the head since it needs to
223+ // run as early as possible, before the `link` tags.
224+ document . head . insertBefore ( script , link ) ;
209225 this . addedCspScriptsDocuments . add ( document ) ;
210226 }
211227}
0 commit comments