From d4544da804f9d27d6ab5a45a3ec85789254e0801 Mon Sep 17 00:00:00 2001 From: Harri Lehtola Date: Sat, 25 Apr 2020 14:13:16 +0300 Subject: [PATCH] refactor(core): split inert strategies to separate classes (#36578) (#36578) The `inertDocument` member is only needed when using the InertDocument strategy. By separating the DOMParser and InertDocument strategies into separate classes, we can easily avoid creating the inert document unnecessarily when using DOMParser. PR Close #36578 --- packages/core/src/render3/i18n.ts | 4 +- .../core/src/sanitization/html_sanitizer.ts | 4 +- packages/core/src/sanitization/inert_body.ts | 66 ++++++++++--------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 6c0c5ec7b1..1377482c80 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -9,7 +9,7 @@ import '../util/ng_i18n_closure_mode'; import {DEFAULT_LOCALE_ID, getPluralCase} from '../i18n/localization'; import {getTemplateContent, SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS} from '../sanitization/html_sanitizer'; -import {InertBodyHelper} from '../sanitization/inert_body'; +import {getInertBodyHelper} from '../sanitization/inert_body'; import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer'; import {addAllToArray} from '../util/array_utils'; import {assertDataInRange, assertDefined, assertEqual} from '../util/assert'; @@ -1233,7 +1233,7 @@ function icuStart( function parseIcuCase( unsafeHtml: string, parentIndex: number, nestedIcus: IcuExpression[], tIcus: TIcu[], expandoStartIndex: number): IcuCase { - const inertBodyHelper = new InertBodyHelper(getDocument()); + const inertBodyHelper = getInertBodyHelper(getDocument()); const inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml); if (!inertBodyElement) { throw new Error('Unable to generate inert body element'); diff --git a/packages/core/src/sanitization/html_sanitizer.ts b/packages/core/src/sanitization/html_sanitizer.ts index 4f79d98927..ab7bbbe836 100644 --- a/packages/core/src/sanitization/html_sanitizer.ts +++ b/packages/core/src/sanitization/html_sanitizer.ts @@ -7,7 +7,7 @@ */ import {isDevMode} from '../util/is_dev_mode'; -import {InertBodyHelper} from './inert_body'; +import {getInertBodyHelper, InertBodyHelper} from './inert_body'; import {_sanitizeUrl, sanitizeSrcset} from './url_sanitizer'; function tagSet(tags: string): {[k: string]: boolean} { @@ -245,7 +245,7 @@ let inertBodyHelper: InertBodyHelper; export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string { let inertBodyElement: HTMLElement|null = null; try { - inertBodyHelper = inertBodyHelper || new InertBodyHelper(defaultDoc); + inertBodyHelper = inertBodyHelper || getInertBodyHelper(defaultDoc); // Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime). let unsafeHtml = unsafeHtmlInput ? String(unsafeHtmlInput) : ''; inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml); diff --git a/packages/core/src/sanitization/inert_body.ts b/packages/core/src/sanitization/inert_body.ts index e859f8af5a..9e3530be71 100644 --- a/packages/core/src/sanitization/inert_body.ts +++ b/packages/core/src/sanitization/inert_body.ts @@ -7,40 +7,29 @@ */ /** - * This helper class is used to get hold of an inert tree of DOM elements containing dirty HTML + * 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 + * Default: DOMParser strategy * Fallback: InertDocument strategy */ -export class InertBodyHelper { - private inertDocument: Document; - - constructor(private defaultDoc: Document) { - this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert'); - if (this.inertDocument.body == null) { - // usually there should be only one body element in the document, but IE doesn't have any, so - // we need to create one. - const inertHtml = this.inertDocument.createElement('html'); - this.inertDocument.appendChild(inertHtml); - const inertBodyElement = this.inertDocument.createElement('body'); - inertHtml.appendChild(inertBodyElement); - } - - this.getInertBodyElement = isDOMParserAvailable() ? this.getInertBodyElement_DOMParser : - this.getInertBodyElement_InertDocument; - } +export function getInertBodyHelper(defaultDoc: Document): InertBodyHelper { + return isDOMParserAvailable() ? new DOMParserHelper() : new InertDocumentHelper(defaultDoc); +} +export interface InertBodyHelper { /** * Get an inert DOM element containing DOM created from the dirty HTML string provided. - * The implementation of this is determined in the constructor, when the class is instantiated. */ getInertBodyElement: (html: string) => HTMLElement | null; +} - /** - * Use DOMParser to create and fill an inert body element in browsers that support it. - */ - private getInertBodyElement_DOMParser(html: string) { +/** + * 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 { + 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 `` do not get hoisted to the // `` tag. @@ -54,13 +43,30 @@ export class InertBodyHelper { return null; } } +} - /** - * Use an HTML5 `template` element, if supported, or an inert body element created via - * `createHtmlDocument` to create and fill an inert DOM element. - * This is the fallback strategy if the browser does not support DOMParser. - */ - private getInertBodyElement_InertDocument(html: string) { +/** + * Use an HTML5 `template` element, if supported, or an inert body element created via + * `createHtmlDocument` 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'); + + if (this.inertDocument.body == null) { + // usually there should be only one body element in the document, but IE doesn't have any, so + // we need to create one. + const inertHtml = this.inertDocument.createElement('html'); + this.inertDocument.appendChild(inertHtml); + const inertBodyElement = this.inertDocument.createElement('body'); + inertHtml.appendChild(inertBodyElement); + } + } + + getInertBodyElement(html: string): HTMLElement|null { // Prefer using