Sfoglia il codice sorgente

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.
Sam Atkins 3 anni fa
parent
commit
b69f6097de

+ 47 - 12
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);

+ 1 - 0
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&);

+ 172 - 4
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
 {

+ 45 - 9
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;
 };