首页
/ Ladybird 浏览器中新增与修改 CSS 属性的完整开发指南:从 Properties.json 数据定义到 ComputedValues 消费

Ladybird 浏览器中新增与修改 CSS 属性的完整开发指南:从 Properties.json 数据定义到 ComputedValues 消费

2026-09-04 15:06:29作者:苗圣禹Peter

本文基于 Ladybird 浏览器官方文档 CSSProperties.md 展开,完整讲解在 LibWeb 引擎中添加或修改一个 CSS 属性需要改动的全部位置:数据定义、解析、计算样式、JavaScript 接口四个环节。阅读并结合仓库源码后,你将能够独立完成诸如"新增一个长手属性"或"为某个简写属性实现自定义解析/序列化"这类引擎级贡献,并理解每个环节的底层调用链与数据流向。

总体流程:Ladybird 处理一个 CSS 属性的四个阶段

Ladybird 处理 CSS 属性的顺序是:数据定义 → 解析 → 计算样式 → 在渲染/JS 层被消费。官方文档明确说明,新增或修改属性需要在多处做改动,"以下按 Ladybird 处理它们的顺序列出,从解析开始,到被使用为止"。各阶段对应的关键文件如下:

阶段 关键文件 职责
数据定义 Properties.jsonKeywords.jsonEnums.jsonLogicalPropertyGroups.json 声明属性的取值、初始值、继承性、动画类型等元数据,驱动代码生成
解析 Parser.h、PropertyParsing.cpp 把 CSS 值字符串解析为 StyleValue 对象;简单属性自动解析,复杂语法需手写
计算样式 ComputedProperties、ComputedValues.h 长手属性以 StyleValue 指针存储,再转换/拷贝为紧凑的扁平结构供布局与绘制读取
JavaScript CSSStyleProperties.cpp 处理 getComputedStyle() 等 JS 场景下的特殊取值规则

第一步:数据定义(CSS/Properties.json)

Properties.json:每个属性的"身份证"

第一个要改动的地方是 Properties.json。该文件包含每个 CSS 属性的定义,用于生成 PropertyID 枚举及一批函数。你可能还需要修改 Keywords.jsonEnums.jsonLogicalPropertyGroups.json。这些 JSON 文件由 Meta/Generators 目录下的生成器在构建时自动编译为 C++ 代码(产出 PropertyID.h/.cppGeneratedCSSStyleProperties.* 等),生成机制的完整细节见配套文档 CSSGeneratedFiles.md

从仓库实际内容看,文件组织为一个 JSON 对象,key 是属性名,value 是该属性的元数据。以 opacity 为例,其完整定义体现了文档中提到的核心字段:

"opacity": {
  "style-group": "EffectsValues",
  "affects-accumulated-visual-contexts": true,
  "animation-type": "by-computed-value",
  "affects-layout": false,
  "affects-stacking-context": true,
  "inherited": false,
  "initial": "1",
  "requires-computation": "cascaded-value",
  "valid-types": [
    "opacity-value"
  ]
}

对照 CSSGeneratedFiles.md 中的字段表,可以逐字段理解这份定义的含义:

  • inherited(必填):opacity 不继承,会进入 ComputedValuesm_noninherited 结构;而继承属性(如下面的 visibility)进入 m_inherited
  • initial(必填):未指定时的初始值,这里为 1
  • animation-type(必填):按 Web Animations 规范声明动画类型,opacity 是按计算值插值(by-computed-value)。
  • affects-layout: false:改变该属性不会使元素布局失效——这正是 opacity 动画不触发重排的数据来源。
  • requires-computation: "cascaded-value":只有当指定值来自级联时才需要跑计算过程,优化了继承/初始路径。

再看一个继承属性的示例,visibility 的定义:

"visibility": {
  "style-group": "InheritedBoxValues",
  "animation-type": "custom",
  "inherited": true,
  "initial": "visible",
  "requires-computation": "never",
  "valid-types": [
    "visibility"
  ]
}

其中 valid-types 里引用的 visibility 是一个枚举类型名——它定义在 Enums.json 中(生成 Visibility 枚举及 Keyword 互转函数)。这就是文档所说"你可能还需要修改 Enums.json"的典型场景:当一组关键字在规范中有名称时(如 border-*-style 共用的 line-style),应定义枚举而非用 valid-identifiers 罗列关键字。

