ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側)

このエントリーをはてなブックマークに追加
どうも、鈴木です。
さて、前回は vim の使用法というじつに低レベルレイヤの出身者的な記事を書きましたが、
今回も懲りずに低レベルのお話しをしたいと思います。

というのも、先日「ブログ書くのめんどくさいよぅ」と駄々をこねていたところ、あまりにレガシーすぎる HTML/CSS/JavaScript 仕様や Flash や Silverlight といったプロプライエタリなリッチコンテンツ用プラグインに日々苦しめられている気弱く善良な一介の WEB プログラマにすぎない我々の希望の星であり、そして同時に新たな巨大クソレガシーの萌芽でもある HTML5 が、いかにイケてないのではなくイケているのであるかを盛んに啓蒙するサイトである HTML5 Rocks (http://www.html5rocks.com/) に、"How Browsers Work" というとても楽しい記事があるのを、我が社のアーリーアダプタでありエヴァンジェリストである ikeike443 氏から教えてもらい、これはなかなかありそうでなかった部分のきちんとしたドキュメントだと刮目したので、これを日本のドメスティックな (つまり私のように楽天にはもう入ることのできない) WEB 開発者にも広く読んでもらおうと考え、今回のブログの記事は、この記事の翻訳にさせてもらおうと勝手に思った次第なのであります。

というわけで元の記事はこちら。

翻訳にあたっては、一部プログラムコードも登場する記事であることから、ある程度ソフトウェアアーキテクチャやアルゴリズム的な用語については、あまり言い換えずにそのままカタカナ語を用いています。
また訳語を当てはめたものについても、わりといい加減に当てはめているので、オライリーのきちんとした和訳本などの用語と若干異同があるかもしれないことはお断りしておきます。
(つまり、そのへんきちんと調べるのをさぼったわけです)

それでは、現存する汎用ソフトウェアの最高峰のひとつである WEB ブラウザのめくるめく (≒ 発狂しそうな) 世界へようこそ。

9/9 追記:
Kosei Moriyama (_kosei_)氏による訳もあるようです。
http://cou929.nu/docs/how-browsers-work/

わたくしめのいい加減な訳のせいで文意がつかめない場合などは、読み比べてみてはいかがでしょう。というか、ならはじめからそちらを読めばいいじゃん、というのは言いっこなしです。また、わたくしとしても「やってくれてるの知ってたら、慣れない和訳なんぞ自分でやろうとは思わなかったであろうよ」的な愚痴は言いっこなしにしたいと思います。

それはともかく、どちらかといえば、氏のもう一つの和訳である Google JavaScript Style Guide 和訳 もたいへんな労作であり、内容も非常に実践的なものであることから、自身は JavaScript プログラマであると認識されている方はぜひ一度目を通しておかれることをお薦めいたします。

ちなみに、わたくし個人といたしましては、このスタイルガイドにだいたい 8 割同意で、残り 2 割は、わたくしなりのわりとめんどくさい理由から反対(別のスタイルがよいと考えている)といったところであります。(おもに、はじめてまともに触った JavaScript フレームワークが ExtJS であったところから来ているのですが)
いずれにせよ、プログラマであれば、こうしたコーディングスタイルの問題は、なんといいますか、(ときに喧嘩になる、というのも含めて)酒の肴的なものであり、その意味でもたいへん楽しく読めるのではないかなと思っております。

また最近は、これベースで JavaScript コードをチェックしてくれる Google Closure Linter なる、まったくもっておせっかいな代物もあるようですので、(C の lint をまったく快く思ってない身としては、lint と聞くとその時点で斜に構えてしまうのであります) こちらなども試してみるとよろしいかと思います。
もっとも、わたくしとしてはまったく同意できない規則がいくつかありますので、使わないわけなんですけれども。

それでは、あらためて本文をどうぞ。



ブラウザってどうやって動いてるの?
モダンWEBブラウザシーンの裏側


Tali Garsiel (著者) - 開発者, Incapsula,
Paul Irish (編集) - 開発者, Google
2011/8/16

はじめに

この Webkit と Gecko の内部処理についての包括的な入門書は、イスラエルの開発者 Tali Garsiel によるリサーチの成果です。
数年にわたり、彼女はブラウザの内部に関する資料(参考文献を参照)をレビューし、実際のブラウザのソースコードを読みこむことに時間を費やしてきました。

彼女は次のように書いています:
IE のシェアが 90% であったころはブラックボックスであったブラウザについて考察すべき事柄はほとんどありませんでした。
ですが、今ではシェアの半分以上を占めるにいたったオープンソースのブラウザがあり、エンジンのフードを開いて WEB ブラウザの内側を見てみるにたいへんよい頃合いです。
まあ、中身ってのは、数百万行の C++ のコードなわけですが。。。
Tali は彼女のサイトでリサーチの結果を公表しているわけですが、我々としては彼女の仕事にはより多くの人に知ってもらう価値があると思っています。そこで今回私たちは彼女の仕事をきちんと出版というかたちで公にすることにしました。

さて、WEB 開発者にとってブラウザ内部の処理を学習することは、自身の WEB 開発作業においてよりよい決定を下し、ベストプラクティスがいかにしてベストプラクティスであるかをきちんと認識することにつながります。
このドキュメントはかなり長いものであるわけなのですが、WEB 開発者は深く知っておくべき分野だと思います。
そんなわけで我々はあなたがこのドキュメントをきちんと読んでくれることを歓迎します。
Paul Irish: Google Chrome 開発者

第一章
イントロ

WEB ブラウザは、今日ではもっとも広く使用されているソフトウェアといえるでしょう。
ここでは、ブラウザが動作するその背後で何が起きているのかについて説明しようと思います。
具体的には、あなたが日々アドレスバーに google.com と入力してアクセスする際に、ブラウザ画面上で何が起きているについて述べてみようと思います。

1.1 ここで取り扱うブラウザについて

ここでは 5 つのメジャーなブラウザを取り上げます。すなわち、インターネット・エクスプローラー、Firefox,サファリ、Chrome、そしてオペラです。
具体的なコードの例としてはオープンソースのブラウザを例にとりたいと思います。すなわち Firefox Chrome それからサファリです。
StatCounter のブラウザ統計 (2011 8月) によると、これらのオープンソースブラウザの合計シェアはじつに 60% に達しています。
つまり、今日ではオープンソースブラウザは、ブラウザ業界の主要なプレイヤであるといえるのです。

1.2 ブラウザの主たる機能

ブラウザの主な機能というのは、ユーザが選択した WEB 上のリソースにアクセスし、ブラウザウィンドウ上にその内容を表示することにあります。
このリソースというのは、通常、HTML ドキュメントであったり、PDF や画像であったりします。
リソースの所在というのは、URI ("Uniform Resource Identifier" 正規化されたリソース ID の頭文字ですが) を用いて指定されます。

ブラウザが HTML ファイルを解釈し表示する方式というのは、HTML と CSS の仕様によって規定されています。
これらの仕様は WEB の標準を規定する組織である W3C (World Wide Web Consortium) によって管理されています。
これまで、各ブラウザはこれらの標準のうちの一部をサポートし、それをそれぞれの拡張機能として開発してきました。
このようなやり口はかなり深刻な互換性の問題を WEB ドキュメントを作成する者たちに引き起こしました。
それゆえ今日では、多かれ少なかれブラウザは標準の仕様に準拠しようとしています。

ブラウザのユーザインターフェースは互いに共通する部分がかなりあります。
各ブラウザに共通するユーザインターフェースというのは:
  • URI を入力するアドレスバー
  • 戻る、進む、ボタン
  • ブックマーク機能
  • 現在読込中のHTMLドキュメントの更新と中止ボタン
  • ユーザのホーム画面を取得するホームボタン
冷静に考えてみるとだいぶおかしなことではあるのですが、これらのブラウザのユーザインターフェースというのは、公式に仕様が定められていません。これらは単に各ブラウザが相互に良い事例を学び、模倣し合った結果としてこうなっているだけなのです。
HTML5 の仕様もこれらの UI 要素をすべてのブラウザが必ず備えるべきだと定義しているわけではありません。ただ、これらの共通する部分についてリストアップはしています。
つまり、アドレスバー、ステータスバー、ツールバーなどです。
もちろん、各ブラウザに固有のフィーチャというのもあります。たとえば、Firefox のダウンロードマネージャなどがその典型になります。

1.3 ブラウザの最上位の設計

ブラウザのメインコンポーネントというのは (1.1):
  1. ユーザインターフェース - アドレスバー、戻る/進むボタン、ブックマークメニューなど。これらの全てはブラウザのメインディスプレイから分けられています。
  2. ブラウザエンジン - UI とレンダリングエンジンを呼び出します。
  3. レンダリングエンジン - 要求されたコンテンツを表示する責任を負います。たとえば、リクエストが HTML であった場合、エンジンは HTML と CSS をパースし、その内容をスクリーンに表示します。
  4. ネットワーク - HTTP のような通信要求から呼び出されます。これは基本的にプラットフォームから独立した (つまり共通した) インターフェースと実装を持っています。
  5. UI バックエンド - コンボボックスやウィンドウのような、基本的なウィジェットの描画に用いられます。これはプラットフォームに依存しない一般的なインターフェースから生成されますが、最終的にはそれぞれの OS のインターフェースを呼び出します。
  6. JavaScript インタプリタ。JavaScript コードをパースし、実行するために用いられます。
  7. データストレージ。これはデータの永続化に用いられるレイヤです。ブラウザはクッキーのような永続するデータのフルセットをハードディスクに保存しなければなりません。新しい HTML の仕様 (HTML5) では、WEB データベースが定義されています。これはブラウザ上での完全な DB として機能します。
図1 : ブラウザのメインコンポーネント

これは重要な注釈であるわけですが、一般的なブラウザとは異なり Chrome はタブごとに複数のレンダリングエンジンを保持します。つまり、各タブが独立したプロセスとなっているのです。

第二章
レンダリングエンジン

WEBブラウザにおいてレンダリングエンジンの担っているのはもちろんレンダリング、すなわちブラウザのスクリーンに要求されたコンテンツを表示することです。

レンダリングエンジンはデフォルトで HTML と XML ドキュメントおよび画像を表示することができます。
PDF ビューアなどのプラグインやブラウザ拡張を用いることでその他のタイプのデータも表示することができますが、この章では主な使用法に焦点を当てたいと思います。
すなわち、HTML と CSS を用いて記述された画像の表示についてです。

2.1 レンダリングエンジンのバリエーション

ここで述べるブラウザ、Firefox、Chrome、Safari は二つのレンダリングエンジンを採用しています。Firefox は Mozilla 謹製の Gecko を採用しています。Safari と Chrome は Webkit を採用しています。

Webkit はオープンソースのレンダリングエンジンで当初 Linux プラットフォーム向けに開発され、Apple によって Mac と Windows の対応がなされました。詳細については webkit.org を見てみてください。

2.2 メインフロー

ネットワークレイヤから要求されたドキュメントの一覧を取得してくるとレンダリングエンジンが起動されます。これは通常、先頭の 8KB 以内のうちに行われます。

その後のレンダリングエンジンの標準的な処理の流れは次のようなものです。


図2 : レンダリングエンジンの標準のフロー

レンダリングエンジンは HTML ドキュメントを解析し始めると、そこに記述されたタグをツリー上の DOM ノードに変換します。これはコンテントツリーと呼ばれています。
また、外部の CSS ファイルと内部の style エレメントからなるスタイルデータをパースします。
このスタイル情報と HTML の視覚情報は一緒にされ、もう一つのツリーを構成することになります。これは描画ツリーと呼ばれています。

描画ツリーは色や大きさなどの視覚属性を持った矩形で構成されており、
この矩形はスクリーン上に表示されるべき順序で正しく並べられています。

描画ツリーの構築後はレイアウト処理が走ります。
これはツリーの各ノードにそれがスクリーン上での正確な表示座標を決定するものです。
次のステージはペインティングです。描画ツリーをなめて、各ノードを UI バックエンドを用いて実際に表示します。

重要なことは、これが漸進的な処理であることです。
よりよいユーザエクスペリエンスのために、レンダリングエンジンは各部分が確定した瞬間にその内容をスクリーン上に表示しようとします。
つまり、全 HTML をパースしてから描画ツリーを構築し、その後、レイアウト、といった手順は踏まないということです。
残りのコンテンツがネットワークから取得されつづける間、この表示内容の部品のパースと表示は繰り返されます。

2.3 メインフローの例


図3 : Webkit のメインフロー



図4 : Mozilla Gecko のレンダリングエンジンのメインフロー (3.6)

図 3 図 4 から Webkit と Gecko は多少異なる用語を用いていますが、流れは基本的に同じであることがわかります。

Gecko は "フレームツリー" と呼ばれる視覚な概念に基づいたツリーを呼び出します。要素はフレームです。
Webkit は "描画ツリー" を用います。これの要素は "描画オブジェクト" です。
Webkit はエレメントを配置する "layout" のための処理ステップがあります。Gecko はこれを "reflow" と呼んでいます。
"attachment" は Webkit の処理ステップで、DOM ノードや視覚情報にアクセスし、描画ツリーを構築します。
微妙な違いとしては Gecko は HTML と DOM ツリーの間にもう一つレイヤを持っています。
これは "content sink" と呼ばれ、DOM エレメント生成のファクトリです。
さて、ではそれぞれのフローを見ていくことにしましょう。


第三章
3.1 パース処理の概要

パース処理はレンダリングエンジンにおいて非常に重要な処理なので、もう少し詳しく見てみることにします。
まずはパース処理のさわりから紹介しましょう。

ドキュメントをパースするということは、ある構造を別の理解でき使用できるコードを持つ構造に翻訳することです。
パースの結果は通常ドキュメントの構造を表現するノードのツリーです。
これはパースツリーとか構文木とか呼ばれます。

例 - 表現 "2 + 3 - 1" のパースは次のようなツリーを返します。


図5 : 数学表記のツリーノード

3.1.1 文法

パース処理は対象のドキュメントの書かれた言語や構文に基づいています。
パース可能なあらゆるフォーマットは、一意に識別可能な正確な文法と語彙それから構文を持っている必要があります。
これを文脈非依存な文法といいます。
自然言語はこのような言語ではありません。このため伝統的なパースの技法ではパースできません。

3.1.2 パーサ/レクサーの連携

パース処理は二つの子処理に分けることができます。語彙解析と構文解析です。

語彙解析は入力をトークンに分割する処理です。
トークンは対象の言語の語彙で、有効なブロック要素の集合です。
自然言語でいえば、これはその言語の辞書に載ったすべての語彙からなります。

構文解析は対象の言語の構文規則を適用することです。

パース処理は通常二つのコンポーネントのやりとりを通して行われます。意味のあるトークンに入力を分割する担当のレクサー (トークン化とも呼ばれます) と、対象の言語の構文に基づいてドキュメント構造を解析し構築されるパースツリーです。
レクサーは空白や改行などの不適切な文字を削除する方法を知っています。



図6 : ソースドキュメントからパースツリーまで

パース処理はインタラクティブに行われます。
パーサは通常レクサーに新しいトークンを問い合わせ、これを構文規則に適用しようとします。
適用させるルールが見つかると、このトークンに対応するノードがパースツリーに追加され、パーサはまたひとつトークンを問い合せます。

ルールが見つからない場合は、パーサはこのトークンを適用可能なルールが見つかるまで保持しておきます。
まったくルールが見つからないときは、パーサは例外をあげます。
これは対象のドキュメントは不正で、構文エラーが含まれているということになります。

3.1.3 翻訳

多くの場合、パースツリーは最終的な成果物ではありません。
パース処理はしばしば入力のドキュメントを別のフォーマットに変換する翻訳のために行われます。
ひとつの例はコンパイルです。
コンパイラはソースコードを機械語にコンパイルします。まずパースしてパースツリーに変換し、それからこのツリーを機械語のドキュメントに翻訳します。



図7 : コンパイル処理フロー

3.1.4 パース処理の例

図 5 では、数学表記からツリーを起こしました。シンプルな数学言語を定義し、これのパース処理を見てみることにしましょう。

語彙: この言語は整数とプラス記号とマイナス記号からなります。

構文:
  1. 言語の構文要素は、表現、語句、操作です。
  2. この言語に表記数の制限はありません。
  3. ひとつの表現は "語句" とそれに続く "操作" 、さらにそれに続く "語句" からなります。
  4. 操作はプラス記号かマイナス記号です。
  5. 語句はひとつの整数トークンか、ひとつの整数表記です。
入力 "2 + 3 - 1" を解析してみましょう。
はじめに、切り出された規則に適合する語句は "2" です。規則 #5 によれば、これは語句です。次にマッチしたのは "2 + 3" でこれは「ひとつの語句とそれに続く操作、さらにそれに続く語句」という第三の規則にマッチします。
次にマッチするのは入力の末尾だけです。2+3 部分がすでにひとつの語句として認識されているので、"2 + 3 - 1" は、ひとつの表現として扱うことができ、また、ひとつの語句とそれに続く操作、さらにそれに続く語句を得ます。
"2 + +" は、どの規則にもマッチしないので、これは不正な入力になります。

3.1.5 語彙と構文の正式な定義

語彙の定義にには通常、正規表現が用いられます。

たとえば、例の言語では次のようになります。
INTEGER :0|[1-9][0-9]*
PLUS : +
MINUS: -
これをみてわかるように、整数は正規表現で定義されています。

構文は通常 BNF と呼ばれるフォーマットで表現されます。例の言語では次のようになります。
expression :=  term  operation  term
operation :=  PLUS | MINUS
term := INTEGER | expression
文法が文脈に依存していない場合、その言語は標準的なパーサによってパース可能である、といいます。
文脈非依存な文法のより直感的な定義は、それが BNF で完全に記述可能であるということです。
より正確な定義については Wikipedia の文脈非依存な文法 (Context-free grammar) の記事を参照してください。


3.1.6 パーサのタイプ

パーサには2つの標準的なタイプがあります。トップダウンのパーサとボトムアップのパーサです。
トップダウンパーサというのは、構文のより上位の構造からマッチさせてゆくパーサだといえば直感的でしょう。
ボトムアップパーサのほうは、まず入力を構文規則にざっくりと変形し、もっとも下位の規則から最上位に抜ける方式です。

さて、ではこの二つのタイプのパーサが今回の例をどのようにパースするのか見てみましょう。

トップダウンパーサはより上位の構文規則から解釈します。つまり "2 + 3" という表現からです。
その次に "2 + 3 - 1"  を表現を認識します。
表現を認識する処理は別の規則にマッチするとそちらに移行します。しかし、はじめはあくまでも最上位のルールからです。

ボトムアップパーサはいずれかの規則にマッチするまで入力をスキャンし、その入力をルールを規則で置き換えます。
これを入力の末尾に到達するまで行います。部分的にマッチした表現は、パーサのスタックに置かれます。

スタック入力

2 + 3 - 1
term+ 3 - 1
term operation3 - 1
expression- 1
expression operation1
expression

ボトムアップ型のパーサは、入力を右側にシフトしてゆき(ポインタが入力の開始位置からじょじょに右に動いてゆく様子を思い浮かべるとよいでしょう)、徐々に構文規則へと縮退させてゆくため、縮退パーサとも呼ばれます。

3.1.7 自動的にパーサを生成する

パーサを生成するためのツールがいくつかあります。
これはパーサジェネレータと呼ばれます。
対象の言語の文法 (語彙および構文規則) を与えると、実際に動作するパーサを生成します。
パーサの生成にはパース処理の深い理解が必要で、最適化されたパーサを自分で書くのはそんなに簡単なことではありません。そこで、パーサジェネレータが役に立ちます。

Webkit は、レクサーを生成するための Flex、パーサ生成のための Bison (たぶん Lex や Yacc という名前で実行することになるでしょう) というよく知られた 2 つのパーサジェネレータを使っています。
Flex の入力は正規表現によるトークンの定義が書かれたファイルです。Bison の入力は BNF で書かれた構文規則です。

3.2 HTML パーサ

HTML パーサの仕事は、HTML マークアップ言語をそのパースツリーに落としこむことです。

3.2.1 The HTML 文法定義

HTML の語彙と構文は W3C によって策定されています。現在のバージョンは HTML4 で、HTML5 の策定が進められています。

3.2.2 だが文脈非依存ではないのである

これまで、パース処理の導入部をみてきました。そこでは構文規則は BNF のような正式なフォーマットによって定義することができました。

残念なことに、これらの基本的なパーサに関する話題のすべてを HTML に適用することはできません (これらは CSS と JavaScript のパースには適用できるでしょうが)。
HTML はパーサにやさしい文脈非依存の文法として、簡潔に定義されません。

HTML を公式に定義するフォーマットとして DTD (Document Type Definition) があります。けれども、これは文脈非依存ではないのです。

これは一見ひじょうに奇妙なことに見えます。HTML はむしろ XML に近いのです。そして、利用可能な XML のパーサはたくさんあります。
それから HTML の XML 版である XHTML もあります。何がそんなに違うのでしょうか。

違いは HTML のアプローチはより "寛容" であるということです。特定のタグが暗黙裡に挿入されたり、ときおり開始タグや終了タグを省略したり、といったことが HTML では起きます。
これらの "ソフトな" 構文は、XML の堅牢で正確な記述を要求する構文とは対照的です。

この一見小さなものにみえる違いは、実際にはまったく世界を変えてしまいます。
このことは HTML がこれほどまでに広く採用されている主な理由である (書き間違いに寛容なのは、WEB 作成者を楽にします) 一方で、
公式な文法に則って書くことをひじょうに困難にします。

まとめると、HTML は簡単にパースできません。文脈非依存でないので標準的なパーサは使えませんし、XML パーサも使えません。

3.2.3 HTML DTD

HTML 定義は DTD フォーマットの一部です。
このフォーマットは SGML ファミリの言語を定義するのに用いられています。
このフォーマットは、すべての利用可能な要素とその属性、階層を持っています。
前述のように、HTML DTD は文脈非依存ではありません。

DTD には少ないですが、バリエーションがあります。
strict モードのみが仕様に適合していますが、他のモードには過去のブラウザのマークアップのサポートも含まれています。
これは古いコンテンツとの後方互換のためです。
現行の strict DTD は次のサイトから: www.w3.org/TR/html4/strict.dtd

3.2.4 DOM

HTML パースの出力ツリー、つまりパースツリーは DOM エレメントと属性のノードです。
DOM は Document Object Model の略です。
これは HTML ドキュメントのオブジェクト形で、HTML エレメントから JavaScript のような外部へのインターフェースとなっています。
ツリーのルートは "Document" オブジェクトです。

DOM はマークアップの記述とほぼ一対一の関係を持っています。たとえば、次のようなマークアップ:
<html>
  <body>
    <p>
      Hello World
    </p>
    <div> <img src="example.png"/></div>
  </body>
</html>
これは次のような DOM ツリーに翻訳されるでしょう:



図 8: 例のマークアップに対応する DOM ツリー

HTML のように、DOM も W3C によって仕様が決められています。www.w3.org/DOM/DOMTR を参照してみてください。
そこではドキュメントを操作する汎用的な仕様が定められています。
そこで指定されたモジュールは HTML に対応するエレメントを記述します。
HTML 定義は次のサイトにあります: www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html.

ツリーには DOM ノードがあるということは、そのツリーは DOM インターフェースを実装したエレメントによって構成されたであるということです。
実際のブラウザはブラウザ内部で用いられる他の属性も含んで実装されています。

3.2.5 パースアルゴリズム

前の章で見てきたように、HTML は標準的なトップダウンやボトムアップのパーサでパースすることはできません。

その理由は:
言語的に寛容な仕様です。
一般的な不正な HTML のサポートなど伝統的なエラーへの受容性を持っているという事実があります。
パース処理は挿し込み可能な処理です。通常、パース処理中にソースの変更はないものですが、HTML においては、script タグは document.write によってトークンを追加することができます。これはつまり、パース処理が実際に入力内容を変更しているということです。
標準的なパースのテクニックは使えないので、ブラウザは HTML のパースに特化したカスタムパーサを持つことになります。

パースのアルゴリズムは HTML5 にかなり詳細に記述されています。アルゴリズムは 2 つのステージからなっています。すなわち、トークン化とツリー生成です。

トークン化は字句解析を行い、入力をトークンに変換する処理です。HTML トークンとは開始タグ、終了タグ、属性名、属性値です。

トークン化処理はトークンを認識し、それをツリー生成処理に渡します。そして、次のトークンを得るために次の文字を読みます。これを入力の末尾までくりかえします。



図9 : HTML パース処理フロー (HTML5 仕様から)

3.2.6 トークン化アルゴリズム

トークン化アルゴリズムの出力は HTML トークンです。
トークン化アルゴリズムは、ステートマシンで表現されます。
各ステート(状態)は入力からひとつまたは複数の文字を読み込み、これらの文字にしたがってステートを更新しながら進みます。
上書きするステートは現在のステートと、ツリー構築のステートに依存します。
これはつまり、同じ文字を読み込んでも、現在のステートに依存して次のステートの状態が変わることを意味しています。
トークン化アルゴリズムは複雑すぎて、ここにすべてを書きだすことはできません。なので、原則を理解するために単純な例を見てみることにしましょう。

標準的な例 - 以下の HTML をトークン化する:
<html>
  <body>
    Hello world
  </body>
</html>
初期ステートは "データステート" です。
'<' 文字が現れると、ステートは "タグオープンステート" に変わります。
a から z までの文字がくると、"開始タグトークン" になり、ステートは "タグ名ステート" になります。
この状態は '>' 文字がくるまで続きます。各文字は新しいトークン名に追加されていきます。
この例では、生成されたトークンは html トークンです。

'>' に到達すると、カレントトークンが正式に発行され、ステートは "データステート" に戻ります。
<body> タグも同じ手順で処理されます。
これで、html と body タグが発行されました。
この時点で "データステート" に戻ってきています。'Hello world' の 'H' にくると文字トークンの発行が行われ、これが '</body>' の先頭の '<' に到達するまで行われます。
つまり、"Hello World" の各文字について、文字トークンの発行が行われるわけです。

さて、この時点では "タグオープンステート" にいます。
次の入力は '/' で、これによって終了タグのトークンの生成が行われ、ステートは "タグ名ステート" に移ります。
そして再び '>' がくるまで、このステートが続くわけです。
そこで新しい tag トークンが発行され、"データステート" に戻ってきます。
'</html>' についてもこれまでと同様に処理されます。


図10 : トークン化の入力例

3.2.7 ツリー構築アルゴリズム

パーサが生成されるのと同時に Document オブジェクトも生成されます。
ツリー構築ステージのあいだ、Document がルートの DOM ツリーは常に変更され、エレメントが追加されていきます。
この処理によって発行された各ノードはツリーコンストラクタに渡されます。
各トークンに対応して DOM エレメントが生成され、組み込まれていきます。
DOM ツリーに所属しないエレメントについても、所属先のないエレメント専用のスタックにつまれます。
このスタックは階層の不整合やタグの閉じ忘れを補完する際に使用されます。
このアルゴリズムはステートマシンとして記述可能です。
このステートを "挿入モード" と呼びます。

ツリー構築処理の例を見てみることにしましょう:
<html>
  <body>
    Hello world
  </body>
</html>
ツリー構築ステージの入力は、トークン化ステージから渡されてきた一連のトークンです。
最初のモードは "初期化" です。
"html" トークンを受け取ると "html 前" モードになり、この状態でトークンの再処理が行われます。
そこでは HTMLHtmlElement エレメントが生成され、ドキュメントルートオブジェクトに追加されます。

次の状態は "head 前" モードになります。ここでは "body" トークンを受け取ることになります。
"head" トークンがない状態ですが、自動で HTMLHeadElement が作られ "head" トークンがツリーに追加されます。

ついで "head 中" モードに移行し、その後、"head 後" になります。
body トークンは再処理され、HTMLBodyElement が作られて、ツリーに追加されます。そしてモードは "body 中" になります。

ついで、文字列 "Hello world" の文字トークン集合を受け取ります。
最初の1文字目で "テキスト" ノードが生成され、ツリーに追加されます。そして続く文字がこのノードに登録されていきます。

body 終タグがやってくると、"body 後" モードになります。
ここでは "body 後の後" モードに遷移する html 終タグを受けとることはありません。
ファイル終端トークンを受け取り、パースを終了します。


図11 : ツリー構築の html 例

3.2.8 パース終了時に起きること

このステージでは、ブラウザはドキュメントをインタラクティブに指定し、"遅延" モードになって、各種スクリプトをパースしはじめます。"遅延" モードは、ドキュメントをパースしたあとに実行されるモードです。
ドキュメントのステートは "完了" になり、load イベントが発行されます。

トークン化とツリー構築のアルゴリズムの全容は HTML5 の仕様で知ることができます。

3.2.9 ブラウザのエラー受容性

HTML ページでは ”構文エラー" は起こりません。ブラウザは間違ったコンテンツを修復し、処理をつづけます。

次のような例を見てみましょう:
<html>
  <mytag>
  </mytag>
  <div>
  <p>
  </div>
    Really lousy HTML
  </p>
</html>
これはひじょうに多くの違反をしています。"mytag" は標準のタグではありませんし、"p" と "div" の階層は間違っています、等々。しかし、ブラウザはこれを正確に表示し、あれこれ文句をいいません。
このような HTML 制作者の間違いを補正するコードはかなり大量にあります。

エラー処理は各ブラウザでかなり統一されていますが、これは現行の HTML の仕様の一部ではありません。
ブックマークや戻る、進むボタンは、多年にわたるブラウザの開発のなかで生み出されてきたものです。
多くのサイトで不正な HTML の構築が繰り返されているわけですが、各ブラウザは他のブラウザと適合したやり方でこれを修正しようと試みています。

HTML5 の仕様はこうした要求のいくつかについて定めています。
Webkit は HTML Parser クラスの先頭のコメントでこれについてよくまとめています。
パーサはトークン化された入力をパースしてドキュメントツリーを作ります。ドキュメントが構文的によくできていれば、パースはすんなりいきます。

けれども残念ながら、実際には構文的によろしくない HTML ドキュメントをハンドリングしなければなりません。つまりパーサはエラーに対して受容性を持たねばならないのです。

最低限、以下のエラー状態を考慮すべきです:
  1. 明示的に、いくつかのエレメントはいくつかのタグの内部にあることが禁止されています。この場合、このエレメントを許容するタグまでを閉じ、その後ろに追加します。
  2. エレメントを直接追加することは許可していません。これはドキュメントの制作者が間にタグを入れ忘れたか、タグが省略可能である場合におこります。
  3. この場合、インラインエレメントを含んだブロックエレメントを追加します。全インラインエレメントを閉じて、直近の上位のブロックエレメントの次に入れるわけです。
  4. これが役に立たない場合、エレメントの追加が可能になるまで閉じていくか、このタグ自体を無視します。

では実際に、Webkit のエラー受容性の具体例を見てみましょう:

<br> ではなく </br> 

いくつかのサイトでは </br> を <br> の代わりに使っています。IE と Firefox との互換性のため、Webkit はこれを <br> のように取り扱います。

次のコード:
if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
     reportError(MalformedBRError);
     t->beginTag = true;
}
註 - このエラー処理は内部的なもので、ユーザに見える状態にはならないでしょう。

