Dev Study
← Rust で小さなブラウザを作る コース

12. 自己終了タグと script の中身 — 「適切な終了タグ」が来るまでは全部文字

<script> の中身にはタグに見える文字列が平気で入ります(if (a < b) や文字列の '</div>')。はこれをタグにしてはいけません。HTML Standard はこのために という別のの組を用意していて、「(appropriate end tag)」つまり直前の開始タグと同じ名前の終了タグが来るまで、文字として流します。

参照実装は 4 つの状態を足しています。ScriptData(< 以外は文字)、ScriptDataLessThanSign(< を読んだ)、ScriptDataEndTagOpen(</ を読んだ)、ScriptDataEndTagName(終了タグ名らしきものの途中)。開始タグを出すときに、名前が script(この実装では style / title / textarea も)なら次の状態を Data ではなく ScriptData にします。本物はこの切り替えをが指示しますが、ここでは字句解析器が自分で切り替えています。

ScriptDataEndTagName が芯です。英字が続く間はタグ名に足しつつ、temp バッファにも同じ文字を溜めます。> が来たとき、作りかけの終了タグの名前が last_start_tag(最後に出した開始タグ)と同じなら終了タグとして出す。違えば、"</" と temp に溜めた文字を全部 Char として流し直し、ScriptData に戻ります。1 回の遷移で複数のを出す必要があるので、pending という待ち行列()を持ち、 はまずそこから返します。

(<br/>、<img .../>)は SelfClosingStartTag 状態で、/ の次に > が来たら self_closing を true にして出します。ただし HTML では、この印に意味があるのは外来(svg など)だけです。br や img が子を持たないのは だからで、<div/> のように void 以外に付けた / は構文解析で無視されます()。参照実装の構文解析器もそうしています。

本物のブラウザでは: script の中身を文字として読む状態は にも kScriptDataState 系として並んでいます。本物が違うのは『script に出会ったら字句解析を止めて実行する』の間に起こることで、Blink は (html_preload_scanner.cc)と BackgroundHTMLScanner が止まっている間も先を走査し、img や link を先に取りに行きます。仕様はこれを speculative HTML parsing と呼びます。Firefox は nsHtml5StreamParser が外で解析し、nsHtml5SpeculativeLoad で同じ先読みをします。script を実行するのは Blink では HTMLParserScriptRunner で、「script 待ちで止まっていたら先にそれを実行して続ける」(html_document_parser.h)と書かれています。

開発のどこに活かすか: 「埋め込まれた別言語の中身を、外側の文法で解釈しない」のは、テンプレートエンジン、Markdown のコード、SQL の文字列リテラル、ログの JSON 部分など、どこにでもある境界の問題です。終了の目印を名前で照合する(適切な終了タグ)という解き方と、照合に失敗したら溜めた文字を戻す(temp と pending)という解き方は、そのまま流用できます。

点線の付いた語はタップすると説明が出ます。用語集を開く →

▶ サンプルコードを実行してみよう(自由に書き換えてOK)

Rust

手元で動かす(配布物)

図やコードに出てくる用語(タップで説明)

サンプルコードで身につける(3本) →

学んだ概念を、素朴な例から実務寄りの例まで3つの具体例で確認します。

公式ドキュメントで詳しく ↗