-
Notifications
You must be signed in to change notification settings - Fork 26.2k
/
Copy pathinert_body.ts
96 lines (87 loc) · 3.34 KB
/
inert_body.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {trustedHTMLFromString} from '../util/security/trusted_types';
/**
* This helper is used to get hold of an inert tree of DOM elements containing dirty HTML
* that needs sanitizing.
* Depending upon browser support we use one of two strategies for doing this.
* Default: DOMParser strategy
* Fallback: InertDocument strategy
*/
export function getInertBodyHelper(defaultDoc: Document): InertBodyHelper {
const inertDocumentHelper = new InertDocumentHelper(defaultDoc);
return isDOMParserAvailable() ? new DOMParserHelper(inertDocumentHelper) : inertDocumentHelper;
}
export interface InertBodyHelper {
/**
* Get an inert DOM element containing DOM created from the dirty HTML string provided.
*/
getInertBodyElement: (html: string) => HTMLElement | null;
}
/**
* Uses DOMParser to create and fill an inert body element.
* This is the default strategy used in browsers that support it.
*/
class DOMParserHelper implements InertBodyHelper {
constructor(private inertDocumentHelper: InertBodyHelper) {}
getInertBodyElement(html: string): HTMLElement | null {
// We add these extra elements to ensure that the rest of the content is parsed as expected
// e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the
// `<head>` tag. Note that the `<body>` tag is closed implicitly to prevent unclosed tags
// in `html` from consuming the otherwise explicit `</body>` tag.
html = '<body><remove></remove>' + html;
try {
const body = new window.DOMParser().parseFromString(
trustedHTMLFromString(html) as string,
'text/html',
).body as HTMLBodyElement;
if (body === null) {
// In some browsers (e.g. Mozilla/5.0 iPad AppleWebKit Mobile) the `body` property only
// becomes available in the following tick of the JS engine. In that case we fall back to
// the `inertDocumentHelper` instead.
return this.inertDocumentHelper.getInertBodyElement(html);
}
body.firstChild?.remove();
return body;
} catch {
return null;
}
}
}
/**
* Use an HTML5 `template` element to create and fill an inert DOM element.
* This is the fallback strategy if the browser does not support DOMParser.
*/
class InertDocumentHelper implements InertBodyHelper {
private inertDocument: Document;
constructor(private defaultDoc: Document) {
this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert');
}
getInertBodyElement(html: string): HTMLElement | null {
const templateEl = this.inertDocument.createElement('template');
templateEl.innerHTML = trustedHTMLFromString(html) as string;
return templateEl;
}
}
/**
* We need to determine whether the DOMParser exists in the global context and
* supports parsing HTML; HTML parsing support is not as wide as other formats, see
* https://developer.mozilla.org/en-US/docs/Web/API/DOMParser#Browser_compatibility.
*
* @suppress {uselessCode}
*/
export function isDOMParserAvailable() {
try {
return !!new window.DOMParser().parseFromString(
trustedHTMLFromString('') as string,
'text/html',
);
} catch {
return false;
}
}