Пойми, как браузер превращает любой поток HTML, даже некорректный, в детерминированное дерево DOM.
Открыть этот урок в KodokonВ отличие от XML, в HTML не существует такого понятия, как фатальная ошибка: спецификация WHATWG описывает, байт за байтом, как любой входной поток - даже некорректный - должен превратиться в дерево DOM. Парсер работает в два этапа: токенизатор нарезает поток на токены (теги, текст, комментарии), затем построитель дерева вставляет их согласно конечному автомату, состояния которого называются режимами вставки (in body, in table и так далее). У каждой «ошибки разбора» есть стандартизированное восстановление: два соответствующих спецификации браузера дают ровно одинаковый DOM.
<table>
<tr><td>Cell</td></tr>
</table>Парсер вставляет неявные теги: вокруг строк появляется <tbody>, а <li>, <p> или <option> закрываются автоматически перед определёнными открывающими тегами. Ещё радикальнее: любое содержимое, найденное внутри <table> там, где оно не разрешено, выносится из неё и вставляется прямо перед ней - этот механизм называется foster parenting. Твои CSS-селекторы и твои скрипты всегда работают с исправленным DOM, а не с твоим исходником.
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>Неправильно вложенные теги форматирования запускают алгоритм агентства по усыновлению (adoption agency algorithm) - одну из самых коварных частей спецификации: парсер клонирует и перераспределяет активные элементы форматирования, чтобы получить корректно вложенное дерево. Перекрещенная разметка ниже показывает результат.
<p>
<b>One <i>two</b> three</i>
</p><b>One <i>two</i></b><i> three</i> - элемент i разделён на два.Парсер работает потоково: он строит дерево по мере поступления байтов. Синхронный <script> приостанавливает его - скрипт может вызвать document.write и вставить токены прямо в поток, - но сканер предзагрузки продолжает просматривать оставшийся HTML, чтобы заранее загрузить изображения, таблицы стилей и скрипты. Вызванный после окончания разбора, document.write запускает document.open и стирает всю страницу: это одна из самых старых ловушек платформы.
<div> в <table><div>Out</div><tr><td>A</td></tr></table>?<td><p><div>X</div></p>?<p></p><div>X</div><p></p>: div закрывает первый p, а осиротевший </p> создаёт второй, пустой<p><div>X</div></p>, сохранённый как есть<div><p>X</p></div>, парсер меняет вложенность местами