2012年1月22日日曜日

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

前回CSS 2.1テストスイートの4章を終わらせました。今回は、5章のセレクタのテストと6章のカスケーディング処理に関するテストを一通り進めて、カスケーディング処理の最適化を行っていきます。

なお、DOM/HTMLモジュールの実装がまだactive, focus, visitedといった状態をきちんとサポートしていないので、その辺に関しては今回は対応を見送っています。

属性セレクタ

・ attribute-002

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

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

r2311
擬似セレクタ

・ c25-pseudo-elmnt-000

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

r2311
r2312で修正しています。

r2312
擬似クラス セレクタ

・ 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属性が指定されていないので、リンク擬似クラスは摘要されない、ということになります。

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

r2313
・ lang-selector-001

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

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

r2314
擬似要素セレクタ

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

・ c23-first-line-000

単純な::first-lineセレクタのテストです。

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

r2316

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

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

r2317
・ first-line-pseudo-002

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

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

r2320

・ first-line-pseudo-004

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

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

r2326

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

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

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要素のスタイルを継承する動作は正しい動作ということがわかります。

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

r2362
なお、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に含めているかどうかテストしています。

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

r2321
first-letter-punctuation-001

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

r2322
r2323で修正しています。

r2323
・ first-letter-punctuation-334

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

r2325
サロゲートペアは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.
のテストになっています。
r2326
r2327で修正しています。

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

カスケーディングの順番

・ c32-cascading-000

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

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

r2328
補足: r2328の修正では、style要素の中でhtml要素のスタイルを定義しても反映できないといった問題がありました。この問題はr2358で修正しています。

・ c31-important-000

各要素のstyle属性で指定した定義よりも、style要素の!importantルールを優先して摘要しているかどうかテストしています。

r2328
r2329で修正しています。

r2329
・ user-stylesheet-001

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

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

r2331
・ cascade-010

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

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

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

html 5ではobsoleteになり使われることもなくなっていくと思いますが、今回はカスケード処理のテストのために、古い属性も一部実装してテストしていきます。

・ html-attribute-003

body要素のbackground属性を使ったテストです。background属性は未実装だったのですが、r2334でひとまずこれまで通りの手順の実装しています。

r2334
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.
という条件を満足できないのでテストに失敗してしまっています。

r2335
r2335では、スタイルの優先度に"!non-css"というESウェブブラウザ独自の優先度を追加して対処しています。(なので、CSSOMのCSSStyleDeclarationインターフェイス経由でも設定できてしまったりしますが使ってはだめです。)

hr_color (ESウェブブラウザで追加したテストです)

hrのcolor属性を実装中に発覚したバグで、下側のhrは緑色で表示されないといけません。

r2339

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ウェブブラウザの実装上は必要なかった、というのが理由で、カスケード処理の順番自体は同じです。)

r2340
・ html-precedence-003

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

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

r2347
::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'が画面からはみ出してしまっていました。

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

r2346
なお、CSS 2.1の仕様では、さらに踏み込んでline-heightの値自体を無視してもよいという例が提示されていますが、同時にCSS 3では専用のプロパティを追加する予定であることも記載されているので、今の段階ではここまでにしておきます。

・ first-letter-selector-006

リストに対して::first-letterが適用されるのは、outsideのマーカーではなくて本文のテキストになる、ということをテストしています。(CSS 2.1では、insideのマーカーに対してはfirst-letterは無視してもよいことになっていてます。)

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

r2349
・ first-letter-selector-010

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

r2349
r2350で対応しています。

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

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

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

r2360
上図のようなよくあるグーグルの検索結果のページを描画するのに、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章までのテストについては比較的詳しく見てくることができました。次回以降はこれまで集中的にテストしてこなかった、フォントやテキストまわりのテストを進めていく予定です。

2012年1月12日木曜日

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

前回CSS 2.1テストスイートを4.3.4まで進めましたが、今回は4章の残りの部分を終わらせます。

カウンタ

・ content-counter-002

このテストでは、contentプロパティでcounter(c, disc)のようにカウンタを使うテストです。

r2285
r2285までは、list-style-typeの場合しかcircleなどを表示出来なかったバグをr2286で修正しています。

r2286
・ content-counter-006

decimal-leading-zeroの表示方法は00から99の範囲を越えるとどうなるのかCSS 2.1の仕様書には明示されていませんが、CSS 3 Listsでは-09から09以外の範囲はdecimalと同じに扱うように規程されています。

r2286
r2287で対応しています。

r2287
・ content-counters-006

r2287では、counter-resetで常にカウンタを入れ子にしていたバグがありました。

r2287
r2288で修正しています。

r2288
・ content-counters-016

r2288では、counters(c, "")のようなセパレーターが空の文字列の場合に通常のcounterと同じ動作になってしまうというバグがありました。

r2288
r2289で修正しています。

r2289
・ counters-hidden-000

仕様書12.4.3の、
Pseudo-elements that are not generated also cannot increment or reset a counter.
という仕様のテストです。

r2289
r2290で修正しています。

r2290
・ counters-scope-000

仕様書12.4.1の、
pseudo-element automatically creates a new instance of the counter. 
という部分のテストです。擬似エレメントでカウンタをリセットした場合、カウンタがもう1段ネストする、ということですね。

r2291
r2292では、ひとまずスタイルルール中に、
.scope:before, .scope:after { content: counter(c); color blue; }
のように要素にマッチするセレクタが2つ以上ある場合でも、最初のひとつしか選択できなかったバグを修正しています。

