Shiki’s Weblog

ESウェブブラウザ通信 - CSS 2.1 Test Suite #13

2012/01/22 #ESウェブブラウザ通信

前回CSS 2.1テストスイートの4章を終わらせました。今回は、5章のセレクタのテストと6章のカスケーディング処理に関するテストを一通り進めて、カスケーディング処理の最適化を行っていきます。
なお、DOM/HTMLモジュールの実装がまだactive, focus, visitedといった状態をきちんとサポートしていないので、その辺に関しては今回は対応を見送っています。

属性セレクタ

attribute-002
属性セレクタのごく単純なテストです。

r2310r2310

HTMLでは、属性値の大文字・小文字は区別するので、それに合わせてr2311で修正しています。

r2311r2311

擬似セレクタ

c25-pseudo-elmnt-000

擬似エレメント セレクタはセレクタの最後にしか現れることができません。このテストでは、2行目でセレクタの途中に擬似エレメント セレクタを挿んで、おかしな動作にならないかテストしています。

r2311r2311

r2312で修正しています。

r2312r2312

擬似クラス セレクタ

c21-pseud-anch-000

CSS 2.1の仕様書5.11.2の、

in HTML4, the link pseudo-classes apply to A elements with an "href" attribute.

という部分のテストです。このテストでは、A要素にhref属性が指定されていないので、リンク擬似クラスは摘要されない、ということになります。

r2312r2312

r2313でひとまず修正しています。

r2313r2313

lang-selector-001
:langセレクタの単純なテストです。

r2313r2313

ダッシュマッチの呼び出し方がひっくり返っていたバグをr2314で修正しています。

r2314r2314

擬似要素セレクタ

CSS  3からは擬似要素はコロン2つ(::)で表記するようになっているので、ここではそれに合わせて、::first-lineのように記述していきます。(互換性のため、:first-line, :first-letter, :before, :afterについては、これまで通りコロン1つで書いても擬似要素になります。)
c23-first-line-000
単純な::first-lineセレクタのテストです。

r2315r2315

r2315では、セレクタの処理ではなくて、1行目かどうかの判定にバグがあったので、r2316で修正しています。small-capsに未対応なので、最後の段落はまだ期待通りの表示ではありません。

r2316r2316

first-line-selector-010

このテストでは、::first-lineの設定が<br>要素以降の要素には摘要されないことをテストしています。

r2316r2316

r2316ではwhite-spaceプロパティによる行頭の空白の処理にバグがあったので、r2317で修正しています。

r2317r2317

first-line-pseudo-002
ブロックボックスの中に作られた、匿名ブロックボックスの中の先頭のラインボックスに不必要に::first-lineスタイルを適用してしまわないかどうかテストしています。

r2319r2319

r2320では、すべての匿名ブロックボックスで::first-lineスタイルを適用しないようにしてしまっていますが、それでは問題があって、次のfirst-line-pseudo-004でもう1度修正しています。

r2320r2320

first-line-pseudo-004
002と比べてみるとおもしろいテストです。下図のr2325で赤くなってしまった1行目の"This line should be green."というテキストはHTMLファイル中では1行目にあるのですが、レンダーツリーの中では、このテキストを含むdiv要素がインラインのテキストと子のdiv要素を両方もっているので、匿名ボックスの中のラインボックスの中に入っています。

r2325r2325

そのため、r2320の修正で::first-lineの設定がこの1行目には摘要されずに赤くなってしまっていました。ブロックボックスの中の先頭の匿名ブロックについては::first-line要素を適用できるようにしておかないといけなかったのでした。r2326で修正しています。

r2326r2326

first-line-pseudo-021

このテストは1行目にレイアウトされるインライン要素に適用されるスタイル定義が'inherit'だった場合には、継承するスタイルは親要素のスタイルではなくて、::first-line要素のスタイルである、ということをテストしています。

ただこの動作はCSS 2.1の仕様書から読み取ることは難しく、さらにCSS 2.1テストスイートでもfirst-line-inherit-002はこのテストと逆の想定をテストしているように見えて、実際のところよくわからないところがありました。

幸いCSS 3のセレクタの仕様書7.1.1.で、

During CSS inheritance, the portion of a child element that occurs on the first line only inherits properties applicable to the ::first-line pseudo-element from the ::first-line pseudo-element.

と明記されているため、このfirst-line-pseudo-021の想定通り、::first-line要素のスタイルを継承する動作は正しい動作ということがわかります。

r2361r2361

r2362でCSS 3 セレクタで明確化された仕様に対応させています。

r2362r2362

なお、IE、WebKit、Operaもfirst-line-pseudo-021にはPASS,line-inherit-002にはFAILという動作になっているようで、line-inherit-002の方は保留としておきます。
c24-first-lttr-000
CSS2.1の仕様書5.12.2では、

Punctuation (i.e, characters defined in Unicode [UNICODE] in the "open" (Ps), "close" (Pe), "initial" (Pi). "final" (Pf)and "other" (Po)punctuation classes), that precedes or follows the first letter should be included

