IE 9 の HTML 解析の相互運用性
本記事は、マイクロソフト本社の IE チームのブログ から記事を抜粋し、翻訳したものです。
【元記事】Interoperable HTML Parsing in IE9 (2010/9/14 7:08 AM )
HTML パーサーは、DOM の構築に関して非常に重要な役割を果たすので、同一のマークアップを実現するために重要な要素となります。HTML パーサーは、DOM API や CSS ルールをどのように適用するかという点でも大きな役割を果たしています。
getElementsByClassName や addEventListener などの注目度の高い API については IE 9 での改善点をこれまでもたくさん解説してきましたが、HTML パーサーの改善点についてはまだ説明していませんでした。
HTML パーサーは当然ながら開発者にとって重要であり、IE 9 標準モードの HTML パーサーは相互運用性の向上のためにいくつかの点が改善されています。
今回の記事では、この改善がサイトに及ぼす影響と、ブラウザー間での動作の違いがなおも残っている箇所への対策について実用的な情報を提供します。
innerHTML
もともと IE 独自の API として導入された innerHTML と outerHTML は、早い時期から標準として注目され、他のブラウザーでも、多少の相違点はありますが、広く実装されています。この innerHTML と outerHTML は、パーサーを呼び出すという点で他の DOM API と異なります。IE 9 では、よく見られる相互運用性の問題への対策としていくつかの点が変更されました。
マイクロソフトは、内部の動作を簡素化することに努めてきました。IE 9 より前のバージョンは、innerHTML および outerHTML に渡される入力をすべて受け入れ、その入力だけがコンテンツとして空白ページの中に存在するかのように処理を行います (結果として、暗黙的な <html>、<head>、<body> などの要素が発生します)。その後、そのページを呼び出し元の要素へ結合しようとしますが、これが "未知の実行時エラー" となることがしばしばありました。
IE 9 ではこの動作が改善され、より多くのケースがサポートされるようになったのと同時に、"未知の実行時エラー" は発生しなくなりました。正常に機能しない場合は、状況が把握しやすい DOMException 例外を取得できます。
一般的なシナリオはブラウザーの種類が違っても正常に機能しますが、これらの API は進化を続けている段階であるため、あらゆるケースで完璧な相互運用性が保証されるわけではありません。たとえば以下のコードは、ブラウザーの種類によって結果が異なります。
var img = document.getElementsByTagName(‘img’)[0];
img.innerHTML = “image text”;
<img> 要素は子を持ちません。そのため上記の例は、Chrome、Safari、IE 8 では機能せず、FF 3.6 では期待と大きく異なる動作をします。IE 9、Opera、FF 4 Beta では、上記のようなコードは期待どおりに動作し、テキスト ノードが適切に挿入されます。
innerHTML による問題を防ぐ最良の方法は、それ自体で有効なマークアップだけを入力することです。たとえば、div.innerHTML = "<p></p>" は正しい呼び出しです。<div> と <p> はそれぞれ互いがなくても存在できるからです。
わずかな編集には、appendChild のような DOM Core API を使うことも可能です。
ジェネリック要素
開発者からの要望に、ジェネリック要素に対するサポートを強化してほしいとの声がありました。ジェネリック要素とは、他の要素と構文は同じだが、HTML で定義されていないタグ名 (<awesome> など) を持つ要素のことです。IE 9 標準モードは HTML 5 仕様に準拠するので、ジェネリック要素を <span> タグのように処理します。これにより、よりわかりやすいタグ名をページに追加でき、他の要素と同じようにスタイルを適用できます。
<awesome style=”font-size: large;”>IE9</awesome>
このおかげで、これまで通常の要素を使って実現してきた機能を損なうことなく、ページのコンテンツの記述に意味を持たせられるようになり、また、他のブラウザーでも同じコードを利用することができるようになります。
空白文字
ほぼすべてのページに影響を及ぼす変更の 1 つとして空白文字の解析方法があります。IE 8 では空白文字を削除 (または折りたたみ) ますが、IE 9 では解析時にすべての空白文字を DOM 内に保持します。下のマークアップを参照してください。
<div>
<span>IE 9</span>
</div>
このマークアップは IE 8 の DOM では以下のように表現されます。
div
|->span
|--->”IE 9”
しかし IE 9 の DOM では、以下のように表現されます (空白文字を赤色で示します)。
div
|->”\n“
|->span
|--->”IE\t9”
|->“\n”
サイトが空白文字の有無に依存する場合は、この変更により大きな影響を受けます。
ドキュメント構造には空白文字のノードがかなり多く含まれるので、firstChild のような API は以前と同じノードを参照できなくなる場合があります。さらに、テキスト ノードの長さも考慮すべきポイントです。テキスト ノード内で空白文字が保持されるようになったことから、文字列内の文字のインデックスが想定と変わる可能性があります。
IE 9 の動作は、HTML 5 仕様に準拠しており、他のブラウザーとの相互運用性があります。ただし、この動作はマークアップ内での空白文字の使い方によっては、ページを壊れやすくしてしまうことがあります。このような問題を避けるためのアドバイスを以下に示します。
- 要素だけが必要な場合は、Element Traversal API を使用します。誤って改行文字だけのノードを参照することがないように、firstElementChild などの関数を使用します。
- 要素以外のテキスト ノードなども必要とする場合は、nodeType や同様の API を使用して明示的な型チェックを行います。テキスト ノード内の個々の文字にアクセスする目的にもよりますが、JavaScript の String オブジェクトの split() メソッドは、調べる文字列を分離するのに便利です。
タグのオーバーラップ
Web 開発者であれば、オーバーラップするタグは望ましくないことは知っていると思われますが、以下のようなマークアップはおそらくだれもが記述したことがあるでしょう。
<b><i>重要な文字</b></i>
タグがオーバーラップするというケースは、皆さんが想像するよりずっと多く発生しています。上のサンプルのように明らかにわかるケースだけではない、というのがその原因の 1 つです。例として、以下のマークアップを取り上げます。
<p><b><div>文字</div></b></p>
文法上 <p> 要素の中に <div> は記述できません。そのため、IE、Firefox、Chrome、Safari は暗黙的に <p> を閉じます。これは、パーサーに以下のようなマークアップを渡しているのとほとんど同じです。
<p><b></p><div>文字</div></b></p>
タグをオーバーラップしていないのに、結果的にそうなっている点に注目してください (この場合、<b> 要素です)。これは極端な例の 1 つに過ぎませんが、さまざまなケースを調べると、相当に複雑になり得るものであることがわかります。
IE 8 の開発者ツールを起動してこのマークアップを調べてみると、以下のような構造が確認できます。
p
|->b
|--->div
|----->"文字"
div
|->"文字"
p
この構造は、十分に合理的であるように見えますが、実際は表面下でさまざまなことが行われています。旧バージョンの IE では、オーバーラップされたマークアップは、おおよそ記述されたとおりに保持されます。つまり、オーバーラップされた要素が DOM ツリー構造内で複数の場所を占めることもあります。
このような状態 ("包含" と呼ばれます) は、特にツリー構造をたどるようなスクリプトを使用しているときに、ブラウザーの種類によって動作が変わる原因になることがあります。たとえば、<b> 要素の nextSibling を呼び出すと 2 番目の <p> が返され、firstChild を呼び出すと null が返されます。これは、<b> 要素が <div> の親であり、兄弟を持たないという事実に反した動作です。
IE 9 は、このような状態を解析時に解決して弊害を防ぐように改善されました。旧バージョンの IE で包含の状態が作られていた箇所には、IE 9 では代わりに要素のコピーが作られます。
したがって、上記のマークアップは、IE 9 の DOM 内では以下のようになります。
p
|->b
b
|--->div
|----->”文字”
p
IE 9 は、暗黙的な終了タグ </p> を検出すると、タグ <b> をコピーします。このため、DOM には 2 つの <b> 要素が含まれることになり、この場合の動作は Chrome および Safari と一致します。HTML 5 アルゴリズム (FF 4 Beta でサポートされる) は、次のテキスト ノードに遭遇した時点で、オーバーラップした要素のコピーを作成するという点が異なります (結果として、上記とは若干異なる DOM 構造が生成されます)。
この種の問題を初期段階で防ぐには、作成したマークアップを W3C のオンライン検証ツールで実行することをお勧めします。このツールは、こうした問題をそれが実際のバグとなる前に検出するのに役立ちます。IE の F12 開発者ツールには、サイトを W3C の 検証ツールに送信するリンクが組み込まれています。
title 要素
IE 8 およびそれより前のバージョンでは、パーサーは <head> を検出すると、その時点で必ず <title> 要素を暗黙的に作成します。このため、IE 8 では、<title> 要素をマークアップに明示的に記述していなくても、head.firstChild は必ず <title> を返すと想定できました。
IE 9 は、相互運用性の面から、他の主要なブラウザーと同様に <head> 内の <title> 要素の位置を尊重するように変更されました。
空白文字の処理のときと同様に、<head> 要素の最初の子が常に <title> であるという前提に依存してアプリケーションを作成している場合は、IE 9 と旧バージョンでサイトの動作が変わることになります。
<title> を取得する必要がある場合は、getElementsByTagName を使うのがより適切な方法と言えます。
object 要素
Web サイトでは、ブラウザー サンドボックスの外部で実行されるネイティブ コードとやり取りするために <object> 要素を使用することが多いため、従来から IE での <object> 要素の動作は独特でした。IE 9 では、<object> の解析が改善され、<object> および <object> 内の要素が、他の要素と同じように DOM に表現されるようになりました。
たとえば、<object> が正常に読み込まれたかどうかに関係なく、<object> 内の <param> 要素やフォールバック コンテンツが DOM 内に保持されます。
これは、以下のような呼び出しが機能することを意味します。
alert(document.getElementsByTagName(‘param’)[0].nodeName)
特別なことは何もしなくても、この新しい機能を活用できます。他の多くの要素と同じように、また他のブラウザーと同じように、<object> を操作できるようになりました。
API の追加や変更と比べると、この記事で紹介してきた変更点はあまり重要ではないように感じるかもしれませんが、実際の Web 開発にはかなりの影響があります。開発者の皆さん、ぜひ最新の Platform Preview で皆さんのサイトを実行し、ここで説明した変更点から問題が発生しないか確認してください。
Connect またはコメント欄からのフィードバックをいつもお待ちしております。
ありがとうございました。
Jonathan Seitel
プログラム マネージャー