浮き上がったテーブル

浮き上がったテーブルとは、別のテーブルの中に置かれていますが、そのテーブルのセルの中ではないようなテーブルです。
実際には次のようなものです:
<table>
    <table>
        <tr><td>inner table</td></tr>
    </table>
    <tr><td>outer table</td></tr>
</table>
Webkit はこの階層を変更し、二つの並列したテーブルにします:
<table>
    <tr><td>outer table</td></tr>
</table>
<table>
    <tr><td>inner table</td></tr>
</table>

対応するコード:
if (m_inStrayTableContent && localName == tableTag)
        popBlock(tableTag);
Webkit は現在のエレメント内容を管理するためにスタックを用いています。このコードでは、外側の table スタックのそとに内側のテーブルを pop しています。これでテーブルは兄弟関係になります。

階層化された form エレメント

ユーザが form の中に別の form をおいた場合はどうなるのでしょう。二番目の form は無視されます。

対応するコード:
if (!m_currentFormElement) {
        m_currentFormElement = new HTMLFormElement(formTag, m_document);
}
深すぎるタグの階層

コメントにはこうあります。
www.liceo.edu.mx は b タグを使って、1500 階層のネストを形成するサイトの一例です。同じ種類のタグについては、最大 20 個までのネストを許可します。
bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)
{
    unsigned i = 0;
    for (HTMLStackElem* curr = m_blockStack;
             i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
         curr = curr->next, i++) { }
    return i != cMaxRedundantTagDepth;
}
正しい場所にない html や body の終了タグ

