HTMLの仕様はWebブラウザというソフトウェアの仕様でもある
「JavaScriptの window と self と globalThis の話(その3)」の続き。前回はHTML仕様の7.5.1節“Shared document creation infrastructure”の“create and initialize a Document object”アルゴリズムの話をしていた。Documentを作成して初期化するアルゴリズムだ。
これを探るためにdocumentの読み込みを辿っていこう。エントリポイントとして分かりやすいのはリンクをクリックしたときの挙動を定義している部分だ。 <a>
要素の定義は4.5.1節にあるがここには実質的な内容がほとんどない。
If the a element has an href attribute, then it represents a hyperlink (a hypertext anchor) labeled by its contents.
の“hyperlink”が4.6節“Links”に飛ぶようになっていて、こちらが本丸だ。4.6.2節“Links created by a and area elements”に
The activation behavior of an a or area element element given an event event is:
というアルゴリズムが定義されていて、これがリンクをクリックしたときの挙動だ。“activation behavior”はDOM仕様の用語で、 click
イベントがキャンセルされなかった場合に呼び出される動作のことである。このアルゴリズムの最後は次のようになっている。
Otherwise, follow the hyperlink created by element with hyperlinkSuffix set to hyperlinkSuffix and userInvolvement set to userInvolvement.
“follow the hyperlink”がリンクになっていて別のアルゴリズムを呼び出している。“created by element”が第1引数だ。残りの引数は本質的ではないので省略。飛び先は4.6.4節“Following Hyperlinks”にある。近くをうろうろしている感じだ。例の如くいきなり最後を見ると
Navigate targetNavigable to urlString using subject's node document, with referrerPolicy set to referrerPolicy and userInvolvement set to userInvolvement.
に飛んでいる。“Navigate”がアルゴリズム呼び出しだ。 targetNavigable
が第1引数で、このアルゴリズムの中で“the rules for choosing a navigable”というアルゴリズムに target
属性の値を渡して呼び出した結果が代入されている。navigableの定義はリンクを辿っていくと7.3節“Infrastructure for sequences of documents”で見つかり“Navigables are a user-facing representation of a sequence of documents, i.e., they represent something that can be navigated between documents. Typical examples are tabs or windows in a web browser, or iframes, or frames in a frameset.”となっている。ブラウザのタブやウィンドウ(あるいはフレーム)を指しているということで、分かりやすい。タブをナビゲートする、つまりページ遷移させるアルゴリズムである。 urlString
は遷移先を表す引数で href
属性を処理した結果が入っている。残りの引数は省略。
“Navigate”アルゴリズムに移る。7.4.2.2節“Beginning navigation”だ。どうでもよいが、この節の親セクションである7.4節“Navigation and session history”は
Welcome to the dragon's maw. Navigation, session history, and the traversal through that session history are some of the most complex parts of this standard.
という一文で始まっている。「Web関連仕様 日本語訳」の該当箇所を見ると「竜の頷へようこそ。」と訳されている。話が逸れた。“Navigate”アルゴリズムだった。これも一気に最後に飛んで20ステップ目の9サブステップ、
Attempt to populate the history entry's document for historyEntry, given navigable, "navigate", sourceSnapshotParams, targetSnapshotParams, navigationId, navigationParams, cspNavigationType, with allowPOST set to true and completionSteps set to the following step:
で“Attempt to populate the history entry's document”がアルゴリズム呼び出しだ。第1引数の historyEntry
は少し上の20ステップ目の6サブステップで
Let historyEntry be a new session history entry, with its URL set to url and its document state set to documentState.
と定義(代入)されている。“session history entry”とは有り体に言うとブラウザの「戻る」ボタンを長押ししたときに出てくるセッション履歴リストの一つのエントリのことで、ここにURLを格納してアルゴリズム間で受け渡す作りになっている。余談になるし触れる余裕もないが、このアルゴリズムは javascript:
URLや #fragment
へのリンクなどを別の処理へ分岐する記述があるのも注目だったりする。さて呼び出しを辿って“Attempt to populate the history entry's document”へ行こう。7.4.5節“Populating a session history entry”だ。5ステップ目の2サブステップで
Otherwise, if all of the following are true:
- entry's URL's scheme is a fetch scheme; and
- documentResource is null, or allowPOST is true and documentResource's request body is not failure,
then set navigationParams to the result of creating navigation params by fetching given entry, navigable, sourceSnapshotParams, targetSnapshotParams, cspNavigationType, navigationId, and navTimingType.
とある(いろいろと条件はあるがたいていの場合はこの行が実行される)。“entry”やその他引数を渡して“navigation params”を作っている。“creating navigation params by fetching”アルゴリズムの中を見てみよう。定義は同じ節の下の方にある。まず3ステップ目で
Let request be a new request, with
- url: entry's URL
とある(以下引数がたくさんあるが省略)。“request”を作っている。飛んで19ステップ目の5サブステップで
If fetchController is null, then set fetchController to the result of fetching request, with processEarlyHintsResponse set to processEarlyHintsResponse as defined below, processResponse set to processResponse as defined below, and useParallelQueue set to true.
Let processEarlyHintsResponse be the following algorithm given a response earlyResponse:
- If commitEarlyHints is null, then set commitEarlyHints to the result of processing early hint headers given earlyResponse and request's reserved client.
Let processResponse be the following algorithm given a response fetchedResponse:
- Set response to fetchedResponse.
とある。“fetching request”が大事で、“request”はさっき見たrequest、“fetching”はそういう名前のアルゴリズムだ。Fetch Standardというfetchのことだけが書かれた超長い仕様があって、そこで定義されている。JavaScriptの fetch()
関数には親しみがあると思うが、それだけではなくブラウザ内部でネットワーク越しに何かを“取ってくる”動作はすべてこのFetch仕様を参照するようになっている。これによってcross originやContent Security Policy (CSP)などのセキュリティのための仕組みを一元的に取り扱うことが実現されているのである。話を戻すと、ここでは遷移先のURLをfetchしているということだ。結果は response
変数に格納される。
同じアルゴリズムの19ステップ目の25サブステップ、最後の部分。
Return a new navigation params, with
- id: navigationId
- navigable: navigable
- request: request
- response: response
以下ほかの引数は略。ということでresponseが入った“navigation params”が作られて呼び出し元に返されている(“navigation params”という《型》の名前からはなかなか想像がつかない。“by fetching”はresponseを「fetchすることによって」得るという意味だったわけだ)。“Attempt to populate the history entry's document”アルゴリズムに戻り後の方へいって、6ステップ目の11サブステップの1サブステップ。
Let document be the result of loading a document given navigationParams, sourceSnapshotParams, and entry's document state's initiator origin.
“loading a document”アルゴリズムへ飛んでいる。これも同じ節に定義がある。2ステップ目。
If the user agent has been configured to process resources of the given type using some mechanism other than rendering the content in a navigable, then skip this step. Otherwise, if the type is one of the following types:
- an HTML MIME type
- Return the result of loading an HTML document, given navigationParams.
- an XML MIME type that is not an explicitly supported XML MIME type
- Return the result of loading an XML document given navigationParams and type.
- a JavaScript MIME type
- a JSON MIME type that is not an explicitly supported JSON MIME type
- "text/css"
- "text/plain"
- "text/vtt"
- Return the result of loading a text document given navigationParams and type.
- "multipart/x-mixed-replace"
- Return the result of loading a multipart/x-mixed-replace document, given navigationParams, sourceSnapshotParams, and initiatorOrigin.
- A supported image, video, or audio type
- Return the result of loading a media document given navigationParams and type.
- "application/pdf"
- "text/pdf"
- If the user agent's PDF viewer supported is true, return the result of creating a document for inline content that doesn't have a DOM given navigationParams's navigable.
responseのMIME型に応じて分岐している。いま追いかけているのは最初のHTMLの場合になるわけだが、他にいくつかの種類のリソースについて処理が決められていることも興味深い(といっても、よく読むとそこまで厳格な要件がないものもある)。HTMLの場合は“loading an HTML document”アルゴリズムへ飛ぶ。7.5.2節“Loading HTML documents”で、最初が
Let document be the result of creating and initializing a Document object given "html", "text/html", and navigationParams.
となっている。問題の“creating and initializing a Document object”に辿り着いた。
このあたりまで潜り込んで読むと、HTML仕様は単にHTMLというファイル形式の仕様なのではなく、Webブラウザというソフトウェア(の一部分、ただし、それなりに大きな一部分)の仕様となっていると感じられる。それはHTMLが単なるマークアップ言語ではなくJavaScriptプログラミング言語を実行できるプラットフォームだからである。そしてプログラミング言語の処理系としてWebブラウザを見ると、明らかに最も多くのユーザを持つ最も普及している処理系であり、そして最も手軽に使われているものでもある。ユーザはWebブラウザがどんなコードを実行しているか知りもしない。そんな環境で互換性とセキュリティを保つために払われている努力は途方もないもので、その成果物がHTML仕様なのである。
……ただし、ブラウザエンジンを実装しているベンダは減少の一途を辿っている。OperaがPrestoの開発をやめ、MicrosoftがEdgeHTMLの開発をやめた。寡占はベンダの利益を招き、ユーザの利益にはつながらない。オープンなウェブは瀕死に陥っている。
ウェブを愛しているので @mozilla に寄付しました。より良く、健全なインターネットのために協力してください。 #lovetheweb https://t.co/82lbAERvg4
— 竹麻呂 (@Takemaro_001) April 3, 2022