Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第4章 ブラウザは HTML を完成させる

書いた覚えのない <html> や <body> が、開発者ツールにはちゃんといる。自分は <p> を 1 つ書いただけなのに、です。これは誰が、いつ足したのでしょうか。

この章では、HTML ソースがそのまま完成品ではなく、ブラウザがパース規則に従って組み立てられた結果として DOM ができることを見ます。

4.1 ソースコードと完成した文書の不一致

HTML を書いていると、つい「自分が書いた文字列がそのまま表示されている」と考えがちです。しかしブラウザが扱っているのは、受け取った文字列そのものではありません。ブラウザはその文字列を読み取り、規則に従って文書構造へ変換し、その結果として DOM を作ります。

この前提を外すと、あとで出てくる tbody や p の自動終了がすべて「謎の親切」に見えてしまいます。実際には、HTML は最初から、入力文字列を読みながら文書を組み立てる形式として設計されています。ソースは材料であり、ブラウザが扱う完成形そのものではありません。

ここでいう DOM は、ブラウザが内部で持つ文書構造です。英語では Document Object Model と呼ばれ、HTML を要素どうしの関係として扱うための形です。開発者ツールで見えているのは、受信した元の文字列ではなく、この構造化された結果です。つまり、HTML では「書いたもの」と「ブラウザが扱うもの」のあいだに、解釈の層が 1 枚入ります。 文字列が、そのままノードの木へ変換されているわけではなく、途中で「この要素は文書のどこに属するか」を判断する段階が入るのです。

4.2 title と p だけでも成立する文書外枠

最小の例で見てみます。

<!DOCTYPE html>
<meta charset="utf-8">
<title>sample</title>
<p>Hello</p>

このソースには、html も head も body も書かれていません。それでもブラウザは、これを壊れた断片として投げ捨てるのではなく、文書として読める形へ組み立てます。おおまかには、次のような構造として扱われます。

<html>
  <head>
    <meta charset="utf-8">
    <title>sample</title>
  </head>
  <body>
    <p>Hello</p>
  </body>
</html>

この順序では、パーサーは head を作って meta と title を収め、p の開始タグで body の処理へ進みます。これはタグ省略の条件と読み取り規則による結果です。上の表示は要素の関係を示しており、文書型ノードは省略しています。最小文書では html タグごと省略したため言語の指定もありません。validator が lang を勧める警告を出すことと、タグ省略の適合性は別です。実際の文書では、後述の雛形のように html に言語を指定します。

「title はどこに書いても head へ移動する」という意味ではありません。入力順やパーサーの状態が違えば結果も変わります。また、コンテンツモデルに違反した要素が常に正しい位置へ移されるわけでもありません。第7章で、この区別を確認します。

html/head/body を書いていないソースから、ブラウザがそれらを補完した DOM の木を組み立てる流れの図
図 4-1 ソースは材料。ブラウザが補って文書の木を組み立てる。

4.3 補完が文書の前提になる理由

この挙動を見ると、「ブラウザが開発者を甘やかしている」と言いたくなるかもしれません。たしかに、他のもっと厳格な形式に慣れていると、入力不足を埋めてくれる振る舞いは甘く見えます。

しかし HTML の文脈では、先にあったのは厳格さではなく、読めることを優先する問題設定でした。前章で見たように、Web が必要としていたのは、異なる環境にある人が同じ文書へ辿りつき、読めることです。その前提に立つと、文書の外枠が少し省略されていても、利用可能な文書として解釈するほうが自然です。

ここでのポイントは、「何でも好きに補ってよい」ではないことです。ここで見た補完は仕様なしの裁量ではなく、どの要素がどこへ入るか、どの時点で文書の外枠を作るかといった規則に従って行われます。だからブラウザごとに気分で別々の木ができるのではなく、同じ HTML に対しておおむね同じ DOM を作れるのです。

この意味でブラウザは、著者の代わりに文章を書く共同著者ではありません。著者が渡した材料を、読める文書構造へ落とし込む実装です。過度に擬人化しないほうが、このあと出てくる仕様の話も理解しやすくなります。

4.4 DevTools が見ているもの

実務で混乱しやすいのは、ここからです。ソースに body を書いていないのに、DevTools を開くと body が見える。すると「ブラウザが勝手に書き換えた」と感じます。

ですが、DevTools が見せているのは、受信した元の文字列ではなく、解釈後の DOM です。つまり、見えている対象が最初から違います。元のソースと DevTools の内容が一致しないこと自体は、異常ではありません。むしろ HTML では、それが普通に起こります。

この違いを 1 度でも正しく掴むと、後の章でかなり楽になります。table の中に tbody が現れることも、閉じていない p が DOM 上では閉じていることも、「ブラウザが勝手に直した」の一言で片づけずに済むからです。見ているものが文字列なのか、補完後の文書構造なのかを分けて考えられるようになります。

上の最小文書を UTF-8 の sample.html として保存して開き、View Source と DevTools の Elements パネルを比較してください。DOCTYPE は互換モードを避けるため、meta は文字コードを宣言するために入れています。

第5〜7章で本文断片を試すときは、次の文書の p を断片に置き換えます。日本語を含むファイルは UTF-8 で保存してください。validator にもこの文書全体を渡します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>HTML の確認</title>
</head>
<body>
  <p>ここを確認したい本文断片に置き換える</p>
</body>
</html>

実務の調査でも、この切り分けは有効です。レスポンス本文を確認したいなら元の HTML を見るべきですし、CSS や JavaScript が相手にしている対象を確認したいなら DOM を見るべきです。同じ画面を見ていても、何を観察しているかで答えが変わります。

ここではまだ View Source と DevTools の違いを本格的には扱いません。その比較は第8章の役割です。この章ではまず、「ブラウザは受け取った文字列をそのまま見せているのではない」という土台だけ押さえれば十分です。

4.5 「ブラウザが勝手にやった」で終わる調査の危うさ

この章の話は、実務でもかなり役立ちます。Rails でテンプレートを書いていると、最終的にブラウザへ届くのは HTML 文字列です。そしてブラウザは、その文字列をそのまま画面へ貼り付けるのではなく、文書として解釈して DOM を組み立てます。

そのため、画面で起きたことを調べるときに、テンプレートの見た目だけで判断すると外しやすくなります。たとえば CSS セレクタが思ったように効かない、JavaScript で取れたノード構造が想定と違う、といった場面では、まず「ブラウザが最終的にどんな DOM を作ったか」を見る必要があります。

ここで重要なのは、ブラウザを責めることではありません。見るべき対象を切り分けることです。テンプレートの文字列を点検したいのか、補完後の DOM を点検したいのか。この区別ができるだけで、HTML まわりの調査はかなり整理されます。

次章では、表の tbody を使い、文字列のパースと DOM API による構築を比べます。

参考資料