@@ -20,6 +20,27 @@ const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
2020 */
2121const CSP_MEDIA_ATTR = 'ngCspMedia' ;
2222
23+ /**
24+ * Script text used to change the media value of the link tags.
25+ */
26+ const LINK_LOAD_SCRIPT_CONTENT = [
27+ `(() => {` ,
28+ // Save the `children` in a variable since they're a live DOM node collection.
29+ // We iterate over the direct descendants, instead of going through a `querySelectorAll`,
30+ // because we know that the tags will be directly inside the `head`.
31+ ` const children = document.head.children;` ,
32+ // Declare `onLoad` outside the loop to avoid leaking memory.
33+ // Can't be an arrow function, because we need `this` to refer to the DOM node.
34+ ` function onLoad() {this.media = this.getAttribute('${ CSP_MEDIA_ATTR } ');}` ,
35+ // Has to use a plain for loop, because some browsers don't support
36+ // `forEach` on `children` which is a `HTMLCollection`.
37+ ` for (let i = 0; i < children.length; i++) {` ,
38+ ` const child = children[i];` ,
39+ ` child.hasAttribute('${ CSP_MEDIA_ATTR } ') && child.addEventListener('load', onLoad);` ,
40+ ` }` ,
41+ `})();` ,
42+ ] . join ( '\n' ) ;
43+
2344export interface InlineCriticalCssProcessOptions {
2445 outputPath : string ;
2546}
@@ -40,6 +61,8 @@ interface PartialHTMLElement {
4061 textContent : string ;
4162 tagName : string | null ;
4263 children : PartialHTMLElement [ ] ;
64+ next : PartialHTMLElement | null ;
65+ prev : PartialHTMLElement | null ;
4366}
4467
4568/** Partial representation of an HTML `Document`. */
@@ -123,15 +146,7 @@ class CrittersExtended extends Critters {
123146 this . conditionallyInsertCspLoadingScript ( document , cspNonce ) ;
124147 }
125148
126- // Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
127- // a way of doing that at the moment so we fall back to doing it any time a `link` tag is
128- // inserted. We mitigate it by only iterating the direct children of the `<head>` which
129- // should be pretty shallow.
130- document . head . children . forEach ( ( child ) => {
131- if ( child . tagName === 'style' && ! child . hasAttribute ( 'nonce' ) ) {
132- child . setAttribute ( 'nonce' , cspNonce ) ;
133- }
134- } ) ;
149+ link . prev ?. setAttribute ( 'nonce' , cspNonce ) ;
135150 }
136151
137152 return returnValue ;
@@ -142,7 +157,8 @@ class CrittersExtended extends Critters {
142157 */
143158 private findCspNonce ( document : PartialDocument ) : string | null {
144159 if ( this . documentNonces . has ( document ) ) {
145- return this . documentNonces . get ( document ) ?? null ;
160+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
161+ return this . documentNonces . get ( document ) ! ;
146162 }
147163
148164 // HTML attribute are case-insensitive, but the parser used by Critters is case-sensitive.
@@ -159,30 +175,14 @@ class CrittersExtended extends Critters {
159175 * Inserts the `script` tag that swaps the critical CSS at runtime,
160176 * if one hasn't been inserted into the document already.
161177 */
162- private conditionallyInsertCspLoadingScript ( document : PartialDocument , nonce : string ) {
178+ private conditionallyInsertCspLoadingScript ( document : PartialDocument , nonce : string ) : void {
163179 if ( this . addedCspScriptsDocuments . has ( document ) ) {
164180 return ;
165181 }
166182
167183 const script = document . createElement ( 'script' ) ;
168184 script . setAttribute ( 'nonce' , nonce ) ;
169- script . textContent = [
170- `(() => {` ,
171- // Save the `children` in a variable since they're a live DOM node collection.
172- // We iterate over the direct descendants, instead of going through a `querySelectorAll`,
173- // because we know that the tags will be directly inside the `head`.
174- ` const children = document.head.children;` ,
175- // Declare `onLoad` outside the loop to avoid leaking memory.
176- // Can't be an arrow function, because we need `this` to refer to the DOM node.
177- ` function onLoad() {this.media = this.getAttribute('${ CSP_MEDIA_ATTR } ');}` ,
178- // Has to use a plain for loop, because some browsers don't support
179- // `forEach` on `children` which is a `HTMLCollection`.
180- ` for (let i = 0; i < children.length; i++) {` ,
181- ` const child = children[i];` ,
182- ` child.hasAttribute('${ CSP_MEDIA_ATTR } ') && child.addEventListener('load', onLoad);` ,
183- ` }` ,
184- `})();` ,
185- ] . join ( '\n' ) ;
185+ script . textContent = LINK_LOAD_SCRIPT_CONTENT ;
186186 // Append the script to the head since it needs to
187187 // run as early as possible, after the `link` tags.
188188 document . head . appendChild ( script ) ;
0 commit comments