Skip to content

Commit

Permalink
Merge pull request #640 from capricorn86/task/639-setting-an-invalid-…
Browse files Browse the repository at this point in the history
…value-to-elementinnerhtml-should-not-throw-an-exception

#639@patch: Element.innerHTML should be able to handle other types th…
  • Loading branch information
capricorn86 committed Oct 25, 2022
2 parents e001788 + c355aec commit 62e2920
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 58 deletions.
120 changes: 62 additions & 58 deletions packages/happy-dom/src/xml-parser/XMLParser.ts
Expand Up @@ -42,76 +42,80 @@ export default class XMLParser {
let lastTextIndex = 0;
let match: RegExpExecArray;

while ((match = markupRegexp.exec(data))) {
const tagName = match[2].toLowerCase();
const isStartTag = !match[1];
if (data !== null && data !== undefined) {
data = String(data);

if (parent && match.index !== lastTextIndex) {
const text = data.substring(lastTextIndex, match.index);
this.appendTextAndCommentNodes(document, parent, text);
}

if (isStartTag) {
const namespaceURI =
tagName === 'svg'
? NamespaceURI.svg
: (<IElement>parent).namespaceURI || NamespaceURI.html;
const newElement = document.createElementNS(namespaceURI, tagName);

// Scripts are not allowed to be executed when they are parsed using innerHTML, outerHTML, replaceWith() etc.
// However, they are allowed to be executed when document.write() is used.
// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement
if (tagName === 'script') {
(<HTMLScriptElement>newElement)._evaluateScript = evaluateScripts;
}
while ((match = markupRegexp.exec(data))) {
const tagName = match[2].toLowerCase();
const isStartTag = !match[1];

// An assumption that the same rule should be applied for the HTMLLinkElement is made here.
if (tagName === 'link') {
(<HTMLLinkElement>newElement)._evaluateCSS = evaluateScripts;
if (parent && match.index !== lastTextIndex) {
const text = data.substring(lastTextIndex, match.index);
this.appendTextAndCommentNodes(document, parent, text);
}

this.setAttributes(newElement, match[3]);
if (isStartTag) {
const namespaceURI =
tagName === 'svg'
? NamespaceURI.svg
: (<IElement>parent).namespaceURI || NamespaceURI.html;
const newElement = document.createElementNS(namespaceURI, tagName);

// Scripts are not allowed to be executed when they are parsed using innerHTML, outerHTML, replaceWith() etc.
// However, they are allowed to be executed when document.write() is used.
// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement
if (tagName === 'script') {
(<HTMLScriptElement>newElement)._evaluateScript = evaluateScripts;
}

if (!match[4] && !VoidElements.includes(tagName)) {
// Some elements are not allowed to be nested (e.g. "<a><a></a></a>" is not allowed.).
// Therefore we will auto-close the tag.
if (parentUnnestableTagName === tagName) {
stack.pop();
parent = <Element>parent.parentNode || root;
// An assumption that the same rule should be applied for the HTMLLinkElement is made here.
if (tagName === 'link') {
(<HTMLLinkElement>newElement)._evaluateCSS = evaluateScripts;
}

parent = <Element>parent.appendChild(newElement);
parentUnnestableTagName = this.getUnnestableTagName(parent);
stack.push(parent);
} else {
parent.appendChild(newElement);
}
lastTextIndex = markupRegexp.lastIndex;

// Tags which contain non-parsed content
// For example: <script> JavaScript should not be parsed
if (ChildLessElements.includes(tagName)) {
let childLessMatch = null;
while ((childLessMatch = markupRegexp.exec(data))) {
if (childLessMatch[2].toLowerCase() === tagName && childLessMatch[1]) {
markupRegexp.lastIndex -= childLessMatch[0].length;
break;
this.setAttributes(newElement, match[3]);

if (!match[4] && !VoidElements.includes(tagName)) {
// Some elements are not allowed to be nested (e.g. "<a><a></a></a>" is not allowed.).
// Therefore we will auto-close the tag.
if (parentUnnestableTagName === tagName) {
stack.pop();
parent = <Element>parent.parentNode || root;
}

parent = <Element>parent.appendChild(newElement);
parentUnnestableTagName = this.getUnnestableTagName(parent);
stack.push(parent);
} else {
parent.appendChild(newElement);
}
lastTextIndex = markupRegexp.lastIndex;

// Tags which contain non-parsed content
// For example: <script> JavaScript should not be parsed
if (ChildLessElements.includes(tagName)) {
let childLessMatch = null;
while ((childLessMatch = markupRegexp.exec(data))) {
if (childLessMatch[2].toLowerCase() === tagName && childLessMatch[1]) {
markupRegexp.lastIndex -= childLessMatch[0].length;
break;
}
}
}
}
} else {
stack.pop();
parent = stack[stack.length - 1] || root;
parentUnnestableTagName = this.getUnnestableTagName(parent);
} else {
stack.pop();
parent = stack[stack.length - 1] || root;
parentUnnestableTagName = this.getUnnestableTagName(parent);

lastTextIndex = markupRegexp.lastIndex;
lastTextIndex = markupRegexp.lastIndex;
}
}
}

// Text after last element
if ((!match && data.length > 0) || (match && lastTextIndex !== match.index)) {
const text = data.substring(lastTextIndex);
this.appendTextAndCommentNodes(document, parent || root, text);
// Text after last element
if ((!match && data.length > 0) || (match && lastTextIndex !== match.index)) {
const text = data.substring(lastTextIndex);
this.appendTextAndCommentNodes(document, parent || root, text);
}
}

return root;
Expand Down
14 changes: 14 additions & 0 deletions packages/happy-dom/test/xml-parser/XMLParser.test.ts
Expand Up @@ -396,5 +396,19 @@ describe('XMLParser', () => {

expect((<IHTMLElement>root.children[0]).innerText).toBe(`console.log('hello')`);
});

it('Handles different value types.', () => {
const root1 = XMLParser.parse(window.document, null);
expect(new XMLSerializer().serializeToString(root1)).toBe('');

const root2 = XMLParser.parse(window.document, undefined);
expect(new XMLSerializer().serializeToString(root2)).toBe('');

const root3 = XMLParser.parse(window.document, <string>(<unknown>1000));
expect(new XMLSerializer().serializeToString(root3)).toBe('1000');

const root4 = XMLParser.parse(window.document, <string>(<unknown>false));
expect(new XMLSerializer().serializeToString(root4)).toBe('false');
});
});
});

0 comments on commit 62e2920

Please sign in to comment.