@lynx-js/css-defines
v0.0.7
Published
Property definitions of Lynx Styling module
Readme
CSS Generator
This package serves as the source of truth for all CSS APIs defined in the Lynx platform. It's used to generate C++ code in the engine for Lynx contributors, as well as Types consumed by the Lynx frontend developers.
Table of Contents
CSS Property Definition
Property vs Attribute
Properties are features of an element that you can change the value of, to make it styled differently (e.g. color, font-size). If the feature is not related to element's style, or it is strongly coupled with a specific element type (e.g. initial-scroll-offset on <scroll-view>), it is recommended to add it as an attribute. Most times, the style property should have a prototype in the W3C CSS specification.
If the implementation of the property is non-standardized, or it is under experimental, it should be a prefixed property.
Vendor Prefix
-x- is the vendor prefix used by Lynx to distinguish CSS properties that are specific to Lynx and not part of the Web standard. They provide Web developers with some layout and styling capabilities specific to Lynx, such as:
.linear {
/* similar to when Grid layout was -ms-grid, -webkit-grid: */
display: -x-linear;
display: linear; /* should also work */
-x-linear-orientation: horizontal;
}Adding a New Property
Once you have decided to add a new property, you can follow the steps below:
Add a definition file under the
css_definesdirectory. The file name should begin with an incremental number indicating the ID of the property. We use the ID at runtime to map the property's parser, getter and setter functions to avoid string comparison. So the ID should be unique, and should not be modified after it is added. The consistency of the ID will be checked according toproperty_index.json. The new ID and property name will be automatically added to the index file bycss_parser_generator.py.Execute
python tools/css_generator/css_parser_generator.pyImplement the code in generated files.
There are a couple of functions that cannot be automatically implemented by the script. According to the complexity of the property's value, you may need to implement the following functions:- Parser: The parser function is used to parse the value of the property.
- ComputedCSSStyle: The setter and getter functions should be manually implemented according to the output of parser. If the property will be consumed by the platform UI layer, you have to add it to the macro
FOREACH_PLATFORM_PROPERTYin the header file.
TypeScript Type Generation
Type Generation Rules
The type definitions in js_libraries/types/types/common/csstype.d.ts are automatically generated from CSS define files in the css_defines directory. The generation process follows these rules:
For enum types (properties with
"type": "enum"):- Uses the
valuesarray to generate a union type of string literals - Example:
display?: 'none' | 'flex' | 'grid' | 'linear' | 'relative' | 'block' | 'auto';
- Uses the
For properties with keywords:
- Uses the
keywordsarray to generate a union type of string literals - Adds
(string & {})for open-ended string types - Example:
animationTimingFunction?: 'linear' | 'ease-in' | 'ease-out' | ... | (string & {});
- Uses the
For other types:
- Uses
stringtype - Example:
color?: string;
- Uses
Build Process
The build process generates TypeScript type definitions and copies them to the types package:
npm run gen:types- Generates types in thedist/directory (gitignored)npm run copy:types- Copies generated types tojs_libraries/types/types/common/npm run build- Runs both steps in sequence
Note: The dist/ directory is gitignored because it's an intermediate build artifact. The final types are always in js_libraries/types/types/common/csstype.d.ts.
Usage
# Validate all CSS defines
npm run validate
# Test validation with invalid CSS defines
npm run test
# Generate types and copy to types package
npm run build
# Generate types in dist/ for testing (without copying)
npm run gen:typesSchema
The schema in css_define_json_schema/css_define_with_doc.schema.json defines the structure of CSS define files and is used for both validation and type generation.
Tutorial: Implementing a CSS Property
The following example demonstrates how to add a new CSS property to the system:
{
"name": "test",
"id": 213,
"type": "complex",
"default_value": "auto",
"version": "1.0",
"author": "wangerpao",
"consumption_status": "layout-only",
"desc": "left offset",
"keywords": ["foo", "bar", "foobar"],
"values": [
{
"value": "test-value",
"version": "1.0"
}
],
"links": [
{
"url": "<reference docs>",
"desc": "description of the reference"
},
{
"url": "123"
}
],
"note": [
{
"literal": "this is a note",
"level": "tip"
},
{
"literal": "This is a warning for user of this property.",
"level": "warning"
}
],
"__compat": {
"description": "<Description of this compat data entry>",
"lynx_path": "<path to api reference in lynx website> docs/zh/api/css/properties/left)",
"mdn_url": "<path to mdn definition> https://developer.mozilla.org/zh-CN/docs/Web/CSS/left",
"spec_url": ["<path to w3c specification file>"],
"status": {
"deprecated": false,
"experimental": false
},
"support": {
"android": {
"version_added": "1.0"
},
"ios": {
"version_added": "1.0"
}
}
}
}Create a new file named
999-test.jsonand copy the above JSON into it.Run
css_parser_generator.pyto generate the necessary files.The
css_property_id.hfile will be autogenerated undercore/renderer/css. You will find your newly added propertytestappended to the end of the macroFOREACH_ALL_PROPERTY, as well as the property enum classCSSPropertyID.Implement the parser for the property value:
- If the type is one of
color,length,time,enum,border-width,border-style,bool,timing-functionoranimation-property, the parser will be auto-generated - Otherwise, manually add it to
core/renderer/css/parserdirectory
Create two new files,
test_handler.handtest_handler.ccunder the directory:#include "core/renderer/css/parser/handler_defines.h" namespace lynx { namespace tasm { namespace TestHandler { HANDLER_REGISTER_DECLARE(); } // namespace TestHandler } // namespace tasm } // namespace lynxRegister your parsing functions into the 'array' to your property id, and implement the parser functions, which converts the input string value into a CSSValue object and put it into the 'output' map. It is recommended that you implement the parser based on CSSStringParser, as it already has some basic tokenizers and lexical checks.
#include "core/renderer/css/parser/test_handler.h" #include <string> #include <utility> #include "base/include/debug/lynx_assert.h" #include "core/renderer/css/parser/css_string_parser.h" #include "core/renderer/css/unit_handler.h" #include "core/renderer/tasm/config.h" namespace lynx { namespace tasm { namespace TestHandler { HANDLER_IMPL() { CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode, TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key), STRING_TYPE) CSSStringParser parser = CSSStringParser::FromLepusString(input, configs); parser.SetIsLegacyParser(configs.enable_legacy_parser); output[kPropertyIDTest] = parser.ParseTest(); return true; } HANDLER_REGISTER_IMPL() { array[kPropertyIDTest] = &Handle; } } // namespace TestHandler } // namespace tasm } // namespace lynx- If the type is one of
Register the parser by adding your customized handler to
core/renderer/css/parser/unit_handler.cc:UnitHandler::UnitHandler() { TestHandler::Register(interceptors_); }Implement setter & getter for ComputedCSSStyle:
After converting the raw string into a CSSValue, in a ComputedCSSValue, it should be a data struct based on primitive types. Implement the value conversion from CSSValue, and calculate it if your value is context related (e.g. length value with
spunit is related to root element'sfont-size).If your property will be consumed by platform layer, add it to macro
FOREACH_PLATFORM_PROPERTY. Then implement your function inprop_bundle_style_writer, to enable the runtime to put your computed value into prop bundle, and send it to platform layer.#define FOREACH_PLATFORM_PROPERTY(V) \ V(Test) bool ComputedCSSValue::SetTest(const tasm::CSSValue& value, bool reset); // In prop_bundle_style_writer static void TestWriterFunc(PropBundle* bundle, CSSPropertyID id, starlight::ComputedCSSStyle* style); static constexpr std::array<WriterFunc, kPropertyEnd> kWriter = [] { std::array<WriterFunc, kPropertyEnd> writer = {nullptr}; for (CSSPropertyID id : kPlatformIDs) { writer[id] = &DefaultWriterFunc; } writer[kPropertyIDTest] = &TestWriterFunc; return writer; }();
