diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2023-05-17 17:23:42 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2023-05-25 06:36:10 +0200 |
commit | bcacc2357ec7b365da5a9f5e5551b6184771151f (patch) | |
tree | 0874011fbd5c15802f3a7399749835f1c29133d4 /Userland/Libraries/LibWeb | |
parent | da4b2d9ca367e8370ff69f0e734969781c11eff0 (diff) | |
download | serenity-bcacc2357ec7b365da5a9f5e5551b6184771151f.zip |
LibWeb: Implement smarter CSS StyleValue parsing
We know what types and identifiers a property can accept, so we can use
that information to only parse things that can be accepted. This solves
some awkward ambiguity problems that we have now or will face in the
future, including:
- Is `0` a number or a length with no unit?
- Is `3.5` a number or a ratio?
- Is `bottom` an identifier, or a custom-ident?
Two CSS Parser methods are introduced here:
`parse_css_value_for_property()` attempts to parse a StyleValue that the
property can accept, skipping any types that it doesn't want.
`parse_css_value_for_properties()` does the same, but takes multiple
PropertyIDs and additionally returns which one the parsed StyleValue is
for. This is intended for parsing shorthands, so you can give it a list
of longhands you haven't yet parsed.
Subsequent commits will actually use these new methods.
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 165 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 6 |
2 files changed, 171 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 676bda7b5c..4b6c8bd5fe 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -7147,6 +7147,171 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue>> Parser::parse_css_value(Property #undef FIXME_TRY } +ErrorOr<RefPtr<StyleValue>> Parser::parse_css_value_for_property(PropertyID property_id, TokenStream<ComponentValue>& tokens) +{ + auto result = parse_css_value_for_properties({ &property_id, 1 }, tokens); + if (result.is_error()) + return result.release_error(); + return result.value().style_value; +} + +ErrorOr<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(ReadonlySpan<PropertyID> property_ids, TokenStream<ComponentValue>& tokens) +{ + auto any_property_accepts_type = [](ReadonlySpan<PropertyID> property_ids, ValueType value_type) -> Optional<PropertyID> { + for (auto const& property : property_ids) { + if (property_accepts_type(property, value_type)) + return property; + } + return {}; + }; + auto any_property_accepts_identifier = [](ReadonlySpan<PropertyID> property_ids, ValueID identifier) -> Optional<PropertyID> { + for (auto const& property : property_ids) { + if (property_accepts_identifier(property, identifier)) + return property; + } + return {}; + }; + + auto& peek_token = tokens.peek_token(); + + if (peek_token.is(Token::Type::Ident)) { + // NOTE: We do not try to parse "CSS-wide keywords" here. https://www.w3.org/TR/css-values-4/#common-keywords + // These are only valid on their own, and so should be parsed directly in `parse_css_value()`. + auto ident = value_id_from_string(peek_token.token().ident()); + if (ident.has_value()) { + if (auto property = any_property_accepts_identifier(property_ids, ident.value()); property.has_value()) { + (void)tokens.next_token(); + return PropertyAndValue { *property, TRY(IdentifierStyleValue::create(ident.value())) }; + } + } + + // FIXME: Custom idents. https://www.w3.org/TR/css-values-4/#identifier-value + } + + if (auto property = any_property_accepts_type(property_ids, ValueType::Color); property.has_value()) { + if (auto maybe_color = TRY(parse_color_value(peek_token))) { + (void)tokens.next_token(); + return PropertyAndValue { *property, maybe_color }; + } + } + + if (auto property = any_property_accepts_type(property_ids, ValueType::Image); property.has_value()) { + if (auto maybe_image = TRY(parse_image_value(peek_token))) { + (void)tokens.next_token(); + return PropertyAndValue { *property, maybe_image }; + } + } + + auto property_accepting_integer = any_property_accepts_type(property_ids, ValueType::Integer); + auto property_accepting_number = any_property_accepts_type(property_ids, ValueType::Number); + bool property_accepts_numeric = property_accepting_integer.has_value() || property_accepting_number.has_value(); + + if (peek_token.is(Token::Type::Number) && property_accepts_numeric) { + auto numeric = TRY(parse_numeric_value(peek_token)); + (void)tokens.next_token(); + if (numeric->as_numeric().has_integer() && property_accepting_integer.has_value()) + return PropertyAndValue { *property_accepting_integer, numeric }; + return PropertyAndValue { property_accepting_integer.value_or(property_accepting_number.value()), numeric }; + } + + if (peek_token.is(Token::Type::Percentage)) { + if (auto property = any_property_accepts_type(property_ids, ValueType::Percentage); property.has_value()) { + (void)tokens.next_token(); + return PropertyAndValue { *property, TRY(PercentageStyleValue::create(Percentage(peek_token.token().percentage()))) }; + } + } + + if (auto property = any_property_accepts_type(property_ids, ValueType::Rect); property.has_value()) { + if (auto maybe_rect = TRY(parse_rect_value(peek_token))) { + (void)tokens.next_token(); + return PropertyAndValue { *property, maybe_rect }; + } + } + + if (peek_token.is(Token::Type::String)) { + if (auto property = any_property_accepts_type(property_ids, ValueType::String); property.has_value()) + return PropertyAndValue { *property, TRY(StringStyleValue::create(TRY(String::from_utf8(tokens.next_token().token().string())))) }; + } + + if (auto property = any_property_accepts_type(property_ids, ValueType::Url); property.has_value()) { + if (auto url = TRY(parse_url_value(peek_token))) { + (void)tokens.next_token(); + return PropertyAndValue { *property, url }; + } + } + + bool property_accepts_dimension = any_property_accepts_type(property_ids, ValueType::Angle).has_value() + || any_property_accepts_type(property_ids, ValueType::Length).has_value() + || any_property_accepts_type(property_ids, ValueType::Percentage).has_value() + || any_property_accepts_type(property_ids, ValueType::Resolution).has_value() + || any_property_accepts_type(property_ids, ValueType::Time).has_value(); + + if (property_accepts_dimension) { + if (auto maybe_dimension = parse_dimension(peek_token); maybe_dimension.has_value()) { + (void)tokens.next_token(); + auto dimension = maybe_dimension.release_value(); + if (dimension.is_angle()) { + if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value()) + return PropertyAndValue { *property, TRY(AngleStyleValue::create(dimension.angle())) }; + } + if (dimension.is_frequency()) { + if (auto property = any_property_accepts_type(property_ids, ValueType::Frequency); property.has_value()) + return PropertyAndValue { *property, TRY(FrequencyStyleValue::create(dimension.frequency())) }; + } + if (dimension.is_length()) { + if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value()) + return PropertyAndValue { *property, TRY(LengthStyleValue::create(dimension.length())) }; + } + if (dimension.is_resolution()) { + if (auto property = any_property_accepts_type(property_ids, ValueType::Resolution); property.has_value()) + return PropertyAndValue { *property, TRY(ResolutionStyleValue::create(dimension.resolution())) }; + } + if (dimension.is_time()) { + if (auto property = any_property_accepts_type(property_ids, ValueType::Time); property.has_value()) + return PropertyAndValue { *property, TRY(TimeStyleValue::create(dimension.time())) }; + } + } + } + + // In order to not end up parsing `calc()` and other math expressions multiple times, + // we parse it once, and then see if its resolved type matches what the property accepts. + if (peek_token.is_function() && (property_accepts_dimension || property_accepts_numeric)) { + if (auto maybe_dynamic = TRY(parse_dynamic_value(peek_token)); maybe_dynamic && maybe_dynamic->is_calculated()) { + (void)tokens.next_token(); + auto& calculated = maybe_dynamic->as_calculated(); + switch (calculated.resolved_type()) { + case CalculatedStyleValue::ResolvedType::Angle: + if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value()) + return PropertyAndValue { *property, calculated }; + break; + case CalculatedStyleValue::ResolvedType::Frequency: + if (auto property = any_property_accepts_type(property_ids, ValueType::Frequency); property.has_value()) + return PropertyAndValue { *property, calculated }; + break; + case CalculatedStyleValue::ResolvedType::Integer: + case CalculatedStyleValue::ResolvedType::Number: + if (property_accepts_numeric) + return PropertyAndValue { property_accepting_integer.value_or(property_accepting_number.value()), calculated }; + break; + case CalculatedStyleValue::ResolvedType::Length: + if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value()) + return PropertyAndValue { *property, calculated }; + break; + case CalculatedStyleValue::ResolvedType::Percentage: + if (auto property = any_property_accepts_type(property_ids, ValueType::Percentage); property.has_value()) + return PropertyAndValue { *property, calculated }; + break; + case CalculatedStyleValue::ResolvedType::Time: + if (auto property = any_property_accepts_type(property_ids, ValueType::Time); property.has_value()) + return PropertyAndValue { *property, calculated }; + break; + } + } + } + + return PropertyAndValue { property_ids.first(), nullptr }; +} + ErrorOr<RefPtr<StyleValue>> Parser::parse_css_value(ComponentValue const& component_value) { if (auto builtin = TRY(parse_builtin_value(component_value))) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 4b1514ee72..0022efa282 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -280,6 +280,12 @@ private: ErrorOr<RefPtr<StyleValue>> parse_radial_gradient_function(ComponentValue const&); ParseErrorOr<NonnullRefPtr<StyleValue>> parse_css_value(PropertyID, TokenStream<ComponentValue>&); + ErrorOr<RefPtr<StyleValue>> parse_css_value_for_property(PropertyID, TokenStream<ComponentValue>&); + struct PropertyAndValue { + PropertyID property; + RefPtr<StyleValue> style_value; + }; + ErrorOr<PropertyAndValue> parse_css_value_for_properties(ReadonlySpan<PropertyID>, TokenStream<ComponentValue>&); ErrorOr<RefPtr<StyleValue>> parse_css_value(ComponentValue const&); ErrorOr<RefPtr<StyleValue>> parse_builtin_value(ComponentValue const&); ErrorOr<RefPtr<StyleValue>> parse_dynamic_value(ComponentValue const&); |