再びコメントから:
完全に壊れた html のサポート。body タグは絶対にとじません。なぜなら、マヌケな WEB ページは実際にはドキュメントが終わってないのに、body 終了タグを書いています。あらゆるものが実際に閉じられるのは end() の呼び出し時です。
if (t->tagName == htmlTag || t->tagName == bodyTag )
        return;
なので WEB ページ製作者は、これまでみた Webkit のエラー受容性の例のような動きをみたいのでなければ、正しい HTML を書くようにしましょう。

3.3 CSS のパース

導入部のパースの基本原則について思い出してください。HTML のような代物でない CSS は文脈非依存の文法で、導入部で言及したようなパーサでパースすることができます。
実際に CSS の仕様は CSS の語彙と構文を規定しています。

いくつか例を見てみましょう:
CSS の語句的な文法 (語彙) はそれぞれ正規表現で定義されています。
comment   \/\*[^*]*\*+([^/*][^*]*\*+)*\/
num      [0-9]+|[0-9]*"."[0-9]+
nonascii [\200-\377]
nmstart  [_a-z]|{nonascii}|{escape}
nmchar   [_a-z0-9-]|{nonascii}|{escape}
name     {nmchar}+
ident    {nmstart}{nmchar}*
"ident" はクラス名と同様 "indentifier" の略です。"name" はエレメントのID です。"#" で参照します。

構文は BNF で記述されます。
ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
selector
  : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]? 
  ;
simple_selector
  : element_name [ HASH | class | attrib | pseudo ]*
  | [ HASH | class | attrib | pseudo ]+
  ;
class
  : '.' IDENT
  ;
element_name
  : IDENT | '*'
  ;
attrib
  : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
    [ IDENT | STRING ] S* ] ']'
  ;
pseudo
  : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
  ;
解説: ひとつの "ruleset" は以下のような構成のものです。
div.error , a.error {
  color:red;
  font-weight:bold;
}
div.error や a.error はセレクタです。中括弧の内側の部分は、このルールセットに適用されるルール群になります。この構造は以下の定義によって、公式に定められています。
ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
これの意味は、ひとつのルールセットは、ひとつのセレクタもしくは、カンマとスペース("S*" は空白の意です)によって区切られた複数のセレクタを持ち、中括弧の内部にひとつの定義、もしくはセミコロンで区切られた複数の定義を持ちます。定義とセレクタはその下の BNF によって規定されます。

3.3.1 Webkit の CSS パーサ

Webkit は Flext と Bison を使って、CSS 文法に則って記述されたファイルを読みこむパーサを自動生成します。
パーサの導入部を思い出してください。Bison はボトムアップ縮退型のパーサを生成します。
Firefox は自前のトップダウン型のパーサを持っています。
どちらも各 CSS ファイルはスタイルシートへと変換され、各オブジェクトは CSS のルールを持っています。
CSS ルールオブジェクトは、セレクタと定義のオブジェクト、それから他の CSS オブジェクトへの参照を持っています。

 図12: CSS のパース

3.4 スクリプトとスタイルシートの処理順序

3.4.1 スクリプト

WEB のモデルは同期的です。
WEB 制作者は、パーサが script タグに到達した場合、スクリプトがその場でパースされ、すぐに実行されることを期待しています。
スクリプトの処理が終わるまで、ドキュメントのパースは一時停止します。
スクリプトが外部にある場合、そのリソースをまずネットワークから取得してこなければなりません。これもまた同期的に行われます。リソースの取得が終わるまで、パースは中断します。
これが長年の基本モデルで、HTML 4 と HTML 5 の両方で規定された仕様です。
制作者はスクリプトの実行を遅延させるように記述することができますが、この場合ドキュメントのパースは中断せず、パース完了後に実行されることになります。
HTML5 は、スクリプトを非同期実行させるように記述するオプションを追加しました。これでスクリプトのパースと実行が別スレッドで行われます。

3.4.2 投機的パース

Webkit と Firefox はこの最適化を行います。
スクリプトの実行中、他のスレッドは残りのドキュメントをパースし、ほかにもリソースが見つかった場合、それをネットワークから取得しようとします。
このリソースの並列ロードは全体の速度を向上させます。

註 - 投機的なパースは、メインのパーサとは分離されており、DOM ツリーをいじりません。外部スクリプトやスタイルシートなどの外部リソースの参照をパースするだけです。

3.4.3 スタイルシート

一方、スタイルシートは異なるモデルを採用しています。
概念的にはスタイルシートは DOM ツリーを変更しないので、ドキュメントのパースを中断して結果を待つ理由は何もないように思えます。
ただひとつ問題があって、それはスクリプトからのスタイルの問い合わせがドキュメントのパースステージ中に起こりうるということです。
スタイルがロードされ、パース済みでないと、スクリプトは間違った答えを出してしまうので、明らかにこれは重大な問題です。
このようなケースはレアに思えますが、実際にはかなり頻繁におこります。
Firefox はスタイルシートがある場合、ロードとパースが完了するまで、すべてのスクリプトの実行を停止します。
Webkit はまだロードされていないスタイルシートによって影響をうけるであろうスタイル属性に実際にアクセスしようとするとブロックします。

第四章
描画ツリーの構築

DOM ツリーができあがったら、ブラウザはもうひとつのツリー、すなわち描画ツリーを構築しにかかります。
このツリーはそれが表示されるべき順序で並べられた表示要素からなっています。
すなわち、このツリーはドキュメントの視覚的な表現で、正しい順序で内容を描画していけるようにするためにあります。

Firefox は描画ツリー内の要素をフレームと呼んでいます。
Webkit は部分レンダラもしくは描画オブジェクトと呼んでいます。
レンダラは自分自身と自分の子供たちをどのようにレイアウトし塗り分けたらいいかを知っています。
Webkit の RenderObject クラスはレンダラのベースクラスですが、これは以下のように定義されます:
class RenderObject{
  virtual void layout();
  virtual void paint(PaintInfo);
  virtual void rect repaintRect();
  Node* node;  //the DOM node
  RenderStyle* style;  // the computed style
  RenderLayer* containgLayer; //the containing z-index layer
}
各レンダラは通常そのノードの CSS ボックス(これは CSS2 の仕様にあります)に対応する矩形エリアを表します。これは幅や高さ、位置のようなジオメトリ情報からなります。
ボックス型は関連づけられた "display" スタイル属性に影響されます。(スタイル処理の項を参照してください)
以下に Webkit の DOM ノードに対応して生成されるレンダラ型を表示属性にしたがって決定するためのコードをしめします。
RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
    Document* doc = node->document();
    RenderArena* arena = doc->renderArena();
    ...
    RenderObject* o = 0;

    switch (style->display()) {
        case NONE:
            break;
        case INLINE:
            o = new (arena) RenderInline(node);
            break;
        case BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case INLINE_BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case LIST_ITEM:
            o = new (arena) RenderListItem(node);
            break;
       ...
    }

    return o;
}
たとえば操作部やテーブルなどは特別のフレームを持ちますが、このようにエレメント型が考慮されています。
Webkit においてはエレメントは createRenderer メソッドをオーバーライドして、特定用途のレンダラを生成します。
レンダラはジオメトリ情報を含まないスタイルオブジェクトを指しています。

4.1 描画ツリーと DOM ツリーの関連づけ

レンダラは DOM エレメントに一対一で対応づけられるわけではありません。
表示されない DOM エレメントは、描画ツリーには登録されません。たとえば、"head" エレメントなどがそれにあたります。
display 属性が "none" になっているエレメントも描画ツリー中には含まれません。("hidden" の属性値のものはツリーに含まれます)

表示オブジェクトに対応する DOM エレメントをいくつか紹介しましょう。
これらは通常、単一の矩形で表現不可能な複雑な構造をもつエレメントです。
たとえば、"select" エレメントは 3 つのレンダラからなります。ひとつめは表示領域用で、もうひとつはドロップダウン時のリストで、最後のひとつはボタンです。
一行に収めるのには長すぎるのでテキストが複数の行に分割される場合も、新しい行が追加のレンダラとして登録されます。
壊れた HTML のレンダラの例というのもあります。
CSS の仕様ではインラインのエレメントは、ブロックのエレメントかインラインのエレメントのどちらか一方しか、その内部に持つことができません。
両者が混合している場合、無名のブロックレンダラが生成され、インラインエレメントをラップします。

いくつかの描画オブジェクトは DOM ノードに関連づけられていますが、ツリー中の同じ場所に配置されるわけではありません。
フロートと絶対位置のエレメントは、処理フローの外部に位置し、描画ツリーとは別の場所で管理され、実際の描画フレームに対してマッピングされます。
プレースホルダフレームはそれらが置かれるべき場所に置かれます。


図13 : 3.1 のDOM ツリーからできた描画ツリー。
"Viewport" は初期のブロック要素です。Webkit においてはこれは "RenderView" オブジェクトです。

4.2 ツリー構築の処理フロー

Firefox では表示は DOM 更新のリスナのひとつとして登録されています。
表示処理は FrameConstructor の生成に移譲され、このコンストラクタがスタイルを解決し (スタイル処理の項を参照してください)、フレームを生成します。

Webkit では、スタイルの解決とレンダラの生成の処理は "attachment" 処理と呼ばれています。
各 DOM ノードは "attach" メソッドを持っています。"attach" の処理は排他的で、ノードの DOM ツリーへの挿入は、新しいノードの "attach" メソッドを呼び出します。

html タグと body タグの処理は結局、描画ツリーをそのルートから構築する処理のことです。
ルート描画オブジェクトは CSS 指定の解決の呼び出しを行います。CSS の最上位のブロックには他のブロックがすべて含まれています。
その表示領域がビューポート、ブラウザのウィンドウ表示領域になります。

Firefox はこれを ViewPortFrame と呼んでおり、Webkit ではこれを RenderView と呼んでいます。
これがドキュメントが割り当てられている描画オブジェクトになります。
ツリーの残りの部分は、DOM ノードの挿入に合わせて構築されていきます。

では、処理モデルからみた CSS2 の仕様についてみてみましょう。

4.3 スタイル処理

描画ツリーの構築には、各描画オブジェクトの表示属性の計算が必要です。
これは、各エレメントのスタイル属性の計算によってなされます。

スタイルには、インラインスタイル要素や HTML に記述された表示属性 (たとえば bgcolor 値のようなものです)のように、いろいろな場所から集めてきたスタイルシートが含まれています。
ここからは CSS スタイル属性を適用という観点から書いてみたいと思います。

スタイルシートのおおもとは、ブラウザのデフォルトスタイルシート、WEB ページの制作者が提供するスタイルシート、そしてユーザのスタイルシート (ブラウザのユーザが適用するスタイルシート) です。
ブラウザはユーザに自分好みのスタイルを適用することを許可しています。たとえば Firefox では、Firefox Profile フォルダにスタイルシートを置くことで適用されるようになります。

スタイル処理にはいくつか難しい部分があります:
  1. スタイルデータは非常に大きな構造をしており、多数のスタイル属性を持っています。これはメモリ使用量の問題を生じさせます。
  2. 各エレメントに適合する規則を見つける処理は、最適化しないとパフォーマンス問題を引き起こします。全規則のリストをなめてマッチするものを見つけだすのは、かなり重い処理です。セレレクタは複雑な構造をしていますが、これによってマッチ処理は、マッチしないことが確実なパスとマッチを試す価値のあるパスとが明らかな状態で始めることができるようになっています。
  3. たとえば、このようなセレクタ:
     div div div div{
     ...
     }
    
  4. これは三階層の div タグの親をもった div に適用される規則です。つまり、この規則は、ひとつの div エレメントに対して適用されることを要求しています。それから、ツリーの上位階層の特定のパスを調べてみることを指定しています。ツリーの上位のノードをなめて二つの div しかないことがわかった場合は、この規則は適用されません。ツリーの他のパスを調べてみる必要があるでしょう。
  5. 実際の規則の適用には規則の階層を持った非常に複雑なカスケード規則が含まれています。

これらの問題にブラウザがどのように対処しているのか見てみることにしましょう:

4.3.1 スタイルデータの共有

Webkit のノードはスタイルオブジェクト (RenderStyle) を参照します。このオブジェクトは特定の条件下でノード間で共有されています。そのノードとは兄弟やいとこ関係にあるもので:
  1. エレメントは同じマウス状態 (たとえば他がそうでない場合には、自分自身も :hover になれない、など) で、
  2. すべて id を持っておらず、
  3. タグ名が一致し、
  4. クラス属性が一致し、
  5. 割り当てられた属性群が一意に定まり、
  6. リンクの状態が一致し、
  7. フォーカスの状態が一致し、
  8. 属性セレクタ、すなわちセレクタ内のどこかで属性セレクタを使用しているものによって影響されておらず、
  9. エレメント中にインラインスタイル属性がなく、
  10. 兄弟セレクタがひとつもないことが必要です。WebCore は単純に、兄弟セレクタを見つけるとグローバルなスイッチを on にして、それがあるうちはドキュメント全体でスタイル共有を中止します。これには + セレクタや :first-child :last-child などの指定が含まれます。

4.3.2 Firefox 規則ツリー

Firefox はスタイル処理のために二つの別ツリーをを持っています。規則ツリーとスタイルコンテキストツリーです。Webkit にもスタイルオブジェクトがありますが、こちらはスタイルコンテキストツリーのようなツリー中に保持されてはおらず、単に DOM ノードが関連するスタイルを参照しているだけです。
図14 : Firefox のスタイルコンテキストツリー (2.2)

スタイルコンテキストは最終的な値を持っています。
この値はマッチする規則を正しい順序で適用し、論理値から実際に適用される値への変換するためにあります。
たとえば、論理値がスクリーンに対するパーセントであった場合、計算が行われ、実際のピクセルへと変換されます。
規則ツリーの概念はじつに賢いもので、ノード間で値を共有し、演算の重複を回避します。それからメモリ消費量を抑制します。

すべてのマッチした規則はスタイルコンテキストツリー内に保持されます。あるパス中の最下層のノードはより高いプライオリティを持っています。ツリーは見つかったすべての規則のパスを持っています。
ルールの登録は遅延実行されます。スタイルコンテキストツリーは各ノードの開始時に計算されるのではなく、特定のノードのスタイルが計算される必要がある瞬間に計算され、パスがツリーに登録されます。

ツリーの各パスは辞書中の単語式に登録されていきます。これによってこの規則ツリーはすでに計算済みであるということになるのです:

コンテントツリー内の他のエレメントに規則をマッチさせ、その結果見つかったルールが B - E - I の順序を持つものであったとしましょう。すでに A - B - E - I - L というパスは計算されていて、すなわちこのパスはすでにツリー内に存在するので計算を省略可能なのです。

では、どのようにしてツリーが処理をけちっているのかみてみましょう。

構造体への分割

スタイルコンテキストはいくつかの構造体に分割されています。
これらの構造体は、境界線や色といった特定のカテゴリのスタイル情報をそれぞれ持っています。
構造体中の値は、継承されたものか、されていないものかのどちらかです。
継承されている値はそのエレメントで定義されているわけではない値で、親から継承してもってきています。
継承されていない値 (リセットと呼びますが) は、もし定義されていない場合はデフォルト値が使われます。

ツリーは全ツリー内で、計算済みの最終的な値を持った構造体をすべてキャッシュしています。
これは、最下層のノードがかならずしも対象の構造体の定義を用意するわけではないので、より上位で作られた構造体のキャッシュを使うわけです。

スタイルコンテキストの計算での規則ツリーの使用

ある特定のエレメントのスタイルコンテキストを計算する際には、まず規則ツリー中のパスを計算し、そのうちの存在するものを使用します。
それから選択したパス中のルールを、新しいスタイルコンテキストの構造体に適用します。
この処理をパス中の最下層のノード(これがすなわち最も優先順位が高いもので、通常はほぼ一意に定まるセレクタなのですが)から開始して、ツリーを巡回しながらさかのぼり、構造体の情報を補完していきます。
その規則ノード中に構造体の指定がない場合、ツリーの上側をなめてきちんと指定しているノードをさがすわけですが、この処理には最適化の余地がたくさんあります。そして、もっともよい最適化は、全構造体を共有してしまうことです。
これは最終的な値の計算を省略し、メモリを節約します。
部分的な定義しかない場合も、構造体がきちんと設定されるまでツリーをさかのぼります。

それぞれの構造体用の定義がまったく見つからない場合、この構造体は "inherited" タイプとなります。これはコンテキストツリー中の親の構造体を指しています。これも構造体の共有のひとつです。
もし構造体がリセットされた場合は、デフォルト値が用いられます。

完全にノードが特定されない状態に値が追加されると、実際の値に変換するための追加計算が必要になります。
この結果はツリー中のノードにキャッシュされ、その子供たちも用いられます。

あるエレメントがそれ自体が兄弟関係、もしくはツリー中の同じノードを指している場合、スタイルコンテキスト全体が共有されます。

例をひとつ見てみましょう: 次のような HTML を考えます。
<html>
  <body>
    <div class="err" id="div1">
      <p>
        this is a <span class="big"> big error </span>
        this is also a
        <span class="big"> very  big  error</span> error
      </p>
    </div>
    <div class="err" id="div2">another error</div>
  </body>
</html>
それから、以下のような規則:
 div {margin:5px;color:black}
 .err {color:red}
 .big {margin-top:3px}
 div span {margin-bottom:4px}
 #div1 {color:blue}
 #div 2 {color:green}
話を単純にするために、色とマージン、この 2 種類の構造体だけをきちんとセットしてみることにしましょう。
色構造体は 1 つのメンバ、つまり色しか持っていません。
マージン構造体は、4 方向の値を持っています。
規則ツリーの計算の結果、次のような結果を得るでしょう。(ノードはノード名付きで表示されています : ルールの # はノード名で指定するものです)

 図15 : 規則ツリー

コンテキストツリーはこのようになるでしょう。(ノード名 : それを指している規則ノード):


図16 : コンテキストツリー

HTML をパースし、二番目の <div> タグを取得します。
このノードのスタイルコンテキストを生成し、そのスタイル構造体をセットする必要があります。
マッチするルールを探して、実際に <div> にマッチするルール 1 2 6 を得ます。
これはつまり、エレメントから参照可能なツリー中に存在するパスがあるので、ここではただ規則 6 (規則ツリー中のノード F ) を追加するだけでいいことになります。
スタイルコンテキストをひとつ生成し、これをコンテキストツリーに挿入します。この新しいスタイルコンテキストは規則ツリー中のノード F を指しているわけです。

ついで、スタイル構造体を埋めていきましょう。
まずはマージン構造体を埋めようとします。
先程の規則ノード F はマージン構造体に何もセットしません。なので、ツリーをさかのぼり、これより前の追加で計算されたノードキャッシュを見つけてこれを使用します。
具体的には、マージンの規則を指定している最上位のノードである B になります。

色構造体の定義も得なければなりませんが、これにはキャッシュされた構造体は使えません。
なぜなら、色はひとつの値しかないので、他の値を埋めるためにツリーをさかのぼる必要はないからです。
最終的な値を計算し (文字列を RGB に変換する等々)、このノードの計算済みの構造体をキャッシュします

次いで、<span> エレメントを処理しましょう。これはだいぶ楽です。
複数の規則がマッチしますが、最終的には、前の span のように規則 G を指すようになります。
同じノードを指している兄弟関係にあるノードがあるので、全スタイルコンテキストは共有可能で、前の span のコンテキストを指すだけでよいわけです。

親から継承された規則を含んだ構造体の場合、キャッシュはコンテキストツリー上で済みます。(色の値は実際に継承されていますが、Firefox はこれをリセットし、規則ツリー上でキャッシュします)
たとえば、フォントを指定するルールを含む行を追加した場合:
p {font-family:Verdana;font size:10px;font-weight:bold} 
コンテキストツリー中でこの段落の子供である div エレメントは、自分の親と同じフォント構造体を共有可能です。
もちろん div を指定したフォント規則がなかった場合ですが。

Webkit では、規則ツリーがない場合、マッチした定義は 4 回探索されます。
一回目は important なしの高いプライオリティの値 (display など他の項目が依存するのではじめに適用されるべき値) が適用されます。
二回目は、important つきの高いプライオリティの値が、三回目に important なしの通常のプライオリティの値、最後に important つきの通常のプライオリティの値が適用されます。
すなわち、それぞれの値は、正しいカスケードの順序に従って処理してくなかで、複数回登場することになるわけです。
そして、最後のものが勝ち残ります。

ここまでの話をまとめると、スタイルオブジェクトの共有 (全体もしくは、そのなかの構造体のいくつか)は 1 と 3 の問題を解決します。
Firefox の規則ツリーは正しい順序で値を適用するのに役立ちます。

マッチングの簡略化のための規則操作

次のようなスタイル規則について考えてみましょう:
  1. 外部のスタイルシートもしくは style エレメントのどちらかにある CSS 規則
        p {color:blue}
    
  2. 次のようなインラインの style 属性
        <p style="color:blue" />
    
  3. HTML 表示属性 (スタイル規則に関連づけられてマッピングされたもの)
        <p bgcolor="blue" />
    
最後の二つは、エレメント中に style 属性があり、HTML 属性はこのエレメントをキーにしてマッピングされるので、簡単にマッチさせることができます。

前述の問題 #2 で述べたように、CSS 規則のマッチングはトリッキーなものになりえます。解決が難しくなる場合、規則はよりアクセスしやすい形に変形されます。

スタイルシートのパース後、そこに含まれていた規則をセレクタにおうじて、いくつかのハッシュマップに追加します。
すなわち、ID、クラス名、タグ名、それからこれらのどれでもない一般的なマッピングです。
セレクタが ID である場合、その規則は ID マップに追加され、クラスであった場合はクラスマップに追加される、といった感じです。
この操作はルールマッチをかなり簡単にします。
これによってすべての定義をなめる必要がなくなり、マップからエレメントに関連づけられた規則だけを抜き出すことができるようになります。
この最適化によってだいたい 95% 以上の規則が除去され、マッチング処理中でこれらの規則については考慮する必要がなくなります。(4.1)

以下のようなスタイル規則の例を見てみましょう:
p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}
最初の規則はクラスマップに追加されます。
2番目のものは ID マップへ、3番目のものはタグマップへ登録されます。

次の HTML の断片:
<p class="error">an error occurred </p>
<div id=" messageDiv">this is a message</div>
まず p エレメントの規則を見つけようとします。
クラスマップにはキー "error" が "p.error" の下に入っており、これが見つかります。
div エレメントは、ID マップとタグマップに関連する規則があります。
なので、残る作業はこれらのキーが展開された実際にマッチする規則を見つけだすことです。

たとえば、div の規則が以下のようだった場合、
table div {margin:5px}
キー (div) は右端のセレクタなので、タグマップからも展開されてくることになりますが、実際には今回の div エレメントは table の子孫でないので、この規則にはマッチしません。

Webkit と Firefox のこの処理は一緒です。


4.3.4 正しいカスケードの順序で規則を適用する

スタイルオブジェクトはすべての表示属性 (全 css 属性を含む、それより一般的な属性) に対応する値を持っています。
対象の値がマッチした規則にまったく定義されていなかった場合、いくつかについては親エレメントのスタイルオブジェクトから継承してくることができます。
そうでなければデフォルト値になります。

ひとつもしくはそれ以上の定義がある場合、問題が生じますが、カスケード順序はこのためにあります。

スタイルシートのカスケード順序

あるスタイル値の宣言が複数のスタイルシートにある、またはひとつのスタイルシート内に複数ある場合、
これの適用順序というのは、非常に重要です。
これが "cascade" 順序というもので、CSS2 の仕様に準拠しています。(下にいくほど高くなります)
  1. ブラウザの定義
  2. ユーザ標準の定義
  3. WEB 制作者の標準の定義
  4. WEB 制作者の important 定義
  5. ユーザの important 定義
ブラウザの定義はもっともどうでもいいもので、ユーザは important を指定した場合にのみ WEB 制作者の定義を上書きできます。
同じ位置の定義は、指定の厳密さ順でソートされ、さらにその中では記述順でソートされます
HTML 表示属性は CSS 定義から変換されます。
これらは順位の低い WEB 制作者の規則として取扱われます。

指定の厳密さ

セレクタの指定の厳密さは CSS2 の仕様で以下のように決められています:
  • 宣言がセレクタつきの規則からではなく style 属性からきている場合カウント 1 そうでなければ 0 (= a)
  • セレクタ中の ID の数をカウントする (= b)
  • セレクタ中の擬似クラスや他の属性の数をカウントする (= c)
  • セレクタ中のエレメント名の数と擬似エレメントの数をカウントする (= d)
この四つの値を a-b-c-d の順序で連結すると、指定の厳密さが求まります。

値の基数は、その値がもっとも大きな値になるものが使用されます。
たとえば a=14 ならばこれを 16 進数として使用できますが、 a=17 の場合、これを 10 進数として扱います。
後者の状況は、次のような場合に起こりえます: html body div div p ... (17 個のタグがセレクタにある。実際にはこれは非常に稀なケース)

いくつか例をあげます:
 *             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
 li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
 li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
 h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
 ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
 li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
 #x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
 style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */
ソートの規則

規則がマッチしたら、それらはカスケード規則にしたがって並べ替えられます。
Webkit はリストが小さい場合バブルソートを使用し、大きい場合はマージソートをします。
Webkit は > 演算子をオーバーライドすることで、これを実装しています。
static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
    int spec1 = r1.selector()->specificity();
    int spec2 = r2.selector()->specificity();
    return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2; 
}
4.4 段階的な処理

Webkit はすべてのトップレベルのスタイルシート (@imports を含む) のロード状態をフラグを使って管理しています。
スタイルを割り当てる際に、スタイルシートがまだ完全にロードされていない場合 (スタイルの割り当て状態はプレースホルダによってドキュメント中でマークされていきます)、ロード完了時に再計算されることになります。

第五章
レイアウト

レンダラが生成されツリーに追加されたときには、サイズと位置を持っていません。これはレイアウトかリフローのときに計算されます。

HTML はレイアウトモデルに基づいたフローを採用しています。これはつまりほとんどの場合、シングルパスでジオメトリを計算することができるということです。
”フロー中" の状態を抜けたエレメントは通常、"フロー中" になっていないエレメントのジオメトリに影響を与えません。これによりレイアウト処理はドキュメント中を "左から右、上から下へ" 進めていくことができます。
ただし、例外がいくつかあります。たとえば、HTML テーブルの解決はワンパスでは確定しません。(3.5)

座標系はルートフレームに原点があり、左上です。

レイアウトは再帰的な処理です。
ルートレンダラ(これは HTML ドキュメントの <html> エレメントに関連づけられています) から処理は始まります。
レイアウトはフレーム階層の部分もしくは全てを再帰的に巡回しながら、各レンダラが必要としているジオメトリ情報を計算していきます。
ルートレンダラの位置は (0, 0) です。ルートレンダラの範囲はビューポート (ブラウザウィンドウの可視部分) 全体です。

全レンダラには "layout" もしくは "reflow" メソッドを持っています。各レンダラは必要がある場合は、自身の子供に対して layout を呼び出します。

5.1 ダーティビットシステム

すべての小さな変更のために全体をレイアウトしなければならなくなるのを避けるため、ブラウザはダーティビット (dirty bit) システムを採用しています。
変更が入ったレンダラにはマークをして、その子供を "汚れた" 状態にします。これはレイアウトが必要です。

これには二つのフラグを用います。"汚れた" と "子供たちが汚れた" です。
子供たちが汚れた、というのは、自分自身としてはよいんだけれども、自身の子供のうちのひとつは少なくともレイアウトが必要だというフラグです。

5.2 グローバルでインクリメンタルなレイアウト

レイアウトは全レンダーツリーに対して発行されることがあります。これがグローバルレイアウトです。
これは次のようなときに起きます:
  1. フォントサイズの変更のような、すべてのレンダラに影響を与えるグローバルなスタイルが変更された。
  2. スクリーンのサイズが変更された。
レイアウトはインクリメンタルでもあります。汚れたレンダラだけがレイアウト処理されます (この結果、何らかのダメージが会った場合は、追加でレイアウトが要求される)
インクリメンタルレイアウトはレンダラが汚れたときに、随時 (非同期的に) 発行されます
たとえば、ネットワークから追加でコンテンツがやってきて DOM ツリーに登録された場合、新しいレンダラが描画ツリーに登録された場合などです。

図17 : インクリメンタルレイアウト - 汚れたレンダラとその子供たちだけがレイアウト処理されます (3.6)。

5.3 非同期レイアウトと同期レイアウト

インクリメンタルレイアウトは非同期的に実行されます。
Firefox はインクリメンタルレイアウト用の "reflow" コマンドをキューイングして、スケジューラがこれをバッチ実行していきます。
Webkit もインクリメンタルレイアウトを実行するタイマーを持っています。このときにツリーを巡回して汚れたレンダラをレイアウト処理します。
"offsightHieght" のようなスタイル情報を問いあわせるスクリプトはインクリメンタルレイアウトを同期的に呼び出すことができます。
グローバルレイアウトは通常、同期的に実行されます。
ときどきスクロール位置のような属性によって、レイアウトが初期レイアウトのあとのコールバック時に発行されることもあります。

5.4 最適化

ウィンドウのリサイズやレンダラの位置 (サイズではなく) の変更によってレイアウトが行われる場合、レンダラの大きさは再計算されずにキャッシュから取得されます。
いくつかのケースではサブツリーしか変更されないことがありますが、この場合ルートからレイアウトが行われるわけではありません。
これは変更がローカルで周囲に影響を及ぼさない場合、たとえばテキストがテキストフィールドに追加された場合などです (ただし、すべてのキーボード操作はルートからのレイアウトを開始させる可能性があります)。

5.5 レイアウト処理

レイアウト処理は通常、次のようなパターンをしています:
  1. 親のレンダラが自分自身の幅を決めます。
  2. 親からその子供たちを巡回して:
    1. 子供のレンダラを配置します ((x, y) をセットします)。
    2. それから必要なら子供のレイアウト処理を呼び出します (汚れていたり、グローバルレイアウト中だったり、その他のいくつかの理由があります) これによって、その子供の高さが計算されます。
  3. 親は子供の累積的な高さと自分自身の高さにマージンとバッティングを加えた高さを用います。これはこの親のさらに親によって使われます。
  4. 汚れたビットを false に設定します。

Firefox は "state" オブジェクト (nsHTMLReflowState) を引数にしてレイアウトを行います。
これは "reflow" で終わりますが、他のステートの場合は、親の幅が入ってきます。
Firefox のレイアウト処理の出力は metrics オブジェクト (nsHTMLReflowMetrics) です。これはレンダラの計算した高さが入っています。

5.6 幅の計算

レンダラの幅はコンテナブロックの幅、レンダラのスタイル "width" 値、マージンとボーダーに基づいて計算されます。
たとえば、以下の div の幅は:
<div style="width:30%"/>
Webkit では次のように計算されます (RenderBox クラスの calcWidth メソッド):

  • コンテナの幅はコンテナの availableWidth と 0 の大きなほうの値になります。availableWidth はこの場合 contentWidth で、これは次のように求められます:
        clientWidth() - paddingLeft() - paddingRight()
    
  • clientWidth と clientHeight は、ボーダーとスクロールバーを除いた、オブジェクト内部を表現しています。
  • エレメントの幅は "width" スタイル属性です。コンテナの幅のパーセント表記の場合は、絶対値が求められます。
  • この値には左右のボーダーとパディングが追加されています。

この段階では、これは適切な幅の計算に過ぎません。
次に幅の最小値と最大値が計算します。
適切な幅が最大幅よりも大きい場合、最大幅が採用されます。
最小幅よりも小さい場合 (改行不可能な最小単位) は最小値が用いられます。

これらの値はレイアウトの幅が変更される必要のないあいだキャッシュされます。

5.7 改行

レイアウトの中央に位置するレンダラは分割される必要があると判断されるときがあります。
そのときは一度処理を停止して、親に自分は分割される必要があることを伝えます。親は追加のレンダラを作成し、レイアウトを呼び出します。

第六章
ペインティング

ペインティングの段階に入ると、描画ツリーをなめて、各レンダーの "paint" メソッドが呼び出され、その内容がスクリーン上に表示されます。
ペインティングには UI インフラのコンポーネントが用いられます。

6.1 グローバルとインクリメンタル

レイアウトのように、ペインティングもグローバル (全ツリーがペイントされます) やインクリメンタルになりえます
インクリメンタルなペインティングは、ツリー全体に影響を与えずにいくつかのレンダラが変更された場合に起こります。
変更の入ったレンダラはスクリーン上の自分の矩形を無効にします。
これは OS に対して汚れた領域として見え、その結果 "paint" イベントが生成されます。
OS はこれを賢く処理し、いくつかの領域をひとつに合わせて表示します。
Chrome では、この過程はもう少し複雑で、というのも Chrome のレンダラーはメインプロセスとは別プロセスだからです。
Chrome はある程度 OS のふるまいをシミュレートします。
表示処理はこれらのイベントを待ち受けて、送られてきたメッセージを描画ルートに送ります。
メッセージに関連するレンダラが見つかるまでツリーをなめます。
レンダラが見つかると、それ自身と通常はその子供は再ペイントされます。

6.2 ペインティングの順序

CSS2 はペインティング処理の順序を定めています。
これは実際には、スタッキングコンテキスト中にスタックされたエレメントの順序です。
この末尾から先頭に向かってペイントされてゆくので、この順序がペインティングの順序になります。
ブロックレンダラがスタックに積まれる順序は:
  1. 背景色
  2. 背景イメージ
  3. ボーダー
  4. 子供たち
  5. アウトライン
6.3 Firefox の表示リスト

Firefox は描画ツリーからペイントされた矩形のために表示リストを作ります。
これには矩形に関連づけられたレンダラが、正しい描画順序 (レンダラの背景からはじまり、その次にボーダー、等々) で入っています。
この方式によって、全ての背景をペイントし、すべてのイメージをペイントし、それからすべてのボーダーを、という再ペイント処理の段取りのなかで、ツリーの巡回が一度だけでよくなります。

Firefox は他の不透明な要素の完全に下にあり、隠れたものになるであろうエレメントの登録を省略して最適化します。

6.4 Webkit の矩形ストレージ

再ペイント処理の前に、Webkit は古い矩形をビットマップとして保存しておきます。これにより、新しい矩形の描画は、古い矩形の描画との差分だけを行えば済むことになります。

第七章
動的な変更

ブラウザは、ある変更に対するレスポンスとして最小限のアクションを実行しようとします。
したがって、あるエレメントの色の変更からはそのエレメントだけの再ペイントが行われます。
そのエレメントの位置が変更された場合は、そのエレメントと、その子供、兄弟のレイアウトと再ペイントが行われます。

ひとつの DOM ノードの追加からはそのノードのレイアウトと再ペイントが行われます。
html エレメントのフォントサイズの変更といった大きな変更の場合は、キャッシュの無効化が行われ、ツリー全体に対して再レイアウトと再ペイントが行われます。

第八章
レンダリングエンジンのスレッド

レンダリングエンジンはシングルスレッドです。ネットワーク系の操作を除いたほとんど全てがシングルスレッド内で起こります。
Firefox と safari では、これがブラウザのメインスレッドです。Chrome は各タブのプロセスがメインスレッドです。
ネットワーク系の操作は、複数の並列なスレッド上で処理されます。
同時コネクション数は上限があります (通常 2 から 6 コネクションです。Firefox 3 では 6 個使っています)。

8.1 イベントループ

ブラウザのメインスレッドはイベントループです。これはプロセスが生きているあいだじゅう維持される無限ループです。
ループのなかでは、レイアウトやペイントといったイベントを待っており、それを実際に処理します。
これが Firefox のメインイベントループのコードです:
while (!mExiting)
    NS_ProcessNextEvent(thread);
第九章
CSS2 のビジュアルモデル

9.1 キャンバス

CSS2 の仕様には、キャンバスについて "フォーマットされた構造が描画されるスペース"、すなわちブラウザがコンテンツを描画する場所と記述されています。
キャンバスは三次元の各方向に無限の広がりを持っていますが、ブラウザはビューポートの各方向の初期幅を選択します。

www.w3.org/TR/CSS2/zindex.html によれば、キャンバスは別のキャンバス中にある場合透明で、そうでない場合、ブラウザが定義した色が与えられます。

9.2 CSS ボックスモデル

CSS ボックスモデルはドキュメントツリー中のエレメントに対して生成され、表示フォーマットモデルにしたがってレイアウトされる矩形のボックスについて記述しています。
各ボックスはコンテンツ領域 (テキストやイメージといった) とオプション的にその周辺のパディングやボーダー、マージン領域を持っています。
図18 : CSS2 ボックスモデル

各ノードは 0 から n 個のこうしたボックスを生成します。
すべてのエレメントは、生成されるボックスの型を決定する "display" 値を持っています。

例:
block - ブロックボックスを生成します。
inline - ひとつか複数のインラインボックスを生成します。
none - ボックスは生成されません。
デフォルトはインラインですが、ブラウザのスタイルシートが他のデフォルト値をセットします。たとえば、div エレメントのデフォルトのディスプレイはブロックです。
デフォルトスタイルシートは次のサイトにあります: www.w3.org/TR/CSS2/sample.html

9.3 配置のスキーム

3 つのスキームがあります:
  1. Normal - オブジェクトはドキュメント中の位置におうじて配置されます。これはつまり、描画ツリー中のそのエレメントの位置は、DOM ツリー中の位置のようで、ボックスタイプと大きさにしたがってレイアウトされます。
  2. Float - オブジェクトはまずノーマルと同じようにレイアウトされます、それから左右にかのうなかぎり移動します。
  3. Absolute - オブジェクトは描画ツリーに DOM ツリー中の位置とはまったく無関係に置かれます。
配置のスキームは "position" 値と "float" 属性によって与えられます。
  • static と relative は normal の処理が行われます。
  • absolute と fixed は absolute 配置が行われます。
static の配置では、位置無しの場合デフォルトの配置が用いられます。absolute のスキームでは、WEB 制作者が上下左右の配置を指定します。

ボックスがレイアウトされる方式は以下のものから決まります:
  1. ボックスタイプ
  2. ボックス座標
  3. 配置スキーム
  4. 外部情報 (画像の幅、高さやスクリーンのサイズなど)
9.4 ボックスタイプ

ブロックボックス: 自分自身がブラウザウィンドウ上に配置される矩形を持つブロックを形成します

図19 : ブロックボックス

インラインボックス: 自分自身のブロックを持ちませんが、自分を含むブロックの内部にあります。

 図20 : インラインボックス

ブロックは縦方向には別のブロックと揃えて配置されます。インラインは横方向に揃えられます。

 図21 : ブロックとインラインの整列

インラインボックスは行集合もしくはラインボックスの中に置かれます。
行集合はボックスが baseline に揃えられている場合、少なくとも最も背の高いボックスと同じ高さをしています。つまり、そのエレメントの底部は別のボックスの底部に揃えられています。
コンテナの幅が十分でない場合、インラインは複数の行にわたって配置されることになります。
これがひとつの段落中で通常起きていることです。

 図22 : 行集合

9.5 配置

9.5.1 relative

相対配置は通常の配置で、必要な差分に応じて位置がずらされます。

 図23 : 相対配置

9.5.2 float

フロートのボックスはその行の左端か右端に移動させられます。これは興味深い機能ですが、他のボックスもその周囲にういていきます。
次のような HTML:
<p>
  <img style="float:right" src="images/image.gif" width="100" height="100">
  Lorem ipsum dolor sit amet, consectetuer...
</p>
これは次のように表示されます:
 図24 : float

9.5.3 absolute と fixed

レイアウトはノーマルフローをまったく考慮されることなく決まります。
また、これらの要素はノーマルフローに関与しません。
ですが、その寸法はコンテナに関連づけられています。

fixed では、コンテナがビューポートになります。

 図25 : 固定配置

注意 - fixed ボックスはドキュメントがスクロールされても動きません。

9.6 レイアウトの階層

レイアウトの階層は CSS の z-index 値によって行われます。
すなわち、ボックスの第三次元、z 方向の位置として表現されます。

ボックスはスタッキングコンテキストと呼ばれるスタックに分割されます。
各スタックはより後ろのエレメントがまず描画され、そこにより前のエレメントが上に、よりユーザに近い位置に載ります。
重なっている場合、全社が隠されることになります。
すなわち、スタックは z-index 値に従って並べられているわけです。
z-index 値つきのボックスはローカルなスタックを構成し、これによりビューポートは外側のスタックを持つことになります。

例:
<style type="text/css">
      div { 
        position: absolute; 
        left: 2in; 
        top: 2in; 
      }
</style>
<p>   
    <div 
         style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
    </div>
    <div
         style="z-index: 1;background-color:green;width: 2in; height: 2in;">
    </div>
 </p>
結果は次の様になるでしょう:
図26 : z-index つきの配置

HTML 中の赤い div は緑のものより前に書かれていますが、これらは通常のフローよりも前にペイントされ、z-index 値が高ければ、ルートボックスが保持しているスタックよりも前方に来ます。

第十章
参考文献
  1. ブラウザアーキテクチャ
    1. Grosskurth, Alan. A Reference Architecture for Web Browsers (pdf)
    2. Gupta, Vineet. How Browsers Work - Part 1 - Architecture
  2. パース
    1. Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (aka the "Dragon book"), Addison-Wesley, 1986
    2. Rick Jelliffe. The Bold and the Beautiful: two new drafts for HTML 5.
  3. Firefox
    1. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers.
    2. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers (Google tech talk video)
    3. L. David Baron, Mozilla's Layout Engine
    4. L. David Baron, Mozilla Style System Documentation
    5. Chris Waterson, Notes on HTML Reflow
    6. Chris Waterson, Gecko Overview
    7. Alexander Larsson, The life of an HTML HTTP request
  4. Webkit
    1. David Hyatt, Implementing CSS(part 1)
    2. David Hyatt, An Overview of WebCore
    3. David Hyatt, WebCore Rendering
    4. David Hyatt, The FOUC Problem
  5. W3C 仕様
    1. HTML 4.01 Specification
    2. W3C HTML5 Specification
    3. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification
  6. ブラウザビルドの手引き
    1. Firefox. https://developer.mozilla.org/en/Build_Documentation
    2. Webkit. http://webkit.org/building/build.html


次の記事
« Prev Post
前の記事
Next Post »

2 コメント

Write コメント
匿名
AUTHOR
2011年9月6日 17:24 delete

s/JavaScripot/JavaScript/

Reply
avatar
kiyoto.s
AUTHOR
2011年9月6日 18:21 delete

%s/JavaScripot/JavaScript/g
1 item matched...fixed...

Reply
avatar
Related Posts Plugin for WordPress, Blogger...