diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index fc2a41b3184..9aa2f958276 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -88,6 +88,9 @@ column-count: auto column-gap: auto content: normal content-visibility: visible +counter-increment: none +counter-reset: none +counter-set: none cursor: auto cx: 0px cy: 0px @@ -120,7 +123,7 @@ grid-row-start: auto grid-template-areas: grid-template-columns: grid-template-rows: -height: 2074px +height: 2125px image-rendering: auto inline-size: auto inset-block-end: auto diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 2a3b82adf37..9e1c338fc14 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -110,6 +110,7 @@ set(SOURCES CSS/StyleValues/ColorStyleValue.cpp CSS/StyleValues/ConicGradientStyleValue.cpp CSS/StyleValues/ContentStyleValue.cpp + CSS/StyleValues/CounterDefinitionsStyleValue.cpp CSS/StyleValues/DisplayStyleValue.cpp CSS/StyleValues/EasingStyleValue.cpp CSS/StyleValues/EdgeStyleValue.cpp diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 1201095f2b1..041ccb4066e 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -315,6 +316,12 @@ struct ContentData { String alt_text {}; }; +struct CounterData { + FlyString name; + bool is_reversed; + Optional value; +}; + struct BorderRadiusData { CSS::LengthPercentage horizontal_radius { InitialValues::border_radius() }; CSS::LengthPercentage vertical_radius { InitialValues::border_radius() }; @@ -632,6 +639,9 @@ protected: LengthPercentage y { InitialValues::x() }; CSS::ScrollbarWidth scrollbar_width { InitialValues::scrollbar_width() }; + Vector counter_increment; + Vector counter_reset; + Vector counter_set; } m_noninherited; }; @@ -774,6 +784,10 @@ public: void set_math_depth(int value) { m_inherited.math_depth = value; } void set_scrollbar_width(CSS::ScrollbarWidth value) { m_noninherited.scrollbar_width = value; } + + void set_counter_increment(Vector value) { m_noninherited.counter_increment = move(value); } + void set_counter_reset(Vector value) { m_noninherited.counter_reset = move(value); } + void set_counter_set(Vector value) { m_noninherited.counter_set = move(value); } }; } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index c9c468f8ca7..cdc1c4c3287 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -2891,6 +2892,63 @@ RefPtr Parser::parse_color_value(TokenStream& tokens return nullptr; } +RefPtr Parser::parse_counter_definitions_value(TokenStream& tokens, AllowReversed allow_reversed, i32 default_value_if_not_reversed) +{ + // If AllowReversed is Yes, parses: + // [ ? | ? ]+ + // Otherwise parses: + // [ ? ]+ + + // FIXME: This disabled parsing of `reversed()` counters. Remove this line once they're supported. + allow_reversed = AllowReversed::No; + + auto transaction = tokens.begin_transaction(); + tokens.skip_whitespace(); + + Vector counter_definitions; + while (tokens.has_next_token()) { + auto per_item_transaction = tokens.begin_transaction(); + CounterDefinition definition {}; + + // | + auto& token = tokens.next_token(); + if (token.is(Token::Type::Ident)) { + definition.name = token.token().ident(); + definition.is_reversed = false; + } else if (allow_reversed == AllowReversed::Yes && token.is_function("reversed"sv)) { + TokenStream function_tokens { token.function().values() }; + function_tokens.skip_whitespace(); + auto& name_token = function_tokens.next_token(); + if (!name_token.is(Token::Type::Ident)) + break; + function_tokens.skip_whitespace(); + if (function_tokens.has_next_token()) + break; + + definition.name = name_token.token().ident(); + definition.is_reversed = true; + } else { + break; + } + tokens.skip_whitespace(); + + // ? + definition.value = parse_integer_value(tokens); + if (!definition.value && !definition.is_reversed) + definition.value = IntegerStyleValue::create(default_value_if_not_reversed); + + counter_definitions.append(move(definition)); + tokens.skip_whitespace(); + per_item_transaction.commit(); + } + + if (counter_definitions.is_empty()) + return {}; + + transaction.commit(); + return CounterDefinitionsStyleValue::create(move(counter_definitions)); +} + RefPtr Parser::parse_ratio_value(TokenStream& tokens) { if (auto ratio = parse_ratio(tokens); ratio.has_value()) @@ -4170,6 +4228,36 @@ RefPtr Parser::parse_content_value(TokenStream& toke return ContentStyleValue::create(StyleValueList::create(move(content_values), StyleValueList::Separator::Space), move(alt_text)); } +// https://drafts.csswg.org/css-lists-3/#propdef-counter-increment +RefPtr Parser::parse_counter_increment_value(TokenStream& tokens) +{ + // [ ? ]+ | none + if (auto none = parse_all_as_single_none_value(tokens)) + return none; + + return parse_counter_definitions_value(tokens, AllowReversed::No, 1); +} + +// https://drafts.csswg.org/css-lists-3/#propdef-counter-reset +RefPtr Parser::parse_counter_reset_value(TokenStream& tokens) +{ + // [ ? | ? ]+ | none + if (auto none = parse_all_as_single_none_value(tokens)) + return none; + + return parse_counter_definitions_value(tokens, AllowReversed::Yes, 0); +} + +// https://drafts.csswg.org/css-lists-3/#propdef-counter-set +RefPtr Parser::parse_counter_set_value(TokenStream& tokens) +{ + // [ ? ]+ | none + if (auto none = parse_all_as_single_none_value(tokens)) + return none; + + return parse_counter_definitions_value(tokens, AllowReversed::No, 0); +} + // https://www.w3.org/TR/css-display-3/#the-display-properties RefPtr Parser::parse_display_value(TokenStream& tokens) { @@ -6758,6 +6846,18 @@ Parser::ParseErrorOr> Parser::parse_css_value(Property if (auto parsed_value = parse_content_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); return ParseError::SyntaxError; + case PropertyID::CounterIncrement: + if (auto parsed_value = parse_counter_increment_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::CounterReset: + if (auto parsed_value = parse_counter_reset_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::CounterSet: + if (auto parsed_value = parse_counter_set_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; case PropertyID::Display: if (auto parsed_value = parse_display_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 1c5af9f7020..e732c510bae 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -288,6 +288,11 @@ private: RefPtr parse_number_or_percentage_value(TokenStream&); RefPtr parse_identifier_value(TokenStream&); RefPtr parse_color_value(TokenStream&); + enum class AllowReversed { + No, + Yes, + }; + RefPtr parse_counter_definitions_value(TokenStream&, AllowReversed, i32 default_value_if_not_reversed); RefPtr parse_rect_value(TokenStream&); RefPtr parse_ratio_value(TokenStream&); RefPtr parse_string_value(TokenStream&); @@ -314,6 +319,9 @@ private: RefPtr parse_border_radius_value(TokenStream&); RefPtr parse_border_radius_shorthand_value(TokenStream&); RefPtr parse_content_value(TokenStream&); + RefPtr parse_counter_increment_value(TokenStream&); + RefPtr parse_counter_reset_value(TokenStream&); + RefPtr parse_counter_set_value(TokenStream&); RefPtr parse_display_value(TokenStream&); RefPtr parse_flex_value(TokenStream&); RefPtr parse_flex_flow_value(TokenStream&); diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 4331f7e29e7..38a75110465 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/Userland/Libraries/LibWeb/CSS/Properties.json @@ -986,6 +986,42 @@ "content-visibility" ] }, + "counter-increment": { + "animation-type": "by-computed-value", + "inherited": false, + "initial": "none", + "valid-types": [ + "custom-ident", + "integer [-∞,∞]" + ], + "valid-identifiers": [ + "none" + ] + }, + "counter-reset": { + "animation-type": "by-computed-value", + "inherited": false, + "initial": "none", + "valid-types": [ + "custom-ident", + "integer [-∞,∞]" + ], + "valid-identifiers": [ + "none" + ] + }, + "counter-set": { + "animation-type": "by-computed-value", + "inherited": false, + "initial": "none", + "valid-types": [ + "custom-ident", + "integer [-∞,∞]" + ], + "valid-identifiers": [ + "none" + ] + }, "cursor": { "affects-layout": false, "animation-type": "discrete", diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index 83ab51ec56a..b4af44601c1 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -1093,6 +1094,42 @@ QuotesData StyleProperties::quotes() const return InitialValues::quotes(); } +Vector StyleProperties::counter_data(PropertyID property_id) const +{ + auto value = property(property_id); + + if (value->is_counter_definitions()) { + auto& counter_definitions = value->as_counter_definitions().counter_definitions(); + Vector result; + for (auto& counter : counter_definitions) { + CounterData data { + .name = counter.name, + .is_reversed = counter.is_reversed, + .value = {}, + }; + if (counter.value) { + if (counter.value->is_integer()) { + data.value = AK::clamp_to(counter.value->as_integer().integer()); + } else if (counter.value->is_calculated()) { + auto maybe_int = counter.value->as_calculated().resolve_integer(); + if (maybe_int.has_value()) + data.value = AK::clamp_to(*maybe_int); + } else { + dbgln("Unimplemented type for {} integer value: '{}'", string_from_property_id(property_id), counter.value->to_string()); + } + } + result.append(move(data)); + } + return result; + } + + if (value->to_identifier() == ValueID::None) + return {}; + + dbgln("Unhandled type for {} value: '{}'", string_from_property_id(property_id), value->to_string()); + return {}; +} + Optional StyleProperties::scrollbar_width() const { auto value = property(CSS::PropertyID::ScrollbarWidth); diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index c8e80082277..9be8c9a60b4 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -177,6 +177,7 @@ public: int math_depth() const { return m_math_depth; } QuotesData quotes() const; + Vector counter_data(PropertyID) const; Optional scrollbar_width() const; diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp index c3cf075fc78..0f97f474689 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index 89e32a4f605..5dd6568e2c7 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -92,6 +92,7 @@ using StyleValueVector = Vector>; __ENUMERATE_STYLE_VALUE_TYPE(Color, color) \ __ENUMERATE_STYLE_VALUE_TYPE(ConicGradient, conic_gradient) \ __ENUMERATE_STYLE_VALUE_TYPE(Content, content) \ + __ENUMERATE_STYLE_VALUE_TYPE(CounterDefinitions, counter_definitions) \ __ENUMERATE_STYLE_VALUE_TYPE(CustomIdent, custom_ident) \ __ENUMERATE_STYLE_VALUE_TYPE(Display, display) \ __ENUMERATE_STYLE_VALUE_TYPE(Easing, easing) \ diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.cpp new file mode 100644 index 00000000000..c46c55d51b0 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CounterDefinitionsStyleValue.h" +#include + +namespace Web::CSS { + +String CounterDefinitionsStyleValue::to_string() const +{ + StringBuilder stb; + + for (auto const& counter_definition : m_counter_definitions) { + if (!stb.is_empty()) + stb.append(' '); + + if (counter_definition.is_reversed) + stb.appendff("reversed({})", counter_definition.name); + else + stb.append(counter_definition.name); + + if (counter_definition.value) + stb.appendff(" {}", counter_definition.value->to_string()); + } + + return stb.to_string_without_validation(); +} + +bool CounterDefinitionsStyleValue::properties_equal(CounterDefinitionsStyleValue const& other) const +{ + if (m_counter_definitions.size() != other.counter_definitions().size()) + return false; + + for (auto i = 0u; i < m_counter_definitions.size(); i++) { + auto const& ours = m_counter_definitions[i]; + auto const& theirs = other.counter_definitions()[i]; + if (ours.name != theirs.name || ours.is_reversed != theirs.is_reversed || ours.value != theirs.value) + return false; + } + return true; +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h new file mode 100644 index 00000000000..5fb2e296d66 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::CSS { + +struct CounterDefinition { + FlyString name; + bool is_reversed; + ValueComparingRefPtr value; +}; + +/** + * Holds a list of CounterDefinitions. + * Shared between counter-increment, counter-reset, and counter-set properties that have (almost) identical grammar. + */ +class CounterDefinitionsStyleValue : public StyleValueWithDefaultOperators { +public: + static ValueComparingNonnullRefPtr create(Vector counter_definitions) + { + return adopt_ref(*new (nothrow) CounterDefinitionsStyleValue(move(counter_definitions))); + } + virtual ~CounterDefinitionsStyleValue() override = default; + + auto const& counter_definitions() const { return m_counter_definitions; } + virtual String to_string() const override; + + bool properties_equal(CounterDefinitionsStyleValue const& other) const; + +private: + explicit CounterDefinitionsStyleValue(Vector counter_definitions) + : StyleValueWithDefaultOperators(Type::CounterDefinitions) + , m_counter_definitions(move(counter_definitions)) + { + } + + Vector m_counter_definitions; +}; + +} diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index 7ac3ff5b1ae..23f42dca77d 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -2711,16 +2711,27 @@ CSS::CountersSet& Element::ensure_counters_set() } // https://drafts.csswg.org/css-lists-3/#auto-numbering -void Element::resolve_counters(CSS::StyleProperties&) +void Element::resolve_counters(CSS::StyleProperties& style) { // Resolving counter values on a given element is a multi-step process: // 1. Existing counters are inherited from previous elements. inherit_counters(); - // TODO: 2. New counters are instantiated (counter-reset). - // TODO: 3. Counter values are incremented (counter-increment). - // TODO: 4. Counter values are explicitly set (counter-set). + // 2. New counters are instantiated (counter-reset). + auto counter_reset = style.counter_data(CSS::PropertyID::CounterReset); + for (auto const& counter : counter_reset) + ensure_counters_set().instantiate_a_counter(counter.name, unique_id(), counter.is_reversed, counter.value); + + // 3. Counter values are incremented (counter-increment). + auto counter_increment = style.counter_data(CSS::PropertyID::CounterIncrement); + for (auto const& counter : counter_increment) + ensure_counters_set().increment_a_counter(counter.name, unique_id(), *counter.value); + + // 4. Counter values are explicitly set (counter-set). + auto counter_set = style.counter_data(CSS::PropertyID::CounterSet); + for (auto const& counter : counter_set) + ensure_counters_set().set_a_counter(counter.name, unique_id(), *counter.value); // 5. Counter values are used (counter()/counters()). // NOTE: This happens when we process the `content` property. diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index a41a5123dbe..7253b9c7365 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -120,6 +120,7 @@ class Clip; class ColorStyleValue; class ConicGradientStyleValue; class ContentStyleValue; +class CounterDefinitionsStyleValue; class CustomIdentStyleValue; class Display; class DisplayStyleValue; diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index dd33d9e4630..3a4fce9093e 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -860,6 +860,9 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) computed_values.set_math_depth(computed_style.math_depth()); computed_values.set_quotes(computed_style.quotes()); + computed_values.set_counter_increment(computed_style.counter_data(CSS::PropertyID::CounterIncrement)); + computed_values.set_counter_reset(computed_style.counter_data(CSS::PropertyID::CounterReset)); + computed_values.set_counter_set(computed_style.counter_data(CSS::PropertyID::CounterSet)); if (auto object_fit = computed_style.object_fit(); object_fit.has_value()) computed_values.set_object_fit(object_fit.value());