LibWeb: Rebuild counter style cache lazily

Stop rebuilding the counter style cache from every style update.
That made unrelated restyles pay the full counter-style cost even when
no relevant stylesheet state had changed.

Dirty the cache when stylesheet rule caches are invalidated and rebuild
it on the first counter-style lookup instead. Also make cold cache
rebuilds include user stylesheets.

Add regression tests covering insertRule() and replaceSync() updates
that should make newly defined counter styles take effect.
This commit is contained in:
Andreas Kling
2026-04-05 11:20:32 +02:00
committed by Andreas Kling
parent 0b5ef8fa22
commit e2e3c7fcdf
Notes: github-actions[bot] 2026-04-05 10:35:26 +00:00
8 changed files with 108 additions and 9 deletions
+15 -5
View File
@@ -95,9 +95,7 @@ void StyleScope::build_rule_cache()
m_selector_insights = make<SelectorInsights>();
m_style_invalidation_data = make<StyleInvalidationData>();
if (auto user_style_source = document().page().user_style(); user_style_source.has_value()) {
m_user_style_sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(document()), user_style_source.value()));
}
build_user_style_sheet_if_needed();
build_qualified_layer_names_cache();
@@ -115,6 +113,7 @@ void StyleScope::build_rule_cache()
void StyleScope::invalidate_rule_cache()
{
document().invalidate_counter_style_cache();
m_author_rule_cache = nullptr;
// NOTE: We could be smarter about keeping the user rule cache, and style sheet.
@@ -131,6 +130,15 @@ void StyleScope::invalidate_rule_cache()
m_style_invalidation_data = nullptr;
}
void StyleScope::build_user_style_sheet_if_needed()
{
if (m_user_style_sheet)
return;
if (auto user_style_source = document().page().user_style(); user_style_source.has_value())
m_user_style_sheet = GC::make_root(parse_css_stylesheet(CSS::Parser::ParsingParams(document()), user_style_source.value()));
}
void StyleScope::build_rule_cache_if_needed() const
{
if (has_valid_rule_cache())
@@ -188,8 +196,10 @@ void StyleScope::for_each_stylesheet(CascadeOrigin cascade_origin, Function<void
callback(svg_stylesheet());
}
if (cascade_origin == CascadeOrigin::User) {
if (m_user_style_sheet)
callback(*m_user_style_sheet);
auto& style_scope = const_cast<StyleScope&>(*this);
style_scope.build_user_style_sheet_if_needed();
if (style_scope.m_user_style_sheet)
callback(*style_scope.m_user_style_sheet);
}
if (cascade_origin == CascadeOrigin::Author) {
for_each_active_css_style_sheet(move(callback));
+1
View File
@@ -90,6 +90,7 @@ public:
[[nodiscard]] RuleCache const& get_pseudo_class_rule_cache(PseudoClass) const;
void for_each_stylesheet(CascadeOrigin, Function<void(CSS::CSSStyleSheet&)> const&) const;
void build_user_style_sheet_if_needed();
void make_rule_cache_for_cascade_origin(CascadeOrigin, SelectorInsights&);
+10 -3
View File
@@ -1800,9 +1800,6 @@ void Document::update_style()
build_registered_properties_cache();
// FIXME: We don't need to rebuild this cache on every style update, just if a @counter-style rule has changed.
build_counter_style_cache();
auto invalidation = update_style_recursively(*this, style_computer(), false, false, false);
if (!invalidation.is_none())
invalidate_display_list();
@@ -6659,6 +6656,14 @@ void Document::for_each_active_css_style_sheet(Function<void(CSS::CSSStyleSheet&
}
}
HashMap<FlyString, NonnullRefPtr<CSS::CounterStyle const>> const& Document::registered_counter_styles() const
{
if (m_needs_counter_style_cache_update)
const_cast<Document&>(*this).build_counter_style_cache();
return m_registered_counter_styles;
}
double Document::ensure_element_shared_css_random_base_value(CSS::RandomCachingKey const& random_caching_key)
{
return m_element_shared_css_random_base_value_cache.ensure(random_caching_key, []() {
@@ -8296,6 +8301,8 @@ void Document::build_counter_style_cache()
--i;
}
}
m_needs_counter_style_cache_update = false;
}
StringView to_string(SetNeedsLayoutReason reason)
+4 -1
View File
@@ -1044,11 +1044,13 @@ public:
Optional<CSS::CustomPropertyRegistration const&> get_registered_custom_property(FlyString const& name) const;
NonnullRefPtr<CSS::StyleValue const> custom_property_initial_value(FlyString const& name) const;
HashMap<FlyString, NonnullRefPtr<CSS::CounterStyle const>> const& registered_counter_styles() const { return m_registered_counter_styles; }
HashMap<FlyString, NonnullRefPtr<CSS::CounterStyle const>> const& registered_counter_styles() const;
CSS::StyleScope const& style_scope() const { return m_style_scope; }
CSS::StyleScope& style_scope() { return m_style_scope; }
void invalidate_counter_style_cache() { m_needs_counter_style_cache_update = true; }
void exit_pointer_lock();
Optional<CSS::SelectorList> const* cached_query_selector_result(String const& selector_text) const;
@@ -1487,6 +1489,7 @@ private:
HashMap<FlyString, CSS::CustomPropertyRegistration> m_registered_property_set;
HashMap<FlyString, CSS::CustomPropertyRegistration> m_cached_registered_properties_from_css_property_rules;
bool m_needs_counter_style_cache_update { true };
HashMap<FlyString, NonnullRefPtr<CSS::CounterStyle const>> m_registered_counter_styles;
CSS::StyleScope m_style_scope;
@@ -0,0 +1,22 @@
Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children: not-inline
BlockContainer <html> at [0,0] [0+0+0 800 0+0+0] [0+0+0 34 0+0+0] [BFC] children: not-inline
BlockContainer <body> at [8,8] [8+0+0 784 0+0+8] [8+0+0 18 0+0+8] children: not-inline
BlockContainer <div> at [8,8] [0+0+0 784 0+0+0] [0+0+0 18 0+0+0] children: inline
InlineNode <(anonymous)> at [8,8] [0+0+0 39.6875 0+0+0] [0+0+0 18 0+0+0]
frag 0 from TextNode start: 0, length: 5, rect: [8,8 39.6875x18] baseline: 13.796875
"*****"
TextNode <#text> (not painted)
BlockContainer <(anonymous)> at [8,26] [0+0+0 784 0+0+0] [0+0+0 0 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x34]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x18]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x18]
PaintableWithLines (InlineNode(anonymous)) [8,8 39.6875x18]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [8,26 784x0]
SC for Viewport<#document> [0,0 800x600] [children: 1] (z-index: auto)
SC for BlockContainer<HTML> [0,0 800x34] [children: 0] (z-index: auto)
@@ -0,0 +1,22 @@
Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children: not-inline
BlockContainer <html> at [0,0] [0+0+0 800 0+0+0] [0+0+0 34 0+0+0] [BFC] children: not-inline
BlockContainer <body> at [8,8] [8+0+0 784 0+0+8] [8+0+0 18 0+0+8] children: not-inline
BlockContainer <div> at [8,8] [0+0+0 784 0+0+0] [0+0+0 18 0+0+0] children: inline
InlineNode <(anonymous)> at [8,8] [0+0+0 39.6875 0+0+0] [0+0+0 18 0+0+0]
frag 0 from TextNode start: 0, length: 5, rect: [8,8 39.6875x18] baseline: 13.796875
"*****"
TextNode <#text> (not painted)
BlockContainer <(anonymous)> at [8,26] [0+0+0 784 0+0+0] [0+0+0 0 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x34]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x18]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x18]
PaintableWithLines (InlineNode(anonymous)) [8,8 39.6875x18]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [8,26 784x0]
SC for Viewport<#document> [0,0 800x600] [children: 1] (z-index: auto)
SC for BlockContainer<HTML> [0,0 800x34] [children: 0] (z-index: auto)
@@ -0,0 +1,11 @@
<!doctype html>
<style id="style">
div::after {
content: counter(a, dynamic-counter-style);
}
</style>
<div style="counter-reset: a 5"></div>
<script>
style.sheet.insertRule("@counter-style dynamic-counter-style { symbols: \"*\"; }", 0);
document.body.offsetWidth;
</script>
@@ -0,0 +1,23 @@
<!doctype html>
<div style="counter-reset: a 5"></div>
<script>
let sheet = new CSSStyleSheet();
sheet.replaceSync(`
div::after {
content: counter(a, dynamic-counter-style);
}
`);
document.adoptedStyleSheets = [sheet];
sheet.replaceSync(`
@counter-style dynamic-counter-style {
symbols: "*";
}
div::after {
content: counter(a, dynamic-counter-style);
}
`);
document.body.offsetWidth;
</script>