简写属性与位置值列表

简写(shorthand)属性通过 longhands 字段声明其展开目标。例如 border-radius

"border-radius": {
  "affects-layout": false,
  "initial": "0",
  "longhands": [
    "border-top-left-radius",
    "border-top-right-radius",
    "border-bottom-left-radius",
    "border-bottom-right-radius"
  ],
  "valid-types": [
    "length [0,∞]",
    "percentage [0,∞]"
  ],
  "percentages-resolve-to": "length"
}

margin 这类"位置值列表简写"还需要额外打上 positional-value-list-shorthand: true 标记:

"margin": {
  "initial": "0",
  "positional-value-list-shorthand": true,
  "longhands": [
    "margin-top",
    "margin-right",
    "margin-bottom",
    "margin-left"
  ],
  "valid-types": [
    "length [-∞,∞]",
    "percentage [-∞,∞]"
  ],
  "valid-identifiers": ["auto"],
  "percentages-resolve-to": "length",
  "quirks": ["unitless-length"],
  "needs-layout-for-getcomputedstyle": true
}

margin 的 1~4 个值如何映射到 4 个长手并非一对一,因此需要该标记让解析与序列化走通用逻辑。此外 quirks 声明了单位无关长度的怪癖模式行为,needs-layout-for-getcomputedstyle 声明了 getComputedStyle() 查询前必须先完成最新布局——这类字段让"属性元数据"直接驱动运行时行为。

逻辑属性与遗留别名

如果新增的是逻辑属性(如 margin-block-start),需要在属性条目中声明 logical-alias-for(含 groupmapping 两个字段),并在 LogicalPropertyGroups.json 中维护对应的物理属性映射,运行时根据书写方向映射到 margin-top 等物理属性。仓库中的 border-radius 组即为实例(border-top-left-radius 等条目里带有 "group": "border-radius")。而规范更名但语法不变的属性(如 font-stretch 更名为 font-width)则用 legacy-alias-for 声明:

"font-stretch": {
  "legacy-alias-for": "font-width"
}

设置了 legacy-alias-forlogical-alias-for 的条目可以省略其余必填字段。

第二步:解析(CSS/Parser)

多数属性无需手写解析代码

文档明确指出:对于接受单个值的属性,或其长手属性列表形式的简写,Properties.json 中的数据就足以完成自动解析。只有语法更复杂的属性才需要自定义解析。

从当前仓库的源码结构可以进一步观察到,通用解析路径已经相当深:Parser.cpp 中的 Parser::parse_css_value_from_source() 会经由 RustValueParsing.cpp 进入 Rust 侧的值解析器(value_parser.rs),基于 JSON 元数据完成 token 到 StyleValue 的转换。也就是说,valid-types/valid-identifiers 写得越规范,引擎能"零代码"覆盖的属性就越多。

复杂语法:在 parse_css_value() 的 switch 中挂接

对于需要自定义解析的属性,代码放在 PropertyParsing.cpp 与 Parser.h。调用入口是 Parser::parse_css_value(),其内部有一个按特定属性分发的 switch,新属性的解析方法从这里调用,并应返回一个指向 StyleValue 或其子类的 RefPtrParser.h 中可以看到该入口的对外声明:

RefPtr<CSS::StyleValue const> parse_css_value(CSS::Parser::ParsingParams const&, StringView, CSS::PropertyID);
RefPtr<CSS::StyleValue const> parse_css_value(CSS::Parser::ParsingParams const&, Utf16View, CSS::PropertyID);

简写属性:ShorthandStyleValue 与自定义序列化

简写属性通常应使用 ShorthandStyleValue,它会负责自动展开(expand)到长手属性值。如果你的简写有特殊的序列化规则,则可能需要修改 ShorthandStyleValueto_string。文档给出的实例是 border-radius:它用 / 分隔水平与垂直两个半径分量(border-radius: 4px / 8px),这种"斜杠列表"无法用通用序列化表达,必须定制。

如果属性的值无法用现有的 StyleValue 子类型表示,可能需要新增一个 style value 类。文档在此处留下了一个幽默的注脚:"如果你需要这样做,去催 @AtkinsSJ 直到他把文档写完为止 ;^)"——可见这部分文档化仍在进行中,动手前建议参照 StyleValues 目录下现有子类的实现模式。

