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

[インデックス 13607] ファイルの概要

このコミットは、Go言語の実験的なHTMLパーサーパッケージ exp/html における、<textarea> タグの処理に関するバグ修正と改善を目的としています。具体的には、パーサーが <textarea> タグを無視する際に、トークナイザーが「raw text mode」から適切に切り替わらない問題を解決し、それによって発生していたテストの失敗を修正します。

コミット

commit e4a50195c389e67d6d3277b46d4179602bec8c41
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Thu Aug 9 09:43:10 2012 +1000

    exp/html: when ignoring <textarea> tag, switch tokenizer out of raw text mode
    
    Pass 1 additional test.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/6459060

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/e4a50195c389e67d6d3277b46d4179602bec8c41

元コミット内容

exp/html: when ignoring <textarea> tag, switch tokenizer out of raw text mode

Pass 1 additional test.

R=nigeltao
CC=golang-dev
https://golang.org/cl/6459060

変更の背景

HTMLパーサーは、HTMLドキュメントを解析し、DOMツリーを構築するための重要なコンポーネントです。HTMLの仕様では、<textarea><script><title> などの特定の要素の内容は、通常のHTML要素としてではなく、プレーンテキストとして扱われる「raw text mode」で解析される必要があります。これは、これらのタグの内部にHTMLタグのような文字列が含まれていても、それらがマークアップとして解釈されるべきではないためです。

このコミットが行われる前、Go言語の exp/html パッケージ(現在の golang.org/x/net/html パッケージの前身)のパーサーは、<textarea> タグを「無視する」と判断した場合に、トークナイザーがこの「raw text mode」から適切に抜け出さないという問題がありました。具体的には、パーサーが何らかの理由で <textarea> タグをスキップまたは無視する決定をした後も、トークナイザーが <textarea> の内容を解析するための特殊なモード(raw text mode)に留まってしまい、その後に続くHTMLコンテンツを誤ってプレーンテキストとして解釈してしまう可能性がありました。

この不具合は、<textarea><option> のような特定のテストケースで顕在化していました。本来であれば <option> は通常のHTML要素として解析されるべきですが、トークナイザーがraw text modeに留まっていると、<option> が単なるテキストとして扱われ、テストが失敗していました。このコミットは、この特定のテストケースをパスさせることを目的としています。

前提知識の解説

HTMLパーシングとトークナイザー

HTMLパーシングは、HTMLドキュメントを読み込み、その構造を理解し、ブラウザがレンダリングできるような内部表現(通常はDOMツリー)に変換するプロセスです。このプロセスは通常、以下の2つの主要なフェーズに分かれます。

  1. トークナイゼーション (Tokenization):

    • HTMLの生バイトストリームを読み込み、それを意味のある「トークン」のシーケンスに変換します。トークンには、開始タグ、終了タグ、属性、テキスト、コメント、DOCTYPE宣言などがあります。
    • このフェーズは、HTMLの字句解析器(Lexer)またはトークナイザーによって行われます。
    • HTMLの仕様には、特定の要素(例: <textarea>, <script>, <title>, <style>) の内容を処理するための特別なルールがあります。これらの要素の内部では、通常のHTML解析ルールが一時的に中断され、その内容が「raw text」として扱われます。これを「raw text mode」と呼びます。このモードでは、<& のような文字も特殊な意味を持たず、単なる文字として扱われます。
  2. ツリー構築 (Tree Construction):

    • トークナイザーから受け取ったトークンのシーケンスを使用して、DOMツリーを構築します。
    • このフェーズは、パーサーによって行われます。パーサーは、トークンの種類と現在のコンテキスト(どの要素の中にいるかなど)に基づいて、DOMノードを作成し、ツリーに追加します。

<textarea> タグと Raw Text Mode

<textarea> 要素は、複数行のプレーンテキスト入力フィールドを定義します。HTMLの仕様では、<textarea> の開始タグと終了タグの間にあるコンテンツは、HTMLマークアップとして解釈されるべきではありません。例えば、<textarea><b>Hello</b></textarea> というHTMLがあった場合、<b> は太字のタグとしてではなく、単なる文字 &lt;b&gt; として扱われるべきです。

このため、HTMLトークナイザーは <textarea> の開始タグを検出すると、「raw text mode」に切り替わります。このモードでは、トークナイザーは </textarea> 終了タグが出現するまで、すべての文字をプレーンテキストとして扱います。</textarea> が検出されると、トークナイザーは通常のデータモードに戻ります。

Go言語の exp/html パッケージ

exp/html は、Go言語でHTML5の仕様に準拠したパーサーを提供する実験的なパッケージでした。後に golang.org/x/net/html として標準化され、GoのWebアプリケーション開発においてHTMLの解析や操作に広く利用されています。このパッケージは、HTMLのトークナイゼーションとツリー構築のプロセスを実装しており、ブラウザの動作を模倣するように設計されています。

