Kodokon kodokon.com

From HTML to the DOM: parsing and error recovery

Understand how the browser turns any HTML stream, even a malformed one, into a deterministic DOM tree.

10 min · 3 questions

Open this lesson in Kodokon

Unlike XML, HTML has no such thing as a fatal error: the WHATWG specification defines, byte by byte, how any input stream - even malformed - must become a DOM tree. The parser works in two stages: the tokenizer slices the stream into tokens (tags, text, comments), then the tree builder inserts them according to a state machine whose states are called insertion modes (in body, in table, and so on). Every "parse error" has a standardized recovery: two conforming browsers produce exactly the same DOM.

HTML
<table>
  <tr><td>Cell</td></tr>
</table>
The resulting DOM contains a tbody you never wrote.

The parser inserts implicit tags: a <tbody> appears around the rows, and <li>, <p> or <option> close automatically in front of certain opening tags. More radical still: any content found inside a <table> where it is not allowed is pulled out of it and reinserted just before, a mechanism called foster parenting. Your CSS selectors and your scripts always work on the corrected DOM, never on your source.

JAVASCRIPT
const html =
  "<table><div>out</div><tr><td>ok</td></tr></table>";
const doc = new DOMParser()
  .parseFromString(html, "text/html");
console.log(doc.body.innerHTML);
// <div>out</div><table>...</table>
Foster parenting observed with DOMParser.

Misnested formatting tags trigger the adoption agency algorithm, one of the most devious parts of the specification: the parser clones and redistributes the active formatting elements to produce a correctly nested tree. The crossed markup below shows the result.

HTML
<p>
  <b>One <i>two</b> three</i>
</p>
Resulting DOM: <b>One <i>two</i></b><i> three</i> - the i is split into two elements.

The parser is a streaming parser: it builds the tree as the bytes arrive. A synchronous <script> suspends it - the script can call document.write and inject tokens into the stream - but the preload scanner keeps sweeping the remaining HTML to preload images, stylesheets and scripts. Called after parsing has finished, document.write triggers document.open and wipes the entire page: it is one of the platform's oldest traps.

Knowledge check

Make sure you remember the key points of this lesson.

  1. What happens to the <div> in <table><div>Out</div><tr><td>A</td></tr></table>?
    • It is silently removed from the DOM
    • It is moved before the table by foster parenting
    • It is wrapped in a <td> cell created for it
    • It stays a direct child of the table
  2. How does the HTML specification handle a malformed document?
    • It lets each browser choose its own recovery strategy
    • It requires switching to quirks mode
    • It defines a deterministic recovery algorithm, identical across all conforming browsers
    • It requires parsing to stop at the first error
  3. What DOM does the fragment <p><div>X</div></p> produce?
    • <p></p><div>X</div><p></p>: the div closes the first p, and the orphaned </p> creates a second, empty one
    • <p><div>X</div></p>, kept as is
    • <div><p>X</p></div>, the parser inverts the nesting