
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.jsonCSS 属性的注册中心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是字符串。未指定时属性的初始值NonnullRefPtrCSSStyleValue property_initial_value(JS::Realm, PropertyID)legacy-alias-for否无字符串。该属性所指向的旧名别名属性见下文logical-alias-for否无字符串数组。该属性所指向的逻辑别名属性见下文longhands否[]字符串数组。若是简写属性shorthand列出其展开的子属性VectorPropertyID longhands_for_shorthand(PropertyID)max-values否1整数。该属性最多可解析多少个值例如margin最多 4 个size_t property_maximum_value_count(PropertyID)percentages-resolve-to否无字符串。百分比解析成什么类型例如width的百分比解析为lengthOptionalValueType property_resolves_percentages_relative_to(PropertyID)quirks否[]字符串数组。属性在 quirks 模式下的特殊行为见下文bool property_has_quirk(PropertyID, Quirk)valid-identifiers否[]字符串数组。属性接受哪些关键字。更推荐定义枚举并把枚举名写进valid-typesbool property_accepts_keyword(PropertyID, Keyword)valid-types否[]字符串数组。属性接受哪些值类型见下文bool property_accepts_type(PropertyID, ValueType)从源码看生成器会逐个遍历该 JSON 对象先判断属性是否设置了legacy-alias-forGenerateCSSPropertyID.cpp处理逻辑别名再检查longhandsGenerateCSSPropertyID.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 animatablenonediscretediscreteby computed valueby-computed-valuerepeatable listrepeatable-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.3quirksQuirks 模式下的特殊行为Quirks 规范定义了以下两种特殊行为规范术语JSON 值The hashless hex color quirkhashless-hex-colorThe unitless length quirkunitless-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使用OptionalKeyword 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 为例每个枚举生成的代码包括枚举类型FooOptionalFoo 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枚举列出所有伪类名OptionalPseudoClass pseudo_class_from_string(StringView)把字符串解析为PseudoClassStringView pseudo_class_name(PseudoClass)把PseudoClass转回字符串PseudoClassMetadata结构体保存 JSON 文件中的数据PseudoClassMetadata pseudo_class_metadata(PseudoClass)获取该元数据六、MediaFeatures.jsonmedia可查询的媒体特性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类型表示颜色位深为 nany-hover使用discrete表示设备是否支持悬停二者求值逻辑截然不同。生成的代码提供MediaFeatureValueType枚举列出可能的取值类型MediaFeatureID枚举列出每个媒体特性OptionalMediaFeatureID media_feature_id_from_string(StringView)字符串转MediaFeatureIDStringView string_from_media_feature_id(MediaFeatureID)MediaFeatureID转回字符串bool media_feature_type_is_range(MediaFeatureID)判断是否为range类型区别于discretebool media_feature_accepts_type(MediaFeatureID, MediaFeatureValueType)是否接受该值类型bool media_feature_accepts_keyword(MediaFeatureID, Keyword)是否接受该关键字七、MathFunctions.jsonCSS 数学函数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.jsonCSS 变换函数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枚举列出全部变换函数OptionalTransformFunction transform_function_from_string(StringView)把字符串解析为TransformFunctionStringView to_string(TransformFunction)把TransformFunction转回字符串TransformFunctionMetadata transform_function_metadata(TransformFunction)获取函数元数据如参数列表九、生成器实现要点代码生成器统一位于 Meta/Lagom/Tools/CodeGenerators/LibWeb每个 JSON 文件对应一个GenerateCSS*工具JSON 输入生成器主要输出Properties.jsonGenerateCSSPropertyID.cppPropertyID.h/cpp、GeneratedCSSStyleProperties.h/cpp/idlKeywords.jsonGenerateCSSKeyword.cppKeyword.h/cppEnums.jsonGenerateCSSEnums.cppEnums.h/cppPseudoClasses.jsonGenerateCSSPseudoClass.cppPseudoClass.h/cppMediaFeatures.jsonGenerateCSSMediaFeatureID.cppMediaFeatureID.h/cppMathFunctions.jsonGenerateCSSMathFunctions.cppMathFunctions.h/cppTransformFunctions.jsonGenerateCSSTransformFunctions.cppTransformFunctions.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),仅供参考