diff --git a/Tests/LibWeb/Layout/expected/css-counters/basic.txt b/Tests/LibWeb/Layout/expected/css-counters/basic.txt
new file mode 100644
index 0000000000000..7520271ecabbd
--- /dev/null
+++ b/Tests/LibWeb/Layout/expected/css-counters/basic.txt
@@ -0,0 +1,134 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+ BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline
+ BlockContainer
at (8,16) content-size 784x314 children: not-inline
+ BlockContainer at (8,16) content-size 784x149 children: not-inline
+ BlockContainer
at (8,16) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 5, rect: [37,16 44.75x17] baseline: 13.296875
+ "Never"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,16 28.8125x17] baseline: 13.296875
+ "1.1: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,49) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 5, rect: [39,49 52.15625x17] baseline: 13.296875
+ "Gonna"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,49 31.28125x17] baseline: 13.296875
+ "1.2: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,82) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 4, rect: [40,82 34.71875x17] baseline: 13.296875
+ "Give"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,82 31.5625x17] baseline: 13.296875
+ "1.3: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,115) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 3, rect: [38,115 31.21875x17] baseline: 13.296875
+ "You"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,115 30.21875x17] baseline: 13.296875
+ "1.4: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,148) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 2, rect: [39,148 20.71875x17] baseline: 13.296875
+ "Up"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,148 30.921875x17] baseline: 13.296875
+ "1.5: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,181) content-size 784x149 children: not-inline
+ BlockContainer
at (8,181) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 5, rect: [39,181 44.75x17] baseline: 13.296875
+ "Never"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,181 31.28125x17] baseline: 13.296875
+ "2.1: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,214) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 5, rect: [42,214 52.15625x17] baseline: 13.296875
+ "Gonna"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,214 33.75x17] baseline: 13.296875
+ "2.2: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,247) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 3, rect: [42,247 26.4375x17] baseline: 13.296875
+ "Let"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,247 34.03125x17] baseline: 13.296875
+ "2.3: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,280) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 3, rect: [41,280 31.21875x17] baseline: 13.296875
+ "You"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,280 32.6875x17] baseline: 13.296875
+ "2.4: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer
at (8,313) content-size 784x17 children: inline
+ frag 0 from TextNode start: 0, length: 4, rect: [41,313 42.328125x17] baseline: 13.296875
+ "Down"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [8,313 33.390625x17] baseline: 13.296875
+ "2.5: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (8,346) content-size 784x0 children: inline
+ TextNode <#text>
+
+ViewportPaintable (Viewport<#document>) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [0,0 800x600]
+ PaintableWithLines (BlockContainer
) [8,16 784x314] overflow: [8,16 784x330]
+ PaintableWithLines (BlockContainer
) [8,16 784x149]
+ PaintableWithLines (BlockContainer
) [8,16 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,49 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,82 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,115 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,148 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,181 784x149]
+ PaintableWithLines (BlockContainer
) [8,181 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,214 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,247 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,280 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
) [8,313 784x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [8,346 784x0]
diff --git a/Tests/LibWeb/Layout/expected/css-counters/counters-function.txt b/Tests/LibWeb/Layout/expected/css-counters/counters-function.txt
new file mode 100644
index 0000000000000..13f00b57ec17d
--- /dev/null
+++ b/Tests/LibWeb/Layout/expected/css-counters/counters-function.txt
@@ -0,0 +1,254 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+ BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline
+ BlockContainer
at (8,8) content-size 784x255 children: not-inline
+ BlockContainer
at (24,8) content-size 768x255 children: not-inline
+ BlockContainer <(anonymous)> at (24,8) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer at (24,8) content-size 768x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [42,8 14.265625x17] baseline: 13.296875
+ "A"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 3, rect: [24,8 18.125x17] baseline: 13.296875
+ "1: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (24,25) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer at (24,25) content-size 768x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [45,25 9.34375x17] baseline: 13.296875
+ "B"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 3, rect: [24,25 20.59375x17] baseline: 13.296875
+ "2: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (24,42) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer at (24,42) content-size 768x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [45,42 10.3125x17] baseline: 13.296875
+ "C"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 3, rect: [24,42 20.875x17] baseline: 13.296875
+ "3: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (24,59) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer at (24,59) content-size 768x153 children: not-inline
+ BlockContainer <(anonymous)> at (24,59) content-size 768x17 children: inline
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 2, rect: [24,59 11.53125x17] baseline: 13.296875
+ "4:"
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer at (40,76) content-size 752x136 children: not-inline
+ BlockContainer <(anonymous)> at (40,76) content-size 752x0 children: inline
+ TextNode <#text>
+ BlockContainer at (40,76) content-size 752x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [70,76 11.140625x17] baseline: 13.296875
+ "D"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [40,76 30.21875x17] baseline: 13.296875
+ "4.1: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (40,93) content-size 752x0 children: inline
+ TextNode <#text>
+ BlockContainer at (40,93) content-size 752x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [73,93 11.859375x17] baseline: 13.296875
+ "E"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [40,93 32.6875x17] baseline: 13.296875
+ "4.2: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (40,110) content-size 752x0 children: inline
+ TextNode <#text>
+ BlockContainer at (40,110) content-size 752x68 children: not-inline
+ BlockContainer <(anonymous)> at (40,110) content-size 752x17 children: inline
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 4, rect: [40,110 24.96875x17] baseline: 13.296875
+ "4.3:"
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer at (56,127) content-size 736x51 children: not-inline
+ BlockContainer <(anonymous)> at (56,127) content-size 736x0 children: inline
+ TextNode <#text>
+ BlockContainer at (56,127) content-size 736x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [100,127 12.546875x17] baseline: 13.296875
+ "F"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 7, rect: [56,127 43.65625x17] baseline: 13.296875
+ "4.3.1: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (56,144) content-size 736x0 children: inline
+ TextNode <#text>
+ BlockContainer at (56,144) content-size 736x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [102,144 13.234375x17] baseline: 13.296875
+ "G"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 7, rect: [56,144 46.125x17] baseline: 13.296875
+ "4.3.2: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (56,161) content-size 736x0 children: inline
+ TextNode <#text>
+ BlockContainer at (56,161) content-size 736x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [102,161 12.234375x17] baseline: 13.296875
+ "H"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 7, rect: [56,161 46.40625x17] baseline: 13.296875
+ "4.3.3: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (56,178) content-size 736x0 children: inline
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (40,178) content-size 752x0 children: inline
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (40,178) content-size 752x0 children: inline
+ TextNode <#text>
+ BlockContainer at (40,178) content-size 752x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [72,178 4.59375x17] baseline: 13.296875
+ "I"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [40,178 31.625x17] baseline: 13.296875
+ "4.4: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (40,195) content-size 752x0 children: inline
+ TextNode <#text>
+ BlockContainer at (40,195) content-size 752x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [72,195 8.90625x17] baseline: 13.296875
+ "J"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 5, rect: [40,195 32.328125x17] baseline: 13.296875
+ "4.5: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (40,212) content-size 752x0 children: inline
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (24,212) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (24,212) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer at (24,212) content-size 768x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [44,212 9.8125x17] baseline: 13.296875
+ "K"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 3, rect: [24,212 20.234375x17] baseline: 13.296875
+ "5: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (24,229) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer at (24,229) content-size 768x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [45,229 10.859375x17] baseline: 13.296875
+ "L"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 3, rect: [24,229 20.515625x17] baseline: 13.296875
+ "6: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (24,246) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer at (24,246) content-size 768x17 children: inline
+ frag 0 from TextNode start: 0, length: 1, rect: [45,246 11.765625x17] baseline: 13.296875
+ "M"
+ InlineNode <(anonymous)>
+ frag 0 from TextNode start: 0, length: 3, rect: [24,246 20.5x17] baseline: 13.296875
+ "7: "
+ TextNode <#text>
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (24,263) content-size 768x0 children: inline
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (8,263) content-size 784x0 children: inline
+ TextNode <#text>
+
+ViewportPaintable (Viewport<#document>) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [8,8 784x255]
+ PaintableWithLines (BlockContainer.ol) [8,8 784x255]
+ PaintableWithLines (BlockContainer(anonymous)) [24,8 768x0]
+ PaintableWithLines (BlockContainer
.li) [24,8 768x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [24,25 768x0]
+ PaintableWithLines (BlockContainer
.li) [24,25 768x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [24,42 768x0]
+ PaintableWithLines (BlockContainer
.li) [24,42 768x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [24,59 768x0]
+ PaintableWithLines (BlockContainer
.li) [24,59 768x153]
+ PaintableWithLines (BlockContainer(anonymous)) [24,59 768x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
.ol) [24,76 768x136]
+ PaintableWithLines (BlockContainer(anonymous)) [40,76 752x0]
+ PaintableWithLines (BlockContainer
.li) [40,76 752x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [40,93 752x0]
+ PaintableWithLines (BlockContainer
.li) [40,93 752x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [40,110 752x0]
+ PaintableWithLines (BlockContainer
.li) [40,110 752x68]
+ PaintableWithLines (BlockContainer(anonymous)) [40,110 752x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer
.ol) [40,127 752x51]
+ PaintableWithLines (BlockContainer(anonymous)) [56,127 736x0]
+ PaintableWithLines (BlockContainer
.li) [56,127 736x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [56,144 736x0]
+ PaintableWithLines (BlockContainer
.li) [56,144 736x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [56,161 736x0]
+ PaintableWithLines (BlockContainer
.li) [56,161 736x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [56,178 736x0]
+ PaintableWithLines (BlockContainer(anonymous)) [40,178 752x0]
+ PaintableWithLines (BlockContainer(anonymous)) [40,178 752x0]
+ PaintableWithLines (BlockContainer
.li) [40,178 752x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [40,195 752x0]
+ PaintableWithLines (BlockContainer
.li) [40,195 752x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [40,212 752x0]
+ PaintableWithLines (BlockContainer(anonymous)) [24,212 768x0]
+ PaintableWithLines (BlockContainer(anonymous)) [24,212 768x0]
+ PaintableWithLines (BlockContainer
.li) [24,212 768x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [24,229 768x0]
+ PaintableWithLines (BlockContainer
.li) [24,229 768x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [24,246 768x0]
+ PaintableWithLines (BlockContainer
.li) [24,246 768x17]
+ InlinePaintable (InlineNode(anonymous))
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ PaintableWithLines (BlockContainer(anonymous)) [24,263 768x0]
+ PaintableWithLines (BlockContainer(anonymous)) [8,263 784x0]
diff --git a/Tests/LibWeb/Layout/input/css-counters/basic.html b/Tests/LibWeb/Layout/input/css-counters/basic.html
new file mode 100644
index 0000000000000..c96a460876076
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/css-counters/basic.html
@@ -0,0 +1,12 @@
+
diff --git a/Tests/LibWeb/Layout/input/css-counters/counters-function.html b/Tests/LibWeb/Layout/input/css-counters/counters-function.html
new file mode 100644
index 0000000000000..467027d1850ed
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/css-counters/counters-function.html
@@ -0,0 +1,36 @@
+
+
+
+
A
+
B
+
C
+
+
K
+
L
+
M
+
diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
index b4af44601c13d..305aa531c55db 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2018-2023, Andreas Kling
- * Copyright (c) 2021-2023, Sam Atkins
+ * Copyright (c) 2021-2024, Sam Atkins
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -647,7 +648,7 @@ Optional StyleProperties::clear() const
return value_id_to_clear(value->to_identifier());
}
-StyleProperties::ContentDataAndQuoteNestingLevel StyleProperties::content(u32 initial_quote_nesting_level) const
+StyleProperties::ContentDataAndQuoteNestingLevel StyleProperties::content(DOM::Element& element, u32 initial_quote_nesting_level) const
{
auto value = property(CSS::PropertyID::Content);
auto quotes_data = quotes();
@@ -710,8 +711,10 @@ StyleProperties::ContentDataAndQuoteNestingLevel StyleProperties::content(u32 in
dbgln("`{}` is not supported in `content` (yet?)", item->to_string());
break;
}
+ } else if (item->is_counter()) {
+ builder.append(item->as_counter().resolve(element));
} else {
- // TODO: Implement counters, images, and other things.
+ // TODO: Implement images, and other things.
dbgln("`{}` is not supported in `content` (yet?)", item->to_string());
}
}
@@ -723,8 +726,10 @@ StyleProperties::ContentDataAndQuoteNestingLevel StyleProperties::content(u32 in
for (auto const& item : content_style_value.alt_text()->values()) {
if (item->is_string()) {
alt_text_builder.append(item->as_string().string_value());
+ } else if (item->is_counter()) {
+ alt_text_builder.append(item->as_counter().resolve(element));
} else {
- // TODO: Implement counters
+ dbgln("`{}` is not supported in `content` alt-text (yet?)", item->to_string());
}
}
content_data.alt_text = MUST(alt_text_builder.to_string());
diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h
index 9be8c9a60b4c8..7558842d50318 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h
+++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h
@@ -85,7 +85,7 @@ class StyleProperties : public RefCounted {
CSS::ContentData content_data;
u32 final_quote_nesting_level { 0 };
};
- ContentDataAndQuoteNestingLevel content(u32 initial_quote_nesting_level) const;
+ ContentDataAndQuoteNestingLevel content(DOM::Element&, u32 initial_quote_nesting_level) const;
Optional content_visibility() const;
Optional cursor() const;
Optional white_space() const;
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp
index 358dc9e62c4d0..bf9fd62d1406f 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp
@@ -1,13 +1,18 @@
/*
+ * Copyright (c) 2018-2022, Andreas Kling
+ * Copyright (c) 2021, Tobias Christiansen
* Copyright (c) 2024, Sam Atkins
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CounterStyleValue.h"
+#include
#include
#include
#include
+#include
+#include
namespace Web::CSS {
@@ -24,6 +29,107 @@ CounterStyleValue::CounterStyleValue(CounterFunction function, FlyString counter
CounterStyleValue::~CounterStyleValue() = default;
+// https://drafts.csswg.org/css-counter-styles-3/#generate-a-counter
+static String generate_a_counter_representation(StyleValue const& counter_style, i32 value)
+{
+ // When asked to generate a counter representation using a particular counter style for a particular
+ // counter value, follow these steps:
+ // TODO: 1. If the counter style is unknown, exit this algorithm and instead generate a counter representation
+ // using the decimal style and the same counter value.
+ // TODO: 2. If the counter value is outside the range of the counter style, exit this algorithm and instead
+ // generate a counter representation using the counter style’s fallback style and the same counter value.
+ // TODO: 3. Using the counter value and the counter algorithm for the counter style, generate an initial
+ // representation for the counter value.
+ // If the counter value is negative and the counter style uses a negative sign, instead generate an
+ // initial representation using the absolute value of the counter value.
+ // TODO: 4. Prepend symbols to the representation as specified in the pad descriptor.
+ // TODO: 5. If the counter value is negative and the counter style uses a negative sign, wrap the representation
+ // in the counter style’s negative sign as specified in the negative descriptor.
+ // TODO: 6. Return the representation.
+
+ // FIXME: Below is an ad-hoc implementation until we support @counter-style.
+ // It's based largely on the ListItemMarkerBox code, with minimal adjustments.
+ if (counter_style.is_custom_ident()) {
+ auto counter_style_name = counter_style.as_custom_ident().custom_ident();
+ auto identifier = value_id_from_string(counter_style_name);
+ if (identifier.has_value()) {
+ auto list_style_type = value_id_to_list_style_type(*identifier);
+ if (list_style_type.has_value()) {
+ switch (*list_style_type) {
+ case ListStyleType::Square:
+ return "▪"_string;
+ case ListStyleType::Circle:
+ return "◦"_string;
+ case ListStyleType::Disc:
+ return "•"_string;
+ case ListStyleType::DisclosureClosed:
+ return "▸"_string;
+ case ListStyleType::DisclosureOpen:
+ return "▾"_string;
+ case ListStyleType::Decimal:
+ return MUST(String::formatted("{}", value));
+ case ListStyleType::DecimalLeadingZero:
+ // This is weird, but in accordance to spec.
+ if (value < 10)
+ return MUST(String::formatted("0{}", value));
+ return MUST(String::formatted("{}", value));
+ case ListStyleType::LowerAlpha:
+ case ListStyleType::LowerLatin:
+ return MUST(String::from_byte_string(ByteString::bijective_base_from(value - 1).to_lowercase()));
+ case ListStyleType::UpperAlpha:
+ case ListStyleType::UpperLatin:
+ return MUST(String::from_byte_string(ByteString::bijective_base_from(value - 1)));
+ case ListStyleType::LowerRoman:
+ return MUST(String::from_byte_string(ByteString::roman_number_from(value).to_lowercase()));
+ case ListStyleType::UpperRoman:
+ return MUST(String::from_byte_string(ByteString::roman_number_from(value)));
+ default:
+ break;
+ }
+ }
+ }
+ }
+ // FIXME: Handle `symbols()` function for counter_style.
+
+ dbgln("FIXME: Unsupported counter style '{}'", counter_style.to_string());
+ return MUST(String::formatted("{}", value));
+}
+
+String CounterStyleValue::resolve(DOM::Element& element) const
+{
+ // "If no counter named exists on an element where counter() or counters() is used,
+ // one is first instantiated with a starting value of 0."
+ auto& counters_set = element.ensure_counters_set();
+ if (!counters_set.last_counter_with_name(m_properties.counter_name).has_value())
+ counters_set.instantiate_a_counter(m_properties.counter_name, element.unique_id(), false, 0);
+
+ // counter( , ? )
+ // "Represents the value of the innermost counter in the element’s CSS counters set named
+ // using the counter style named ."
+ if (m_properties.function == CounterFunction::Counter) {
+ // NOTE: This should always be present because of the handling of a missing counter above.
+ auto& counter = counters_set.last_counter_with_name(m_properties.counter_name).value();
+ return generate_a_counter_representation(m_properties.counter_style, counter.value.value_or(0).value());
+ }
+
+ // counters( , , ? )
+ // "Represents the values of all the counters in the element’s CSS counters set named
+ // using the counter style named , sorted in outermost-first to innermost-last order
+ // and joined by the specified ."
+ // NOTE: The way counters sets are inherited, this should be the order they appear in the counters set.
+ StringBuilder stb;
+ for (auto const& counter : counters_set.counters()) {
+ if (counter.name != m_properties.counter_name)
+ continue;
+
+ auto counter_string = generate_a_counter_representation(m_properties.counter_style, counter.value.value_or(0).value());
+ if (!stb.is_empty())
+ stb.append(m_properties.join_string);
+ stb.append(counter_string);
+ }
+ return stb.to_string_without_validation();
+}
+
// https://drafts.csswg.org/cssom-1/#ref-for-typedef-counter
String CounterStyleValue::to_string() const
{
diff --git a/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp b/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp
index 61e4f566fc4b4..98c7d3f88d415 100644
--- a/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp
+++ b/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp
@@ -198,7 +198,7 @@ void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Se
return;
auto initial_quote_nesting_level = m_quote_nesting_level;
- auto [pseudo_element_content, final_quote_nesting_level] = pseudo_element_style->content(initial_quote_nesting_level);
+ auto [pseudo_element_content, final_quote_nesting_level] = pseudo_element_style->content(element, initial_quote_nesting_level);
m_quote_nesting_level = final_quote_nesting_level;
auto pseudo_element_display = pseudo_element_style->display();
// ::before and ::after only exist if they have content. `content: normal` computes to `none` for them.