SerenityOS LibWeb CSS 代码生成体系:从 JSON 定义到 C++ 实现
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
导读
SerenityOS 的 LibWeb 引擎在构建时会从一组 JSON 文件中批量生成大量 CSS 相关 C++ 代码。这些文件定义了每个 CSS 属性的取值、继承性、初始值、动画类型,以及关键字、枚举、伪类、媒体特性、数学函数与变换函数等元数据。本文以 CSSGeneratedFiles.md 为主线,完整梳理 7 个 JSON 输入文件的结构与字段语义,并结合 LibWeb/CSS 源码目录与 LibWeb 代码生成器 中的实现细节,讲解如何为浏览器引擎新增或修改一个 CSS 属性及其取值。读完本文,你将掌握 LibWeb 中 CSS 元数据的组织方式、生成器的产出清单,以及参与 CSS 规范实现时的标准工作流。
一、整体架构:JSON 定义、生成器与构建集成
LibWeb 的 CSS 实现采用"数据驱动 + 构建期代码生成"的模式:
- 输入:一个或多个
.json文件,位于 Userland/Libraries/LibWeb/CSS,目前包含Properties.json、Keywords.json、Enums.json、PseudoClasses.json、MediaFeatures.json、MathFunctions.json、TransformFunctions.json(另外仓库中还存在EasingFunctions.json)。 - 生成器:位于 Meta/Lagom/Tools/CodeGenerators/LibWeb,包含
GenerateCSSPropertyID.cpp、GenerateCSSKeyword.cpp、GenerateCSSEnums.cpp、GenerateCSSPseudoClass.cpp、GenerateCSSMediaFeatureID.cpp、GenerateCSSMathFunctions.cpp、GenerateCSSTransformFunctions.cpp、GenerateCSSStyleProperties.cpp等。 - 输出:生成结果落在构建目录
Build/<build-preset>/Lagom/Userland/Libraries/LibWeb/CSS/下(如PropertyID.h/cpp、Keyword.h/cpp等)。
这些生成器会在构建过程中自动运行,通常开发者无需手动干预。但当你需要新增或修改一个 CSS 属性及其取值时,就必然要与这些 JSON 文件打交道。生成器内部通过AK::SourceGenerator输出 C++ 代码,并借助 GeneratorUtil.h 等公共工具完成头文件守卫、write_if_changed之类的落盘逻辑(参见 GenerateCSSPropertyID.cpp)。
二、Properties.json:CSS 属性的"注册中心"
Properties.json(约 2800 行)为每个 CSS 属性维护一条记录,描述它接受哪些值、是否继承、初始值等元数据。它会生成以下文件:
PropertyID.h/PropertyID.cpp:属性 ID 枚举及各种查询函数GeneratedCSSStyleProperties.h/GeneratedCSSStyleProperties.cpp:绑定到 WebIDL 的属性访问器GeneratedCSSStyleProperties.idl
文件结构是一个 JSON 对象,键为属性名,值为该属性的数据。大多数元数据都来自对应 CSS 规范中该属性的"信息框"(information box)。每个属性会带有下表的部分字段(注意:带legacy-alias-for或logical-alias-for的属性不要求必填字段):
| 字段 | 必填 | 默认值 | 描述 | 生成的函数 |
|---|---|---|---|---|
affects-layout | 否 | true | 布尔值。修改该属性是否会令元素的布局失效 | bool property_affects_layout(PropertyID) |
affects-stacking-context | 否 | false | 布尔值。该属性是否会让元素产生新的层叠上下文 | bool property_affects_stacking_context(PropertyID) |
animation-type | 是 | 字符串。规范定义的属性动画方式,见下文 | AnimationType animation_type_from_longhand_property(PropertyID) | |
inherited | 是 | 布尔值。属性是否被子元素继承 | bool is_inherited_property(PropertyID) | |
initial | 是 | 字符串。未指定时属性的初始值 | NonnullRefPtr<CSSStyleValue> property_initial_value(JS::Realm&, PropertyID) | |
legacy-alias-for | 否 | 无 | 字符串。该属性所指向的旧名别名属性,见下文 | |
logical-alias-for | 否 | 无 | 字符串数组。该属性所指向的逻辑别名属性,见下文 | |
longhands | 否 | [] | 字符串数组。若是简写属性(shorthand),列出其展开的子属性 | Vector<PropertyID> longhands_for_shorthand(PropertyID) |
max-values | 否 | 1 | 整数。该属性最多可解析多少个值,例如margin最多 4 个 | size_t property_maximum_value_count(PropertyID) |
percentages-resolve-to | 否 | 无 | 字符串。百分比解析成什么类型,例如width的百分比解析为length | Optional<ValueType> property_resolves_percentages_relative_to(PropertyID) |
quirks | 否 | [] | 字符串数组。属性在 quirks 模式下的特殊行为,见下文 | bool property_has_quirk(PropertyID, Quirk) |
valid-identifiers | 否 | [] | 字符串数组。属性接受哪些关键字。更推荐定义枚举并把枚举名写进valid-types | bool property_accepts_keyword(PropertyID, Keyword) |
valid-types | 否 | [] | 字符串数组。属性接受哪些值类型,见下文 | bool property_accepts_type(PropertyID, ValueType) |
从源码看,生成器会逐个遍历该 JSON 对象:先判断属性是否设置了legacy-alias-for(GenerateCSSPropertyID.cpp),处理逻辑别名,再检查longhands(GenerateCSSPropertyID.cpp)与valid-types数组(GenerateCSSPropertyID.cpp),最终为每个属性产出对应的枚举成员、初始值函数与类型判定函数。
仓库中的真实示例可以直观印证字段用法。例如兼容性前缀属性的定义非常精简:
"-webkit-align-content": { "legacy-alias-for": "align-content" }而一个完整属性则同时携带动画类型、继承性与初始值,例如color一类:
"animation-type": "by-computed-value", "inherited": true, "initial": "currentColor", "valid-types": [ ... ](对应 Properties.json 附近的color定义;animation简写属性则使用"initial": "none 0s ease 1 normal running 0s none"这样的复合初始值,见 Properties.json。)
2.1animation-type:属性如何被动画化
该字段的合法取值由 Web Animations 规范定义,JSON 值与规范术语的对应关系如下:
| 规范术语 | JSON 值 |
|---|---|
| not animatable | none |
| discrete | discrete |
| by computed value | by-computed-value |
| repeatable list | repeatable-list |
| (见规范正文) | custom |
从仓库数据看,color是by-computed-value(按计算值平滑过渡),align-content等布局相关属性是discrete(离散跳变),animation-duration则是none(不可动画)。
2.2legacy-alias-for与logical-alias-for:两个名字相似但概念不同的别名
- 旧名别名(legacy name alias):属性在规范中的名字发生了变化,但语法没有变,因此设置旧名等同于直接设置新名。例如
font-stretch被重命名为font-width,于是font-stretch成为font-width的旧名别名。仓库中大量-webkit-*属性就是这类别名的典型:-webkit-align-content、-webkit-animation、-webkit-border-radius等全部通过legacy-alias-for指回标准属性名(见 Properties.json)。 - 逻辑别名(logical alias):例如
margin-block-start,它会根据应用到的元素,把值赋给margin-top、margin-bottom、margin-left或margin-right中的某一个。因此需要在logical-alias-for中列出所有可能被其指向的属性。
2.3quirks:Quirks 模式下的特殊行为
Quirks 规范定义了以下两种特殊行为:
| 规范术语 | JSON 值 |
|---|---|
| The hashless hex color quirk | hashless-hex-color |
| The unitless length quirk | unitless-length |
例如在 quirks 模式下,允许background-color: f00这种省略#的十六进制颜色写法,或width: 10这种省略单位的长度写法。是否启用这些宽松解析,正是由该字段驱动。
2.4valid-types:值类型与带括号区间记法
valid-types数组列出的是 CSS Values and Units 规范中定义的值类型名(去掉尖括号后的名字)。对数值类型,项目使用带括号区间记法(bracketed range notation):例如width可以接受任意非负长度,因此其valid-types数组中含有"length [0,∞]"。这种写法让生成器可以直接生成带范围约束的解析与校验逻辑,将规范约束落到类型系统层面。
三、Keywords.json:全局关键字注册表
Keywords.json(共 430 行)是一个纯字符串数组,每个元素是一个 CSS 关键字,例如auto、none、medium、currentcolor。它会生成Keyword.h与Keyword.cpp。任何属性或媒体特性用到的关键字都必须在这里登记。
除了标准关键字,仓库中还登记了一批内部关键字,例如-libweb-center、-libweb-left、-libweb-link,以及一系列-libweb-palette-*关键字(如-libweb-palette-base、-libweb-palette-selection),它们用于把 SerenityOS 系统调色板暴露给 Web 内容(见 Keywords.json)。这说明了该文件的扩展边界:不仅服务标准 CSS,也承载浏览器自身的私有扩展。
生成的代码提供:
Keyword枚举,供CSSKeywordValue使用Optional<Keyword> keyword_from_string(StringView):尝试把字符串转成KeywordStringView string_from_keyword(Keyword):把Keyword转回字符串bool is_css_wide_keyword(StringView):判断字符串是否为特殊的 "CSS-wide keywords"(如inherit、initial、unset、revert)
四、Enums.json:一键生成"关键字集合"枚举
Enums.json(共 521 行)是一个 JSON 对象,键是枚举名,值是关键字名数组。它生成Enums.h与Enums.cpp。
很多属性需要接受一组固定的关键字,逐个重复书写valid-identifiers容易出错且冗长。Enums.json允许自动生成这类枚举,以及枚举与Keyword、字符串之间的互转函数。生成的枚举还可以直接通过枚举名出现在Properties.json的valid-types数组中,从而在属性定义中被复用。典型的例子是border-*-style系列属性接受同一组关键字,因此被实现为line-style枚举(见 Enums.json)。仓库数据还显示align-content、align-items、align-self等各自的取值集合也都以枚举形式集中定义(见 Enums.json)。
以枚举 "foo" 为例,每个枚举生成的代码包括:
- 枚举类型
Foo Optional<Foo> keyword_to_foo(Keyword):把Keyword转换为FooKeyword to_keyword(Foo):把Foo转回KeywordStringView to_string(Foo):直接把Foo转成字符串
五、PseudoClasses.json:伪类元数据
PseudoClasses.json(共 146 行)是一个 JSON 对象,键为选择器伪类名,值为描述该伪类的对象。它生成PseudoClass.h与PseudoClass.cpp。
每个条目只有一个必填字段argument,它是伪类函数参数的语法(grammar)字符串;对标识符式伪类(如:hover、:active)则为空字符串。语法直接取自规范。仓库中的实例:
"active": { "argument": "" }, "dir": { "argument": "<ident>" }, "has": { "argument": "<forgiving-relative-selector-list>" }, "host": { "argument": "<compound-selector>?" }, "is": { "argument": "<forgiving-selector-list>" }, "lang": { "argument": "<language-ranges>" }(分别见 PseudoClasses.json、PseudoClasses.json、PseudoClasses.json、PseudoClasses.json、PseudoClasses.json、PseudoClasses.json。)
从中可以看到:has()、:is()这类接受"宽松选择器列表"的新伪类,与:hover这类无参伪类的差别——argument直接承载了后续解析函数所需的关键信息。
生成的代码提供:
PseudoClass枚举,列出所有伪类名Optional<PseudoClass> pseudo_class_from_string(StringView):把字符串解析为PseudoClassStringView pseudo_class_name(PseudoClass):把PseudoClass转回字符串PseudoClassMetadata结构体,保存 JSON 文件中的数据PseudoClassMetadata pseudo_class_metadata(PseudoClass):获取该元数据
六、MediaFeatures.json:@media可查询的媒体特性
MediaFeatures.json(共 261 行)是一个 JSON 对象,键为媒体特性名,值为描述该特性的对象。它生成MediaFeatureID.h与MediaFeatureID.cpp。
<media-feature>是媒体查询可以检查的取值,列在最新 Media Queries 规范的@media描述符表中。这里的定义可以看作Properties.json定义的简化版本:
| 字段 | 描述 |
|---|---|
type | 字符串。媒体特性的求值方式:discrete(离散)或range(范围) |
values | 字符串数组。直接取自规范:关键字原样保留,类型名带<>。类型可以是<boolean>、<integer>、<length>、<ratio>或<resolution> |
仓库中的真实定义示例:
"any-hover": { "type": "discrete", "values": ["none", "hover"] }, "color": { "type": "range", "values": ["<integer>"] }, "color-gamut": { "type": "discrete", "values": ["srgb", "p3", "rec2020"] }, "aspect-ratio":{ "type": "range", "values": ["<ratio>"] }(分别见 MediaFeatures.json、MediaFeatures.json、MediaFeatures.json、MediaFeatures.json。)color使用range类型表示"颜色位深为 n",any-hover使用discrete表示设备是否支持悬停,二者求值逻辑截然不同。
生成的代码提供:
MediaFeatureValueType枚举,列出可能的取值类型MediaFeatureID枚举,列出每个媒体特性Optional<MediaFeatureID> media_feature_id_from_string(StringView):字符串转MediaFeatureIDStringView string_from_media_feature_id(MediaFeatureID):MediaFeatureID转回字符串bool media_feature_type_is_range(MediaFeatureID):判断是否为range类型(区别于discrete)bool media_feature_accepts_type(MediaFeatureID, MediaFeatureValueType):是否接受该值类型bool media_feature_accepts_keyword(MediaFeatureID, Keyword):是否接受该关键字
七、MathFunctions.json:CSS 数学函数
MathFunctions.json(共 232 行)是一个 JSON 对象,描述每个 CSS 数学函数,键为函数名,值为描述函数属性的对象。它生成MathFunctions.h与MathFunctions.cpp。
每个条目目前只有一个属性parameters,即参数定义对象数组。参数定义具有以下字段:
| 字段 | 描述 |
|---|---|
name | 字符串。参数名,与规范一致 |
type | 字符串。参数可接受类型,单个字符串,用\|分隔 |
required | 布尔值。该参数是否必填 |
仓库中的示例:
"abs": { "parameters": [ { "name": "value", "type": "<number>|<dimension>|<percentage>", "required": true } ] }, "atan2": { "parameters": [ { "name": "y", "type": "<number>|<dimension>|<percentage>", "required": true }, { "name": "x", "type": "<number>|<dimension>|<percentage>", "required": true } ] }, "clamp": { "parameters": [ { "name": "min", ... }, { "name": "central", ... }, ... ] }(见 MathFunctions.json、MathFunctions.json、MathFunctions.json。)atan2(y, x)的两个参数都是必填的数值类参数,abs(value)接受数值、维度或百分比。
生成的代码提供:
MathFunction枚举,列出全部数学函数- CSS Parser 的
parse_math_function()方法的实现
也就是说,MathFunctions.json不止生成数据,还会直接生成解析器的函数体,把"支持哪些数学函数、每个函数接受什么参数"编译进解析流程。
八、TransformFunctions.json:CSS 变换函数
TransformFunctions.json(共 290 行)是一个 JSON 对象,描述每个 CSS 变换函数,键为函数名,值为描述函数属性的对象。它生成TransformFunctions.h与TransformFunctions.cpp。
每个条目目前只有一个属性parameters(参数定义对象数组),参数定义字段如下:
| 字段 | 描述 |
|---|---|
type | 字符串。参数可接受类型 |
required | 布尔值。该参数是否必填 |
与数学函数不同,变换函数的参数定义不携带name字段。仓库中的示例——matrix()有 6 个必填<number>参数,matrix3d()则有 16 个:
"matrix": { "parameters": [ { "type": "<number>", "required": true }, { "type": "<number>", "required": true }, ... 共 6 个 ... ] }, "matrix3d": { "parameters": [ ... 共 16 个 ... ] }(见 TransformFunctions.json、TransformFunctions.json。)
生成的代码提供:
TransformFunction枚举,列出全部变换函数Optional<TransformFunction> transform_function_from_string(StringView):把字符串解析为TransformFunctionStringView to_string(TransformFunction):把TransformFunction转回字符串TransformFunctionMetadata transform_function_metadata(TransformFunction):获取函数元数据(如参数列表)
九、生成器实现要点
代码生成器统一位于 Meta/Lagom/Tools/CodeGenerators/LibWeb,每个 JSON 文件对应一个GenerateCSS*工具:
| JSON 输入 | 生成器 | 主要输出 |
|---|---|---|
Properties.json | GenerateCSSPropertyID.cpp | PropertyID.h/cpp、GeneratedCSSStyleProperties.h/cpp/idl |
Keywords.json | GenerateCSSKeyword.cpp | Keyword.h/cpp |
Enums.json | GenerateCSSEnums.cpp | Enums.h/cpp |
PseudoClasses.json | GenerateCSSPseudoClass.cpp | PseudoClass.h/cpp |
MediaFeatures.json | GenerateCSSMediaFeatureID.cpp | MediaFeatureID.h/cpp |
MathFunctions.json | GenerateCSSMathFunctions.cpp | MathFunctions.h/cpp |
TransformFunctions.json | GenerateCSSTransformFunctions.cpp | TransformFunctions.h/cpp |
从实现细节看(以 GenerateCSSPropertyID.cpp 为例):
- 生成器以
LibMain/Main.h为入口,使用LibCore::ArgsParser解析命令行参数,通过AK::SourceGenerator组织输出文本(见 GenerateCSSPropertyID.cpp)。 - 输出代码中注入必要的头文件(如
AK/NonnullRefPtr.h、LibJS/Forward.h、LibWeb/Forward.h),保证生成的.h/.cpp可以独立编译(见 GenerateCSSPropertyID.cpp)。 - 生成的
.cpp还会#include <LibWeb/CSS/Enums.h>,把Enums.json生成的枚举直接嵌入属性类型判定逻辑(见 GenerateCSSPropertyID.cpp)——这印证了Enums.json与Properties.json之间的联动关系。 - 生成器会自动跳过只含
legacy-alias-for的别名属性,避免为别名生成重复的完整定义。
由于生成是构建期自动完成的,修改 JSON 后重新构建即可看到新生成的代码出现在Build/<build-preset>/Lagom/Userland/Libraries/LibWeb/CSS/中。日常开发中基本无需手工触碰生成产物。
十、实战工作流:如何新增一个 CSS 属性或关键字
综合以上内容,在 SerenityOS/LibWeb 中新增一个 CSS 能力通常遵循以下步骤:
- 登记关键字:如果新属性用到的新关键字尚未收录,先在 Keywords.json 的字符串数组中追加(所有属性、媒体特性共用的关键字池)。
- 定义枚举(可选):若属性接受一组固定关键字(如新的
border-*-style同类属性),在 Enums.json 中添加枚举定义,便于在多个属性间复用。 - 注册属性:在 Properties.json 中为属性新增条目,按需填写必填字段
animation-type、inherited、initial,以及valid-types/valid-identifiers、longhands、max-values、percentages-resolve-to、quirks等可选字段。 - 新增伪类/媒体特性/函数(视情况):在对应的 PseudoClasses.json(填
argument语法)、MediaFeatures.json(填type与values)、MathFunctions.json 或 TransformFunctions.json(填parameters)中登记。 - 重新构建:构建系统会自动运行对应生成器,产出新的枚举、ID 与解析函数;此后即可在解析器、样式计算与布局代码中消费这些生成接口。
- 查阅生成产物:如需确认生成结果,查看
Build/<build-preset>/Lagom/Userland/Libraries/LibWeb/CSS/下的新文件。
这套流程的每个环节都有源码级支撑:关键字注册表、枚举复用、属性元数据、伪类语法、媒体特性类型、数学/变换函数参数,全部由统一的 JSON 数据驱动,最终在构建期被Meta/Lagom/Tools/CodeGenerators/LibWeb下对应的生成器转换为可编译、可调用的 C++ 代码。这也是 LibWeb 能够以较低维护成本跟上 CSS 规范演进的关键机制之一。
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考