mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibWeb: Support symbols() function in <counter-style>
This commit is contained in:
Notes:
github-actions[bot]
2026-02-27 16:27:03 +00:00
Author: https://github.com/Calme1709 Commit: https://github.com/LadybirdBrowser/ladybird/commit/81cb968beba Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8196 Reviewed-by: https://github.com/AtkinsSJ ✅
@@ -965,6 +965,13 @@
|
||||
"round",
|
||||
"bevel"
|
||||
],
|
||||
"symbols-type": [
|
||||
"cyclic",
|
||||
"numeric",
|
||||
"alphabetic",
|
||||
"symbolic",
|
||||
"fixed"
|
||||
],
|
||||
"table-layout": [
|
||||
"auto",
|
||||
"fixed"
|
||||
|
||||
@@ -2713,15 +2713,61 @@ Optional<FlyString> Parser::parse_counter_style_name(TokenStream<ComponentValue>
|
||||
RefPtr<StyleValue const> Parser::parse_counter_style_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <counter-style> = <counter-style-name> | <symbols()>
|
||||
// <symbols()> = symbols( <symbols-type>? [ <string> | <image> ]+ )
|
||||
// <symbols-type> = cyclic | numeric | alphabetic | symbolic | fixed
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
|
||||
// <counter-style-name>
|
||||
if (auto const& counter_style_name = parse_counter_style_name(tokens); counter_style_name.has_value()) {
|
||||
transaction.commit();
|
||||
return CounterStyleStyleValue::create(counter_style_name.value());
|
||||
}
|
||||
|
||||
// FIXME: Support <symbols()>
|
||||
// <symbols()>
|
||||
auto const& maybe_function_token = tokens.consume_a_token();
|
||||
|
||||
if (maybe_function_token.is_function("symbols"sv)) {
|
||||
TokenStream argument_tokens { maybe_function_token.function().value };
|
||||
|
||||
// <symbols-type>?
|
||||
// NB: <symbols-type> defaults to symbolic if not provided.
|
||||
SymbolsType symbols_type = SymbolsType::Symbolic;
|
||||
if (auto keyword = parse_keyword_value(argument_tokens); keyword) {
|
||||
auto maybe_symbols_type = keyword_to_symbols_type(keyword->to_keyword());
|
||||
|
||||
if (!maybe_symbols_type.has_value())
|
||||
return nullptr;
|
||||
|
||||
symbols_type = maybe_symbols_type.value();
|
||||
}
|
||||
|
||||
// [ <string> | <image> ]+
|
||||
// FIXME: In line with <symbol> we don't support <image> here - we may need to revisit this if other browsers
|
||||
// implement it.
|
||||
Vector<FlyString> symbols;
|
||||
while (argument_tokens.has_next_token()) {
|
||||
auto maybe_string = parse_string_value(argument_tokens);
|
||||
|
||||
if (!maybe_string)
|
||||
break;
|
||||
|
||||
symbols.append(maybe_string->string_value());
|
||||
}
|
||||
|
||||
argument_tokens.discard_whitespace();
|
||||
|
||||
if (argument_tokens.has_next_token())
|
||||
return nullptr;
|
||||
|
||||
// https://drafts.csswg.org/css-counter-styles-3/#symbols-function
|
||||
// If the system is alphabetic or numeric, there must be at least two <string>s or <image>s, or else the function is invalid.
|
||||
if (symbols.is_empty() || (first_is_one_of(symbols_type, SymbolsType::Alphabetic, SymbolsType::Numeric) && symbols.size() < 2))
|
||||
return nullptr;
|
||||
|
||||
transaction.commit();
|
||||
return CounterStyleStyleValue::create(CounterStyleStyleValue::SymbolsFunction { symbols_type, move(symbols) });
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -7,18 +7,79 @@
|
||||
#include "CounterStyleStyleValue.h"
|
||||
#include <LibWeb/CSS/CounterStyle.h>
|
||||
#include <LibWeb/CSS/Enums.h>
|
||||
#include <LibWeb/CSS/Serialize.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
void CounterStyleStyleValue::serialize(StringBuilder& builder, SerializationMode) const
|
||||
{
|
||||
builder.append(m_name);
|
||||
m_value.visit(
|
||||
[&](FlyString const& name) {
|
||||
builder.append(name);
|
||||
},
|
||||
[&](SymbolsFunction const& symbols_function) {
|
||||
builder.append("symbols("sv);
|
||||
if (symbols_function.type != SymbolsType::Symbolic)
|
||||
builder.appendff("{} ", CSS::to_string(symbols_function.type));
|
||||
|
||||
for (size_t i = 0; i < symbols_function.symbols.size(); ++i) {
|
||||
if (i > 0)
|
||||
builder.append(' ');
|
||||
serialize_a_string(builder, symbols_function.symbols[i]);
|
||||
}
|
||||
builder.append(')');
|
||||
});
|
||||
}
|
||||
|
||||
RefPtr<CounterStyle const> CounterStyleStyleValue::resolve_counter_style(HashMap<FlyString, NonnullRefPtr<CounterStyle const>> const& registered_counter_styles) const
|
||||
{
|
||||
// FIXME: Support symbols() function for anonymous counter style
|
||||
return registered_counter_styles.get(m_name).value_or(nullptr);
|
||||
return m_value.visit(
|
||||
[&](FlyString const& name) -> RefPtr<CounterStyle const> {
|
||||
return registered_counter_styles.get(name).value_or(nullptr);
|
||||
},
|
||||
[&](SymbolsFunction const& symbols_function) -> RefPtr<CounterStyle const> {
|
||||
// https://drafts.csswg.org/css-counter-styles-3/#symbols-function
|
||||
|
||||
auto algorithm = [&]() -> CounterStyleAlgorithm {
|
||||
// The counter style’s algorithm is constructed by consulting the previous chapter using the provided
|
||||
// system — or symbolic if the system was omitted — and the provided <string>s and <image>s as the value
|
||||
// of the symbols property. If the system is fixed, the first symbol value is 1.
|
||||
switch (symbols_function.type) {
|
||||
case SymbolsType::Cyclic:
|
||||
return GenericCounterStyleAlgorithm { CounterStyleSystem::Cyclic, symbols_function.symbols };
|
||||
case SymbolsType::Numeric:
|
||||
return GenericCounterStyleAlgorithm { CounterStyleSystem::Numeric, symbols_function.symbols };
|
||||
case SymbolsType::Alphabetic:
|
||||
return GenericCounterStyleAlgorithm { CounterStyleSystem::Alphabetic, symbols_function.symbols };
|
||||
case SymbolsType::Symbolic:
|
||||
return GenericCounterStyleAlgorithm { CounterStyleSystem::Symbolic, symbols_function.symbols };
|
||||
case SymbolsType::Fixed:
|
||||
return FixedCounterStyleAlgorithm { .first_symbol = 1, .symbol_list = symbols_function.symbols };
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}();
|
||||
|
||||
// The symbols() function defines an anonymous counter style with no name, a prefix of "" (empty string) and
|
||||
// suffix of " " (U+0020 SPACE), a range of auto, a fallback of decimal, a negative of "\2D" ("-"
|
||||
// hyphen-minus), a pad of 0 "", and a speak-as of auto.
|
||||
// FIXME: Pass the correct speak-as value once we support that.
|
||||
|
||||
auto definition = CounterStyleDefinition::create(
|
||||
// NB: We use empty string instead of no name for simplicity - this doesn't clash with
|
||||
// <counter-style-name> since that is a <custom-ident> which can't be an empty string.
|
||||
""_fly_string,
|
||||
algorithm,
|
||||
CounterStyleNegativeSign { "-"_fly_string, ""_fly_string },
|
||||
""_fly_string,
|
||||
" "_fly_string,
|
||||
AutoRange {},
|
||||
"decimal"_fly_string,
|
||||
CounterStylePad { 0, ""_fly_string });
|
||||
|
||||
// NB: We don't need to pass registered counter styles here since we don't rely on extension.
|
||||
return CounterStyle::from_counter_style_definition(definition, {});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,9 +13,16 @@ namespace Web::CSS {
|
||||
class CounterStyleStyleValue : public StyleValueWithDefaultOperators<CounterStyleStyleValue> {
|
||||
|
||||
public:
|
||||
static ValueComparingNonnullRefPtr<CounterStyleStyleValue const> create(FlyString name)
|
||||
struct SymbolsFunction {
|
||||
SymbolsType type;
|
||||
Vector<FlyString> symbols;
|
||||
|
||||
bool operator==(SymbolsFunction const& other) const = default;
|
||||
};
|
||||
|
||||
static ValueComparingNonnullRefPtr<CounterStyleStyleValue const> create(Variant<FlyString, SymbolsFunction> value)
|
||||
{
|
||||
return adopt_ref(*new (nothrow) CounterStyleStyleValue(move(name)));
|
||||
return adopt_ref(*new (nothrow) CounterStyleStyleValue(move(value)));
|
||||
}
|
||||
|
||||
virtual ~CounterStyleStyleValue() override = default;
|
||||
@@ -24,16 +31,16 @@ public:
|
||||
|
||||
RefPtr<CounterStyle const> resolve_counter_style(HashMap<FlyString, NonnullRefPtr<CounterStyle const>> const& registered_counter_styles) const;
|
||||
|
||||
bool properties_equal(CounterStyleStyleValue const& other) const { return m_name == other.m_name; }
|
||||
bool properties_equal(CounterStyleStyleValue const& other) const { return m_value == other.m_value; }
|
||||
|
||||
private:
|
||||
explicit CounterStyleStyleValue(FlyString name)
|
||||
explicit CounterStyleStyleValue(Variant<FlyString, SymbolsFunction> value)
|
||||
: StyleValueWithDefaultOperators(Type::CounterStyle)
|
||||
, m_name(move(name))
|
||||
, m_value(move(value))
|
||||
{
|
||||
}
|
||||
|
||||
FlyString m_name;
|
||||
Variant<FlyString, SymbolsFunction> m_value;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -467,6 +467,7 @@ enum class Scroller : u8;
|
||||
enum class StepPosition : u8;
|
||||
enum class StrokeLinecap : u8;
|
||||
enum class StrokeLinejoin : u8;
|
||||
enum class SymbolsType : u8;
|
||||
enum class TextRendering : u8;
|
||||
enum class TextUnderlinePositionHorizontal : u8;
|
||||
enum class TextUnderlinePositionVertical : u8;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<title>CSS Reference: symbols function, invalid</title>
|
||||
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
|
||||
<link rel="stylesheet" href="../../../../../input/wpt-import/css/css-counter-styles/counter-style-at-rule/support/test-common.css">
|
||||
<style type="text/css">
|
||||
.invalid {
|
||||
list-style-type: lower-greek;
|
||||
}
|
||||
</style>
|
||||
<ol start="-2" class="invalid">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
@@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<title>CSS Reference: symbols function, invalid</title>
|
||||
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
|
||||
<link rel="stylesheet" href="../../../../../input/wpt-import/css/css-counter-styles/counter-style-at-rule/support/test-common.css">
|
||||
<style type="text/css">
|
||||
@counter-style cyclic {
|
||||
system: cyclic;
|
||||
symbols: '*' '\2020' '\2021' '\A7';
|
||||
suffix: ' ';
|
||||
}
|
||||
@counter-style numeric {
|
||||
system: numeric;
|
||||
symbols: '0' '1' '2';
|
||||
suffix: ' ';
|
||||
}
|
||||
@counter-style alphabetic {
|
||||
system: alphabetic;
|
||||
symbols: '\26AA' '\26AB';
|
||||
suffix: ' ';
|
||||
}
|
||||
@counter-style symbolic {
|
||||
system: symbolic;
|
||||
symbols: '*' '\2020' '\2021' '\A7';
|
||||
suffix: ' ';
|
||||
}
|
||||
@counter-style fixed {
|
||||
system: fixed;
|
||||
symbols: '\25F0' '\25F1' '\25F2' '\25F3';
|
||||
suffix: ' ';
|
||||
}
|
||||
@counter-style counter {
|
||||
symbols: '*';
|
||||
}
|
||||
@counter-style counters {
|
||||
system: numeric;
|
||||
symbols: '0' '1';
|
||||
}
|
||||
.counter { counter-reset: a; }
|
||||
.counter p { counter-increment: a 1; }
|
||||
.counter p::after {
|
||||
content: counter(a, counter);
|
||||
}
|
||||
.counter, .counters {
|
||||
list-style-type: none;
|
||||
counter-reset: a;
|
||||
}
|
||||
.counter li, .counters li {
|
||||
counter-increment: a;
|
||||
padding-right: .5em;
|
||||
}
|
||||
.counter li::after {
|
||||
content: counter(a, counter);
|
||||
}
|
||||
.counters .counters li::after {
|
||||
content: counters(a, '.', counters);
|
||||
}
|
||||
</style>
|
||||
<ol start="-2" style="list-style-type: symbolic">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" style="list-style-type: cyclic">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" style="list-style-type: numeric">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" style="list-style-type: alphabetic">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" style="list-style-type: symbolic">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" style="list-style-type: fixed">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol class="counter">
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol class="counters">
|
||||
<li><ol class="counters"><li><li><li><li><li></ol></li>
|
||||
<li><ol class="counters"><li><li><li><li><li></ol></li>
|
||||
</ol>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<title>CSS Test: symbols function, invalid</title>
|
||||
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#symbols-function">
|
||||
<link rel="match" href="../../../../../expected/wpt-import/css/css-counter-styles/counter-style-at-rule/symbols-function-invalid-ref.html">
|
||||
<link rel="stylesheet" href="support/test-common.css">
|
||||
<style type="text/css">
|
||||
.invalid {
|
||||
list-style-type: lower-greek;
|
||||
list-style-type: symbols(a b c);
|
||||
list-style-type: symbols(alphabetic a b c);
|
||||
list-style-type: symbols(numeric 0 1 2);
|
||||
list-style-type: symbols(additive 'a' 'b');
|
||||
list-style-type: symbols(fixed);
|
||||
list-style-type: symbols(alphabetic 'a');
|
||||
list-style-type: symbols(numeric '0');
|
||||
}
|
||||
</style>
|
||||
<ol start="-2" class="invalid">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="UTF-8">
|
||||
<title>CSS Test: symbols function</title>
|
||||
<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#symbols-function">
|
||||
<link rel="match" href="../../../../../expected/wpt-import/css/css-counter-styles/counter-style-at-rule/symbols-function-ref.html">
|
||||
<link rel="stylesheet" href="support/test-common.css">
|
||||
<style type="text/css">
|
||||
.default {
|
||||
list-style-type: symbols('*' '\2020' '\2021' '\A7');
|
||||
}
|
||||
.cyclic {
|
||||
list-style-type: symbols(cyclic '*' '\2020' '\2021' '\A7');
|
||||
}
|
||||
.numeric {
|
||||
list-style-type: symbols(numeric '0' '1' '2');
|
||||
}
|
||||
.alphabetic {
|
||||
list-style-type: symbols(alphabetic '\26AA' '\26AB');
|
||||
}
|
||||
.symbolic {
|
||||
list-style-type: symbols(symbolic '*' '\2020' '\2021' '\A7');
|
||||
}
|
||||
.fixed {
|
||||
list-style-type: symbols(fixed '\25F0' '\25F1' '\25F2' '\25F3');
|
||||
}
|
||||
.counter, .counters {
|
||||
list-style-type: none;
|
||||
counter-reset: a;
|
||||
}
|
||||
.counter li, .counters li {
|
||||
counter-increment: a;
|
||||
padding-right: .5em;
|
||||
}
|
||||
.counter li::after {
|
||||
content: counter(a, symbols('*'));
|
||||
}
|
||||
.counters .counters li::after {
|
||||
content: counters(a, '.', symbols(numeric '0' '1'));
|
||||
}
|
||||
</style>
|
||||
<ol start="-2" class="default">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" class="cyclic">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" class="numeric">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" class="alphabetic">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" class="symbolic">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol start="-2" class="fixed">
|
||||
<li><li><li><li><li>
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol class="counter">
|
||||
<li><li><li><li><li>
|
||||
</ol>
|
||||
<ol class="counters">
|
||||
<li><ol class="counters"><li><li><li><li><li></ol></li>
|
||||
<li><ol class="counters"><li><li><li><li><li></ol></li>
|
||||
</ol>
|
||||
@@ -2,8 +2,7 @@ Harness status: OK
|
||||
|
||||
Found 27 tests
|
||||
|
||||
21 Pass
|
||||
6 Fail
|
||||
27 Pass
|
||||
Pass Property list-style-type value 'none'
|
||||
Pass Property list-style-type value 'disc'
|
||||
Pass Property list-style-type value 'circle'
|
||||
@@ -25,9 +24,9 @@ Pass Property list-style-type value '"marker string"'
|
||||
Pass Property list-style-type value '"Note: "'
|
||||
Pass Property list-style-type value 'counter-Style-Name'
|
||||
Pass Property list-style-type value 'CounterStyleName'
|
||||
Fail Property list-style-type value 'symbols(cyclic "string")'
|
||||
Fail Property list-style-type value 'symbols(cyclic "○" "●")'
|
||||
Fail Property list-style-type value 'symbols(fixed "1")'
|
||||
Fail Property list-style-type value 'symbols("string")'
|
||||
Fail Property list-style-type value 'symbols(alphabetic "first" "second")'
|
||||
Fail Property list-style-type value 'symbols(numeric "first" "second")'
|
||||
Pass Property list-style-type value 'symbols(cyclic "string")'
|
||||
Pass Property list-style-type value 'symbols(cyclic "○" "●")'
|
||||
Pass Property list-style-type value 'symbols(fixed "1")'
|
||||
Pass Property list-style-type value 'symbols("string")'
|
||||
Pass Property list-style-type value 'symbols(alphabetic "first" "second")'
|
||||
Pass Property list-style-type value 'symbols(numeric "first" "second")'
|
||||
@@ -2,8 +2,7 @@ Harness status: OK
|
||||
|
||||
Found 27 tests
|
||||
|
||||
21 Pass
|
||||
6 Fail
|
||||
27 Pass
|
||||
Pass e.style['list-style-type'] = "none" should set the property value
|
||||
Pass e.style['list-style-type'] = "disc" should set the property value
|
||||
Pass e.style['list-style-type'] = "circle" should set the property value
|
||||
@@ -25,9 +24,9 @@ Pass e.style['list-style-type'] = "\"marker string\"" should set the property va
|
||||
Pass e.style['list-style-type'] = "\"Note: \"" should set the property value
|
||||
Pass e.style['list-style-type'] = "counter-Style-Name" should set the property value
|
||||
Pass e.style['list-style-type'] = "CounterStyleName" should set the property value
|
||||
Fail e.style['list-style-type'] = "symbols(cyclic \"string\")" should set the property value
|
||||
Fail e.style['list-style-type'] = "symbols(cyclic \"○\" \"●\")" should set the property value
|
||||
Fail e.style['list-style-type'] = "symbols(fixed \"1\")" should set the property value
|
||||
Fail e.style['list-style-type'] = "symbols(symbolic \"string\")" should set the property value
|
||||
Fail e.style['list-style-type'] = "symbols(alphabetic \"first\" \"second\")" should set the property value
|
||||
Fail e.style['list-style-type'] = "symbols(numeric \"first\" \"second\")" should set the property value
|
||||
Pass e.style['list-style-type'] = "symbols(cyclic \"string\")" should set the property value
|
||||
Pass e.style['list-style-type'] = "symbols(cyclic \"○\" \"●\")" should set the property value
|
||||
Pass e.style['list-style-type'] = "symbols(fixed \"1\")" should set the property value
|
||||
Pass e.style['list-style-type'] = "symbols(symbolic \"string\")" should set the property value
|
||||
Pass e.style['list-style-type'] = "symbols(alphabetic \"first\" \"second\")" should set the property value
|
||||
Pass e.style['list-style-type'] = "symbols(numeric \"first\" \"second\")" should set the property value
|
||||
Reference in New Issue
Block a user