mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 00:19:53 +01:00
LibWeb/CSS: Implement "legacy value aliases" in generated code
This uses a `foo>bar` notation in the `valid-identifiers` field of Properties.json, to say "replace `foo` with `bar`". The motivation here is to avoid calling `parse_css_value_for_property()` inside the per-property switch in `parse_css_value()`. Eventually we'll need to be able to call that switch from `parse_css_value_for_properties()` so that shorthands can make use of any bespoke parsing code to parse their longhands.
This commit is contained in:
parent
062862f315
commit
943cc0e32a
|
|
@ -19,22 +19,22 @@ 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)` |
|
||||
| `inherited` | Yes | | Boolean. Whether the property is inherited by its child elements. | `bool is_inherited_property(PropertyID)` |
|
||||
| `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr<CSSStyleValue const> property_initial_value(PropertyID)` |
|
||||
| `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | |
|
||||
| `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)` |
|
||||
| `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)` |
|
||||
| `valid-identifiers` | No | `[]` | Array of strings. Which keywords the property accepts. Consider defining an enum instead and putting its name in the `valid-types` array. | `bool property_accepts_keyword(PropertyID, Keyword)` |
|
||||
| `valid-types` | No | `[]` | Array of strings. Which value types the property accepts. See below. | `bool property_accepts_type(PropertyID, ValueType)` |
|
||||
| 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)` |
|
||||
| `inherited` | Yes | | Boolean. Whether the property is inherited by its child elements. | `bool is_inherited_property(PropertyID)` |
|
||||
| `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr<CSSStyleValue const> property_initial_value(PropertyID)` |
|
||||
| `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | |
|
||||
| `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)` |
|
||||
| `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)` |
|
||||
| `valid-identifiers` | No | `[]` | Array of strings. Which keywords the property accepts. See below. | `bool property_accepts_keyword(PropertyID, Keyword)`<br/>`Optional<Keyword> resolve_legacy_value_alias(PropertyID, Keyword)` |
|
||||
| `valid-types` | No | `[]` | Array of strings. Which value types the property accepts. See below. | `bool property_accepts_type(PropertyID, ValueType)` |
|
||||
|
||||
### `animation-type`
|
||||
|
||||
|
|
@ -84,6 +84,14 @@ The [Quirks spec](https://quirks.spec.whatwg.org/#css) defines these.
|
|||
| The hashless hex color quirk | `hashless-hex-color` |
|
||||
| The unitless length quirk | `unitless-length` |
|
||||
|
||||
### `valid-identifiers`
|
||||
|
||||
A list of CSS keyword names, that the property accepts. Consider defining an enum instead and putting its name in the
|
||||
`valid-types` array, especially if the spec provides a name to a set of such keywords.
|
||||
|
||||
Some properties have [legacy value aliases](https://drafts.csswg.org/css-cascade-5/#css-legacy-value-alias), where one
|
||||
keyword is parsed as another. These are supported as `"foo>bar"`, to make `foo` an alias for `bar`.
|
||||
|
||||
### `valid-types`
|
||||
|
||||
The `valid-types` array lists the names of CSS value types, as defined in the latest
|
||||
|
|
|
|||
|
|
@ -140,6 +140,8 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
|
|||
if (keyword.has_value()) {
|
||||
if (auto property = any_property_accepts_keyword(property_ids, keyword.value()); property.has_value()) {
|
||||
tokens.discard_a_token();
|
||||
if (auto resolved_keyword = resolve_legacy_value_alias(property.value(), keyword.value()); resolved_keyword.has_value())
|
||||
return PropertyAndValue { *property, CSSKeywordValue::create(resolved_keyword.value()) };
|
||||
return PropertyAndValue { *property, CSSKeywordValue::create(keyword.value()) };
|
||||
}
|
||||
}
|
||||
|
|
@ -673,17 +675,6 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue const>> Parser::parse_css_value
|
|||
if (auto parsed_value = parse_overflow_value(tokens); parsed_value && !tokens.has_next_token())
|
||||
return parsed_value.release_nonnull();
|
||||
return ParseError::SyntaxError;
|
||||
case PropertyID::OverflowX:
|
||||
case PropertyID::OverflowY:
|
||||
if (auto parsed_value = parse_css_value_for_property(property_id, tokens); parsed_value && !tokens.has_next_token()) {
|
||||
// https://drafts.csswg.org/css-overflow-3/#valdef-overflow-overlay
|
||||
// User agents must also support the overlay keyword as a legacy value alias of auto.
|
||||
// FIXME: Figure out a generic way of supporting legacy value aliases.
|
||||
if (parsed_value->to_keyword() == Keyword::Overlay)
|
||||
return CSSKeywordValue::create(Keyword::Auto);
|
||||
return parsed_value.release_nonnull();
|
||||
}
|
||||
return ParseError::SyntaxError;
|
||||
case PropertyID::PlaceContent:
|
||||
if (auto parsed_value = parse_place_content_value(tokens); parsed_value && !tokens.has_next_token())
|
||||
return parsed_value.release_nonnull();
|
||||
|
|
@ -724,17 +715,6 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue const>> Parser::parse_css_value
|
|||
if (auto parsed_value = parse_text_decoration_line_value(tokens); parsed_value && !tokens.has_next_token())
|
||||
return parsed_value.release_nonnull();
|
||||
return ParseError::SyntaxError;
|
||||
case PropertyID::TextJustify:
|
||||
if (auto parsed_value = parse_css_value_for_property(property_id, tokens); parsed_value && !tokens.has_next_token()) {
|
||||
// https://drafts.csswg.org/css-text-3/#valdef-text-justify-distribute
|
||||
// For legacy reasons, UAs must also support the alternate keyword distribute which must compute to
|
||||
// inter-character, thus having the exact same meaning and behavior. UAs may treat this as a legacy value alias.
|
||||
// FIXME: Figure out a generic way of supporting legacy value aliases.
|
||||
if (parsed_value->to_keyword() == Keyword::Distribute)
|
||||
return CSSKeywordValue::create(Keyword::InterCharacter);
|
||||
return parsed_value.release_nonnull();
|
||||
}
|
||||
return ParseError::SyntaxError;
|
||||
case PropertyID::TextShadow:
|
||||
if (auto parsed_value = parse_shadow_value(tokens, AllowInsetKeyword::No); parsed_value && !tokens.has_next_token())
|
||||
return parsed_value.release_nonnull();
|
||||
|
|
@ -3622,24 +3602,11 @@ RefPtr<CSSStyleValue const> Parser::parse_opacity_value(PropertyID property_id,
|
|||
|
||||
RefPtr<CSSStyleValue const> Parser::parse_overflow_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// https://drafts.csswg.org/css-overflow-3/#valdef-overflow-overlay
|
||||
// User agents must also support the overlay keyword as a legacy value alias of auto.
|
||||
// FIXME: Figure out a generic way of supporting legacy value aliases.
|
||||
// FIXME: Even better, make parsing the longhands go through their proper parsing routine.
|
||||
auto parse_an_overflow_longhand_value = [this](PropertyID property, auto& tokens) -> RefPtr<CSSStyleValue const> {
|
||||
auto maybe_value = parse_css_value_for_property(property, tokens);
|
||||
if (!maybe_value)
|
||||
return nullptr;
|
||||
if (maybe_value->to_keyword() == Keyword::Overlay)
|
||||
return CSSKeywordValue::create(Keyword::Auto);
|
||||
return maybe_value.release_nonnull();
|
||||
};
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
auto maybe_x_value = parse_an_overflow_longhand_value(PropertyID::OverflowX, tokens);
|
||||
auto maybe_x_value = parse_css_value_for_property(PropertyID::OverflowX, tokens);
|
||||
if (!maybe_x_value)
|
||||
return nullptr;
|
||||
auto maybe_y_value = parse_an_overflow_longhand_value(PropertyID::OverflowY, tokens);
|
||||
auto maybe_y_value = parse_css_value_for_property(PropertyID::OverflowY, tokens);
|
||||
transaction.commit();
|
||||
if (maybe_y_value) {
|
||||
return ShorthandStyleValue::create(PropertyID::Overflow,
|
||||
|
|
|
|||
|
|
@ -2649,7 +2649,7 @@
|
|||
"overflow"
|
||||
],
|
||||
"valid-identifiers": [
|
||||
"overlay"
|
||||
"overlay>auto"
|
||||
]
|
||||
},
|
||||
"overflow-y": {
|
||||
|
|
@ -2660,7 +2660,7 @@
|
|||
"overflow"
|
||||
],
|
||||
"valid-identifiers": [
|
||||
"overlay"
|
||||
"overlay>auto"
|
||||
]
|
||||
},
|
||||
"padding": {
|
||||
|
|
@ -3163,7 +3163,7 @@
|
|||
"text-justify"
|
||||
],
|
||||
"valid-identifiers": [
|
||||
"distribute"
|
||||
"distribute>inter-character"
|
||||
]
|
||||
},
|
||||
"text-overflow": {
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@ NonnullRefPtr<CSSStyleValue const> property_initial_value(PropertyID);
|
|||
|
||||
bool property_accepts_type(PropertyID, ValueType);
|
||||
bool property_accepts_keyword(PropertyID, Keyword);
|
||||
Optional<Keyword> resolve_legacy_value_alias(PropertyID, Keyword);
|
||||
Optional<ValueType> property_resolves_percentages_relative_to(PropertyID);
|
||||
Vector<StringView> property_custom_ident_blacklist(PropertyID);
|
||||
|
||||
|
|
@ -904,7 +905,7 @@ bool property_accepts_keyword(PropertyID property_id, Keyword keyword)
|
|||
{
|
||||
switch (property_id) {
|
||||
)~~~");
|
||||
properties.for_each_member([&](auto& name, auto& value) {
|
||||
properties.for_each_member([&](auto& name, JsonValue const& value) {
|
||||
VERIFY(value.is_object());
|
||||
auto& object = value.as_object();
|
||||
if (is_legacy_alias(object))
|
||||
|
|
@ -917,9 +918,15 @@ bool property_accepts_keyword(PropertyID property_id, Keyword keyword)
|
|||
if (auto maybe_valid_identifiers = object.get_array("valid-identifiers"sv); maybe_valid_identifiers.has_value() && !maybe_valid_identifiers->is_empty()) {
|
||||
property_generator.appendln(" switch (keyword) {");
|
||||
auto& valid_identifiers = maybe_valid_identifiers.value();
|
||||
for (auto& keyword : valid_identifiers.values()) {
|
||||
for (auto& keyword_value : valid_identifiers.values()) {
|
||||
auto keyword_generator = generator.fork();
|
||||
keyword_generator.set("keyword:titlecase", title_casify(keyword.as_string()));
|
||||
auto const& keyword_string = keyword_value.as_string();
|
||||
if (keyword_string.contains('>')) {
|
||||
auto parts = MUST(keyword_string.split_limit('>', 2));
|
||||
keyword_generator.set("keyword:titlecase", title_casify(parts[0]));
|
||||
} else {
|
||||
keyword_generator.set("keyword:titlecase", title_casify(keyword_string));
|
||||
}
|
||||
keyword_generator.appendln(" case Keyword::@keyword:titlecase@:");
|
||||
}
|
||||
property_generator.append(R"~~~(
|
||||
|
|
@ -956,6 +963,62 @@ bool property_accepts_keyword(PropertyID property_id, Keyword keyword)
|
|||
}
|
||||
}
|
||||
|
||||
Optional<Keyword> resolve_legacy_value_alias(PropertyID property_id, Keyword keyword)
|
||||
{
|
||||
switch (property_id) {
|
||||
)~~~");
|
||||
properties.for_each_member([&](auto& name, JsonValue const& value) {
|
||||
VERIFY(value.is_object());
|
||||
auto& object = value.as_object();
|
||||
if (is_legacy_alias(object))
|
||||
return;
|
||||
if (auto maybe_valid_identifiers = object.get_array("valid-identifiers"sv); maybe_valid_identifiers.has_value() && !maybe_valid_identifiers->is_empty()) {
|
||||
auto& valid_identifiers = maybe_valid_identifiers.value();
|
||||
|
||||
bool has_any_legacy_value_aliases = false;
|
||||
for (auto& keyword_value : valid_identifiers.values()) {
|
||||
if (keyword_value.as_string().contains('>')) {
|
||||
has_any_legacy_value_aliases = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_any_legacy_value_aliases)
|
||||
return;
|
||||
|
||||
auto property_generator = generator.fork();
|
||||
property_generator.set("name:titlecase", title_casify(name));
|
||||
property_generator.append(R"~~~(
|
||||
case PropertyID::@name:titlecase@:
|
||||
switch (keyword) {)~~~");
|
||||
for (auto& keyword_value : valid_identifiers.values()) {
|
||||
auto const& keyword_string = keyword_value.as_string();
|
||||
if (!keyword_string.contains('>'))
|
||||
continue;
|
||||
|
||||
auto keyword_generator = generator.fork();
|
||||
auto parts = MUST(keyword_string.split_limit('>', 2));
|
||||
keyword_generator.set("from_keyword:titlecase", title_casify(parts[0]));
|
||||
keyword_generator.set("to_keyword:titlecase", title_casify(parts[1]));
|
||||
keyword_generator.append(R"~~~(
|
||||
case Keyword::@from_keyword:titlecase@:
|
||||
return Keyword::@to_keyword:titlecase@;)~~~");
|
||||
}
|
||||
property_generator.append(R"~~~(
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
)~~~");
|
||||
}
|
||||
});
|
||||
|
||||
generator.append(R"~~~(
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<ValueType> property_resolves_percentages_relative_to(PropertyID property_id)
|
||||
{
|
||||
switch (property_id) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user