Dev Study
← Rust で小さなブラウザを作る コース

20. 継承と初期値 — 宣言が無いとき、値はどこから来るか

でが 1 つも当たらなかったの値は、2 通りの決まり方をします。プロパティ(color、font-size など)なら「親におけるそのプロパティの」(CSS Cascade Level 4 §7)。継承しないプロパティ(background-color、display、width など)なら、そのプロパティの。これが defaulting です。

参照実装の compute_style は、まず ::default()(初期値: display は 、color は黒、背景は無し、font-size は 16px)から始め、親があれば color / font-size / text-decoration を親の計算値で上書きし、そのあと 既定とを当てます。宣言が当たれば上書きされ、当たらなければ継承した値(または初期値)がそのまま残る、という順番です。text-decoration は本物の仕様では継承しませんが、線が子孫にも引かれて見えるので、この実装では単純化して継承扱いにしています。

値には段階があります。CSS Cascade Level 4 §4 は、(作者が意図した値)→ 計算値(相対的な値を解決した値。この実装では に直した font-size など)→ 使用値(レイアウトで最終的に使う値。width: auto を実際の幅にしたもの)→ 実効値(端末の都合で丸めた値)、と並べています。親から子へ継承されるのは計算値です。だから親の font-size が 20px なら、子には "20px" が渡ります。

apply_declaration が各プロパティの値を読む部分です。display は Ident( / inline / none)、色は名前か #hex(rgb() は未対応)、font-size は px の Dimension か絶対サイズの、width / height は px か auto。読めない値の宣言は無視します(CSS は不正な値の宣言を無かったことにします)。前のステップの実行結果で、<span> が親の navy を受け継ぎ、<p class="note"> の background-color が <span> に継承されないことを確かめてください。

本物のブラウザでは: Firefox の ComputedStyle は「プロパティを個別に持たず、関連するプロパティのまとまり(style struct)に分けて持つ」設計で、struct は『既定で継承するもの』と『継承しないもの(reset)』に分かれています。親子で同じ struct を共有できるので、継承プロパティのメモリが節約できます。 は ComputedStyle(core/style/computed_style.h)を持ち、::UpdateStyleAndLayoutTree() が を で歩いて再計算します。この回の『親の計算値をコピーしてから作者宣言を重ねる』は、その 1 要素分です。text-decoration を継承扱いにした簡略化は本物にはありません。

開発のどこに活かすか: 「継承するプロパティとしないプロパティがある」「継承されるのは計算値」の 2 点を知っていると、em と rem の違い(親の計算値基準か、根の基準か)、なぜ background が子に伝わらないのか、なぜ font-size: inherit を書く場面があるのか、が一本の線でつながります。また「既定値 → 親から受け継ぐ → 明示の設定で上書き」という 3 層は、設定管理の継承(組織 → チーム → 個人)の実装にも同じ形で出てきます。

点線の付いた語はタップすると説明が出ます。用語集を開く →

サンプルコード(フレームワーク環境が必要なため表示のみ)

// src/renderer/style.rs の apply_declaration(宣言 1 つを計算値に反映する)
fn apply_declaration(style: &mut ComputedStyle, d: &Declaration) {
    let first = d.value.first();
    match d.property.as_str() {
        "display" => {
            if let Some(CssToken::Ident(v)) = first {
                match v.as_str() {
                    "block" => style.display = Display::Block,
                    "inline" => style.display = Display::Inline,
                    "none" => style.display = Display::None,
                    _ => {}
                }
            }
        }
        "color" => {
            if let Some(c) = Color::from_tokens(&d.value) {
                style.color = c;
            }
        }
        "background-color" | "background" => match first {
            Some(CssToken::Ident(v)) if v == "transparent" => style.background_color = None,
            _ => {
                if let Some(c) = Color::from_tokens(&d.value) {
                    style.background_color = Some(c);
                }
            }
        },
        "font-size" => match first {
            Some(CssToken::Dimension(n, unit)) if unit == "px" => style.font_size = *n as f32,
            Some(CssToken::Ident(v)) => {
                // 絶対サイズのキーワード(CSS Fonts の absolute-size)。medium を 16px とした比
                style.font_size = match v.as_str() {
                    "x-small" => 10.0,
                    "small" => 13.0,
                    "medium" => 16.0,
                    "large" => 18.0,
                    "x-large" => 24.0,
                    "xx-large" => 32.0,
                    _ => style.font_size,
                };
            }
            _ => {}
        },
        "text-decoration" | "text-decoration-line" => {
            if let Some(CssToken::Ident(v)) = first {
                match v.as_str() {
                    "underline" => style.text_decoration = TextDecoration::Underline,
                    "none" => style.text_decoration = TextDecoration::None,
                    _ => {}
                }
            }
        }
        "width" => style.width = px_or_auto(first),
        "height" => style.height = px_or_auto(first),
        _ => {} // 未対応のプロパティは無視
    }
}

手元で動かす(配布物)

サンプルコードで身につける(3本) →

学んだ概念を、素朴な例から実務寄りの例まで3つの具体例で確認します。

公式ドキュメントで詳しく ↗