mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 00:19:53 +01:00
LibWeb: Support top-level tree counting functions
Adds support for `sibling-index()` and `sibling-count()` when parsing
`<number>` and `<integer>`. This is achieved by a new
`TreeCountingFunctionStyleValue` class which is converted within
`absolutized` to `NumberStyleValue` and `IntegerStyleValue` respectively
There are still a few kinks to work out in order to support these
everywhere, namely:
- There are some `StyleValue`s which aren't absolutized (i.e. those
which are stored within another `StyleValue` without an
`absolutize()` method.
- We don't have a way to represent this new `StyleValue` within
`{Number,Integer}OrCalculated`. This would be fixed if we were to
instead just use the `StyleValue` classes until style computation at
which time they would be absolutized into their respective
primitives (double, i64, etc) bypassing the need for *OrCalculated
entirely.
This commit is contained in:
parent
9cd23e3ae5
commit
831e471444
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/StyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/TreeCountingFunctionStyleValue.h>
|
||||
#include <LibWeb/CSS/Supports.h>
|
||||
#include <LibWeb/CSS/URL.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
|
@ -169,6 +170,7 @@ public:
|
|||
NonnullRefPtr<StyleValue const> parse_with_a_syntax(Vector<ComponentValue> const& input, SyntaxNode const& syntax, Optional<DOM::AbstractElement> const& element = {});
|
||||
|
||||
RefPtr<CalculatedStyleValue const> parse_calculated_value(ComponentValue const&);
|
||||
RefPtr<TreeCountingFunctionStyleValue const> parse_tree_counting_function(TokenStream<ComponentValue>&, TreeCountingFunctionStyleValue::ComputedType);
|
||||
|
||||
private:
|
||||
Parser(ParsingParams const&, Vector<Token>);
|
||||
|
|
|
|||
|
|
@ -190,11 +190,7 @@ Optional<Parser::PropertyAndValue> 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::PropertyAndValue> 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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,6 +256,9 @@ Optional<FrequencyPercentage> Parser::parse_frequency_percentage(TokenStream<Com
|
|||
|
||||
Optional<IntegerOrCalculated> Parser::parse_integer(TokenStream<ComponentValue>& 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<LengthPercentage> Parser::parse_length_percentage(TokenStream<Component
|
|||
|
||||
Optional<NumberOrCalculated> Parser::parse_number(TokenStream<ComponentValue>& 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<StyleValue const> Parser::parse_integer_value(TokenStream<ComponentValue>
|
|||
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<StyleValue const> Parser::parse_number_value(TokenStream<ComponentValue>&
|
|||
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<CalculationNode const> Parser::parse_a_calculation(Vector<ComponentValue>
|
|||
return simplify_a_calculation_tree(*calculation_tree, context, CalculationResolutionContext {});
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-values-5/#tree-counting
|
||||
RefPtr<TreeCountingFunctionStyleValue const> Parser::parse_tree_counting_function(TokenStream<ComponentValue>& tokens, TreeCountingFunctionStyleValue::ComputedType computed_type)
|
||||
{
|
||||
if (!context_allows_tree_counting_functions())
|
||||
return nullptr;
|
||||
|
||||
auto has_no_arguments = [](Vector<ComponentValue> 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<StyleValue const> Parser::parse_opacity_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@
|
|||
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/TransitionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/TreeCountingFunctionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/URLStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/UnicodeRangeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.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) \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Callum Law <callumlaw1709@outlook.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "TreeCountingFunctionStyleValue.h"
|
||||
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
|
||||
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<StyleValue const> 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<double>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Callum Law <callumlaw1709@outlook.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/StyleValues/StyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
class TreeCountingFunctionStyleValue final : public StyleValue {
|
||||
public:
|
||||
enum class TreeCountingFunction : u8 {
|
||||
SiblingCount,
|
||||
SiblingIndex
|
||||
};
|
||||
|
||||
enum class ComputedType : u8 {
|
||||
Number,
|
||||
Integer
|
||||
};
|
||||
|
||||
static ValueComparingNonnullRefPtr<TreeCountingFunctionStyleValue const> 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<StyleValue const> 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -385,6 +385,7 @@ class TimeStyleValue;
|
|||
class Transformation;
|
||||
class TransformationStyleValue;
|
||||
class TransitionStyleValue;
|
||||
class TreeCountingFunctionStyleValue;
|
||||
class UnicodeRangeStyleValue;
|
||||
class UnresolvedStyleValue;
|
||||
class URL;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)'
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
1 Pass
|
||||
Pass Host children have sibling-index() and sibling-count() based on the DOM tree order
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Pass sibling-index() and sibling-count() evaluate as normal from inner tree
|
||||
Loading…
Reference in New Issue
Block a user