.. Translation of "How Browsers Work" documentation master file, created by
sphinx-quickstart on Fri Aug 19 00:54:08 2011.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
How Browsers Work - Behind the Scenes of Modern Web Browsers
==============================================================
この翻訳について
--------------------------------------------------------------
この文章は `HTML5 Rocks `_ で公開されている `How Browsers Work: Behind the Scenes of Modern Web Browsers `_ を非公式に和訳したものです. 内容の正確性は保証しません. ライセンスは原文と同じく, 文章は `Creative Commons Attribution 3.0 License `_ , サンプルコードは `Apache 2.0 License `_ です. フィードバックは `Issue への登録 `_ , あるいは `Kosei Moriyama `_ (`@cou929 `_ または cou929 at gmail.com) へ直接お願いします. GitHub に `この和訳のリポジトリ `_ があります.
以下の Preface でも言及されていますが, この文章は `Tali Garsiel `_ さんが自身のサイトで公開していたものを `Paul Irish `_ さんが "より多くの人の目に届けるため" に HTML5 Rocks で再公開したものです. すばらしいドキュメントを書いてくれた Tali さん, 及びその文章を知るきっかけを提供してくれた Paul さんに感謝します.
Author, Editor
--------------------------------------------------------------
* Tali Garsiel (author) - Developer, Incapsula,
* Paul Irish (editor) - Developer Relations, Google
Updated at (original document)
--------------------------------------------------------------
August 16, 2011
Preface
--------------------------------------------------------------
この WebKit と Gecko の内部動作についての包括的な入門書は, イスラエルの開発者 Tali Garsiel の多大な調査によるものです. 彼女は数年にわたりブラウザの内部に関する多くの文章を読み (詳しくは Resources を参照), またブラウザのコードを読むことにも多くの時間を費やしました. 彼女はこう書いています:
IE が 90% のシェアを占めていたの時代, 多くのことができませんでしたがブラウザは "ブラックボックス" と考えられていました. しかし現在はオープンソースのブラウザが `過半数のシェアを占めているので `_ ブラウザの中身を覗くには良いタイミングです. ただし中身は何百万行の C++ コードでした…
彼女のこの調査は `彼女のサイト `_ で公開されています. しかし私達はこの文章はより多くの人に読まれるべきと考え, `ここ `_ に再公開します.
Web 開発者とって **ブラウザの内部動作を知ることは, よりよい判断をすること・開発のベストプラクティスの背後にある理由を知る手助けになります**. この文章は長文になるので, より深掘りして読むことをお勧めします. そうすることで最後によかったと思えることを私たちは保証しましょう.
*Paul Irish, Chrome Developer Relations*
Introducion
--------------------------------------------------------------
ブラウザは最も広く使われているソフトウエアです. この入門書ではブラウザが裏でどのように動いているかを説明します. 私たちがアドレスバーに ``google.com`` とタイプしてから Google のページが表示されるまでに何が起こっているかを見ていきましょう.
The browsers we will talk about
**************************************************************
今日では IE, Firefox, Safari, Chrome, Opera という5つのメジャーブラウザがあります. ここではオープンソースのブラウザ - Firefox, Chrome, Safari (一部はオープンソースです) - を取り上げます. `StatCounter browser statistics `_ によると, 現在 (2011年8月) では Firefox, Safari, Chrome のシェアを合計すると 60% に達します. 今ではオープンソースのブラウザがブラウザビジネスの大きな部分を占めています.
The browser's main functionality
**************************************************************
ブラウザの主要機能はユーザが選択した Web のリソースを表示させることです. サーバにリクエストを出し, それをブラウザウィンドウに表示させます. リソースのフォーマットは通常は HTML ですが pdf や画像にも対応しています. リソースの場所はユーザによって URI (Uniform resource Identifier) で指定されます.
ブラウザがどのように HTML を解釈し表示するかは HTML と CSS ファイルで指定されています. その仕様は **W3C** (World Wide Web Consortium) によってメンテナンスされています. W3C は Web の標準化団体です.
数年にわたり各ブラウザは標準の全てに準拠せず, 独自の拡張を進めてきました. その結果, Web サイト制作者にとっては深刻なブラウザ互換の問題が引き起こされました. 今日では多くのブラウザがより仕様に準拠するようになっています.
各ブラウザのユーザインタフェースは多くの部分で共通しています. 代表的なものは以下です:
* URI を入力するアドレスバー
* 戻る・進むボタン
* ブックマーク機能
* 再読み込み・読み込み停止ボタン
* ホームボタン
奇妙なことにこれらのユーザインタフェースは仕様で定められているわけではありません. これらは長年のブラウザの歴史の中で互いに洗練されてきたグッドプラクティスです. HTML5 ではブラウザが持たなければいけない UI 要素は定義していませんが, アドレスバー・ステータスバー・ツールバーなど, 一般的な要素をリストアップしています. もちろん, Firefox のダウンロードマネージャのようにブラウザ特有の要素もあります.
The browser's high level structure
**************************************************************
ブラウザの主要コンポーネントは以下です [#ref1.1]_
1. **ユーザインタフェース** - アドレスバー, 戻る・進むボタン, ブックマークメニューなど, メインウィンドウに表示されるページ以外の部分.
2. **ブラウザエンジン** - UI とレンダリングエンジン間のアクションを制御するもの
3. **レンダリングエンジン** - リクエストしたコンテンツを表示させるもの. 例えばコンテンツが HTML の場合, HTML と CSS をパースし, パースしたコンテンツをスクリーンに表示させることに責任を持つ
4. **ネットワーク** - HTTP リクエストのようなネットワーク通信を行う. プラットフォーム非依存で, その差異を吸収している.
5. **UI バックグラウンド** - コンボボックスやウィンドウなど基本的な UI 要素. プラットフォーム非依存のインタフェースを提供する. 背後では OS の UI メソッドを使用している.
6. **JavaScript インタプリタ** - JavaScript をパースし実行する.
7. **データストレージ** - 永続的なレイヤー. クッキーなど, ある種のデータをハードディスクに保存する. HTML5 では *Web Database* など完全な (かつ軽量な) DB がブラウザで提供される.
.. figure:: _static/layers.png
:alt: Browser main components
Figure 1: Browser main components
Chrome は他のブラウザと異なり, タブごとに別のレンダリングエンジンのインスタンスを持ちます. つまりタブごとに別プロセスを立ち上げています.
The rendering engine
--------------------------------------------------------------
レンダリングエンジンの役割は…レンダリングです. つまりリクエストしたコンテンツをブラウザのスクリーンに表示させることです.
デフォルトではレンダリングエンジンは HTML, XML, 画像に対応しています. プラグイン (またはブラウザエクステンション) を入れることで PDF など別のフォーマットのデータも表示できます. しかしこの文章では主要なユースケースである CSS によってフォーマットされた HTML と画像のレンダリングについて扱います.
Rendering engines
**************************************************************
今回私たちが参考にする Firefox, Chrome, Safari は2つのレンダリングエンジンの上に構築されています. Firefox は Mozilla 製の **Gecko** を, Safari と Chrome は **WebKit** を使用しています.
WebKit はオープンソースのレンダリングエンジンです. もとは Linux で動作するように始まったものを Apple が Mac と Windows に対応するよう改良しました. 詳細は `webkit.org `_ を参照してください.
The main flow
**************************************************************
レンダリングエンジンはネットワークレイヤーからリクエストしたコンテンツを受け取ります. 通常 8K のチャンクです.
その後, 通常は次のようなフローを経ます.
.. figure:: _static/flow.png
:alt: Rendering engine basic flow
Figure 2: Rendering engine basic flow
レンダリングエンジンは HTML をパースしタグを **コンテントツリー** の DOM ノードに変換します. その後外部 CSS ファイルやスタイル要素の CSS をパースします. スタイルの情報が取り込まれ, **レンダーツリー** という別の木が作成されます.
レンダーツリーは色や大きさといった属性値をもつ矩形を持っています. これらの矩形はスクリーンに表示される正しい並びで配置されています.
レンダーツリーができたあとはレイアウトプロセスに移行します. このプロセスでは各要素がスクリーン上のどの位置に表示されるかが決まります. 次は描画 (Painting) です. レンダーツリーが走査され, 各ノードが UI バックエンドレイヤーによって描画されます.
重要なのはこれらのプロセスが漸進的に実行されるということです. よりよいユーザ体験のためには, よりはやくコンテンツを表示させることが重要です. そのためすべての HTML がパースされるのを待たずにレンダーツリーを作り始めます. ネットワークから残りのコンテンツを受信している間に, 受信済みのコンテンツをパース・表示していきます.
gradual
**************************************************************
.. figure:: _static/webkitflow.png
:alt: Webkit main flow
Figure 3: Webkit main flow
.. figure:: _static/image008.jpg
:alt: Figure : Mozilla's Gecko rendering engine main flow
Figure 4: Mozilla's Gecko rendering engine main flow [#ref3.6]_
図3, 4 より WebKit と Gecko は違う用語をつかっていますが, 主な流れは大体同じということがわかります.
Gecko ではビジュアル要素のツリーを *Flame tree* と呼んでいます. 各ノードはフレームです. WebKit では *Render tree* で, 各ノードは *Render Object* です. WebKit ではその要素の配置を行うことは *Layout* と呼ばれますが, Gecko では *Reflow* です. DOM ノードとビジュアル要素をつなげレンダーツリーを作る過程は, WebKit では *Attachment* と呼ばれます. また Gecko は HTML と DOM ツリーの間に *Content sink* という, DOM 要素を作る別のレイヤーを持っています. 次章以降ではこれらのフローをそれぞれ見ていきます.
Parsing and DOM tree construction
--------------------------------------------------------------
Parsing - general
**************************************************************
パース (構文解析) はレンダリングエンジンのなかでも特に重要な部分なので, より深く見ていきましょう. まずはパースについての簡単なイントロダクションから始めます.
文章をパースするとは, 文章を意味のある構造に変換するということを意味します. "意味のある" とはコードから理解・利用できるという意味です. パースされた結果は通常文章の構造を表す木になります. この木構造はパースツリー・シンタックスツリー (構文木) と呼ばれます.
例として ``2 + 3 - 1`` という式をパースすると次のような木が得られます.
.. figure:: _static/image009.png
:alt: mathematical expression tree node
Figure 5: mathematical expression tree node
Grammars
##############################################################
パースはその対象の文章が従う構文ルールに基づいて行われます. パースできるフォーマットは必ず **語彙** (*vocabulary*) と **構文ルール** (*syntax rule*) から成る文法で記述できる必要があります. これは **文脈自由文法** (*Context free grammer*) と呼ばれるものです. 例えば自然言語はこのように文法を記述できないので, ここで述べるテクニックではパースできません.
Parser - Lexer combination
##############################################################
パースは字句解析と構文解析という2つのサブプロセスに分割できます.
字句解析は入力をトークンに分割するプロセスです. トークンとはその言語の語彙, つまりその言語の valid なかたまりのことです. 自然言語で例えるとその言語の辞書に載っている単語ということになります.
構文解析はその言語の構文ルールを適用するプロセスです.
パーサーは通常2つのコンポーネントに分割できます. 字句解析を行う **lexer** (あるいは tokenizer) と, 構文ルールに基づきパースツリーを作成する **parser** です. lexer は空白や改行などの関係のない文字を除くことも行います.
.. figure:: _static/image011.png
:alt: from source document to parse trees
Figure 6: from source document to parse trees
パースのプロセスは反復的です. parser は lexer から新たなトークンを受け取り, それを構文ルールにマッチさせます. もしいずれかのルールにマッチしたら, そのトークンに紐付くノードはパースツリーに追加され, parser は次のトークンを lexer から受け取ります.
もしルールにマッチしなかった場合, parser はそのトークンを内部的に保持し, 保持しているすべてのトークンがマッチするようなルールが見つかるまで繰り返します. もし最後までどのルールにもマッチしなかった場合例外が投げられます. つまりドキュメントが valid でなく, シンタックスエラーが起こったということです.
Translation
###############################################################
多くの場合パースツリーが最終目的ではありません. パースはトランスレーション, つまり入力されたドキュメントを別のフォーマットに変換するために使われます. コンパイルはその例です. コンパイラはソースコードをパースし, それを機械後に変換します.
.. figure:: _static/image013.png
:alt: compilation flow
Figure 7: compilation flow
Parsing example
###############################################################
図5 では数式からパースツリーを作成しました. ここではシンプルな数学言語を定義してパースのプロセスを見てみましょう.
まず語彙として, 私たちの言語は整数とプラス・マイナス記号を扱います.
言語の構文は:
1. 言語の構文は expressions, terms, operations から成ります
2. expression はいくつでも使えます
3. expression は "term opeation term" という形式です
4. operations は プラストークンかマイナストークンのいずれかです
5. term は整数トークン, または expression です
ここで ``2 + 3 - 1`` という式を分析してみましょう.
最初にルールにマッチするのは ``2`` という文字です. ルール 5 によりこれは term となります. 次にマッチするのは ``2 + 3`` です. これは3番目のルールにマッチします. 次にマッチするのはインプットの最後で ``2 + 3 - 1`` です. ``2 + 3`` が term であることはすでにわかっているので, これも "term operation term" のフォーマットになっていることがわかります. そして, 例えば ``2 + +`` というインプットはどのルールにもマッチしないので invalid な入力ということになります.
Formal definitions for vocabulary and syntax
###############################################################
語彙は通常 `正規表現 `_ で表されます.
例えば上記の私たちの言語の語彙は次のようになります.::
INTEGER :0|[1-9][0-9]*
PLUS : +
MINUS: -
このように INTEGER は正規表現で表されています.
構文は通常 `BNF `_ と呼ばれる記法で定義されます. 私たちの言語の場合は以下です.::
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression
文脈自由文法に従う文章は通常のパーサーでパースできると述べました. 文脈自由文法の直感的な説明は, BNF で完全に記述できる文法ということです. より形式的な定義は `Wikipedia の Context-free grammer の項 `_ を参照してください.
Types of parsers
###############################################################
パーサーには一般的にトップダウンパーサ・ボトムアップパーサの二種類があります. 直感的に説明すると, トップダウンパーサは文法のハイレベルな構造から見ていき, そのうちのどれかにマッチさせていきます. ボトムアップパーサは入力データからスタートし徐々に文法をローレベルからハイレベルへ当てはめていきます.
上記の例を二種類のパーサに当てはめてみましょう.
トップダウンパーサは高いレベルのルールからスタートします. まず ``2 + 3`` が expression としてマッチし, 次に ``2 + 3 - 1`` がマッチします. (実際に expression がマッチするのは別のルールですが, スタート地点は最高レベルのルールからです)
ボトムアップパーサはルールにマッチするまで入力を読み取り, マッチした入力をルールで置き換えます. これを入力の最後まで続けます. マッチ途中の expression はパーサースタックに格納されます.
===================== ==============
Stack Input
===================== ==============
[empty] ``2 + 3 - 1``
--------------------- --------------
term ``+ 3 - 1``
--------------------- --------------
term operation ``3 - 1``
--------------------- --------------
expression ``- 1``
--------------------- --------------
expression operation ``1``
--------------------- --------------
expression
===================== ==============
このタイプのボトムアップパーサは *Shift-reduce parser* とも呼ばれます. 入力が右にシフトし (最初ポインタが入力の先頭を指していて, 右に動いていくとイメージしてください) 構文ルールによって徐々に減っていくことからです.
Generating parsers automatically
###############################################################
パーサジェネレータというパーサを生成してくれるツールがあります. 自分の言語の文法 (語彙と構文) を渡すとパーサを作成してくれます. パーサをいちから作るには構文解析に対する深い理解が必要で, パーサーの最適化も簡単ではありません. よってパーサジェネレータは非常に有用なツールです.
WebKit は有名なパーサジェネレータである `Flex `_ (lexer) と `Bison `_ (パーサ生成) を使用しています. (Lex and Yacc という名前で聞いたことがある人もいるかも知れません). Flex の入力はトークンを定義する正規表現, Bison の入力は BNF です.
HTML Parser
****************************************************************
HTML パーサの仕事は HTML マークアップをパースしパースツリーを作ることです.
The HTML grammar definition
###############################################################
HTML の語彙と構文は W3C によって定義されています. 現在のバージョンは HTML4 で HTML5 の策定が進行中です.
Not a context free grammar
###############################################################
前節のパーサーのイントロダクションではグラマーは BNF など形式的なフォーマットで表現できると述べました.
しかしこれは HTML には当てはまりません (しかしただ単に趣味でパーサーについて説明したわけではありません. CSS と JavaScript は前述の方法でパースされています). HTML は文脈自由文法では簡単に表現できないからです.
DTD (Document Type Definition) と呼ばれる HTML の形式的な定義があります. しかしこれは文脈自由文法ではありません.
HTML は XML と似ているので, これがおかしなことに思えるかもしれません. XML のパーサはたくさんあります. それにXML のバリエーションの1つである XHTML もあるくらいです. では XML と HTML の大きな違いとは何なのでしょうか.
違いは HTML が "寛容" であることです. 開きタグ, 閉じタグが抜けていても暗黙に補完してくれたりします. XML の固く厳しい構文に比べ, HTML は Soft です.
この小さな違いは 2 つの大きく違う世界を生み出しています. 片面はこのことによって HTML がここまで広く使用されることになったということです. HTML はミスに寛容で Web サイト制作者はとても簡単に Web サイトを作成できます. 一方で文法を形式的に記述することが非常に難しくなっています. HTML をパースするのは簡単ではなく, 通常のパーサや XML パーサをそのまま適用することはできません.
HTML DTD
###############################################################
HTML は DTD 形式で定義されています. これは `SGML `_ ファミリーを定義するのに使われるフォーマットです. このフォーマットではすべての許可される要素とその属性, 及びヒエラルキーが定義されています. 前述のように DTD は文脈自由文法を形成しません.
DTD にはいくつかのバージョンがあります. strict モードは単独で標準に準拠しますが, 他に過去のブラウザで使われていたマークアップを含むものもあります. これは古いコンテンツの後方互換性のためです. 現在の strict な DTD はこちらです `www.w3.org/TR/html4/strict.dtd `_ .
DOM
###############################################################
HTML をパースすると **パースツリー** という木構造のデータが出力されます. このツリーのノードは DOM 要素とその属性です. DOM とは Document Object Model の略です. DOM は HTML ドキュメントのオブジェクト表現で JavaScript などの外部から HTML 要素を操作するためのインタフェースです. DOM ツリーのルートは `Document `_ オブジェクトです.
DOM はマークアップとほぼ 1 対 1 の関係になります. 例えば以下のマークアップは
.. code-block:: html
Hello World
このような DOM ツリーになります.
.. figure:: _static/image015.png
:alt: DOM tree of the example markup
Figure 8: DOM tree of the example markup
HTMLと同様に DOM も W3C で仕様が策定されています. ``_ を参照してください. これは HTML に限らず一般的な文章を操作するための仕様です. HTML 特有の仕様は ``_ です.
この文章で私が DOM ノードを含むツリーのことを言う時は, その要素が DOM インタフェースのいずれかの実装である木のことを意味します. ブラウザはブラウザ内部で使う属性を含んだ実装をしています.
The parsing algorithm
###############################################################
前述のように HTML はトップダウンパーサ・ボトムアップパーサではパースができません. その理由は,
1. 言語の寛容な特性のため
2. invalid な HTML をサポートするためにエラーに対して寛大にならないといけないため
3. 再入可能なパースプロセスのため. 通常パースが完了するまで入力は変化しませんが, 例えば script 要素に ``document.write`` がある場合など, パース中にも入力が変化します.
このように通常のテクニックが使えないので, ブラウザは HTML をパースするのにカスタムパーサを用いています.
`パースのアルゴリズムは HTLM5 の仕様で詳しく説明されています `_ . アルゴリズムは *tokenization*, *tree construction* の2つにわかれます.
tokenization では字句解析を行い入力をトークンに分割します. HTML のトークンは開始タグ, 終了タグ, 属性名, 属性値などです.
tokenizer は新たなトークンを認識し次第それを tree constructer に渡し次の入力を読み込んでいきます. これを入力が終わるまで続けます.
.. figure:: _static/image017.png
:alt: HTML parsing flow (taken from HTML5 spec)
Figure 9: HTML parsing flow (taken from HTML5 spec)
The tokenization algorithm
###############################################################
アルゴリズムの出力は HTML トークンです. アルゴリズムはステートマシンとして表現できます. 各状態は入力の 1 つ以上の文字を消費し, その文字に応じて次の状態へ遷移します. 遷移のルールはその時のトークンの状態と tree constructer の状態によって決まります. つまり同じトークンが来ても状態に応じて違う結果になりうるということです. アルゴリズム全体を扱うのは非常に複雑なので, 原理を理解しやすいようシンプルな例を見てみましょう.
次の HTML を tokenize すると考えてください.
.. code-block:: html
Hello world
最初の状態は *Date state* です. ``<`` に達したとき状態は *Tag open state* に変わります. ``a-z`` の文字を消費し *Start tag token* を作ります. 状態は *Tag name state* に変わります. ``>`` に達するまではそのままの状態で, その間の文字はトークン名となります. この場合は ``html`` トークンです.
``>`` に到達したらその時のトークンが発行され状態は *Date state* に戻ります. ```` タグも同様に処理されます. Hello の ``H`` 文字から ```` の ``<`` まではキャラクタートークンです.
この段階で *Tag open state* に戻っています. ``/`` に達したときに *End tag token* を作成し *Tag name state* に遷移します. そしてまた ``>`` に達するまでこの状態にとどまります. そしてトークンを発行し *Data state* に戻ります. ``