diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index a4fe7cc6b64..2a23e751f2c 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -275,6 +275,7 @@ set(SOURCES CSS/StyleValues/TextUnderlinePositionStyleValue.cpp CSS/StyleValues/TransformationStyleValue.cpp CSS/StyleValues/TransitionStyleValue.cpp + CSS/StyleValues/TreeCountingFunctionStyleValue.cpp CSS/StyleValues/UnicodeRangeStyleValue.cpp CSS/StyleValues/UnresolvedStyleValue.cpp CSS/Supports.cpp diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index cc09831d568..299c95ef99f 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,7 @@ public: NonnullRefPtr parse_with_a_syntax(Vector const& input, SyntaxNode const& syntax, Optional const& element = {}); RefPtr parse_calculated_value(ComponentValue const&); + RefPtr parse_tree_counting_function(TokenStream&, TreeCountingFunctionStyleValue::ComputedType); private: Parser(ParsingParams const&, Vector); diff --git a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp index d28707f8c6c..8392063f669 100644 --- a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp @@ -190,11 +190,7 @@ Optional Parser::parse_css_value_for_properties(Readon auto context_guard = push_temporary_value_parsing_context(*property); auto transaction = tokens.begin_transaction(); if (auto value = parse_integer_value(tokens)) { - if (value->is_calculated()) { - transaction.commit(); - return PropertyAndValue { *property, value }; - } - if (value->is_integer() && property_accepts_integer(*property, value->as_integer().integer())) { + if ((value->is_integer() && property_accepts_integer(*property, value->as_integer().integer())) || !value->is_integer()) { transaction.commit(); return PropertyAndValue { *property, value }; } @@ -205,11 +201,7 @@ Optional Parser::parse_css_value_for_properties(Readon auto context_guard = push_temporary_value_parsing_context(*property); auto transaction = tokens.begin_transaction(); if (auto value = parse_number_value(tokens)) { - if (value->is_calculated()) { - transaction.commit(); - return PropertyAndValue { *property, value }; - } - if (value->is_number() && property_accepts_number(*property, value->as_number().number())) { + if ((value->is_number() && property_accepts_number(*property, value->as_number().number())) || !value->is_number()) { transaction.commit(); return PropertyAndValue { *property, value }; } diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index 696e41fc11d..a4f31452862 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -256,6 +256,9 @@ Optional Parser::parse_frequency_percentage(TokenStream Parser::parse_integer(TokenStream& tokens) { + // FIXME: We don't have a way to represent tree counting functions within IntegerOrCalculated, we should avoid + // parsing directly to IntegerOrCalculated unless tree counting functions are disallowed in the relevant + // context if (auto value = parse_integer_value(tokens)) { if (value->is_integer()) return value->as_integer().integer(); @@ -293,6 +296,9 @@ Optional Parser::parse_length_percentage(TokenStream Parser::parse_number(TokenStream& tokens) { + // FIXME: We don't have a way to represent tree counting functions within NumberOrCalculated, we should avoid + // parsing directly to NumberOrCalculated unless tree counting functions are disallowed in the relevant + // context if (auto value = parse_number_value(tokens)) { if (value->is_number()) return value->as_number().number(); @@ -786,6 +792,9 @@ RefPtr Parser::parse_integer_value(TokenStream return calc; } + if (auto tree_counting_function = parse_tree_counting_function(tokens, TreeCountingFunctionStyleValue::ComputedType::Integer); tree_counting_function) + return tree_counting_function; + return nullptr; } @@ -802,6 +811,9 @@ RefPtr Parser::parse_number_value(TokenStream& return calc; } + if (auto tree_counting_function = parse_tree_counting_function(tokens, TreeCountingFunctionStyleValue::ComputedType::Number); tree_counting_function) + return tree_counting_function; + return nullptr; } @@ -4455,6 +4467,33 @@ RefPtr Parser::parse_a_calculation(Vector return simplify_a_calculation_tree(*calculation_tree, context, CalculationResolutionContext {}); } +// https://drafts.csswg.org/css-values-5/#tree-counting +RefPtr Parser::parse_tree_counting_function(TokenStream& tokens, TreeCountingFunctionStyleValue::ComputedType computed_type) +{ + if (!context_allows_tree_counting_functions()) + return nullptr; + + auto has_no_arguments = [](Vector const& component_values) { + return !any_of(component_values, [](ComponentValue const& value) { return !value.is(Token::Type::Whitespace); }); + }; + + auto transaction = tokens.begin_transaction(); + + auto token = tokens.consume_a_token(); + + if (token.is_function("sibling-count"sv) && has_no_arguments(token.function().value)) { + transaction.commit(); + return TreeCountingFunctionStyleValue::create(TreeCountingFunctionStyleValue::TreeCountingFunction::SiblingCount, computed_type); + } + + if (token.is_function("sibling-index"sv) && has_no_arguments(token.function().value)) { + transaction.commit(); + return TreeCountingFunctionStyleValue::create(TreeCountingFunctionStyleValue::TreeCountingFunction::SiblingIndex, computed_type); + } + + return nullptr; +} + // https://drafts.csswg.org/css-color-4/#typedef-opacity-opacity-value RefPtr Parser::parse_opacity_value(TokenStream& tokens) { diff --git a/Libraries/LibWeb/CSS/StyleValues/StyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/StyleValue.cpp index 7abcd7b09e0..1c8ad466cff 100644 --- a/Libraries/LibWeb/CSS/StyleValues/StyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/StyleValue.cpp @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include diff --git a/Libraries/LibWeb/CSS/StyleValues/StyleValue.h b/Libraries/LibWeb/CSS/StyleValues/StyleValue.h index 3d53fa08eab..fa28326743e 100644 --- a/Libraries/LibWeb/CSS/StyleValues/StyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/StyleValue.h @@ -87,6 +87,7 @@ namespace Web::CSS { __ENUMERATE_CSS_STYLE_VALUE_TYPE(Time, time, TimeStyleValue) \ __ENUMERATE_CSS_STYLE_VALUE_TYPE(Transformation, transformation, TransformationStyleValue) \ __ENUMERATE_CSS_STYLE_VALUE_TYPE(Transition, transition, TransitionStyleValue) \ + __ENUMERATE_CSS_STYLE_VALUE_TYPE(TreeCountingFunction, tree_counting_function, TreeCountingFunctionStyleValue) \ __ENUMERATE_CSS_STYLE_VALUE_TYPE(UnicodeRange, unicode_range, UnicodeRangeStyleValue) \ __ENUMERATE_CSS_STYLE_VALUE_TYPE(Unresolved, unresolved, UnresolvedStyleValue) \ __ENUMERATE_CSS_STYLE_VALUE_TYPE(URL, url, URLStyleValue) \ diff --git a/Libraries/LibWeb/CSS/StyleValues/TreeCountingFunctionStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/TreeCountingFunctionStyleValue.cpp new file mode 100644 index 00000000000..045989d94c0 --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleValues/TreeCountingFunctionStyleValue.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025, Callum Law + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "TreeCountingFunctionStyleValue.h" +#include +#include + +namespace Web::CSS { + +String TreeCountingFunctionStyleValue::to_string(SerializationMode) const +{ + switch (m_function) { + case TreeCountingFunction::SiblingCount: + return "sibling-count()"_string; + case TreeCountingFunction::SiblingIndex: + return "sibling-index()"_string; + } + + VERIFY_NOT_REACHED(); +} + +size_t TreeCountingFunctionStyleValue::resolve(TreeCountingFunctionResolutionContext const& tree_counting_function_resolution_context) const +{ + switch (m_function) { + case TreeCountingFunction::SiblingCount: + return tree_counting_function_resolution_context.sibling_count; + case TreeCountingFunction::SiblingIndex: + return tree_counting_function_resolution_context.sibling_index; + } + + VERIFY_NOT_REACHED(); +} + +ValueComparingNonnullRefPtr TreeCountingFunctionStyleValue::absolutized(ComputationContext const& computation_context) const +{ + // FIXME: We should clamp this value in case it falls outside the valid range for the context it is in + VERIFY(computation_context.tree_counting_function_resolution_context.has_value()); + + size_t value = resolve(computation_context.tree_counting_function_resolution_context.value()); + + switch (m_computed_type) { + case ComputedType::Integer: + return IntegerStyleValue::create(value); + case ComputedType::Number: + return NumberStyleValue::create(static_cast(value)); + } + + VERIFY_NOT_REACHED(); +} + +bool TreeCountingFunctionStyleValue::equals(StyleValue const& other) const +{ + if (type() != other.type()) + return false; + + auto const& other_tree_counting_function = other.as_tree_counting_function(); + + return m_function == other_tree_counting_function.m_function && m_computed_type == other_tree_counting_function.m_computed_type; +} + +} diff --git a/Libraries/LibWeb/CSS/StyleValues/TreeCountingFunctionStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/TreeCountingFunctionStyleValue.h new file mode 100644 index 00000000000..31361e5db82 --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleValues/TreeCountingFunctionStyleValue.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, Callum Law + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::CSS { + +class TreeCountingFunctionStyleValue final : public StyleValue { +public: + enum class TreeCountingFunction : u8 { + SiblingCount, + SiblingIndex + }; + + enum class ComputedType : u8 { + Number, + Integer + }; + + static ValueComparingNonnullRefPtr create(TreeCountingFunction function, ComputedType computed_type) + { + return adopt_ref(*new (nothrow) TreeCountingFunctionStyleValue(function, computed_type)); + } + virtual ~TreeCountingFunctionStyleValue() override = default; + + virtual String to_string(SerializationMode) const override; + + size_t resolve(TreeCountingFunctionResolutionContext const&) const; + + virtual ValueComparingNonnullRefPtr absolutized(ComputationContext const&) const override; + + virtual bool equals(StyleValue const& other) const override; + +private: + TreeCountingFunctionStyleValue(TreeCountingFunction function, ComputedType computed_type) + : StyleValue(Type::TreeCountingFunction) + , m_function(function) + , m_computed_type(computed_type) + { + } + + TreeCountingFunction m_function; + ComputedType m_computed_type; +}; + +} diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 4e2184ba339..72ed6cbed4d 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -385,6 +385,7 @@ class TimeStyleValue; class Transformation; class TransformationStyleValue; class TransitionStyleValue; +class TreeCountingFunctionStyleValue; class UnicodeRangeStyleValue; class UnresolvedStyleValue; class URL; diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-valid.txt index 72ef2ef9f09..6eaeca8f4cd 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-valid.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-valid.txt @@ -2,8 +2,7 @@ Harness status: OK Found 22 tests -21 Pass -1 Fail +22 Pass Pass e.style['animation-timing-function'] = "linear" should set the property value Pass e.style['animation-timing-function'] = "ease" should set the property value Pass e.style['animation-timing-function'] = "ease-in" should set the property value @@ -13,7 +12,7 @@ Pass e.style['animation-timing-function'] = "cubic-bezier(0.1, 0.2, 0.8, 0.9)" s Pass e.style['animation-timing-function'] = "cubic-bezier(0, -2, 1, 3)" should set the property value Pass e.style['animation-timing-function'] = "cubic-bezier(0, 0.7, 1, 1.3)" should set the property value Pass e.style['animation-timing-function'] = "cubic-bezier(calc(-2), calc(0.7 / 2), calc(1.5), calc(0))" should set the property value -Fail e.style['animation-timing-function'] = "cubic-bezier(0, sibling-index(), 1, sign(2em - 20px))" should set the property value +Pass e.style['animation-timing-function'] = "cubic-bezier(0, sibling-index(), 1, sign(2em - 20px))" should set the property value Pass e.style['animation-timing-function'] = "steps(4, start)" should set the property value Pass e.style['animation-timing-function'] = "steps(2, end)" should set the property value Pass e.style['animation-timing-function'] = "steps( 2, end )" should set the property value diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/font-variation-settings-calc.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/font-variation-settings-calc.txt index d8d74583ad4..c96c9f28d5d 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/font-variation-settings-calc.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/font-variation-settings-calc.txt @@ -2,13 +2,12 @@ Harness status: OK Found 8 tests -6 Pass -2 Fail +8 Pass Pass e.style['font-variation-settings'] = "\"wght\" sign(1em - 1px)" should set the property value -Fail e.style['font-variation-settings'] = "\"wght\" sibling-index()" should set the property value +Pass e.style['font-variation-settings'] = "\"wght\" sibling-index()" should set the property value Pass e.style['font-variation-settings'] = "\"wght\" calc(10)" should set the property value Pass e.style['font-variation-settings'] = "\"wght\" sign(2px)" should set the property value Pass Property font-variation-settings value '"wght" sign(1em - 1px)' -Fail Property font-variation-settings value '"wght" sibling-index()' +Pass Property font-variation-settings value '"wght" sibling-index()' Pass Property font-variation-settings value '"wght" calc(10)' Pass Property font-variation-settings value '"wght" sign(2px)' \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-transitions/parsing/transition-timing-function-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-transitions/parsing/transition-timing-function-valid.txt index 952a8458826..c90a20723fe 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-transitions/parsing/transition-timing-function-valid.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-transitions/parsing/transition-timing-function-valid.txt @@ -2,8 +2,7 @@ Harness status: OK Found 22 tests -21 Pass -1 Fail +22 Pass Pass e.style['transition-timing-function'] = "linear" should set the property value Pass e.style['transition-timing-function'] = "linear(0 0%, 0.5 50%, 1 100%)" should set the property value Pass e.style['transition-timing-function'] = "linear(0 0%, 10 10%, 10 50%, 25.4 75%, 100 100%)" should set the property value @@ -24,5 +23,5 @@ Pass e.style['transition-timing-function'] = "steps(2, jump-start)" should set t Pass e.style['transition-timing-function'] = "steps(2, jump-end)" should set the property value Pass e.style['transition-timing-function'] = "steps(2, jump-both)" should set the property value Pass e.style['transition-timing-function'] = "steps(2, jump-none)" should set the property value -Fail e.style['transition-timing-function'] = "steps(sibling-index(), jump-none)" should set the property value +Pass e.style['transition-timing-function'] = "steps(sibling-index(), jump-none)" should set the property value Pass e.style['transition-timing-function'] = "linear, ease, linear" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/calc-sibling-function-in-shadow-dom.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/calc-sibling-function-in-shadow-dom.txt index 5e506cbb3b2..975e735a4b1 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/calc-sibling-function-in-shadow-dom.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/calc-sibling-function-in-shadow-dom.txt @@ -2,5 +2,5 @@ Harness status: OK Found 1 tests -1 Fail -Fail Host children have sibling-index() and sibling-count() based on the DOM tree order \ No newline at end of file +1 Pass +Pass Host children have sibling-index() and sibling-count() based on the DOM tree order \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/calc-sibling-function-parsing.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/calc-sibling-function-parsing.txt index 04f1d0f5f3f..d447c469ae3 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/calc-sibling-function-parsing.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/calc-sibling-function-parsing.txt @@ -2,15 +2,15 @@ Harness status: OK Found 10 tests -4 Pass -6 Fail +6 Pass +4 Fail Fail e.style['left'] = "calc(1px * sibling-index())" should set the property value Fail e.style['left'] = "calc(1px * sibling-index( ))" should set the property value -Fail e.style['z-index'] = "sibling-index()" should set the property value +Pass e.style['z-index'] = "sibling-index()" should set the property value Pass e.style['left'] = "calc(1px * sibling-index(100px))" should not set the property value Pass e.style['left'] = "calc(1px * sibling-index(1))" should not set the property value Fail e.style['left'] = "calc(1px * sibling-count())" should set the property value Fail e.style['left'] = "calc(1px * sibling-count( ))" should set the property value -Fail e.style['z-index'] = "sibling-count()" should set the property value +Pass e.style['z-index'] = "sibling-count()" should set the property value Pass e.style['left'] = "calc(1px * sibling-count(100px))" should not set the property value Pass e.style['left'] = "calc(1px * sibling-count(1))" should not set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/sibling-index-keyframe-font-variation-settings-dynamic.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/sibling-index-keyframe-font-variation-settings-dynamic.txt index a73490ced9c..dd94d213a13 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/sibling-index-keyframe-font-variation-settings-dynamic.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/sibling-index-keyframe-font-variation-settings-dynamic.txt @@ -2,6 +2,7 @@ Harness status: OK Found 2 tests -2 Fail -Fail Initially, the sibling-index() is 3 for #target +1 Pass +1 Fail +Pass Initially, the sibling-index() is 3 for #target Fail Removing a preceding sibling of #target reduces the sibling-index() \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.txt index a73490ced9c..dd94d213a13 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/sibling-index-keyframe-value-dynamic.txt @@ -2,6 +2,7 @@ Harness status: OK Found 2 tests -2 Fail -Fail Initially, the sibling-index() is 3 for #target +1 Pass +1 Fail +Pass Initially, the sibling-index() is 3 for #target Fail Removing a preceding sibling of #target reduces the sibling-index() \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/tree-scoped-sibling-function.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/tree-scoped-sibling-function.txt index 262b84b3780..a10550a10af 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/tree-scoped-sibling-function.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-values/tree-counting/tree-scoped-sibling-function.txt @@ -2,6 +2,7 @@ Harness status: OK Found 2 tests -2 Fail +1 Pass +1 Fail Fail sibling-index() and sibling-count() evaluates to 0 from outer tree with ::part -Fail sibling-index() and sibling-count() evaluate as normal from inner tree \ No newline at end of file +Pass sibling-index() and sibling-count() evaluate as normal from inner tree \ No newline at end of file