JavaScriptでCSSパーサーを書くための情報を収集中(5.5日目)
window.name + フレーム のくだりについてちょっと追記
uuCSSParser.js は uuAltCSS.js の古い呼び名です。
昨日の段階で、最新のCSS(CSS3)のセレクタを古いブラウザでも利用可能になりました。
このスクリプトを発展させれば、CSSハックや、ブラウザ毎にCSSファイルを用意する必要性を減らせることになります。
ただ、ちょっとした問題が見つかっていますので、今日はそれを解決します。
問題ズ
- Firefox3.5+, Safari3.1+, Opera9.5+, Google Chrome 2+ など CSS3 セレクタをブラウザがネイティブにサポートしている環境でも動いてしまう。
- ユニバーサルセレクタ(*)を見つけると、全ての要素に class="..." を追加してしまい、あまりよろしくない。
- IE6 で、アドレスバーからURLを直接入力したり、HTMLファイルをブラウザにドロップすると、cssText が改変済みの状態でロードされてしまい動かない。
これが <style> E > F {} :digit {} </style> 最初からこうなっている <style> UNKNOWN {} :unknown {} </style>
では、それぞれ解決していきましょう。
問題解決のために追加した処理は、ドーモ君(▼〜▲) で囲ってあります。
問題1. 最新のブラウザで動作する必要がないのに動いてしまう。
この表は「uuCSSParser.js を組み込むことにより、IE6, IE7, Firefox2, Firefox3, Safari3 で使用可能になるセレクタ」の一覧です。
Expr | CSS | NG Browser |
---|---|---|
E > F | 2 | IE6 |
E + F | 2 | IE6 |
E ~ F | 3 | IE6 |
[ATTR] | 2 | IE6 |
[ATTR=VALUE] | 3 | IE6 |
.class.class | 2 | IE6 |
:first-child | 2 | IE6 |
:focus (※) | 2 | IE6,IE7 |
:not | 3 | IE6,IE7 |
:root | 3 | IE6,IE7 |
:target | 3 | IE6,IE7 |
:enabled | 3 | IE6,IE7 |
:disabled | 3 | IE6,IE7 |
:checked | 3 | IE6,IE7 |
:empty (※) | 3 | IE6,IE7 |
:last-child | 3 | IE6,IE7,Sf3 |
:only-child | 3 | IE6,IE7,Sf3 |
:nth-child | 3 | IE6,IE7,Fx2,Fx3,Sf3 |
:nth-of-type | 3 | IE6,IE7,Fx2,Fx3,Sf3 |
※ IE で動かないかも
http://www.quirksmode.org/css/contents.html を参考にしました。
この表から導かれるコードを、初期化処理に追加します。
function autoexec() { _cssp.execute(_cssp.parse()); _mm.event.unbind(_win, "load", autoexec); } // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ if ((_ie && (_uaver < 8 || !_mm.iemode8)) || (_mm.opera && _uaver < 9.5) || (_mm.gecko && _uaver < 3.5) || (_mm.safari && _uaver < 3.1)) _mm.event.bind(_win, "load", autoexec); } // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
問題2. ユニバーサルセレクタを見つけると全ての要素に class="..." を追加してしまい、あまりよろしくない。
* { color: red }
としてしまうと、全ての要素に上記のスタイルを実現するコードが埋め込まれてしまうのと、ユニバーサルセレクタ(*)は古いブラウザでもほぼ完璧にサポートされているため、ケアする必要がありません。
uuCSSParser.execute() の途中に判定を追加します。
for (j = 0, jz = data.length; j < jz; ++j) { expr = data[j].expr; // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ if (expr === "*") { // skip universal selector continue; } // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
問題3. IE6 で、アドレスバーからURLを直接入力したり、HTMLファイルをブラウザにドロップすると、cssText が改変済みの状態でロードされてしまい動かない。
ページの更新ボタンをクリックした時には発生せず、URLの直接入力やHTMLファイルのドラッグ&ドロップで発生するようです。
uuCSSParser.parse() に判定を追加します。
var _win = window; parse: function(css) { // @param CSSString(= ""): // @return Hash: { specs: [spec-num1, spec-num2, ...], // data[spec-num1]: { rule, expr, decl }, // data[spec-num2]: ... } var specs = [], data = {}, reload = 0; _ie && _cssp._memento(); if (!css) { css = _cssp._importStyleSheets(); // ▼▼▼▼▼▼▼▼▼ if (_ie && _uaver < 7) { // Auto reload for IE6 // case 1: Drop of HTML file // case 2: Input of URL to address bar if (/UNKNOWN[^\{]+?\{|:unknown[^\{]+?\{/.test(css) && "UNKNOWN" !== _win.name) { reload = 1; } _win.name = ""; if (reload) { _win.name = "UNKNOWN"; location.reload(false); // reload from cache } } // ▲▲▲▲▲▲▲▲▲ } _cssp._collectStyleSheets(specs, data, css); specs.sort(function(a, b) { return b - a; }); // sort of number(10 -> 1) return { specs: specs, data: data }; },
処理を読み解くためのヒントは、
- IE の window.name はページをリロードしてもリロード前の内容を保持している
- キャッシュからリロードする場合は、解釈できないCSSセレクタを含んでいても、UNKNOWN や :unknown に改変されない
です。
同様の処理をクロスブラウザで実現するには、クッキーやローカルストレージを使うことになるでしょう。
window.name を使用しているので、フレームを使用していて、各フレームを名前(window.name)で判別しているコードの邪魔をすることになりますが、それについては問題無いと判断しています。
- HTML5 ではフレーム(frameset)が利用できない。
- フレーム(frameset)は XHTML1.0 でも使えるが、
XHTML は HTML5の登場で死んでゆく規格なので気にしない。IE6 は、<!doctype>宣言を見て標準モードにするだけで、XHTML1.0 と HTML4.01 は同じものととして扱いますね。 - Javadoc などのツールがフレーム(frameset)を必要とするHTMLを生成するが、frameset を利用したHTMLを人間が生成するケースは減少するはず。
という考えを持っています。ちょっと独善的かもしれませんね。
以下追記
- window.name を利用した上記のコードは、IE6 だけで動作します。
- クッキーやローカルストレージは OFF にできますが、window.name は ON/OFF できません。今回のケースでは、window.name を使う必然性があります。
- frameset を求めているユーザが、同時に CSS3 セレクタを欲しがるケースはほとんど無いはずです。これらはかなり排他的な関係です。
- iframe 要素にも name 属性がありますが、IE6 の仕様だと、iframe.name ではなく、iframe.contentWindow.name にアクセスすることになります。
- 昨今の人なら、iframe.name は使わずに、ちゃんとDOM(iframe.id) を使うんじゃないかと。
つまり、<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"> な HTML で、CSS3 セレクタを使いたい場合に、Web1.0 な書き方をすると、IE6 で問題が発生するかもしれませんが、ニッチすぎるケースなんで、そのへんは「仕様」ということになりますね。
うーんと。まだ考えに抜けがあるかもしれませんね。引き続きご意見をお待ちしています。