r2292
続いて、r2293で擬似要素では新しいカウンタ制御用のコンテキストを使用するように修正しています。

r2293
・ counters-scope-implied-000

仕様書12.4.1の、
If 'counter-increment' or 'content' on an element or pseudo-element refers to a counter that is not in the scope of any 'counter-reset', implementations should behave as though a 'counter-reset' had reset the counter to 0 on that element or pseudo-element.
という仕様のテストです。 counter-resetの呼び出しは省略可ということですね。

r2293
r2294, r2295r2296で対応しています。

r2295
以上で、ECMAScript用のAPIが未実装といった理由で失敗するものを除けばと、4章のカウンタ自体の処理に関するテストは成功するようになりました。

カラー

・ color-000

仕様書4.3.6の、
The format of an RGB value in the functional notation is 'rgb(' followed by a comma-separated list of three numerical values (either three integer values or three percentage values) followed by ')'.
という仕様のテストです。関数形式でrgb(...)のように色を指定する場合、r, g, bの各値は整数かパーセンテージかどちらかに統一されていないといけなくて、rgb(255, 0, 0%)のような書き方は無効ということですね。

r2296
 r2297で対応しています。

r2297
キャラクター エンコーディング

ここからは、@charsetルールなど、CSSファイルのキャラクター エンコーディングに関するテストになります。

・ at-charset-002

仕様書4.4の、
1. An HTTP "charset" parameter in a "Content-Type" field (or similar parameters in other protocols)
というプロトコルで指定された文字セットを最優先するという仕様のテストです。

r2297
テスト中、
@import "support/at-charset-002.css";
という文で参照されているat-charset-002.cssはHTTPサーバーからはShiftJISエンコードで送信されてきます。この場合、at-charset-002.css中の、
@charset "windows-1252";
という設定は無視して、ShiftJISのファイルとしてat-charset-002.cssは処理しないといけません。r2298およびr2301で修正しています。

r2298

このテストはマイクロソフト社から提供されたものですが、使用している漢字のセレクタが".平和"というのはいいですね。

このあと、link要素のcharset属性を使用しているテストがあります。HTML 5ではa要素およびlink要素のcharset属性はnon-conformingになって使用が禁止されています。今回は対応を見送っておいて、今後、互換性で実際に問題になるようなサイトが多いようであれば検討することにします(実装自体はそれほど面倒ではありませんが)。

・ at-charset-014

UTF-8のBOM(バイトオーダーマーク)を使って文字コードを指定しているテストです。

r2299
r2299では、BOMがCSSパーサーにそのまま渡って文法エラーになってしまっていました。r2300で修正しています。

r2300
・ at-charset-029

BOMなしのUTF-16エンコーディングで保存されているスタイルシートを参照するテストです。

r2301

このテストをサポートしなければいけない現実的な意義というのはほとんどなさそうですが(WebKit系のブラウザも対応していません)、r2302でひとまず対応しました。

r2302
なおCSS 2.1の仕様書ではUTF-32についても触れていますが、HTML 5ではUTF-32を使わない方向(Authors should not use UTF-32 - 4.2.5.5)になっていますので、CSSでも同様に考えておくべきなのだと思います。CSS 2.1テストスイートでもUTF-32を使用しているものはないようです。


仕様書4.4の、
User agents must ignore style sheets in unknown encodings.
という規則のテストなのですが、BOM付きのUTF-16で記述したスタイルシートのファイルにわざわざ、
@charset "bogus"; 
と指定してある、というあまり現実にはなさそうなテストです。

r2303
これまでの修正のバグも合わせて、r2304で対応しています。

r2304

このテストの、
@charset"Shift-JIS"; 
という記述は、前回修正したCSSの文法の観点だけからはどこもおかしいところはありません。ただ@charsetルールには、4.4でさらに、
@charset must be written literally, i.e., the 10 characters '@charset "' (lowercase, no backslash escapes), followed by the encoding name, followed by '";'.
という規則が追加されていて、そのテストになっています。できる限りファイルの先頭部分だけで文字セットを識別できるように、という工夫だと考えられます。ただ、このテストもWebKit系はFAILしてしまいます。

r2305
r2306, r2307で修正しています。

r2306


仕様書4.4の、
4. charset of referring style sheet or document (if any)
という、外部のCSSファイルのエンコーディングが特に指定されていなければ、参照元のエンコーディングを使う、というテストです。

r2307
r2308で対応しています。

r2308
ちなみに、このテストはW3CのRichard Ishidaさんの書かれたテストのようです。僕はもらいそびれてしまったのですが、お会いする機会があればぜひ名刺を頂いた方がいいと思います :-)


このテストでは、参照しているcharset15-nocolon.cssにはUTF-8のBOMがついているのでUTF-8のファイルとして処理を開始するのですが、同時に@charsetルールをもっていて、そちらでは、
@charset "iso-8859-15"
dummy { font-family: serif; }
と行末の;(セミコロン)がないので、2行で全体として不正な@ルール文ということで無視されて、処理が続くという具合になっています。

r2308
r2308ではiso-8859-15の指定は無視できているのですが、@charset文の文法エラーにきちんと対応できていませんでした。r2309で修正しています。

r2309
以上で4章についてはほぼテストにパスするようになりました。

まとめ

今回は短めですが、キリが良いのでここまでにしておきます。テストスイート全体の成功率は前回から1%向上して約65%になりました。次回からは5章のセレクタのテストに進んでいきます。