@@ -21,23 +21,34 @@ const CSP_MEDIA_ATTR = 'ngCspMedia';
21
21
22
22
/**
23
23
* 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
24
29
*/
25
30
const 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
+ '})();' ,
41
52
] . join ( '\n' ) ;
42
53
43
54
export interface InlineCriticalCssProcessOptions {
@@ -62,6 +73,7 @@ interface PartialHTMLElement {
62
73
hasAttribute ( name : string ) : boolean ;
63
74
removeAttribute ( name : string ) : void ;
64
75
appendChild ( child : PartialHTMLElement ) : void ;
76
+ insertBefore ( newNode : PartialHTMLElement , referenceNode ?: PartialHTMLElement ) : void ;
65
77
remove ( ) : void ;
66
78
name : string ;
67
79
textContent : string ;
@@ -164,7 +176,7 @@ class CrittersExtended extends Critters {
164
176
// `addEventListener` to apply the media query instead.
165
177
link . removeAttribute ( 'onload' ) ;
166
178
link . setAttribute ( CSP_MEDIA_ATTR , crittersMedia [ 1 ] ) ;
167
- this . conditionallyInsertCspLoadingScript ( document , cspNonce ) ;
179
+ this . conditionallyInsertCspLoadingScript ( document , cspNonce , link ) ;
168
180
}
169
181
170
182
// Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
@@ -204,7 +216,11 @@ class CrittersExtended extends Critters {
204
216
* Inserts the `script` tag that swaps the critical CSS at runtime,
205
217
* if one hasn't been inserted into the document already.
206
218
*/
207
- private conditionallyInsertCspLoadingScript ( document : PartialDocument , nonce : string ) : void {
219
+ private conditionallyInsertCspLoadingScript (
220
+ document : PartialDocument ,
221
+ nonce : string ,
222
+ link : PartialHTMLElement ,
223
+ ) : void {
208
224
if ( this . addedCspScriptsDocuments . has ( document ) ) {
209
225
return ;
210
226
}
@@ -219,9 +235,9 @@ class CrittersExtended extends Critters {
219
235
const script = document . createElement ( 'script' ) ;
220
236
script . setAttribute ( 'nonce' , nonce ) ;
221
237
script . textContent = LINK_LOAD_SCRIPT_CONTENT ;
222
- // Append the script to the head since it needs to
223
- // run as early as possible, after the `link` tags.
224
- document . head . appendChild ( script ) ;
238
+ // Prepend the script to the head since it needs to
239
+ // run as early as possible, before the `link` tags.
240
+ document . head . insertBefore ( script , link ) ;
225
241
this . addedCspScriptsDocuments . add ( document ) ;
226
242
}
227
243
}
0 commit comments