Understand how the browser turns any HTML stream, even a malformed one, into a deterministic DOM tree.
Open this lesson in KodokonUnlike 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.
<table>
<tr><td>Cell</td></tr>
</table>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.
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>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.
<p>
<b>One <i>two</b> three</i>
</p><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.
<div> in <table><div>Out</div><tr><td>A</td></tr></table>?<td> cell created for it<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