happy-dom icon indicating copy to clipboard operation
happy-dom copied to clipboard

innerHTML gives invalid DOM

Open duongphuhiep opened this issue 1 year ago • 1 comments

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:

image

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);

duongphuhiep avatar May 19 '23 13:05 duongphuhiep

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)

leonadler avatar Jun 28 '23 11:06 leonadler