2012年11月7日水曜日

ESウェブブラウザ通信 - インクリメンタル リフロー #2

現在インクリメンタル リフローの実装まっ最中ということで、前回から少し時間があいてしまいました。今回は前回端折ってしまったところもあったので、前段をすこし掘り下げてから、インクリメンタル リフローの処理の続きについてまとめていきます。

ドキュメントとビューとウィンドウ


ESウェブブラウザ内部は大きく分けると、
  1. DOMツリーを保持しているドキュメント
  2. 各DOM要素に対応するCSSのスタイルとレンダーツリーを保持しているビュー
  3. ドキュメントとビューを管理しているウィンドウ
の3つの部品で構成されています。感覚としてはMVCモデルに近いものがありますが、MVCモデル全般について話しだすと議論が飛びやすくなりそうな気がするので、ここでは基本線として:
The interesting thing to me is that the DOM is what's meant to be the model originally, as far as I can tell, with the CSS presentation being the view.... - Boris Zbarsky
ということで進めていきます。DOMがモデルで、CSSがビュー、というわけです。

現在はobsoleteになっていますが、歴史的にはW3Cの仕様 "Document Object Model (DOM) Level 2 Views Specification" の中で、ドキュメントに対応するビューの抽象インターフェイスとしてAbstractViewインターフェイスが定義されて、CSSによるAbstractViewの実現が、 "Document Object Model CSS" の中で、ViewCSSインターフェイスとして規定されていました。

これらを図にしたものが下図になります:

Document, ViewCSS, and Window

最新の仕様ではViewCSSは独立したインターフェイスとしてはもう規定されていなくて、CSSOMの中でWindowインターフェイスの拡張として定義されています。そういう流れがあるので、最初に紹介したBorisさんの含みのあるコメントにつながっていくのだろうと思います。

そういうわけで仕様書の中からは消えてしまったViewCSSですが、ESウェブブラウザの実装にあたっては独立したクラスViewCSSImpとして残っています。そしてブラウザ内部でのこの3つのオブジェクトのインターラクションは依然としてMVCモデル的な動きになっています:
  • ドキュメント内で要素を追加・変更・削除したりすると、Documentはミューテーション イベントを発行します。ESウェブブラウザはこのミューテーション イベントをViewCSSでも拾うようにしています。ミューテーション イベントを通知されたら、ViewCSSはレンダー ツリーを構成しなおして、Windowに画面の再描画が可能なことを通知します。
  •  ウィンドウ内をマウスでクリックすると、WindowはViewCSSからマウス カーソルの位置にあるドキュメント要素を問い合わせて、マウス イベントをドキュメント内のその要素に向けてディスパッチします。

レイアウト処理の流れ


前回説明したようなViewCSS内部でのレイアウト処理の流れを図にしたものが下図になります:

CSSのレンダリング ステージ - (右側は対応するViewCSSImpクラスのメソッド名)

5月のこのブログで紹介した図からは前回説明した"block construction"ステージが増えています。また:hoverの処理の高速化のために追加した"partial style recalculation"ステージが削除されています。これはインクリメンタル リフローが入ると、どのステージも更新が必要な部分だけを更新することができるようになっているためです。

hoverする要素が変わった場合には、r3096以降からは、hover効果が有効になった要素と無効になった要素についてstyle recalculationまで戻って処理をします。それでもインクリメンタルに必要な部分だけで処理が進んでいくので、単に文字の色だけを変えるということであれば不要なblock constructionやreflowはスキップされて処理時間はほとんど変わりません。

インクリメンタル リフロー (続き)


前回、ViewCSSが考慮しないといけないミューテーション イベントには以下のものがあることを説明しました:
  1. テキスト ノードの内容が変更された
  2. テキスト ノードが追加・削除された
  3. DOM要素が追加・削除された
  4. DOM要素の属性が変更された
  5. DOM要素のスタイルのプロパティの値が変更された
1.から3.の場合の対応については前回説明した通りで、今回は4.から説明していきます。

4.の場合は、要素にマッチするIDセレクタ、属性セレクタ、およびクラス セレクタが変わる可能性があります()。r3092で、その要素についてはセレクタ マッチングからやり直すようにしています。属性の変更を頻繁に使うということであればスタイル再計算からのやり直しで対応するような実装も考えられそうですが、いまとのころそこまでは、という理解です。

5.の場合は、 まず変更を加えた要素に対応するスタイルの再計算を実行します。その結果、値が変化したプロパティーの種類によって、ブロックの再組み立てが必要になる場合(display, float, positionなど)、リフローが必要になる場合(width, heightなど)、リペイントが必要になる場合(colorなど)に分類して処理を進めていきます。r3039以降で実装が進んでいます。

なお、位置づけされた要素のz-indexの変更にともなうスタッキング コンテキストの順番の入れ替えは、r3088以降でスタイル再計算中に処理するようにしています。

またテーブルについてはレイアウトの考え方がまったく異なるのでいつものように別処理で対応を進めています。現在はr3071でセル内部でリフローが発生しても、セルのサイズが変わらなければテーブル全体の再レイアウトはしないようにする処理が入っています。それ以外にも行の追加・削除といった最適化が適用可能な場面が考えられますが、いまのところテーブル全体を再レイアウトするようになっています。このあたりについては改善できたらまた報告していきます。

インクリメンタル リフローの評価


インクリメンタル リフローの効果としてよく例に挙げられているのが、インプット要素のテキストボックスに1文字挿入するという処理です。この例は、テキストボックスにOSのコントロールを使っている場合には当てはまりませんが、ESウェブブラウザのようにフォーム コントロールもHTML(XBL 2.0)で作られている場合には、インクリメンタル リフローがないとページ全体を再レイアウトしないといけないことになってしまいます。

r3121の段階では、インプット要素に1文字挿入した際にレイアウト処理を終えるまでに使用するCPU時間は、表示しているHTMLページの複雑さには影響をほとんど受けずに3.4ミリ秒程(Core i7 3770, -O2)になっています。いままではページ全体をリフローしないといけなかったので、例えばESオペレーティング システムのページですと20倍くらい速くなったような感じになっています。
r3096 (indroductionまでマウス カーソルを移動させると、¶ 記号が表示されます)

まとめ


今回はインクリメンタル リフローの処理の残りの実装について報告しました。まだ実装が入っていない部分が残っていますが、考え方としては今回説明したとおりで対応できるはずです。

CSS 2.1に関しては、エスリル内部のソースコード レポジトリを確認してみると実装を開始したのは2010年の12月からなので、2年近くかけてようやく骨格が固まってきたというところです。CSS 2.1適合テストスイートのチェックを後回しにすればもっと早くここまではきた可能性もあります。ただそれだとインクリメンタルな処理と一緒にテストスイートの達成率を上げていかないといけないので、トータルではさらに時間がかかることになったんじゃないかなと思います。

次回はescort 0.2.3のリリースを行なってからの予定です。Fedora 18が12月11日リリース予定、Ubuntuも12.10が先月リリース済みということで、リリース作業が少しややこしくなりそうな予感もしますが、年内に0.2.3、可能なら0.2.4まで、というようなつもりでいます。というわけで今回はここまでです。