diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2022-01-25 14:45:08 +0000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-02-04 13:52:02 +0100 |
commit | b69f6097ded6b50ee049b097623fc379c78f588d (patch) | |
tree | 006ac62ef7065e90d793e21bb484421a7af4b912 /Userland/Libraries | |
parent | b818d952d16cd6b575bb474c5483dd2beafab8e9 (diff) | |
download | serenity-b69f6097ded6b50ee049b097623fc379c78f588d.zip |
LibWeb: Resolve type of calc() expressions at parse-time
See https://www.w3.org/TR/css-values-3/#calc-type-checking
If the sub-expressions' types are incompatible, we discard the calc() as
invalid.
Had to do some minor rearranging/renaming of the Calc structs to make
the `resolve_foo_type()` templates work too.
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 59 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleValue.cpp | 176 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleValue.h | 54 |
4 files changed, 265 insertions, 25 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 1534364ebf..0721ffacf2 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -2091,17 +2091,52 @@ RefPtr<StyleValue> Parser::parse_builtin_value(StyleComponentValueRule const& co return {}; } +RefPtr<StyleValue> Parser::parse_calculated_value(Vector<StyleComponentValueRule> const& component_values) +{ + auto calc_expression = parse_calc_expression(component_values); + if (calc_expression == nullptr) + return nullptr; + + auto calc_type = calc_expression->resolved_type(); + if (!calc_type.has_value()) { + dbgln("calc() resolved as invalid!!!"); + return nullptr; + } + + [[maybe_unused]] auto to_string = [](CalculatedStyleValue::ResolvedType type) { + switch (type) { + case CalculatedStyleValue::ResolvedType::Angle: + return "Angle"sv; + case CalculatedStyleValue::ResolvedType::Frequency: + return "Frequency"sv; + case CalculatedStyleValue::ResolvedType::Integer: + return "Integer"sv; + case CalculatedStyleValue::ResolvedType::Length: + return "Length"sv; + case CalculatedStyleValue::ResolvedType::Number: + return "Number"sv; + case CalculatedStyleValue::ResolvedType::Percentage: + return "Percentage"sv; + case CalculatedStyleValue::ResolvedType::Time: + return "Time"sv; + } + VERIFY_NOT_REACHED(); + }; + dbgln_if(CSS_PARSER_DEBUG, "Deduced calc() resolved type as: {}", to_string(calc_type.value())); + + // FIXME: Either produce a string value of calc() here, or do so in CalculatedStyleValue::to_string(). + return CalculatedStyleValue::create("(FIXME:calc to string)", calc_expression.release_nonnull(), calc_type.release_value()); +} + RefPtr<StyleValue> Parser::parse_dynamic_value(StyleComponentValueRule const& component_value) { if (component_value.is_function()) { auto& function = component_value.function(); - if (function.name().equals_ignoring_case("calc")) { - auto calc_expression = parse_calc_expression(function.values()); - // FIXME: Either produce a string value of calc() here, or do so in CalculatedStyleValue::to_string(). - if (calc_expression) - return CalculatedStyleValue::create("(FIXME:calc to string)", calc_expression.release_nonnull()); - } else if (function.name().equals_ignoring_case("var")) { + if (function.name().equals_ignoring_case("calc")) + return parse_calculated_value(function.values()); + + if (function.name().equals_ignoring_case("var")) { // Declarations using `var()` should already be parsed as an UnresolvedStyleValue before this point. VERIFY_NOT_REACHED(); } @@ -4189,7 +4224,7 @@ OwnPtr<CalculatedStyleValue::CalcProductPartWithOperator> Parser::parse_calc_pro // Note: The default value is not used or passed around. auto product_with_operator = make<CalculatedStyleValue::CalcProductPartWithOperator>( CalculatedStyleValue::ProductOperation::Multiply, - CalculatedStyleValue::CalcNumberValue(0)); + CalculatedStyleValue::CalcNumberValue { 0 }); tokens.skip_whitespace(); @@ -4227,7 +4262,7 @@ OwnPtr<CalculatedStyleValue::CalcNumberProductPartWithOperator> Parser::parse_ca // Note: The default value is not used or passed around. auto number_product_with_operator = make<CalculatedStyleValue::CalcNumberProductPartWithOperator>( CalculatedStyleValue::ProductOperation::Multiply, - CalculatedStyleValue::CalcNumberValue(0)); + CalculatedStyleValue::CalcNumberValue { 0 }); tokens.skip_whitespace(); @@ -4259,7 +4294,7 @@ OwnPtr<CalculatedStyleValue::CalcNumberProductPartWithOperator> Parser::parse_ca OwnPtr<CalculatedStyleValue::CalcNumberProduct> Parser::parse_calc_number_product(TokenStream<StyleComponentValueRule>& tokens) { auto calc_number_product = make<CalculatedStyleValue::CalcNumberProduct>( - CalculatedStyleValue::CalcNumberValue(0), + CalculatedStyleValue::CalcNumberValue { 0 }, NonnullOwnPtrVector<CalculatedStyleValue::CalcNumberProductPartWithOperator> {}); auto first_calc_number_value_or_error = parse_calc_number_value(tokens); @@ -4330,20 +4365,20 @@ Optional<CalculatedStyleValue::CalcNumberValue> Parser::parse_calc_number_value( auto block_values = TokenStream(first.block().values()); auto calc_number_sum = parse_calc_number_sum(block_values); if (calc_number_sum) - return { calc_number_sum.release_nonnull() }; + return CalculatedStyleValue::CalcNumberValue { calc_number_sum.release_nonnull() }; } if (!first.is(Token::Type::Number)) return {}; tokens.next_token(); - return first.token().number_value(); + return CalculatedStyleValue::CalcNumberValue { static_cast<float>(first.token().number_value()) }; } OwnPtr<CalculatedStyleValue::CalcProduct> Parser::parse_calc_product(TokenStream<StyleComponentValueRule>& tokens) { auto calc_product = make<CalculatedStyleValue::CalcProduct>( - CalculatedStyleValue::CalcValue(0), + CalculatedStyleValue::CalcValue { 0 }, NonnullOwnPtrVector<CalculatedStyleValue::CalcProductPartWithOperator> {}); auto first_calc_value_or_error = parse_calc_value(tokens); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 38713894ed..0149c7df82 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -213,6 +213,7 @@ private: RefPtr<StyleValue> parse_css_value(StyleComponentValueRule const&); RefPtr<StyleValue> parse_builtin_value(StyleComponentValueRule const&); RefPtr<StyleValue> parse_dynamic_value(StyleComponentValueRule const&); + RefPtr<StyleValue> parse_calculated_value(Vector<StyleComponentValueRule> const&); RefPtr<StyleValue> parse_dimension_value(StyleComponentValueRule const&); RefPtr<StyleValue> parse_numeric_value(StyleComponentValueRule const&); RefPtr<StyleValue> parse_identifier_value(StyleComponentValueRule const&); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp index dad0f97289..67d057ca83 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp @@ -289,7 +289,7 @@ Optional<Length> CalculatedStyleValue::resolve_length(Layout::Node const& layout static float resolve_calc_value(CalculatedStyleValue::CalcValue const& calc_value, Layout::Node const& layout_node) { - return calc_value.visit( + return calc_value.value.visit( [](float value) { return value; }, [&](Length const& length) { return length.resolved_or_zero(layout_node).to_px(layout_node); @@ -325,7 +325,7 @@ static float resolve_calc_number_sum(NonnullOwnPtr<CalculatedStyleValue::CalcNum auto value = resolve_calc_number_product(calc_number_sum->first_calc_number_product); for (auto& additional_product : calc_number_sum->zero_or_more_additional_calc_number_products) { - auto additional_value = resolve_calc_number_product(additional_product.calc_number_product); + auto additional_value = resolve_calc_number_product(additional_product.value); if (additional_product.op == CSS::CalculatedStyleValue::SumOperation::Add) value += additional_value; else if (additional_product.op == CSS::CalculatedStyleValue::SumOperation::Subtract) @@ -339,7 +339,7 @@ static float resolve_calc_number_sum(NonnullOwnPtr<CalculatedStyleValue::CalcNum static float resolve_calc_number_value(CalculatedStyleValue::CalcNumberValue const& number_value) { - return number_value.visit( + return number_value.value.visit( [](float number) { return number; }, [](NonnullOwnPtr<CalculatedStyleValue::CalcNumberSum> const& calc_number_sum) { return resolve_calc_number_sum(calc_number_sum); @@ -374,7 +374,7 @@ static float resolve_calc_sum(NonnullOwnPtr<CalculatedStyleValue::CalcSum> const auto value = resolve_calc_product(calc_sum->first_calc_product, layout_node); for (auto& additional_product : calc_sum->zero_or_more_additional_calc_products) { - auto additional_value = resolve_calc_product(additional_product.calc_product, layout_node); + auto additional_value = resolve_calc_product(additional_product.value, layout_node); if (additional_product.op == CalculatedStyleValue::SumOperation::Add) value += additional_value; else if (additional_product.op == CalculatedStyleValue::SumOperation::Subtract) @@ -386,6 +386,174 @@ static float resolve_calc_sum(NonnullOwnPtr<CalculatedStyleValue::CalcSum> const return value; } +static bool is_number(CalculatedStyleValue::ResolvedType type) +{ + return type == CalculatedStyleValue::ResolvedType::Number || type == CalculatedStyleValue::ResolvedType::Integer; +} + +static bool is_dimension(CalculatedStyleValue::ResolvedType type) +{ + return type != CalculatedStyleValue::ResolvedType::Number + && type != CalculatedStyleValue::ResolvedType::Integer + && type != CalculatedStyleValue::ResolvedType::Percentage; +} + +template<typename SumWithOperator> +static Optional<CalculatedStyleValue::ResolvedType> resolve_sum_type(CalculatedStyleValue::ResolvedType first_type, NonnullOwnPtrVector<SumWithOperator> const& zero_or_more_additional_products) +{ + auto type = first_type; + + for (auto const& product : zero_or_more_additional_products) { + auto maybe_product_type = product.resolved_type(); + if (!maybe_product_type.has_value()) + return {}; + auto product_type = maybe_product_type.value(); + + // At + or -, check that both sides have the same type, or that one side is a <number> and the other is an <integer>. + // If both sides are the same type, resolve to that type. + if (product_type == type) + continue; + + // If one side is a <number> and the other is an <integer>, resolve to <number>. + if (is_number(type) && is_number(product_type)) { + type = CalculatedStyleValue::ResolvedType::Number; + continue; + } + + // FIXME: calc() handles <percentage> by allowing them to pretend to be whatever <dimension> type is allowed at this location. + // Since we can't easily check what that type is, we just allow <percentage> to combine with any other <dimension> type. + if (type == CalculatedStyleValue::ResolvedType::Percentage && is_dimension(product_type)) { + type = product_type; + continue; + } + if (is_dimension(type) && product_type == CalculatedStyleValue::ResolvedType::Percentage) + continue; + + return {}; + } + return type; +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcSum::resolved_type() const +{ + auto maybe_type = first_calc_product->resolved_type(); + if (!maybe_type.has_value()) + return {}; + auto type = maybe_type.value(); + return resolve_sum_type(type, zero_or_more_additional_calc_products); +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberSum::resolved_type() const +{ + auto maybe_type = first_calc_number_product->resolved_type(); + if (!maybe_type.has_value()) + return {}; + auto type = maybe_type.value(); + return resolve_sum_type(type, zero_or_more_additional_calc_number_products); +} + +template<typename ProductWithOperator> +static Optional<CalculatedStyleValue::ResolvedType> resolve_product_type(CalculatedStyleValue::ResolvedType first_type, NonnullOwnPtrVector<ProductWithOperator> const& zero_or_more_additional_values) +{ + auto type = first_type; + + for (auto const& value : zero_or_more_additional_values) { + auto maybe_value_type = value.resolved_type(); + if (!maybe_value_type.has_value()) + return {}; + auto value_type = maybe_value_type.value(); + + if (value.op == CalculatedStyleValue::ProductOperation::Multiply) { + // At *, check that at least one side is <number>. + if (!(is_number(type) || is_number(value_type))) + return {}; + // If both sides are <integer>, resolve to <integer>. + if (type == CalculatedStyleValue::ResolvedType::Integer && value_type == CalculatedStyleValue::ResolvedType::Integer) { + type = CalculatedStyleValue::ResolvedType::Integer; + } else { + // Otherwise, resolve to the type of the other side. + if (is_number(type)) + type = value_type; + } + + continue; + } else { + VERIFY(value.op == CalculatedStyleValue::ProductOperation::Divide); + // At /, check that the right side is <number>. + if (!is_number(value_type)) + return {}; + // If the left side is <integer>, resolve to <number>. + if (type == CalculatedStyleValue::ResolvedType::Integer) { + type = CalculatedStyleValue::ResolvedType::Number; + } else { + // Otherwise, resolve to the type of the left side. + } + + // FIXME: Division by zero makes the whole calc() expression invalid. + } + } + return type; +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcProduct::resolved_type() const +{ + auto maybe_type = first_calc_value.resolved_type(); + if (!maybe_type.has_value()) + return {}; + auto type = maybe_type.value(); + return resolve_product_type(type, zero_or_more_additional_calc_values); +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcSumPartWithOperator::resolved_type() const +{ + return value->resolved_type(); +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberProduct::resolved_type() const +{ + auto maybe_type = first_calc_number_value.resolved_type(); + if (!maybe_type.has_value()) + return {}; + auto type = maybe_type.value(); + return resolve_product_type(type, zero_or_more_additional_calc_number_values); +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberProductPartWithOperator::resolved_type() const +{ + return value.resolved_type(); +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberSumPartWithOperator::resolved_type() const +{ + return value->resolved_type(); +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcProductPartWithOperator::resolved_type() const +{ + return value.visit( + [](CalcValue const& calc_value) { + return calc_value.resolved_type(); + }, + [](CalcNumberValue const& calc_number_value) { + return calc_number_value.resolved_type(); + }); +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcValue::resolved_type() const +{ + return value.visit( + [](float) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Number }; }, + [](Length const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Length }; }, + [](NonnullOwnPtr<CalcSum> const& sum) { return sum->resolved_type(); }); +} + +Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberValue::resolved_type() const +{ + return value.visit( + [](float) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Number }; }, + [](NonnullOwnPtr<CalcNumberSum> const& sum) { return sum->resolved_type(); }); +} + // https://www.w3.org/TR/css-color-4/#serializing-sRGB-values String ColorStyleValue::to_string() const { diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index 22cdbed287..04afe626a9 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -654,6 +654,16 @@ private: class CalculatedStyleValue : public StyleValue { public: + enum class ResolvedType { + Angle, + Frequency, + Integer, + Length, + Number, + Percentage, + Time, + }; + struct CalcSum; struct CalcSumPartWithOperator; struct CalcProduct; @@ -663,8 +673,15 @@ public: struct CalcNumberProduct; struct CalcNumberProductPartWithOperator; - using CalcNumberValue = Variant<float, NonnullOwnPtr<CalcNumberSum>>; - using CalcValue = Variant<float, CSS::Length, NonnullOwnPtr<CalcSum>>; + struct CalcNumberValue { + Variant<float, NonnullOwnPtr<CalcNumberSum>> value; + Optional<ResolvedType> resolved_type() const; + }; + + struct CalcValue { + Variant<float, CSS::Length, NonnullOwnPtr<CalcSum>> value; + Optional<ResolvedType> resolved_type() const; + }; enum class SumOperation { Add, @@ -683,6 +700,8 @@ public: NonnullOwnPtr<CalcProduct> first_calc_product; NonnullOwnPtrVector<CalcSumPartWithOperator> zero_or_more_additional_calc_products; + + Optional<ResolvedType> resolved_type() const; }; struct CalcNumberSum { @@ -692,63 +711,80 @@ public: NonnullOwnPtr<CalcNumberProduct> first_calc_number_product; NonnullOwnPtrVector<CalcNumberSumPartWithOperator> zero_or_more_additional_calc_number_products; + + Optional<ResolvedType> resolved_type() const; }; struct CalcProduct { CalcValue first_calc_value; NonnullOwnPtrVector<CalcProductPartWithOperator> zero_or_more_additional_calc_values; + + Optional<ResolvedType> resolved_type() const; }; struct CalcSumPartWithOperator { CalcSumPartWithOperator(SumOperation op, NonnullOwnPtr<CalcProduct> calc_product) : op(op) - , calc_product(move(calc_product)) {}; + , value(move(calc_product)) {}; SumOperation op; - NonnullOwnPtr<CalcProduct> calc_product; + NonnullOwnPtr<CalcProduct> value; + + Optional<ResolvedType> resolved_type() const; }; struct CalcProductPartWithOperator { ProductOperation op; Variant<CalcValue, CalcNumberValue> value; + + Optional<ResolvedType> resolved_type() const; }; struct CalcNumberProduct { CalcNumberValue first_calc_number_value; NonnullOwnPtrVector<CalcNumberProductPartWithOperator> zero_or_more_additional_calc_number_values; + + Optional<ResolvedType> resolved_type() const; }; struct CalcNumberProductPartWithOperator { ProductOperation op; CalcNumberValue value; + + Optional<ResolvedType> resolved_type() const; }; struct CalcNumberSumPartWithOperator { CalcNumberSumPartWithOperator(SumOperation op, NonnullOwnPtr<CalcNumberProduct> calc_number_product) : op(op) - , calc_number_product(move(calc_number_product)) {}; + , value(move(calc_number_product)) {}; SumOperation op; - NonnullOwnPtr<CalcNumberProduct> calc_number_product; + NonnullOwnPtr<CalcNumberProduct> value; + + Optional<ResolvedType> resolved_type() const; }; - static NonnullRefPtr<CalculatedStyleValue> create(String const& expression_string, NonnullOwnPtr<CalcSum> calc_sum) + static NonnullRefPtr<CalculatedStyleValue> create(String const& expression_string, NonnullOwnPtr<CalcSum> calc_sum, ResolvedType resolved_type) { - return adopt_ref(*new CalculatedStyleValue(expression_string, move(calc_sum))); + return adopt_ref(*new CalculatedStyleValue(expression_string, move(calc_sum), resolved_type)); } String to_string() const override { return m_expression_string; } + ResolvedType resolved_type() const { return m_resolved_type; } NonnullOwnPtr<CalcSum> const& expression() const { return m_expression; } Optional<Length> resolve_length(Layout::Node const& layout_node) const; private: - explicit CalculatedStyleValue(String const& expression_string, NonnullOwnPtr<CalcSum> calc_sum) + explicit CalculatedStyleValue(String const& expression_string, NonnullOwnPtr<CalcSum> calc_sum, ResolvedType resolved_type) : StyleValue(Type::Calculated) + , m_resolved_type(resolved_type) , m_expression_string(expression_string) , m_expression(move(calc_sum)) { } + ResolvedType m_resolved_type; String m_expression_string; NonnullOwnPtr<CalcSum> m_expression; }; |