1
1
import { ReplacementImage } from "../gui/base/icons/Icons"
2
- import { downcast , isEmpty , stringToUtf8Uint8Array , utf8Uint8ArrayToString } from "@tutao/tutanota-utils"
2
+ import { downcast , isEmpty , memoized , stringToUtf8Uint8Array , utf8Uint8ArrayToString } from "@tutao/tutanota-utils"
3
3
import { DataFile } from "../api/common/DataFile"
4
- import { encodeSVG } from "../gui/base/GuiUtils.js"
5
4
import DOMPurify , { Config } from "dompurify"
6
- import { splitTextForHighlighting , SearchToken } from "../api/common/utils/QueryTokenUtils"
7
-
8
- /** Data url for an SVG image that will be shown in place of external content. */
9
- export const PREVENT_EXTERNAL_IMAGE_LOADING_ICON : string = encodeSVG ( ReplacementImage )
5
+ import { SearchToken , splitTextForHighlighting } from "../api/common/utils/QueryTokenUtils"
10
6
11
7
// background attribute is deprecated but still used in common browsers
12
8
const EXTERNAL_CONTENT_ATTRS = Object . freeze ( [
@@ -123,9 +119,9 @@ export class HtmlSanitizer {
123
119
private links ! : Array < Link >
124
120
private purifier ! : typeof DOMPurify
125
121
126
- constructor ( ) {
122
+ constructor ( private readonly replacementImageUrl : string ) {
127
123
if ( DOMPurify . isSupported ) {
128
- this . purifier = DOMPurify
124
+ this . purifier = DOMPurify ( )
129
125
// Do changes in afterSanitizeAttributes and not afterSanitizeElements so that images are not removed again because of the SVGs.
130
126
this . purifier . addHook ( "afterSanitizeAttributes" , this . afterSanitizeAttributes . bind ( this ) )
131
127
this . purifier . addHook ( "beforeSanitizeElements" , this . beforeSanitizeElements . bind ( this ) )
@@ -335,15 +331,15 @@ export class HtmlSanitizer {
335
331
336
332
this . inlineImageCids . push ( cid )
337
333
338
- attribute . value = PREVENT_EXTERNAL_IMAGE_LOADING_ICON
334
+ attribute . value = this . replacementImageUrl
339
335
htmlNode . setAttribute ( "cid" , cid )
340
336
htmlNode . classList . add ( "tutanota-placeholder" )
341
337
} else if ( config . blockExternalContent && attribute . name === "srcset" ) {
342
338
this . externalContent ++
343
339
344
340
htmlNode . setAttribute ( "draft-srcset" , attribute . value )
345
341
htmlNode . removeAttribute ( "srcset" )
346
- htmlNode . setAttribute ( "src" , PREVENT_EXTERNAL_IMAGE_LOADING_ICON )
342
+ htmlNode . setAttribute ( "src" , this . replacementImageUrl )
347
343
htmlNode . style . maxWidth = "100px"
348
344
} else if (
349
345
config . blockExternalContent &&
@@ -360,7 +356,7 @@ export class HtmlSanitizer {
360
356
this . externalContent ++
361
357
362
358
htmlNode . setAttribute ( "draft-" + attribute . name , attribute . value )
363
- attribute . value = PREVENT_EXTERNAL_IMAGE_LOADING_ICON
359
+ attribute . value = this . replacementImageUrl
364
360
htmlNode . attributes . setNamedItem ( attribute )
365
361
htmlNode . style . maxWidth = "100px"
366
362
} else if ( ! config . blockExternalContent && DRAFT_ATTRIBUTES . includes ( attribute . name ) ) {
@@ -413,7 +409,7 @@ export class HtmlSanitizer {
413
409
// image-set('test.jpg' 1x, 'test-2x.jpg' 2x)
414
410
if ( value . includes ( "url(" ) && value . match ( / u r l \( / g) ?. length !== value . match ( / u r l \( [ " ' ] ? d a t a : / g) ?. length ) {
415
411
this . externalContent ++
416
- ; ( htmlNode . style as any ) [ styleAttributeName ] = `url("${ PREVENT_EXTERNAL_IMAGE_LOADING_ICON } ")`
412
+ ; ( htmlNode . style as any ) [ styleAttributeName ] = `url("${ this . replacementImageUrl } ")`
417
413
418
414
if ( limitWidth ) {
419
415
htmlNode . style . maxWidth = "100px"
@@ -509,4 +505,14 @@ function isTextElement(node: Node): node is Text {
509
505
return node . nodeName === "#text"
510
506
}
511
507
512
- export const htmlSanitizer : HtmlSanitizer = new HtmlSanitizer ( )
508
+ export const getHtmlSanitizer = memoized ( ( ) => {
509
+ // Create a blob URL for the replacement image instead of inlining SVG as data URL.
510
+ // This way we sidestep serialization/escaping problems plus DOM is smaller.
511
+ // It is never revoked and should only be run in browser context so we lazily instantiate
512
+ // it once.
513
+ const blob = new Blob ( [ ReplacementImage ] , {
514
+ type : "image/svg+xml" ,
515
+ } )
516
+
517
+ return new HtmlSanitizer ( URL . createObjectURL ( blob ) )
518
+ } )
0 commit comments