LibWeb: Implement counter-[increment,reset,set] properties

These control the state of CSS counters.

Parsing code for `reversed(counter-name)` is implemented, but disabled
for now until we are able to resolve values for those.
This commit is contained in:
Sam Atkins 2024-07-24 15:47:11 +01:00
parent 4c42e93853
commit 017d6c3314
Notes: github-actions[bot] 2024-07-26 10:05:39 +00:00
15 changed files with 315 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -14,6 +14,7 @@
#include <LibWeb/CSS/CalculatedOr.h>
#include <LibWeb/CSS/Clip.h>
#include <LibWeb/CSS/ColumnCount.h>
#include <LibWeb/CSS/CountersSet.h>
#include <LibWeb/CSS/Display.h>
#include <LibWeb/CSS/GridTrackPlacement.h>
#include <LibWeb/CSS/GridTrackSize.h>
@ -315,6 +316,12 @@ struct ContentData {
String alt_text {};
};
struct CounterData {
FlyString name;
bool is_reversed;
Optional<CounterValue> 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<CounterData, 0> counter_increment;
Vector<CounterData, 0> counter_reset;
Vector<CounterData, 0> 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<CounterData> value) { m_noninherited.counter_increment = move(value); }
void set_counter_reset(Vector<CounterData> value) { m_noninherited.counter_reset = move(value); }
void set_counter_set(Vector<CounterData> value) { m_noninherited.counter_set = move(value); }
};
}

View File

@ -44,6 +44,7 @@
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
@ -2891,6 +2892,63 @@ RefPtr<StyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tokens
return nullptr;
}
RefPtr<StyleValue> Parser::parse_counter_definitions_value(TokenStream<ComponentValue>& tokens, AllowReversed allow_reversed, i32 default_value_if_not_reversed)
{
// If AllowReversed is Yes, parses:
// [ <counter-name> <integer>? | <reversed-counter-name> <integer>? ]+
// Otherwise parses:
// [ <counter-name> <integer>? ]+
// 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<CounterDefinition> counter_definitions;
while (tokens.has_next_token()) {
auto per_item_transaction = tokens.begin_transaction();
CounterDefinition definition {};
// <counter-name> | <reversed-counter-name>
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();
// <integer>?
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<StyleValue> Parser::parse_ratio_value(TokenStream<ComponentValue>& tokens)
{
if (auto ratio = parse_ratio(tokens); ratio.has_value())
@ -4170,6 +4228,36 @@ RefPtr<StyleValue> Parser::parse_content_value(TokenStream<ComponentValue>& 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<StyleValue> Parser::parse_counter_increment_value(TokenStream<ComponentValue>& tokens)
{
// [ <counter-name> <integer>? ]+ | 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<StyleValue> Parser::parse_counter_reset_value(TokenStream<ComponentValue>& tokens)
{
// [ <counter-name> <integer>? | <reversed-counter-name> <integer>? ]+ | 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<StyleValue> Parser::parse_counter_set_value(TokenStream<ComponentValue>& tokens)
{
// [ <counter-name> <integer>? ]+ | 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<StyleValue> Parser::parse_display_value(TokenStream<ComponentValue>& tokens)
{
@ -6758,6 +6846,18 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue>> 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();

View File

@ -288,6 +288,11 @@ private:
RefPtr<StyleValue> parse_number_or_percentage_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_identifier_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_color_value(TokenStream<ComponentValue>&);
enum class AllowReversed {
No,
Yes,
};
RefPtr<StyleValue> parse_counter_definitions_value(TokenStream<ComponentValue>&, AllowReversed, i32 default_value_if_not_reversed);
RefPtr<StyleValue> parse_rect_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_ratio_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_string_value(TokenStream<ComponentValue>&);
@ -314,6 +319,9 @@ private:
RefPtr<StyleValue> parse_border_radius_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_border_radius_shorthand_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_content_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_counter_increment_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_counter_reset_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_counter_set_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_display_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_flex_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_flex_flow_value(TokenStream<ComponentValue>&);

View File

@ -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",

View File

@ -11,6 +11,7 @@
#include <LibWeb/CSS/StyleProperties.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTemplateAreaStyleValue.h>
@ -1093,6 +1094,42 @@ QuotesData StyleProperties::quotes() const
return InitialValues::quotes();
}
Vector<CounterData> 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<CounterData> 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<i32>(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<i32>(*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<CSS::ScrollbarWidth> StyleProperties::scrollbar_width() const
{
auto value = property(CSS::PropertyID::ScrollbarWidth);

View File

@ -177,6 +177,7 @@ public:
int math_depth() const { return m_math_depth; }
QuotesData quotes() const;
Vector<CounterData> counter_data(PropertyID) const;
Optional<CSS::ScrollbarWidth> scrollbar_width() const;

View File

@ -20,6 +20,7 @@
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/ConicGradientStyleValue.h>
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>

View File

@ -92,6 +92,7 @@ using StyleValueVector = Vector<ValueComparingNonnullRefPtr<StyleValue const>>;
__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) \

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CounterDefinitionsStyleValue.h"
#include <LibWeb/CSS/Serialize.h>
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;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <LibWeb/CSS/StyleValue.h>
namespace Web::CSS {
struct CounterDefinition {
FlyString name;
bool is_reversed;
ValueComparingRefPtr<StyleValue const> 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<CounterDefinitionsStyleValue> {
public:
static ValueComparingNonnullRefPtr<CounterDefinitionsStyleValue> create(Vector<CounterDefinition> 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<CounterDefinition> counter_definitions)
: StyleValueWithDefaultOperators(Type::CounterDefinitions)
, m_counter_definitions(move(counter_definitions))
{
}
Vector<CounterDefinition> m_counter_definitions;
};
}

View File

@ -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.

View File

@ -120,6 +120,7 @@ class Clip;
class ColorStyleValue;
class ConicGradientStyleValue;
class ContentStyleValue;
class CounterDefinitionsStyleValue;
class CustomIdentStyleValue;
class Display;
class DisplayStyleValue;

View File

@ -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());