IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

CSS の構文解析と @-webkit-*** ルールの意味

はじめに

あのDan Kogai 氏にも好評な噂の名著「Jythonプログラミング」がついに発売されるわけですが、皆様いかがお過ごしでしょうか。今日は Jython にちなんで、 CSS構文解析のお話をしたいと思います。(関係ない)

知ってますか?

Safari(などの WebKit を使ったブラウザ)にある以下のルールを

これらのルールの使い方を知っていますか?
僕は、今日まで使い方が分からなくて困っていたのです><

話は変わって

最近、僕の中では CSS (の実装)ブームです。
前回のパーサブームでは JavaScriptXPath を作ったわけですが、そのときはパーサを手で書いていました。
じゃあ、今回は yacc でも使ってみるかと鼻息荒めで挑んでみたのですが、少し困ったことがありました。
手書きパーサだと、パーサ全体の一部分だけを使ったパーサを簡単に作ることができるじゃないですか><
たとえば、

// ↓ こういうケースで、文法全体の一部分だけでパースしたいってことあるよねん><

// 宣言だけパースしたい><
element.style.cssText = 'background: red; color: blue';

// Media Query だけパースしたい><
linkElement.media.appendMedium('print and (min-device-width: 800px)');

// 色だけパースしたい><
element.style.color = 'rgb(100, 0, 100)';

でも、 yacc を使うとそれができないんです>< 困った/(^o^)\
じゃあ、何個もパーサ作りますか?
やだやだ!めんどいめどい><!!
1つの文法定義ファイルでぜーんぶやりたいです><

じゃあ、実際ブラウザはどうやってるのですか><

WebKit のソースを見てみました。
まず、 yacc の定義を探してみます。
http://svn.webkit.org/repository/webkit/trunk/WebCore/css/CSSGrammar.y
これ一個しか yacc の文法定義っぽいファイルはないなあ><
じゃあ WebKit では、この 1 ファイルだけを使ってやってるのかなあ。
さらに追いかけてみる

JS から CSS の値が設定された時、何が起こるか

element.style.color = 'red';

まずは、 CSSStyleDeclaration の setProperty が呼ばれる
http://svn.webkit.org/repository/webkit/trunk/WebCore/css/CSSStyleDeclaration.cpp

void CSSStyleDeclaration::setProperty(const String& propertyName, const String& value, ExceptionCode& ec) 
{
    int important = value.find("!important", 0, false);
    if (important == -1) 
        setProperty(propertyName, value, "", ec);
    else
        setProperty(propertyName, value.left(important - 1), "important", ec);
}

void CSSStyleDeclaration::setProperty(const String& propertyName, const String& value, const String& priority, ExceptionCode& ec)
{
    int propID = cssPropertyID(propertyName);
    if (!propID) {
        // FIXME: Should we raise an exception here?
        return;
    }   
    bool important = priority.find("important", 0, false) != -1;
    setProperty(propID, value, important, ec);
}

さらに、今度は CSSStyleDeclaration#setProperty からサブクラスの CSSMutableStyleDeclaration#setProperty が呼び出される
http://svn.webkit.org/repository/webkit/trunk/WebCore/css/CSSMutableStyleDeclaration.cpp

void CSSMutableStyleDeclaration::setProperty(int propertyID, const String& value, bool important, ExceptionCode& ec)
{
    setProperty(propertyID, value, important, true, ec);
}

bool CSSMutableStyleDeclaration::setProperty(int propertyID, const String& value, bool important, bool notifyChanged, Exceptio
nCode& ec)
{
    ec = 0;

    if (value.isEmpty()) {
        removeProperty(propertyID, notifyChanged, false, ec);
        return ec == 0;
    }

    // ■■ わー!ここで与えられた文字列をパースしようとしてる><
    CSSParser parser(useStrictParsing());
    bool success = parser.parseValue(this, propertyID, value, important);

    if (!success) {
    } else if (notifyChanged)
        setChanged();
    ASSERT(!ec);
    return success;
}

というわけで、 CSSParser オブジェクトの中身に答えがありそうですね。
http://svn.webkit.org/repository/webkit/trunk/WebCore/css/CSSParser.cpp

bool CSSParser::parseValue(CSSMutableStyleDeclaration *declaration, int _id, const String &string, bool _important)
{
    styleElement = declaration->stylesheet();

    // ■■ これが答えだ!!
    setupParser("@-webkit-value{", string, "} ");

    id = _id;
    important = _important;
    
    CSSParser* old = currentParser;
    currentParser = this;

    // ここで yacc で作った関数を呼ぶ
    cssyyparse(this);
    currentParser = old;
    
    rule = 0;

    bool ok = false;
    if (numParsedProperties) {
        ok = true;
        declaration->addParsedProperties(parsedProperties, numParsedProperties);
        clearProperties();
    }

    return ok;
}

きた!そういうことか!と思った!
つまり、 JS で

element.style.color = 'red';

とすると

@-webkit-value { red }

がパースされるってことか!!

つまり

最初に紹介した

という webkit の拡張ルールは、 CSS の機能を拡張する訳でもなんでもなくて一つの yacc の定義で複数のパーサを作るためにあったんです><
なるほどおおお

まとめ

すっきりした!
すっきりした!