Kodokon kodokon.com

HTMLからDOMへ: パースとエラー回復

ブラウザがあらゆるHTMLストリームを、たとえ不正なものであっても、いかにして決定論的なDOMツリーへと変換するかを理解します。

10 分 · 3 問

このレッスンを Kodokon で開く

XMLとは異なり、HTMLには致命的なエラーというものが存在しません。WHATWG仕様は、どんな入力ストリーム、たとえ不正なものであっても、それがどのようにDOMツリーになるべきかをバイト単位で定義しています。パーサーは2つの段階で動作します。まずトークナイザーがストリームをトークン(タグ、テキスト、コメント)に切り分け、次にツリービルダーが、挿入モード(in bodyin tableなど)と呼ばれる状態を持つ状態機械に従ってそれらを挿入します。あらゆる「パースエラー」には標準化された回復方法があり、仕様に準拠した2つのブラウザはまったく同じDOMを生成します。

HTML
<table>
  <tr><td>Cell</td></tr>
</table>
結果として得られるDOMには、あなたが一度も書いていないtbodyが含まれます。

パーサーは暗黙のタグを挿入します。行の周りに<tbody>が現れ、<li><p><option>は特定の開始タグの前で自動的に閉じられます。さらに過激なことに、<table>の内部で許可されていない場所に見つかったコンテンツはそこから引き抜かれ、その直前に再挿入されます。これはフォスターペアレンティングと呼ばれる仕組みです。あなたのCSSセレクターやスクリプトは、常にソースではなく、修正されたDOMに対して動作します。

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>
DOMParserで観察されるフォスターペアレンティング。

誤ってネストされた整形タグは、仕様の中でも最も厄介な部分の一つである採用エージェンシーアルゴリズムを引き起こします。パーサーはアクティブな整形要素をクローンして再配置し、正しくネストされたツリーを生成します。以下の交差したマークアップがその結果を示しています。

HTML
<p>
  <b>One <i>two</b> three</i>
</p>
結果のDOM: <b>One <i>two</i></b><i> three</i> - iは2つの要素に分割されます。

パーサーはストリーミングパーサーです。バイトが到着するにつれてツリーを構築していきます。同期的な<script>はそれを一時停止させます。スクリプトはdocument.writeを呼び出してストリームにトークンを注入できるからです。しかしプリロードスキャナーは残りのHTMLを走査し続け、画像、スタイルシート、スクリプトをプリロードします。パースが完了した後に呼び出されたdocument.writedocument.openを引き起こし、ページ全体を消去します。これはプラットフォームの最も古い罠の一つです。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. <table><div>Out</div><tr><td>A</td></tr></table>の中の<div>はどうなりますか?
    • DOMから静かに削除される
    • フォスターペアレンティングによってテーブルの前に移動される
    • そのために作成された<td>セルで包まれる
    • テーブルの直接の子のまま残る
  2. HTML仕様は不正なドキュメントをどのように扱いますか?
    • 各ブラウザが独自の回復戦略を選ぶに任せる
    • quirksモードへの切り替えを要求する
    • 決定論的な回復アルゴリズムを定義し、準拠するすべてのブラウザで同一である
    • 最初のエラーでパースを停止することを要求する
  3. フラグメント<p><div>X</div></p>はどんなDOMを生成しますか?
    • <p></p><div>X</div><p></p>: divが最初のpを閉じ、宙に浮いた</p>が2つ目の空のpを作る
    • <p><div>X</div></p>、そのまま保持される
    • <div><p>X</p></div>、パーサーがネストを反転させる