LibWeb/CSS: Mark list-valued properties

Typed-OM requires us to have a generic way of asking "does property X
accept a list or a single value?" so this exists mainly for that.
Coordinating lists are annotated too - I'm not clear on exactly what
will be needed for those, but giving them a unique value now at the
worst will make them easier to find later.
This commit is contained in:
Sam Atkins 2025-09-19 12:13:15 +01:00 committed by Andreas Kling
parent b3ad4be90c
commit 1e1752b33b
3 changed files with 123 additions and 19 deletions

View File

@ -20,7 +20,7 @@ Each property will have some set of these fields on it:
(Note that required fields are not required on properties with `legacy-alias-for` or `logical-alias-for` set.)
| Field | Required | Default | Description | Generated functions |
|-----------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|-------------------------------------|----------|------------|-------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `affects-layout` | No | `true` | Boolean. Whether changing this property will invalidate the element's layout. | `bool property_affects_layout(PropertyID)` |
| `affects-stacking-context` | No | `false` | Boolean. Whether this property can cause a new stacking context for the element. | `bool property_affects_stacking_context(PropertyID)` |
| `animation-type` | Yes | | String. How the property should be animated. Defined by the spec. See below. | `AnimationType animation_type_from_longhand_property(PropertyID)` |
@ -30,6 +30,7 @@ Each property will have some set of these fields on it:
| `logical-alias-for` | No | Nothing | An object. See below. | `bool property_is_logical_alias(PropertyID);`<br/>`PropertyID map_logical_alias_to_physical_property(PropertyID, LogicalAliasMappingContext const&)` |
| `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> shorthands_for_longhand(PropertyID)` |
| `max-values` | No | `1` | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values. | `size_t property_maximum_value_count(PropertyID)` |
| `multiplicity` | No | `"single"` | String. Category for whether this property is a single value or a list of values. See below. | `bool property_is_single_valued(PropertyID)`<br/>`bool property_is_list_valued(PropertyID)` |
| `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional<ValueType> property_resolves_percentages_relative_to(PropertyID)` |
| `positional-value-list-shorthand` | No | `false` | Boolean. Whether this property is a "positional value list shorthand". See below. | `bool property_is_positional_value_list_shorthand(PropertyID)` |
| `quirks` | No | `[]` | Array of strings. Some properties have special behavior in "quirks mode", which are listed here. See below. | `bool property_has_quirk(PropertyID, Quirk)` |
@ -69,6 +70,16 @@ Logical aliases are properties like `margin-block-start`, which may assign a val
| `group` | String. Name of the logical property group this is associated with. (See [LogicalPropertyGroups.json](#logicalpropertygroupsjson).) |
| `mapping` | String. How this relates to the group. eg, if it's the block end value, `block-end`. |
### `multiplicity`
The three possible values are `"single"`, `"list"`, and `"coordinating-list"`.
Most properties represent a single "thing", possibly made of multiple parts. For example, `border` takes a color, style
and thickness, but it's still a single border. Others are a list: `background` takes multiple layers, separated by
commas; `counter-increment` doesn't have commas but also is a list of counters that are incremented.
Lists can also be [coordinating list properties](https://drafts.csswg.org/css-values-4/#linked-properties), in which
case they are marked as `coordinating-list` instead of `list`.
### `positional-value-list-shorthand`
Some shorthand properties work differently to normal in that mapping of provided values to longhands isn't necessarily
1-to-1 and instead depends on the number of values provided, for example `margin`, `border-width`, `gap`, etc.

View File

@ -198,6 +198,8 @@
"affects-layout": false,
"inherited": false,
"initial": "none 0s ease 1 normal running 0s none",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"longhands": [
"animation-duration",
"animation-timing-function",
@ -225,6 +227,8 @@
"animation-type": "none",
"inherited": false,
"initial": "0s",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"valid-types": [
"time [-∞,∞]"
]
@ -234,6 +238,8 @@
"animation-type": "none",
"inherited": false,
"initial": "normal",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"valid-identifiers": [
"normal",
"reverse",
@ -246,6 +252,8 @@
"animation-type": "none",
"inherited": false,
"initial": "0s",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"valid-types": [
"time [0,∞]"
],
@ -258,6 +266,8 @@
"animation-type": "none",
"inherited": false,
"initial": "none",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"valid-identifiers": [
"none",
"forwards",
@ -270,6 +280,8 @@
"animation-type": "none",
"inherited": false,
"initial": "1",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"valid-types": [
"number [0,∞]"
],
@ -282,6 +294,8 @@
"animation-type": "none",
"inherited": false,
"initial": "none",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"valid-types": [
"string",
"custom-ident ![none]"
@ -295,6 +309,8 @@
"animation-type": "none",
"inherited": false,
"initial": "running",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"valid-identifiers": [
"running",
"paused"
@ -305,6 +321,8 @@
"animation-type": "none",
"inherited": false,
"initial": "ease",
"__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single",
"valid-types": [
"easing-function"
]
@ -336,6 +354,7 @@
"inherited": false,
"initial": "none",
"__comment": "FIXME: List `filter-value-list` as a valid-type once it's generically supported.",
"multiplicity": "list",
"valid-identifiers": [
"none"
]
@ -353,13 +372,15 @@
"background-position",
"background-repeat",
"background-size"
]
],
"multiplicity": "coordinating-list"
},
"background-attachment": {
"affects-layout": false,
"animation-type": "discrete",
"inherited": false,
"initial": "scroll",
"multiplicity": "coordinating-list",
"valid-types": [
"background-attachment"
]
@ -368,6 +389,7 @@
"animation-type": "discrete",
"inherited": false,
"initial": "normal",
"multiplicity": "coordinating-list",
"valid-types": [
"mix-blend-mode"
]
@ -377,6 +399,7 @@
"animation-type": "repeatable-list",
"inherited": false,
"initial": "border-box",
"multiplicity": "coordinating-list",
"valid-types": [
"background-box"
]
@ -398,6 +421,7 @@
"animation-type": "discrete",
"inherited": false,
"initial": "none",
"multiplicity": "coordinating-list",
"valid-types": [
"image"
],
@ -410,6 +434,7 @@
"animation-type": "repeatable-list",
"inherited": false,
"initial": "padding-box",
"multiplicity": "coordinating-list",
"valid-types": [
"background-box"
]
@ -419,6 +444,7 @@
"inherited": false,
"initial": "0% 0%",
"max-values": 4,
"multiplicity": "coordinating-list",
"valid-types": [
"background-position"
],
@ -436,6 +462,7 @@
"animation-type": "repeatable-list",
"inherited": false,
"initial": "0%",
"multiplicity": "coordinating-list",
"valid-types": [
"length [-∞,∞]",
"percentage [-∞,∞]"
@ -452,6 +479,7 @@
"animation-type": "repeatable-list",
"inherited": false,
"initial": "0%",
"multiplicity": "coordinating-list",
"valid-types": [
"length [-∞,∞]",
"percentage [-∞,∞]"
@ -468,6 +496,7 @@
"animation-type": "discrete",
"inherited": false,
"initial": "repeat",
"multiplicity": "coordinating-list",
"max-values": 2,
"valid-types": [
"repetition"
@ -482,6 +511,7 @@
"animation-type": "repeatable-list",
"inherited": false,
"initial": "auto",
"multiplicity": "coordinating-list",
"max-values": 2,
"valid-types": [
"length [0,∞]",
@ -1158,6 +1188,7 @@
"animation-type": "custom",
"inherited": false,
"initial": "none",
"multiplicity": "list",
"valid-identifiers": [
"none"
]
@ -1386,6 +1417,7 @@
"animation-type": "by-computed-value",
"inherited": false,
"initial": "none",
"multiplicity": "list",
"valid-types": [
"custom-ident ![none]",
"integer [-∞,∞]"
@ -1398,6 +1430,7 @@
"animation-type": "by-computed-value",
"inherited": false,
"initial": "none",
"multiplicity": "list",
"valid-types": [
"custom-ident ![none]",
"integer [-∞,∞]"
@ -1410,6 +1443,7 @@
"animation-type": "by-computed-value",
"inherited": false,
"initial": "none",
"multiplicity": "list",
"valid-types": [
"custom-ident ![none]",
"integer [-∞,∞]"
@ -1423,6 +1457,7 @@
"animation-type": "discrete",
"inherited": true,
"initial": "auto",
"multiplicity": "list",
"valid-types": [
"url",
"cursor-predefined",
@ -1524,6 +1559,7 @@
"inherited": false,
"initial": "none",
"__comment": "FIXME: List `filter-value-list` as a valid-type once it's generically supported.",
"multiplicity": "list",
"valid-identifiers": [
"none"
]
@ -1642,6 +1678,7 @@
"animation-type": "discrete",
"inherited": true,
"initial": "serif",
"multiplicity": "list",
"valid-types": [
"custom-ident",
"string"
@ -1651,6 +1688,7 @@
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"multiplicity": "list",
"valid-types": [
"integer [0,∞]",
"opentype-tag"
@ -1786,6 +1824,7 @@
"animation-type": "custom",
"inherited": true,
"initial": "normal",
"multiplicity": "list",
"valid-types": [
"number",
"opentype-tag"
@ -2444,6 +2483,7 @@
"inherited": false,
"affects-layout": false,
"initial": "none",
"multiplicity": "coordinating-list",
"longhands": [
"mask-clip",
"mask-composite",
@ -2459,6 +2499,7 @@
"animation-type": "discrete",
"inherited": false,
"affects-layout": false,
"multiplicity": "coordinating-list",
"valid-types": [
"coord-box"
],
@ -2471,6 +2512,7 @@
"animation-type": "discrete",
"inherited": false,
"affects-layout": false,
"multiplicity": "coordinating-list",
"valid-types": [
"compositing-operator"
],
@ -2481,6 +2523,7 @@
"inherited": false,
"affects-layout": false,
"affects-stacking-context": true,
"multiplicity": "coordinating-list",
"valid-types": [
"image",
"url"
@ -2494,6 +2537,7 @@
"animation-type": "discrete",
"inherited": false,
"affects-layout": false,
"multiplicity": "coordinating-list",
"valid-types": [
"masking-mode"
],
@ -2503,6 +2547,7 @@
"animation-type": "discrete",
"inherited": false,
"affects-layout": false,
"multiplicity": "coordinating-list",
"valid-types": [
"coord-box"
],
@ -2513,6 +2558,7 @@
"affects-layout": false,
"inherited": false,
"initial": "0% 0%",
"multiplicity": "coordinating-list",
"valid-types": [
"position"
],
@ -2523,6 +2569,7 @@
"animation-type": "discrete",
"inherited": false,
"initial": "repeat",
"multiplicity": "coordinating-list",
"max-values": 2,
"valid-types": [
"repetition"
@ -2537,6 +2584,7 @@
"animation-type": "repeatable-list",
"inherited": false,
"initial": "auto",
"multiplicity": "coordinating-list",
"max-values": 2,
"valid-types": [
"length [0,∞]",
@ -2553,6 +2601,7 @@
"animation-type": "discrete",
"inherited": false,
"affects-layout": false,
"multiplicity": "coordinating-list",
"valid-types": [
"mask-type"
],
@ -3048,6 +3097,7 @@
"animation-type": "discrete",
"inherited": true,
"initial": "auto",
"multiplicity": "list",
"valid-types": [
"string"
],
@ -3508,7 +3558,8 @@
"initial": "none",
"affects-layout": false,
"affects-stacking-context": true,
"percentages-resolve-to": "length"
"percentages-resolve-to": "length",
"multiplicity": "list"
},
"transform-box": {
"animation-type": "discrete",
@ -3542,6 +3593,7 @@
"affects-layout": false,
"inherited": false,
"initial": "none",
"multiplicity": "coordinating-list",
"longhands": [
"transition-property",
"transition-duration",
@ -3555,6 +3607,7 @@
"animation-type": "none",
"inherited": false,
"initial": "normal",
"multiplicity": "coordinating-list",
"valid-types": [
"transition-behavior"
]
@ -3564,6 +3617,7 @@
"animation-type": "none",
"inherited": false,
"initial": "0s",
"multiplicity": "coordinating-list",
"valid-types": [
"time"
]
@ -3573,6 +3627,7 @@
"animation-type": "none",
"inherited": false,
"initial": "0s",
"multiplicity": "coordinating-list",
"valid-types": [
"time [0,∞]"
]
@ -3582,6 +3637,7 @@
"animation-type": "none",
"inherited": false,
"initial": "all",
"multiplicity": "coordinating-list",
"valid-types": [
"custom-ident ![all,none]"
],
@ -3595,6 +3651,7 @@
"animation-type": "none",
"inherited": false,
"initial": "ease",
"multiplicity": "coordinating-list",
"valid-types": [
"easing-function"
]
@ -3729,6 +3786,7 @@
"animation-type": "none",
"inherited": false,
"initial": "auto",
"multiplicity": "list",
"valid-types": [
"custom-ident ![all,auto,contents,none,scroll-position,will-change]"
],

View File

@ -254,6 +254,9 @@ WEB_API Optional<PropertyID> property_id_from_string(StringView);
WEB_API bool is_inherited_property(PropertyID);
NonnullRefPtr<StyleValue const> property_initial_value(PropertyID);
bool property_is_single_valued(PropertyID);
bool property_is_list_valued(PropertyID);
bool property_accepts_type(PropertyID, ValueType);
struct AcceptedTypeRange {
float min;
@ -775,6 +778,38 @@ NonnullRefPtr<StyleValue const> property_initial_value(PropertyID property_id)
VERIFY_NOT_REACHED();
}
bool property_is_single_valued(PropertyID property_id)
{
return !property_is_list_valued(property_id);
}
bool property_is_list_valued(PropertyID property_id)
{
switch (property_id) {
)~~~");
properties.for_each_member([&](auto& name, JsonValue const& value) {
auto property = value.as_object();
if (auto multiplicity = property.get_string("multiplicity"sv);
multiplicity.has_value() && multiplicity != "single"sv) {
if (!first_is_one_of(multiplicity, "list"sv, "coordinating-list"sv)) {
dbgln("'{}' is not a valid value for 'multiplicity'. Accepted values are: 'single', 'list', 'coordinating-list'", multiplicity.value());
VERIFY_NOT_REACHED();
}
auto property_generator = generator.fork();
property_generator.set("name:titlecase", title_casify(name));
property_generator.appendln(" case PropertyID::@name:titlecase@:");
}
});
generator.append(R"~~~(
return true;
default:
return false;
}
}
bool property_has_quirk(PropertyID property_id, Quirk quirk)
{
switch (property_id) {