技術的詳細

このコミットの技術的な核心は、HTMLパーサーが <textarea> タグを「無視する」と判断した場合のトークナイザーの状態管理にあります。

src/pkg/exp/html/parse.go 内の inSelectIM 関数は、<select> 要素の内部でのHTML解析ロジックを処理しています。この関数内で、パーサーは特定のトークン(例えば、<textarea>)を処理する際に、それを無視するかどうかを決定します。

元のコードでは、パーサーが <textarea> タグを無視すると決定した場合(return true でトークンをスキップする場合)、トークナイザーの rawTag フィールドがクリアされませんでした。rawTag フィールドは、トークナイザーが現在raw text modeで処理しているタグの名前を保持するために使用されます。例えば、<textarea> タグが検出されると、トークナイザーはこのフィールドを "textarea" に設定し、raw text modeに入ります。

パーサーが <textarea> タグを無視したにもかかわらず rawTag がクリアされないままだと、トークナイザーは引き続きraw text modeにあると誤解します。その結果、次に続くHTMLコンテンツ(例えば、<option> タグ)が、通常のHTML要素としてではなく、プレーンテキストとして解釈されてしまいます。これは、HTMLの仕様に反する動作であり、DOMツリーの不正な構築につながります。

このコミットでは、この問題を解決するために、<textarea> タグを無視する際に明示的に p.tokenizer.rawTag = "" を設定することで、トークナイザーをraw text modeから抜け出させています。これにより、パーサーが <textarea> を無視した後も、トークナイザーが正しいモードで後続のHTMLコンテンツを処理できるようになります。

この修正により、src/pkg/exp/html/testlogs/tests_innerHTML_1.dat.log に含まれる <textarea><option> のテストケースが FAIL から PASS に変更されました。これは、修正が意図した通りに機能し、パーサーが <textarea> の後の <option> を正しくHTML要素として認識できるようになったことを示しています。

コアとなるコードの変更箇所

変更は主に src/pkg/exp/html/parse.go ファイルにあります。

--- a/src/pkg/exp/html/parse.go
+++ b/src/pkg/exp/html/parse.go
@@ -1618,6 +1618,8 @@ func inSelectIM(p *parser) bool {
 			return false
 		}
 		// Ignore the token.
+		// In order to properly ignore <textarea>, we need to change the tokenizer mode.
+		p.tokenizer.rawTag = ""
 		return true
 	case a.Script:
 		return inHeadIM(p)

また、この修正によってテスト結果が変更されたことを示すログファイルの変更も含まれています。

--- a/src/pkg/exp/html/testlogs/tests_innerHTML_1.dat.log
+++ b/src/pkg/exp/html/testlogs/tests_innerHTML_1.dat.log
@@ -79,7 +79,7 @@ PASS "<table><td><td>"
 PASS "</select><option>"
 PASS "<input><option>"
 PASS "<keygen><option>"
-FAIL "<textarea><option>"
+PASS "<textarea><option>"
 FAIL "</html><!--abc-->"
 PASS "</frameset><frame>"
 PASS ""

コアとなるコードの解説

src/pkg/exp/html/parse.go の変更箇所は、inSelectIM 関数内の case a.Textarea: ブロックにあります。

inSelectIM 関数は、<select> 要素の内部でパーサーがトークンを処理する際の挙動を定義しています。case a.Textarea: は、パーサーが <textarea> の開始タグを検出したときの処理です。

元のコードでは、パーサーが <textarea> タグを無視すると判断した場合(return true)、単にトークンをスキップしていました。しかし、このときトークナイザーの内部状態である p.tokenizer.rawTag がクリアされず、トークナイザーがまだraw text modeにあると誤解していました。

追加された2行は以下の通りです。

// In order to properly ignore <textarea>, we need to change the tokenizer mode.
p.tokenizer.rawTag = ""
  • // In order to properly ignore <textarea>, we need to change the tokenizer mode.:これは追加されたコメントで、なぜこの変更が必要なのかを説明しています。<textarea> を適切に無視するためには、トークナイザーのモードを変更する必要があることを示唆しています。
  • p.tokenizer.rawTag = "":この行が実際の修正です。パーサーのトークナイザー (p.tokenizer) の rawTag フィールドを空文字列に設定しています。これにより、トークナイザーは現在raw text modeではないと認識し、通常のデータモードに戻ります。

この修正により、<textarea> タグが無視された後でも、トークナイザーは後続のHTMLコンテンツを正しく解析できるようになり、<textarea><option> のようなテストケースが期待通りにパスするようになりました。

src/pkg/exp/html/testlogs/tests_innerHTML_1.dat.log の変更は、この修正が実際にテスト結果に影響を与え、以前は失敗していたテストが成功するようになったことを示しています。これは、コードの変更が意図した問題を解決したことの直接的な証拠です。

関連リンク

参考にした情報源リンク