fix(ivy): i18n instructions thrown off by sanitizer in IE11 (#34305)
While sanitizing on browsers that don't support the `template` element (pretty much only IE), we create an inert document and we insert content into it via `document.body.innerHTML = unsafeHTML`. The problem is that IE appears to parse the HTML passed to `innerHTML` differently, depending on whether the element has been inserted into a document or not. In particular, it seems to split some strings into multiple text nodes, which would've otherwise been a single node. This ended up throwing off some of the i18n code down the line and causing a handful of failures. I've worked around it by creating a new inert `body` element into which the HTML would be inserted. PR Close #34305
This commit is contained in:
parent
17f7f06ca5
commit
0100a39e21
|
@ -15,33 +15,31 @@
|
||||||
* Default: InertDocument strategy
|
* Default: InertDocument strategy
|
||||||
*/
|
*/
|
||||||
export class InertBodyHelper {
|
export class InertBodyHelper {
|
||||||
private inertBodyElement: HTMLElement;
|
|
||||||
private inertDocument: Document;
|
private inertDocument: Document;
|
||||||
|
|
||||||
constructor(private defaultDoc: Document) {
|
constructor(private defaultDoc: Document) {
|
||||||
this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert');
|
this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert');
|
||||||
this.inertBodyElement = this.inertDocument.body;
|
let inertBodyElement = this.inertDocument.body;
|
||||||
|
|
||||||
if (this.inertBodyElement == null) {
|
if (inertBodyElement == null) {
|
||||||
// usually there should be only one body element in the document, but IE doesn't have any, so
|
// usually there should be only one body element in the document, but IE doesn't have any, so
|
||||||
// we need to create one.
|
// we need to create one.
|
||||||
const inertHtml = this.inertDocument.createElement('html');
|
const inertHtml = this.inertDocument.createElement('html');
|
||||||
this.inertDocument.appendChild(inertHtml);
|
this.inertDocument.appendChild(inertHtml);
|
||||||
this.inertBodyElement = this.inertDocument.createElement('body');
|
inertBodyElement = this.inertDocument.createElement('body');
|
||||||
inertHtml.appendChild(this.inertBodyElement);
|
inertHtml.appendChild(inertBodyElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inertBodyElement.innerHTML = '<svg><g onload="this.parentNode.remove()"></g></svg>';
|
inertBodyElement.innerHTML = '<svg><g onload="this.parentNode.remove()"></g></svg>';
|
||||||
if (this.inertBodyElement.querySelector && !this.inertBodyElement.querySelector('svg')) {
|
if (inertBodyElement.querySelector && !inertBodyElement.querySelector('svg')) {
|
||||||
// We just hit the Safari 10.1 bug - which allows JS to run inside the SVG G element
|
// We just hit the Safari 10.1 bug - which allows JS to run inside the SVG G element
|
||||||
// so use the XHR strategy.
|
// so use the XHR strategy.
|
||||||
this.getInertBodyElement = this.getInertBodyElement_XHR;
|
this.getInertBodyElement = this.getInertBodyElement_XHR;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inertBodyElement.innerHTML =
|
inertBodyElement.innerHTML = '<svg><p><style><img src="</style><img src=x onerror=alert(1)//">';
|
||||||
'<svg><p><style><img src="</style><img src=x onerror=alert(1)//">';
|
if (inertBodyElement.querySelector && inertBodyElement.querySelector('svg img')) {
|
||||||
if (this.inertBodyElement.querySelector && this.inertBodyElement.querySelector('svg img')) {
|
|
||||||
// We just hit the Firefox bug - which prevents the inner img JS from being sanitized
|
// We just hit the Firefox bug - which prevents the inner img JS from being sanitized
|
||||||
// so use the DOMParser strategy, if it is available.
|
// so use the DOMParser strategy, if it is available.
|
||||||
// If the DOMParser is not available then we are not in Firefox (Server/WebWorker?) so we
|
// If the DOMParser is not available then we are not in Firefox (Server/WebWorker?) so we
|
||||||
|
@ -122,15 +120,23 @@ export class InertBodyHelper {
|
||||||
return templateEl;
|
return templateEl;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inertBodyElement.innerHTML = html;
|
// Note that previously we used to do something like `this.inertDocument.body.innerHTML = html`
|
||||||
|
// and we returned the inert `body` node. This was changed, because IE seems to treat setting
|
||||||
|
// `innerHTML` on an inserted element differently, compared to one that hasn't been inserted
|
||||||
|
// yet. In particular, IE appears to split some of the text into multiple text nodes rather
|
||||||
|
// than keeping them in a single one which ends up messing with Ivy's i18n parsing further
|
||||||
|
// down the line. This has been worked around by creating a new inert `body` and using it as
|
||||||
|
// the root node in which we insert the HTML.
|
||||||
|
const inertBody = this.inertDocument.createElement('body');
|
||||||
|
inertBody.innerHTML = html;
|
||||||
|
|
||||||
// Support: IE 9-11 only
|
// Support: IE 9-11 only
|
||||||
// strip custom-namespaced attributes on IE<=11
|
// strip custom-namespaced attributes on IE<=11
|
||||||
if ((this.defaultDoc as any).documentMode) {
|
if ((this.defaultDoc as any).documentMode) {
|
||||||
this.stripCustomNsAttrs(this.inertBodyElement);
|
this.stripCustomNsAttrs(inertBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.inertBodyElement;
|
return inertBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue