)
SerenityOS LibWeb CSS 代碼生成體系從 JSON 定義到 C 實現(xiàn)【免費下載鏈接】serenityThe Serenity Operating System 項目地址: https://gitcode.com/GitHub_Trending/se/serenity導(dǎo)讀SerenityOS 的 LibWeb 引擎在構(gòu)建時會從一組 JSON 文件中批量生成大量 CSS 相關(guān) C 代碼。這些文件定義了每個 CSS 屬性的取值、繼承性、初始值、動畫類型以及關(guān)鍵字、枚舉、偽類、媒體特性、數(shù)學(xué)函數(shù)與變換函數(shù)等元數(shù)據(jù)。本文以 CSSGeneratedFiles.md 為主線完整梳理 7 個 JSON 輸入文件的結(jié)構(gòu)與字段語義并結(jié)合 LibWeb/CSS 源碼目錄與 LibWeb 代碼生成器 中的實現(xiàn)細節(jié)講解如何為瀏覽器引擎新增或修改一個 CSS 屬性及其取值。讀完本文你將掌握 LibWeb 中 CSS 元數(shù)據(jù)的組織方式、生成器的產(chǎn)出清單以及參與 CSS 規(guī)范實現(xiàn)時的標(biāo)準工作流。一、整體架構(gòu)JSON 定義、生成器與構(gòu)建集成LibWeb 的 CSS 實現(xiàn)采用數(shù)據(jù)驅(qū)動 構(gòu)建期代碼生成的模式輸入一個或多個.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等。輸出生成結(jié)果落在構(gòu)建目錄Build/build-preset/Lagom/Userland/Libraries/LibWeb/CSS/下如PropertyID.h/cpp、Keyword.h/cpp等。這些生成器會在構(gòu)建過程中自動運行通常開發(fā)者無需手動干預(yù)。但當(dāng)你需要新增或修改一個 CSS 屬性及其取值時就必然要與這些 JSON 文件打交道。生成器內(nèi)部通過AK::SourceGenerator輸出 C 代碼并借助 GeneratorUtil.h 等公共工具完成頭文件守衛(wèi)、write_if_changed之類的落盤邏輯參見 GenerateCSSPropertyID.cpp。二、Properties.jsonCSS 屬性的注冊中心Properties.json約 2800 行為每個 CSS 屬性維護一條記錄描述它接受哪些值、是否繼承、初始值等元數(shù)據(jù)。它會生成以下文件PropertyID.h/PropertyID.cpp屬性 ID 枚舉及各種查詢函數(shù)GeneratedCSSStyleProperties.h/GeneratedCSSStyleProperties.cpp綁定到 WebIDL 的屬性訪問器GeneratedCSSStyleProperties.idl文件結(jié)構(gòu)是一個 JSON 對象鍵為屬性名值為該屬性的數(shù)據(jù)。大多數(shù)元數(shù)據(jù)都來自對應(yīng) CSS 規(guī)范中該屬性的信息框information box。每個屬性會帶有下表的部分字段注意帶legacy-alias-for或logical-alias-for的屬性不要求必填字段字段必填默認值描述生成的函數(shù)affects-layout否true布爾值。修改該屬性是否會令元素的布局失效bool property_affects_layout(PropertyID)affects-stacking-context否false布爾值。該屬性是否會讓元素產(chǎn)生新的層疊上下文bool property_affects_stacking_context(PropertyID)animation-type是字符串。規(guī)范定義的屬性動畫方式見下文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否無字符串?dāng)?shù)組。該屬性所指向的邏輯別名屬性見下文longhands否[]字符串?dāng)?shù)組。若是簡寫屬性shorthand列出其展開的子屬性VectorPropertyID longhands_for_shorthand(PropertyID)max-values否1整數(shù)。該屬性最多可解析多少個值例如margin最多 4 個size_t property_maximum_value_count(PropertyID)percentages-resolve-to否無字符串。百分比解析成什么類型例如width的百分比解析為lengthOptionalValueType property_resolves_percentages_relative_to(PropertyID)quirks否[]字符串?dāng)?shù)組。屬性在 quirks 模式下的特殊行為見下文bool property_has_quirk(PropertyID, Quirk)valid-identifiers否[]字符串?dāng)?shù)組。屬性接受哪些關(guān)鍵字。更推薦定義枚舉并把枚舉名寫進valid-typesbool property_accepts_keyword(PropertyID, Keyword)valid-types否[]字符串?dāng)?shù)組。屬性接受哪些值類型見下文bool property_accepts_type(PropertyID, ValueType)從源碼看生成器會逐個遍歷該 JSON 對象先判斷屬性是否設(shè)置了legacy-alias-forGenerateCSSPropertyID.cpp處理邏輯別名再檢查longhandsGenerateCSSPropertyID.cpp與valid-types數(shù)組GenerateCSSPropertyID.cpp最終為每個屬性產(chǎn)出對應(yīng)的枚舉成員、初始值函數(shù)與類型判定函數(shù)。倉庫中的真實示例可以直觀印證字段用法。例如兼容性前綴屬性的定義非常精簡-webkit-align-content: { legacy-alias-for: align-content }而一個完整屬性則同時攜帶動畫類型、繼承性與初始值例如color一類animation-type: by-computed-value, inherited: true, initial: currentColor, valid-types: [ ... ]對應(yīng) Properties.json 附近的color定義animation簡寫屬性則使用initial: none 0s ease 1 normal running 0s none這樣的復(fù)合初始值見 Properties.json。2.1animation-type屬性如何被動畫化該字段的合法取值由 Web Animations 規(guī)范定義JSON 值與規(guī)范術(shù)語的對應(yīng)關(guān)系如下規(guī)范術(shù)語JSON 值not animatablenonediscretediscreteby computed valueby-computed-valuerepeatable listrepeatable-list見規(guī)范正文custom從倉庫數(shù)據(jù)看color是by-computed-value按計算值平滑過渡align-content等布局相關(guān)屬性是discrete離散跳變animation-duration則是none不可動畫。2.2legacy-alias-for與logical-alias-for兩個名字相似但概念不同的別名舊名別名legacy name alias屬性在規(guī)范中的名字發(fā)生了變化但語法沒有變因此設(shè)置舊名等同于直接設(shè)置新名。例如font-stretch被重命名為font-width于是font-stretch成為font-width的舊名別名。倉庫中大量-webkit-*屬性就是這類別名的典型-webkit-align-content、-webkit-animation、-webkit-border-radius等全部通過legacy-alias-for指回標(biāo)準屬性名見 Properties.json。邏輯別名logical alias例如margin-block-start它會根據(jù)應(yīng)用到的元素把值賦給margin-top、margin-bottom、margin-left或margin-right中的某一個。因此需要在logical-alias-for中列出所有可能被其指向的屬性。2.3quirksQuirks 模式下的特殊行為Quirks 規(guī)范定義了以下兩種特殊行為規(guī)范術(shù)語JSON 值The hashless hex color quirkhashless-hex-colorThe unitless length quirkunitless-length例如在 quirks 模式下允許background-color: f00這種省略#的十六進制顏色寫法或width: 10這種省略單位的長度寫法。是否啟用這些寬松解析正是由該字段驅(qū)動。2.4valid-types值類型與帶括號區(qū)間記法valid-types數(shù)組列出的是 CSS Values and Units 規(guī)范中定義的值類型名去掉尖括號后的名字。對數(shù)值類型項目使用帶括號區(qū)間記法bracketed range notation例如width可以接受任意非負長度因此其valid-types數(shù)組中含有l(wèi)ength [0,∞]。這種寫法讓生成器可以直接生成帶范圍約束的解析與校驗邏輯將規(guī)范約束落到類型系統(tǒng)層面。三、Keywords.json全局關(guān)鍵字注冊表Keywords.json共 430 行是一個純字符串?dāng)?shù)組每個元素是一個 CSS 關(guān)鍵字例如auto、none、medium、currentcolor。它會生成Keyword.h與Keyword.cpp。任何屬性或媒體特性用到的關(guān)鍵字都必須在這里登記。除了標(biāo)準關(guān)鍵字倉庫中還登記了一批內(nèi)部關(guān)鍵字例如-libweb-center、-libweb-left、-libweb-link以及一系列-libweb-palette-*關(guān)鍵字如-libweb-palette-base、-libweb-palette-selection它們用于把 SerenityOS 系統(tǒng)調(diào)色板暴露給 Web 內(nèi)容見 Keywords.json。這說明了該文件的擴展邊界不僅服務(wù)標(biāo)準 CSS也承載瀏覽器自身的私有擴展。生成的代碼提供Keyword枚舉供CSSKeywordValue使用OptionalKeyword keyword_from_string(StringView)嘗試把字符串轉(zhuǎn)成KeywordStringView string_from_keyword(Keyword)把Keyword轉(zhuǎn)回字符串bool is_css_wide_keyword(StringView)判斷字符串是否為特殊的 CSS-wide keywords如inherit、initial、unset、revert四、Enums.json一鍵生成關(guān)鍵字集合枚舉Enums.json共 521 行是一個 JSON 對象鍵是枚舉名值是關(guān)鍵字名數(shù)組。它生成Enums.h與Enums.cpp。很多屬性需要接受一組固定的關(guān)鍵字逐個重復(fù)書寫valid-identifiers容易出錯且冗長。Enums.json允許自動生成這類枚舉以及枚舉與Keyword、字符串之間的互轉(zhuǎn)函數(shù)。生成的枚舉還可以直接通過枚舉名出現(xiàn)在Properties.json的valid-types數(shù)組中從而在屬性定義中被復(fù)用。典型的例子是border-*-style系列屬性接受同一組關(guān)鍵字因此被實現(xiàn)為line-style枚舉見 Enums.json。倉庫數(shù)據(jù)還顯示align-content、align-items、align-self等各自的取值集合也都以枚舉形式集中定義見 Enums.json。以枚舉 foo 為例每個枚舉生成的代碼包括枚舉類型FooOptionalFoo keyword_to_foo(Keyword)把Keyword轉(zhuǎn)換為FooKeyword to_keyword(Foo)把Foo轉(zhuǎn)回KeywordStringView to_string(Foo)直接把Foo轉(zhuǎn)成字符串五、PseudoClasses.json偽類元數(shù)據(jù)PseudoClasses.json共 146 行是一個 JSON 對象鍵為選擇器偽類名值為描述該偽類的對象。它生成PseudoClass.h與PseudoClass.cpp。每個條目只有一個必填字段argument它是偽類函數(shù)參數(shù)的語法grammar字符串對標(biāo)識符式偽類如:hover、:active則為空字符串。語法直接取自規(guī)范。倉庫中的實例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直接承載了后續(xù)解析函數(shù)所需的關(guān)鍵信息。生成的代碼提供PseudoClass枚舉列出所有偽類名OptionalPseudoClass pseudo_class_from_string(StringView)把字符串解析為PseudoClassStringView pseudo_class_name(PseudoClass)把PseudoClass轉(zhuǎn)回字符串PseudoClassMetadata結(jié)構(gòu)體保存 JSON 文件中的數(shù)據(jù)PseudoClassMetadata pseudo_class_metadata(PseudoClass)獲取該元數(shù)據(jù)六、MediaFeatures.jsonmedia可查詢的媒體特性MediaFeatures.json共 261 行是一個 JSON 對象鍵為媒體特性名值為描述該特性的對象。它生成MediaFeatureID.h與MediaFeatureID.cpp。media-feature是媒體查詢可以檢查的取值列在最新 Media Queries 規(guī)范的media描述符表中。這里的定義可以看作Properties.json定義的簡化版本字段描述type字符串。媒體特性的求值方式discrete離散或range范圍values字符串?dāng)?shù)組。直接取自規(guī)范關(guān)鍵字原樣保留類型名帶。類型可以是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表示設(shè)備是否支持懸停二者求值邏輯截然不同。生成的代碼提供MediaFeatureValueType枚舉列出可能的取值類型MediaFeatureID枚舉列出每個媒體特性O(shè)ptionalMediaFeatureID media_feature_id_from_string(StringView)字符串轉(zhuǎn)MediaFeatureIDStringView string_from_media_feature_id(MediaFeatureID)MediaFeatureID轉(zhuǎn)回字符串bool media_feature_type_is_range(MediaFeatureID)判斷是否為range類型區(qū)別于discretebool media_feature_accepts_type(MediaFeatureID, MediaFeatureValueType)是否接受該值類型bool media_feature_accepts_keyword(MediaFeatureID, Keyword)是否接受該關(guān)鍵字七、MathFunctions.jsonCSS 數(shù)學(xué)函數(shù)MathFunctions.json共 232 行是一個 JSON 對象描述每個 CSS 數(shù)學(xué)函數(shù)鍵為函數(shù)名值為描述函數(shù)屬性的對象。它生成MathFunctions.h與MathFunctions.cpp。每個條目目前只有一個屬性parameters即參數(shù)定義對象數(shù)組。參數(shù)定義具有以下字段字段描述name字符串。參數(shù)名與規(guī)范一致type字符串。參數(shù)可接受類型單個字符串用\|分隔required布爾值。該參數(shù)是否必填倉庫中的示例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)的兩個參數(shù)都是必填的數(shù)值類參數(shù)abs(value)接受數(shù)值、維度或百分比。生成的代碼提供MathFunction枚舉列出全部數(shù)學(xué)函數(shù)CSS Parser 的parse_math_function()方法的實現(xiàn)也就是說MathFunctions.json不止生成數(shù)據(jù)還會直接生成解析器的函數(shù)體把支持哪些數(shù)學(xué)函數(shù)、每個函數(shù)接受什么參數(shù)編譯進解析流程。八、TransformFunctions.jsonCSS 變換函數(shù)TransformFunctions.json共 290 行是一個 JSON 對象描述每個 CSS 變換函數(shù)鍵為函數(shù)名值為描述函數(shù)屬性的對象。它生成TransformFunctions.h與TransformFunctions.cpp。每個條目目前只有一個屬性parameters參數(shù)定義對象數(shù)組參數(shù)定義字段如下字段描述type字符串。參數(shù)可接受類型required布爾值。該參數(shù)是否必填與數(shù)學(xué)函數(shù)不同變換函數(shù)的參數(shù)定義不攜帶name字段。倉庫中的示例——matrix()有 6 個必填number參數(shù)matrix3d()則有 16 個matrix: { parameters: [ { type: number, required: true }, { type: number, required: true }, ... 共 6 個 ... ] }, matrix3d: { parameters: [ ... 共 16 個 ... ] }見 TransformFunctions.json、TransformFunctions.json。生成的代碼提供TransformFunction枚舉列出全部變換函數(shù)OptionalTransformFunction transform_function_from_string(StringView)把字符串解析為TransformFunctionStringView to_string(TransformFunction)把TransformFunction轉(zhuǎn)回字符串TransformFunctionMetadata transform_function_metadata(TransformFunction)獲取函數(shù)元數(shù)據(jù)如參數(shù)列表九、生成器實現(xiàn)要點代碼生成器統(tǒng)一位于 Meta/Lagom/Tools/CodeGenerators/LibWeb每個 JSON 文件對應(yīng)一個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從實現(xiàn)細節(jié)看以 GenerateCSSPropertyID.cpp 為例生成器以LibMain/Main.h為入口使用LibCore::ArgsParser解析命令行參數(shù)通過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之間的聯(lián)動關(guān)系。生成器會自動跳過只含legacy-alias-for的別名屬性避免為別名生成重復(fù)的完整定義。由于生成是構(gòu)建期自動完成的修改 JSON 后重新構(gòu)建即可看到新生成的代碼出現(xiàn)在Build/build-preset/Lagom/Userland/Libraries/LibWeb/CSS/中。日常開發(fā)中基本無需手工觸碰生成產(chǎn)物。十、實戰(zhàn)工作流如何新增一個 CSS 屬性或關(guān)鍵字綜合以上內(nèi)容在 SerenityOS/LibWeb 中新增一個 CSS 能力通常遵循以下步驟登記關(guān)鍵字如果新屬性用到的新關(guān)鍵字尚未收錄先在 Keywords.json 的字符串?dāng)?shù)組中追加所有屬性、媒體特性共用的關(guān)鍵字池。定義枚舉可選若屬性接受一組固定關(guān)鍵字如新的border-*-style同類屬性在 Enums.json 中添加枚舉定義便于在多個屬性間復(fù)用。注冊屬性在 Properties.json 中為屬性新增條目按需填寫必填字段animation-type、inherited、initial以及valid-types/valid-identifiers、longhands、max-values、percentages-resolve-to、quirks等可選字段。新增偽類/媒體特性/函數(shù)視情況在對應(yīng)的 PseudoClasses.json填argument語法、MediaFeatures.json填type與values、MathFunctions.json 或 TransformFunctions.json填parameters中登記。重新構(gòu)建構(gòu)建系統(tǒng)會自動運行對應(yīng)生成器產(chǎn)出新的枚舉、ID 與解析函數(shù)此后即可在解析器、樣式計算與布局代碼中消費這些生成接口。查閱生成產(chǎn)物如需確認生成結(jié)果查看Build/build-preset/Lagom/Userland/Libraries/LibWeb/CSS/下的新文件。這套流程的每個環(huán)節(jié)都有源碼級支撐關(guān)鍵字注冊表、枚舉復(fù)用、屬性元數(shù)據(jù)、偽類語法、媒體特性類型、數(shù)學(xué)/變換函數(shù)參數(shù)全部由統(tǒng)一的 JSON 數(shù)據(jù)驅(qū)動最終在構(gòu)建期被Meta/Lagom/Tools/CodeGenerators/LibWeb下對應(yīng)的生成器轉(zhuǎn)換為可編譯、可調(diào)用的 C 代碼。這也是 LibWeb 能夠以較低維護成本跟上 CSS 規(guī)范演進的關(guān)鍵機制之一。【免費下載鏈接】serenityThe Serenity Operating System 項目地址: https://gitcode.com/GitHub_Trending/se/serenity創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考