happy-dom
happy-dom copied to clipboard
innerHTML gives invalid DOM
Describe the bug
innerHTML gives invalid DOM
To Reproduce
const { Window } = require('happy-dom');
const window = new Window();
const document = window.document;
document.body.innerHTML = `
<div>
<form id="f1"></form>
<input id="e1" />
<form id="f2">
<div>
<form id="f3"></form>
<input id="e2" />
<div>
<form id="f4">
<input id="e3" />
</form>
<form id="f6">
<input id="e5" />
</form>
</div>
</div>
<!--Remark: browser will move #e4 outside #f2-->
<div id="e4"></div>
</form>
<form id="f5"></form>
</div>`;
console.log(document.body.innerHTML);
Actual behavior
the output is an invalid DOM (a </form>
is missing)
<div>
<form id="f1"></form>
<input id="e1">
<form id="f2">
<div>
</div></form><form id="f3"></form>
<input id="e2">
<div>
<form id="f4">
<input id="e3">
</form>
<form id="f6">
<input id="e5">
</form>
</div>
</div>
<!--Remark: browser will move #e4 outside #f2-->
<div id="e4"></div>
<form id="f5"></form>
Expected behavior
<div>
<form id="f1"></form>
<input id="e1">
<form id="f2">
<div>
<input id="e2">
<div>
<form id="f4">
<input id="e3">
</form>
<form id="f6">
<input id="e5">
</form>
</div>
</div></form>
<!--Remark: browser will move #e4 outside #f2-->
<div id="e4"></div>
<form id="f5"></form>
</div>
Screenshots
Here is the DOM rendered in Edge:
Device:
- OS: Linux Mint
- Browser: Edge
- Version: happy-dom 9.19.2,
Additional context
JSDOM (22.0.0) works well in this case
const { JSDOM } = require('jsdom');
const dom = new JSDOM(
`<html><body>
<div>
<form id="f1"></form>
<input id="e1" />
<form id="f2">
<div>
<form id="f3"></form>
<input id="e2" />
<div>
<form id="f4">
<input id="e3" />
</form>
<form id="f6">
<input id="e5" />
</form>
</div>
</div>
<!--Remark: browser will move #e4 outside #f2-->
<div id="e4"></div>
</form>
<form id="f5"></form>
</div>
</body</html>
`
);
console.log(dom.window.document.body.innerHTML);
Forms inside forms don't seem to be valid HTML, if I'm not mistaken. That seems to cause happy-dom to become unhappy-dom:
https://www.w3.org/TR/xhtml1/#prohibitions "form must not contain other form elements."
(I couldn't find a newer spec that mentions this explicitly)
Chrome does this when setting innerHTML
:
const div = document.createElement('div');
div.innerHtml = `
<form id="a">
<form id="b">
<form id="c">d</form>
</form>
</form>
`;
assertEqual(div.innerHtml, `
<form id="a">
d</form>
`);
assertEqual(div.children[0].id, "a");
assertEqual(div.children[0].children[0], undefined);
Interestingly enough, Chrome allows this via DOM APIs:
const div = document.createElement('div');
const a = div.appendChild(document.createElement('form'));
const b = a.appendChild(document.createElement('form'));
const c = b.appendChild(document.createElement('form'));
const d = c.appendChild(document.createElement('form'));
assertEqual(div.innerHtml, `<form><form><form><form></form></form></form></form>`);
assertEqual(div.children[0], a);
assertEqual(div.children[0].children[0], b);
assertEqual(div.children[0].children[0].children[0], c);
assertEqual(div.children[0].children[0].children[0].children[0], d);
HTML5 mentions a valid alternative:
<form action="/a" method="POST">
<input type="text" name="foo" placeholder="Enter foo" />
<input type="text" name="bar" placeholder="Enter bar" />
<input type="text" name="data-for-b" form="form-that-submits-to-b" placeholder="Data for form B" />
<button type="submit">Submit to endpoint A</button>
<button type="submit" form="form-that-submits-to-b">Submit to endpoint B</button>
</form>
<form id="form-that-submits-to-b" action="/b" method="POST"></form>
(Still, the returned html of innerHTML should be valid)