のように、::first-letterは単純に先頭の1文字ではなくて、その前後のユニコードの句読点も含めるように規定しています。
このテストでは、ユニコードの句読点(")に続く1文字を::first-letterに含めているかどうかテストしています。

r2320r2320

r2321でひとまず修正しています。

r2321r2321

first-letter-punctuation-001
この一連のテストは句読点に該当する文字を正しく判別しているかどうかテストしているものですが、::first-letter要素には後に続く句読点も含めないといけない、という仕様のテストにもなっています。例えば、『(1)いろは・・・』のようにはじまる先頭行の::first-letterは"(1)"ということですね。

r2322r2322

r2323で修正しています。

r2323r2323

first-letter-punctuation-334
このテストはfirst-letter-punctuation-001とほぼ同様なのですが、使っている句読点がUTF-16ではサロゲートペアを使って表されている文字になっています。r2323ではブラウザごとクラッシュしてしまっていました。

r2325r2325

サロゲートペアはUTF-16の文字列を利用している場合に気をつけないといけないところですね。r2325で修正しています。
ちなみに使われているコードはU+10100、Aegean Word Separator Lineだそうです。IPAフォントにも含まれていないグリフなのでスクリーンショットでは×印で表示されていますが、本来は小さな縦線のようです。
first-letter-selector-004
CSS 2.1の仕様書5.12.2の、

If an element has ':before' or ':after' content, the ':first-letter applies to the first letter of the element including that content.

のテストになっています。

r2326r2326

r2327で修正しています。

r2327r2327

今回で5章のテストのPASS率は約91%となりました。今回はここで6章に進んでより基本的な問題を先に修正していきます。

カスケーディングの順番

c32-cascading-000
スタイルシートを処理していく順番はソースのHTMLファイルから参照されている順になります。そのテストになっています。

r2327r2327

r2327では、link要素から参照されるスタイルシートはstyle要素よりも必ずあと回しになり、HTTP経由で取得できた順に処理するようになってしまっていました。r2328で修正しています。

r2328r2328

補足:r2328の修正では、style要素の中でhtml要素のスタイルを定義しても反映できないといった問題がありました。この問題はr2358で修正しています。
c31-important-000
各要素のstyle属性で指定した定義よりも、style要素の!importantルールを優先して摘要しているかどうかテストしています。

r2328r2328

r2329で修正しています。

r2329r2329

user-stylesheet-001
ユーザースタイルシートを利用する単純なテストです。

r2330r2330

r2331でユーザースタイルシートに対応しました。

r2331r2331

cascade-010
ユーザースタイルシートの中で!importantルールを使うテストです。

r2332r2332

r2333でユーザースタイルシートの!importantルールに対応しました。

r2333r2333

HTMLのプレゼンテーション属性

html 5ではobsoleteになり使われることもなくなっていくと思いますが、今回はカスケード処理のテストのために、古い属性も一部実装してテストしていきます。
html-attribute-003
body要素のbackground属性を使ったテストです。background属性は未実装だったのですが、r2334でひとまずこれまで通りの手順の実装しています。

r2334r2334

r2334のようなプレゼンテーション属性を要素に指定されたCSSのスタイルと同列に扱う実装では、仕様書6.4.4に記載されている、

these attributes are translated to the corresponding CSS rules with specificity equal to 0 , and are treated as if they were inserted at the start of the author style sheet.

という条件を満足できないのでテストに失敗してしまっています。

r2335r2335

r2335では、スタイルの優先度に"!non-css"というESウェブブラウザ独自の優先度を追加して対処しています。(なので、CSSOMのCSSStyleDeclarationインターフェイス経由でも設定できてしまったりしますが使ってはだめです。)
hr_color(ESウェブブラウザで追加したテストです)
hrのcolor属性を実装中に発覚したバグで、下側のhrは緑色で表示されないといけません。

r2339r2339

r2339では、カスケードする順序の処理でオリジン(ユーザーエージェント、ユーザー、オーサー)に基づいた優先度づけができていませんでした。そのため、hrのcolor属性から作られる'border-color'スタイルよりもデフォルトのスタイルシートの'border'スタイルが優先されてしまっていました。
r2340で修正しています。この修正で合わせて詳細度(speficity)の扱いをCSS 2.1の{ a, b, c, d }から、CSS 3の{ a, b, c }に変更しています。(CSS 2.1のもともとのaはESウェブブラウザの実装上は必要なかった、というのが理由で、カスケード処理の順番自体は同じです。)

r2340r2340

html-precedence-003
font要素に対応するHTMLFontElementインターフェイス自体が未対応だったためにテストに失敗していました。

r2346r2346

r2340で対応しました。(font要素もhtml5ではobsoleteなので使われることはなくなっていくと思いますが、今回はカスケード処理の確認のために実装しています。)

r2347r2347

::first-letterのより複雑なテスト

c26-psudo-nest-000
このテストでは、::first-letterを適用する部分はあっているのですが、line-heightが拡大された文字に合わさって広がっていない、という問題がありました。
CSS 2.1の仕様書5.12.2では、

To allow UAs to render a typographically correct drop cap or initial cap, the UA may choose a line-height, width and height based on the shape of the letter, unlike for normal elements.

とあって、::first-letterを適用した行の高さ調整にはある程度自由度を持たせているようです。
r2345では、行のline-heightの解決値を求めてしまって計算値(normal)の情報がなくなってしまった状態で1文字目のレイアウトを行っているので、'T'が画面からはみ出してしまっていました。

r2345r2345

r2346では、line-heightの解決値を求めた後も元の計算値(normal)を記録しておくことで、line-heightが1文字目のフォントの高さに連動するようにしています。(small-capsには未対応なので、このテスト全体としてはまだFAILです。)

r2346r2346

なお、CSS 2.1の仕様では、さらに踏み込んでline-heightの値自体を無視してもよいという例が提示されていますが、同時にCSS 3では専用のプロパティを追加する予定であることも記載されているので、今の段階ではここまでにしておきます。
first-letter-selector-006
リストに対して::first-letterが適用されるのは、outsideのマーカーではなくて本文のテキストになる、ということをテストしています。(CSS 2.1では、insideのマーカーに対してはfirst-letterは無視してもよいことになっていてます。)

r2348r2348

r2348ではマーカーの方に色が付いてしまっていました。r2349で修正しました。

r2349r2349

first-letter-selector-010
::first-letter要素にfloatプロパティを適用させられるかどうかをテストしています。西欧では1文字目のレイアウトにいろいろな歴史があるみたいですね。

r2349r2349

r2350で対応しています。

r2350r2350

今回で、6章のテストのPASS率は約88%となりました。各プロパティを要素に割り当てていく処理自体には問題はなくなってきました。残りのFAILしているテストは、ECMAScript用のAPIが未実装だったり描画処理を原因とするものです。6章のテストは今回はここまでにして、今後開発の進展に応じて改めてテストしていくことにします。

カスケーディング処理の高速化

CSSのカスケーディング処理の実装について仕様通りに動作していることが確認できたので、以前から気になってきていたパフォーマンスの問題をひとつ解決しておくことにします。

r2360r2360

上図のようなよくあるグーグルの検索結果のページを描画するのに、ESウェブブラウザが内部でどれくらい処理に時間がかかっているかというのをr2350の段階でまとめたものが下の表になります(測定に使用したPCはCore2 2.4GHzとGeForce 210の組み合わせです)。
処理
時間[秒]
カスケーディング
1.397
レイアウト
0.070
レンダリング
0.019
r2350 (コンパイラ最適化なし)
表中、「カスケーディング」は要素に適用するスタイルのプロパティを計算していく処理、「レイアウト」はDOMツリーからレンダーツリーを生成する処理、「レンダリング」はレンダーツリーに基づいてOpenGLの描画命令を発行していく処理になります。
その中では、圧倒的にカスケーディング処理に時間がかかっていることがわかります。r2350では、各要素について、すべてのスタイルのルールがそれぞれ合致するかどうか判定してプロパティを割り当てていく、という実装になっています。そのため処理には(要素数)x(全ルール数)分の時間がかかってしまっています。
これを改善する一番基本的な手法にルール フィルタリングというのがあります。以前はここ(リンク切れです)にDave Hyattさんの書かれた詳しい解説があったのですが、その内容は、
1. セレクタの一番右側の単純なセレクタに注目して、IDセレクタ、クラス セレクタ、タイプ セレクタ、その他の4グループにルールを予め分類しておく。
2. 合致判定では、要素のID(もしあれば)、クラス(もしあれば)、タイプから対応するグループのルールをまず検索するようにする。

というものです。ルール フィルタリングを使用すると、ひとつのグループの中から対応するルールをすぐに見つけられるアルゴリズムはあるので、計算量を(要素数)x(全ルール数)から(要素数)x(要素に合致する可能性のあるルール数)に減らすことができるわけです。

r2351,r2355でルール フィルタリングを実装して、同じページを処理するにかかった時間を測定したものが下の表になります。

処理 時間[秒]
カスケーディング 0.385
レイアウト 0.070
レンダリング 0.019

r2355 (コンパイラ最適化なし)

ルール フィルタリングだけでもカスケーディング処理が3倍以上高速化されることがわかります。ここで、さらにgccの最適化(-02)を使っ場合の結果が下の表になります。

処理 時間[秒]
カスケーディング 0.086
レイアウト 0.051
レンダリング 0.017

r2355 (コンパイラ最適化あり)

さらに4倍以上高速化されて、元の最適化なしの場合と比べると16倍の高速化といった具合になりました。このあたりまでくると検索結果が返ってくるまでの時間(0.15秒)からすれば感覚的には許容できる範囲という感じになってきます。
とはいっても、アニメーションしたりするページのことを考慮した最適化などもDOM/HTMLモジュールの開発の進展に合わせて取り組んでいかないといけない感じですね。

まとめ

今回、5章と6章のテストをひとまず終えて、CSS 2.1テストスイート全体のPASS率は約70%となりました。これで1章から10章までのテストについては比較的詳しく見てくることができました。次回以降はこれまで集中的にテストしてこなかった、フォントやテキストまわりのテストを進めていく予定です。