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),
_ => {} // 未対応のプロパティは無視
}
}
手元で動かす(配布物)
- ステップ 8 の 1 ファイル版(step08_style.rs)inherited_and_not_inherited のテストがこの回の主題
学んだ概念を、素朴な例から実務寄りの例まで3つの具体例で確認します。
公式ドキュメントで詳しく ↗