@@ -182,23 +182,9 @@ function sanitizeHtml(
182182 const disallowedList = doc . querySelectorAll ( removeTags . join ( ', ' ) ) ;
183183 disallowedList . forEach ( element => element . remove ( ) ) ;
184184
185- // Move styles from head to body.
186- const styleList = doc . querySelectorAll ( 'head > style' ) ;
187- styleList . forEach ( element => {
188- doc . body . appendChild ( element ) ;
189- } ) ;
190-
191185 // Filter other tags.
192186 const toRemove : Element [ ] = [ ] ;
193- const elementIter = doc . createNodeIterator (
194- doc . body ,
195- NodeFilter . SHOW_ELEMENT ,
196- {
197- acceptNode : ( ) => {
198- return NodeFilter . FILTER_ACCEPT ;
199- } ,
200- } ,
201- ) ;
187+ const elementIter = doc . createNodeIterator ( doc . body , NodeFilter . SHOW_ELEMENT ) ;
202188
203189 while ( ( node = elementIter . nextNode ( ) ) ) {
204190 const element = node as HTMLElement ;
@@ -287,67 +273,75 @@ function sanitizeHtml(
287273 }
288274 }
289275
290- // Prepend wrapper ID.
291- const bodyStyleList = doc . querySelectorAll ( 'body style' ) ;
292- bodyStyleList . forEach ( element => {
293- const styleElement = element as HTMLStyleElement ;
294- const stylesheet = styleElement . sheet as CSSStyleSheet ;
276+ const styleList = doc . querySelectorAll ( 'style' ) ;
277+
278+ if ( styleList . length ) {
279+ const sanitizedStyle = doc . createElement ( 'style' ) ;
280+
281+ doc . body . append ( sanitizedStyle ) ;
282+
283+ const sheet = sanitizedStyle . sheet ! ;
295284 const newRules : CSSRule [ ] = [ ] ;
296285
297- if ( ! stylesheet . cssRules ) {
298- styleElement . textContent = '' ;
299- return ;
300- }
286+ styleList . forEach ( element => {
287+ const styleElement = element as HTMLStyleElement ;
288+ const stylesheet = styleElement . sheet as CSSStyleSheet ;
301289
302- for ( let i = 0 ; i < stylesheet . cssRules . length ; i ++ ) {
303- const rule = stylesheet . cssRules [ i ] as CSSStyleRule ;
304-
305- if ( 'selectorText' in rule ) {
306- sanitizeCssRule (
307- rule ,
308- id ,
309- allowedSchemas ,
310- allowedCssProperties ,
311- preserveCssPriority ,
312- rewriteExternalResources ,
313- ) ;
314- newRules . push ( rule ) ;
315- } else if ( 'cssRules' in rule && 'media' in rule ) {
316- // According to https://www.caniemail.com/,
317- // out of all at-rules, Gmail only supports @media.
318- const mediaRule = rule as any as CSSMediaRule ;
319- const newRulesMedia : CSSRule [ ] = [ ] ;
320-
321- for ( let i = 0 ; i < mediaRule . cssRules . length ; i ++ ) {
322- const rule = mediaRule . cssRules [ i ] as CSSStyleRule ;
323-
324- if ( rule . type === rule . STYLE_RULE ) {
325- sanitizeCssRule (
326- rule ,
327- id ,
328- allowedSchemas ,
329- allowedCssProperties ,
330- preserveCssPriority ,
331- rewriteExternalResources ,
332- ) ;
333- newRulesMedia . push ( rule ) ;
334- }
335- }
290+ if ( ! stylesheet . cssRules ) {
291+ styleElement . textContent = '' ;
292+ return ;
293+ }
336294
337- while ( mediaRule . cssRules . length > 0 ) {
338- mediaRule . deleteRule ( 0 ) ;
339- }
295+ for ( let i = 0 ; i < stylesheet . cssRules . length ; i ++ ) {
296+ const rule = stylesheet . cssRules [ i ] ;
297+
298+ if ( rule instanceof CSSStyleRule ) {
299+ sanitizeCssRule (
300+ rule ,
301+ id ,
302+ allowedSchemas ,
303+ allowedCssProperties ,
304+ preserveCssPriority ,
305+ rewriteExternalResources ,
306+ ) ;
340307
341- for ( const rule of newRulesMedia ) {
342- mediaRule . insertRule ( rule . cssText , mediaRule . cssRules . length ) ;
343- }
308+ newRules . push ( rule ) ;
309+ } else if ( rule instanceof CSSMediaRule ) {
310+ const idx = sheet . insertRule ( '@media {}' , sheet . cssRules . length ) ;
311+ const sanitizedMediaRule = sheet . cssRules [ idx ] as CSSMediaRule ;
312+
313+ // According to https://www.caniemail.com/,
314+ // out of all at-rules, Gmail only supports @media.
315+ const mediaRule = rule as any as CSSMediaRule ;
316+
317+ for ( let i = 0 ; i < mediaRule . cssRules . length ; i ++ ) {
318+ const rule = mediaRule . cssRules [ i ] ;
319+
320+ if ( rule instanceof CSSStyleRule ) {
321+ sanitizeCssRule (
322+ rule ,
323+ id ,
324+ allowedSchemas ,
325+ allowedCssProperties ,
326+ preserveCssPriority ,
327+ rewriteExternalResources ,
328+ ) ;
329+ sanitizedMediaRule . insertRule (
330+ rule . cssText ,
331+ sanitizedMediaRule . cssRules . length ,
332+ ) ;
333+ }
334+ }
344335
345- newRules . push ( mediaRule ) ;
336+ newRules . push ( mediaRule ) ;
337+ }
346338 }
347- }
348339
349- styleElement . textContent = newRules . map ( rule => rule . cssText ) . join ( '\n' ) ;
350- } ) ;
340+ styleElement . remove ( ) ;
341+ } ) ;
342+
343+ sanitizedStyle . textContent = newRules . map ( rule => rule . cssText ) . join ( '\n' ) ;
344+ }
351345
352346 // Wrap body inside of a div with the generated ID.
353347 if ( noWrapper ) {
0 commit comments