第三步:计算样式(ComputedProperties 到 ComputedValues)

长手存储:ComputedProperties

解析和样式计算完成后,长手属性以 StyleValue 指针的形式存储在 ComputedProperties 中。所有简写此时已被展开,因此不需要直接存储。

随后,这些长手值需要被转换成更易用的紧凑形式。做法是:在 ComputedProperties 中添加一个与属性同名的 getter,返回一个以紧凑形式持有值的类型。这一步的意义在于避免每次布局/绘制都从 StyleValue 对象树中解引用取值。

ComputedValues:扁平化的最终形态

ComputedValues.h 中有三个相关的类:

  • ComputedValues:以扁平格式持有每个属性的计算值。根据属性是否继承,字段需要加到 m_inheritedm_noninherited 结构体中,并配一个对应的 getter;
  • MutableComputedValues:还需要为属性加一个 setter,供样式计算写入;
  • InitialValues:为属性的默认值提供 getter。并非总是需要——例如当默认计算值就是空的 OptionalVector 时。

从当前仓库源码看,ComputedValues.h 中每个 getter 都带有静态初始值兜底(如 static Visibility visibility() { return Visibility::Visible; }static float opacity() { return 1.0f; }),与实例级 getter 并存;实例 getter 则从对应的 m_inherited/m_noninherited 结构字段中读出。

拷贝与消费:apply_style() 与 getter

样式从 ComputedProperties 拷贝到 ComputedValues 发生在 NodeWithStyle::apply_style() 中——每个属性是逐个拷贝的。当前实现位于 Layout/Node.cppapply_style 发布样式记录视图后,各消费方即可读取紧凑值。

文档给出的消费端示例是读取 visibilityopacity 的判断可见性代码(当前仓库中对应逻辑已演进为 BoxViews.cpp 中的 layout_node_is_visible / is_visible,判据不变):

bool Paintable::is_visible() const
{
    auto const& computed_values = this->computed_values();
    return computed_values.visibility() == CSS::Visibility::Visible && computed_values.opacity() != 0;
}

这个例子恰好串起了前几步的全部成果:visibility 的枚举来自 Enums.json 的生成代码,opacityfloat 紧凑类型来自 ComputedValues 的转换层,最终在绘制阶段被 O(1) 地消费。

第四步:JavaScript 层的特殊取值规则

部分属性在从 JS 读取计算值时有特殊规则(典型如 getComputedStyle() 的已解析值语义)。这些需要在 CSSStyleProperties.cppCSSStyleProperties::style_value_for_computed_property() 中登记处理;源码中该函数的注释也印证了这一点——它"枚举了需要此类处理的属性"。而以不寻常方式序列化的简写,则需要在 CSSShorthandStyleValue::to_string() 内部处理(与解析阶段的序列化定制相呼应,例如 border-radius/ 分隔符)。

实战清单:新增一个属性的完整改动路径

综合全文,新增一个长手属性按 Ladybird 的处理顺序需要:

  1. Properties.json:添加条目,填全必填字段(animation-typeinheritedinitialrequires-computation),按需声明 valid-types(引用 Enums.json 中的枚举名或 length [0,∞] 这类区间类型)、valid-identifiersaffects-layoutpercentages-resolve-to 等;新关键字同步登记到 Keywords.json
  2. PropertyParsing.cpp / Parser.h:仅当语法复杂时在 parse_css_value() 的 switch 中挂接自定义解析,返回 StyleValue 子类的 RefPtr;简写用 ShorthandStyleValue,特殊序列化改 to_string
  3. ComputedProperties:添加同名 getter,返回紧凑类型。
  4. ComputedValues.h:字段按继承性加入 m_inherited / m_noninherited + getter;MutableComputedValues 加 setter;InitialValues 按需加默认值 getter。
  5. CSSStyleProperties.cpp:若 JS 读取计算值有特殊规则,登记到 style_value_for_computed_property()

验证时,可参考 Tests/LibWeb 下与 CSS 相关的测试用例(如 text 类型的解析测试与 html 类型的布局/渲染测试)为新属性补充覆盖。整条链路的元数据驱动设计是 Ladybird 引擎的一个显著特点:JSON 定义既是文档也是代码,构建期生成保证"数据—枚举—函数"三者永不失同步。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384