LibWeb: Make :hover invalidation logic reusable for all pseudo classes

We achieve this by keeping track of all checked pseudo class selectors
in the SelectorEngine code. We also give StyleComputer per-pseudo-class
rule caches.
This commit is contained in:
Andreas Kling 2025-04-17 13:39:30 +02:00 committed by Andreas Kling
parent ed35f9e7c2
commit e1777f6e79
Notes: github-actions[bot] 2025-04-17 17:47:22 +00:00
13 changed files with 134 additions and 59 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org> * Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
@ -18,6 +18,8 @@
#include <LibWeb/CSS/ComputedValues.h> #include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/CSS/LengthBox.h> #include <LibWeb/CSS/LengthBox.h>
#include <LibWeb/CSS/PropertyID.h> #include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/PseudoClass.h>
#include <LibWeb/CSS/PseudoClassBitmap.h>
#include <LibWeb/CSS/StyleProperty.h> #include <LibWeb/CSS/StyleProperty.h>
namespace Web::CSS { namespace Web::CSS {
@ -228,8 +230,15 @@ public:
static float resolve_opacity_value(CSSStyleValue const& value); static float resolve_opacity_value(CSSStyleValue const& value);
bool did_match_any_hover_rules() const { return m_did_match_any_hover_rules; } bool has_attempted_match_against_pseudo_class(PseudoClass pseudo_class) const
void set_did_match_any_hover_rules() { m_did_match_any_hover_rules = true; } {
return m_attempted_pseudo_class_matches.get(pseudo_class);
}
void set_attempted_pseudo_class_matches(PseudoClassBitmap const& results)
{
m_attempted_pseudo_class_matches = results;
}
private: private:
friend class StyleComputer; friend class StyleComputer;
@ -256,7 +265,7 @@ private:
Optional<CSSPixels> m_line_height; Optional<CSSPixels> m_line_height;
bool m_did_match_any_hover_rules { false }; PseudoClassBitmap m_attempted_pseudo_class_matches;
}; };
} }

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/PseudoClass.h>
namespace Web::CSS {
class PseudoClassBitmap {
public:
PseudoClassBitmap() = default;
~PseudoClassBitmap() = default;
void set(PseudoClass pseudo_class, bool bit)
{
size_t const index = to_underlying(pseudo_class);
if (bit)
m_bits |= 1LLU << index;
else
m_bits &= ~(1LLU << index);
}
bool get(PseudoClass pseudo_class) const
{
size_t const index = to_underlying(pseudo_class);
return (m_bits & (1LLU << index)) != 0;
}
void operator|=(PseudoClassBitmap const& other)
{
m_bits |= other.m_bits;
}
private:
u64 m_bits { 0 };
};
// NOTE: If this changes, we'll have to tweak PseudoClassBitmap a little bit :)
static_assert(to_underlying(PseudoClass::__Count) <= 64);
}

View file

@ -101,16 +101,12 @@ Selector::Selector(Vector<CompoundSelector>&& compound_selectors)
break; break;
} }
if (simple_selector.type == SimpleSelector::Type::PseudoClass) { if (simple_selector.type == SimpleSelector::Type::PseudoClass) {
if (simple_selector.pseudo_class().type == PseudoClass::Hover) { m_contained_pseudo_classes.set(simple_selector.pseudo_class().type, true);
m_contains_hover_pseudo_class = true;
}
for (auto const& child_selector : simple_selector.pseudo_class().argument_selector_list) { for (auto const& child_selector : simple_selector.pseudo_class().argument_selector_list) {
if (child_selector->contains_the_nesting_selector()) { if (child_selector->contains_the_nesting_selector()) {
m_contains_the_nesting_selector = true; m_contains_the_nesting_selector = true;
} }
if (child_selector->contains_hover_pseudo_class()) { m_contained_pseudo_classes |= child_selector->m_contained_pseudo_classes;
m_contains_hover_pseudo_class = true;
}
} }
if (m_contains_the_nesting_selector) if (m_contains_the_nesting_selector)
break; break;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org> * Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
@ -14,6 +14,7 @@
#include <LibWeb/CSS/Keyword.h> #include <LibWeb/CSS/Keyword.h>
#include <LibWeb/CSS/Parser/ComponentValue.h> #include <LibWeb/CSS/Parser/ComponentValue.h>
#include <LibWeb/CSS/PseudoClass.h> #include <LibWeb/CSS/PseudoClass.h>
#include <LibWeb/CSS/PseudoClassBitmap.h>
#include <LibWeb/CSS/PseudoElement.h> #include <LibWeb/CSS/PseudoElement.h>
namespace Web::CSS { namespace Web::CSS {
@ -236,7 +237,7 @@ public:
Optional<PseudoElementSelector> const& pseudo_element() const { return m_pseudo_element; } Optional<PseudoElementSelector> const& pseudo_element() const { return m_pseudo_element; }
NonnullRefPtr<Selector> relative_to(SimpleSelector const&) const; NonnullRefPtr<Selector> relative_to(SimpleSelector const&) const;
bool contains_the_nesting_selector() const { return m_contains_the_nesting_selector; } bool contains_the_nesting_selector() const { return m_contains_the_nesting_selector; }
bool contains_hover_pseudo_class() const { return m_contains_hover_pseudo_class; } bool contains_pseudo_class(PseudoClass pseudo_class) const { return m_contained_pseudo_classes.get(pseudo_class); }
bool contains_unknown_webkit_pseudo_element() const; bool contains_unknown_webkit_pseudo_element() const;
RefPtr<Selector> absolutized(SimpleSelector const& selector_for_nesting) const; RefPtr<Selector> absolutized(SimpleSelector const& selector_for_nesting) const;
u32 specificity() const; u32 specificity() const;
@ -259,7 +260,8 @@ private:
bool m_can_use_fast_matches { false }; bool m_can_use_fast_matches { false };
bool m_can_use_ancestor_filter { false }; bool m_can_use_ancestor_filter { false };
bool m_contains_the_nesting_selector { false }; bool m_contains_the_nesting_selector { false };
bool m_contains_hover_pseudo_class { false };
PseudoClassBitmap m_contained_pseudo_classes;
void collect_ancestor_hashes(); void collect_ancestor_hashes();

View file

@ -433,7 +433,10 @@ static bool matches_optimal_value_pseudo_class(DOM::Element const& element, HTML
static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector const& pseudo_class, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind) static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector const& pseudo_class, DOM::Element const& element, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, GC::Ptr<DOM::ParentNode const> scope, SelectorKind selector_kind)
{ {
context.attempted_pseudo_class_matches.set(pseudo_class.type, true);
switch (pseudo_class.type) { switch (pseudo_class.type) {
case CSS::PseudoClass::__Count:
VERIFY_NOT_REACHED();
case CSS::PseudoClass::Link: case CSS::PseudoClass::Link:
case CSS::PseudoClass::AnyLink: case CSS::PseudoClass::AnyLink:
// NOTE: AnyLink should match whether the link is visited or not, so if we ever start matching // NOTE: AnyLink should match whether the link is visited or not, so if we ever start matching
@ -448,7 +451,6 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
case CSS::PseudoClass::Active: case CSS::PseudoClass::Active:
return element.is_active(); return element.is_active();
case CSS::PseudoClass::Hover: case CSS::PseudoClass::Hover:
context.did_match_any_hover_rules = true;
return matches_hover_pseudo_class(element); return matches_hover_pseudo_class(element);
case CSS::PseudoClass::Focus: case CSS::PseudoClass::Focus:
return element.is_focused(); return element.is_focused();
@ -586,6 +588,8 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
int index = 1; int index = 1;
switch (pseudo_class.type) { switch (pseudo_class.type) {
case CSS::PseudoClass::__Count:
VERIFY_NOT_REACHED();
case CSS::PseudoClass::NthChild: { case CSS::PseudoClass::NthChild: {
if (!matches_selector_list(pseudo_class.argument_selector_list, element)) if (!matches_selector_list(pseudo_class.argument_selector_list, element))
return false; return false;

View file

@ -20,7 +20,7 @@ struct MatchContext {
GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule {}; GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule {};
GC::Ptr<DOM::Element const> subject {}; GC::Ptr<DOM::Element const> subject {};
bool collect_per_element_selector_involvement_metadata { false }; bool collect_per_element_selector_involvement_metadata { false };
bool did_match_any_hover_rules { false }; CSS::PseudoClassBitmap attempted_pseudo_class_matches {};
}; };
bool matches(CSS::Selector const&, DOM::Element const&, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, Optional<CSS::PseudoElement> = {}, GC::Ptr<DOM::ParentNode const> scope = {}, SelectorKind selector_kind = SelectorKind::Normal, GC::Ptr<DOM::Element const> anchor = nullptr); bool matches(CSS::Selector const&, DOM::Element const&, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, Optional<CSS::PseudoElement> = {}, GC::Ptr<DOM::ParentNode const> scope = {}, SelectorKind selector_kind = SelectorKind::Normal, GC::Ptr<DOM::Element const> anchor = nullptr);

View file

@ -422,10 +422,10 @@ RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin casc
return true; return true;
} }
RuleCache const& StyleComputer::get_hover_rules() const RuleCache const& StyleComputer::get_pseudo_class_rule_cache(PseudoClass pseudo_class) const
{ {
build_rule_cache_if_needed(); build_rule_cache_if_needed();
return *m_hover_rule_cache; return *m_pseudo_class_rule_cache[to_underlying(pseudo_class)];
} }
InvalidationSet StyleComputer::invalidation_set_for_properties(Vector<InvalidationSet::Property> const& properties) const InvalidationSet StyleComputer::invalidation_set_for_properties(Vector<InvalidationSet::Property> const& properties) const
@ -472,7 +472,7 @@ bool StyleComputer::invalidation_property_used_in_has_selector(InvalidationSet::
return false; return false;
} }
Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_hover_rules, FlyString const& qualified_layer_name) const Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin, Optional<CSS::PseudoElement> pseudo_element, PseudoClassBitmap& attempted_pseudo_class_matches, FlyString const& qualified_layer_name) const
{ {
auto const& root_node = element.root(); auto const& root_node = element.root();
auto shadow_root = is<DOM::ShadowRoot>(root_node) ? static_cast<DOM::ShadowRoot const*>(&root_node) : nullptr; auto shadow_root = is<DOM::ShadowRoot>(root_node) ? static_cast<DOM::ShadowRoot const*>(&root_node) : nullptr;
@ -567,8 +567,7 @@ Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element c
.collect_per_element_selector_involvement_metadata = true, .collect_per_element_selector_involvement_metadata = true,
}; };
ScopeGuard guard = [&] { ScopeGuard guard = [&] {
if (context.did_match_any_hover_rules) attempted_pseudo_class_matches |= context.attempted_pseudo_class_matches;
did_match_any_hover_rules = true;
}; };
if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element)) if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element))
continue; continue;
@ -1566,24 +1565,24 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
// https://www.w3.org/TR/css-cascade/#cascading // https://www.w3.org/TR/css-cascade/#cascading
// https://drafts.csswg.org/css-cascade-5/#layering // https://drafts.csswg.org/css-cascade-5/#layering
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, ComputeStyleMode mode) const GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode mode) const
{ {
auto cascaded_properties = m_document->heap().allocate<CascadedProperties>(); auto cascaded_properties = m_document->heap().allocate<CascadedProperties>();
// First, we collect all the CSS rules whose selectors match `element`: // First, we collect all the CSS rules whose selectors match `element`:
MatchingRuleSet matching_rule_set; MatchingRuleSet matching_rule_set;
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, did_match_any_hover_rules); matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, attempted_pseudo_class_matches);
sort_matching_rules(matching_rule_set.user_agent_rules); sort_matching_rules(matching_rule_set.user_agent_rules);
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, did_match_any_hover_rules); matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, attempted_pseudo_class_matches);
sort_matching_rules(matching_rule_set.user_rules); sort_matching_rules(matching_rule_set.user_rules);
// @layer-ed author rules // @layer-ed author rules
for (auto const& layer_name : m_qualified_layer_names_in_order) { for (auto const& layer_name : m_qualified_layer_names_in_order) {
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, did_match_any_hover_rules, layer_name); auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, attempted_pseudo_class_matches, layer_name);
sort_matching_rules(layer_rules); sort_matching_rules(layer_rules);
matching_rule_set.author_rules.append({ layer_name, layer_rules }); matching_rule_set.author_rules.append({ layer_name, layer_rules });
} }
// Un-@layer-ed author rules // Un-@layer-ed author rules
auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, did_match_any_hover_rules); auto unlayered_author_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, attempted_pseudo_class_matches);
sort_matching_rules(unlayered_author_rules); sort_matching_rules(unlayered_author_rules);
matching_rule_set.author_rules.append({ {}, unlayered_author_rules }); matching_rule_set.author_rules.append({ {}, unlayered_author_rules });
@ -2427,8 +2426,8 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
// 1. Perform the cascade. This produces the "specified style" // 1. Perform the cascade. This produces the "specified style"
bool did_match_any_pseudo_element_rules = false; bool did_match_any_pseudo_element_rules = false;
bool did_match_any_hover_rules = false; PseudoClassBitmap attempted_pseudo_class_matches;
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, did_match_any_hover_rules, mode); auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, attempted_pseudo_class_matches, mode);
element.set_cascaded_properties(pseudo_element, cascaded_properties); element.set_cascaded_properties(pseudo_element, cascaded_properties);
@ -2462,8 +2461,7 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
} }
auto computed_properties = compute_properties(element, pseudo_element, cascaded_properties); auto computed_properties = compute_properties(element, pseudo_element, cascaded_properties);
if (did_match_any_hover_rules) computed_properties->set_attempted_pseudo_class_matches(attempted_pseudo_class_matches);
computed_properties->set_did_match_any_hover_rules();
return computed_properties; return computed_properties;
} }
@ -2834,10 +2832,17 @@ void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_ori
} }
} }
if (selector.contains_hover_pseudo_class()) { for (size_t i = 0; i < to_underlying(PseudoClass::__Count); ++i) {
// For hover rule cache we intentionally pass pseudo_element as None, because we don't want to bucket hover rules by pseudo element type auto pseudo_class = static_cast<PseudoClass>(i);
m_hover_rule_cache->add_rule(matching_rule, {}, contains_root_pseudo_class); // If we're not building a rule cache for this pseudo class, just ignore it.
if (!m_pseudo_class_rule_cache[i])
continue;
if (selector.contains_pseudo_class(pseudo_class)) {
// For pseudo class rule caches we intentionally pass no pseudo-element, because we don't want to bucket pseudo class rules by pseudo-element type.
m_pseudo_class_rule_cache[i]->add_rule(matching_rule, {}, contains_root_pseudo_class);
}
} }
rule_cache.add_rule(matching_rule, pseudo_element, contains_root_pseudo_class); rule_cache.add_rule(matching_rule, pseudo_element, contains_root_pseudo_class);
} }
++rule_index; ++rule_index;
@ -2965,7 +2970,14 @@ void StyleComputer::build_rule_cache()
build_qualified_layer_names_cache(); build_qualified_layer_names_cache();
m_hover_rule_cache = make<RuleCache>(); m_pseudo_class_rule_cache[to_underlying(PseudoClass::Hover)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Active)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Focus)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusWithin)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::FocusVisible)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::Target)] = make<RuleCache>();
m_pseudo_class_rule_cache[to_underlying(PseudoClass::TargetWithin)] = make<RuleCache>();
make_rule_cache_for_cascade_origin(CascadeOrigin::Author, *m_selector_insights); make_rule_cache_for_cascade_origin(CascadeOrigin::Author, *m_selector_insights);
make_rule_cache_for_cascade_origin(CascadeOrigin::User, *m_selector_insights); make_rule_cache_for_cascade_origin(CascadeOrigin::User, *m_selector_insights);
make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent, *m_selector_insights); make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent, *m_selector_insights);
@ -2985,7 +2997,7 @@ void StyleComputer::invalidate_rule_cache()
// If we are sure that it's safe, we could keep it as an optimization. // If we are sure that it's safe, we could keep it as an optimization.
m_user_agent_rule_cache = nullptr; m_user_agent_rule_cache = nullptr;
m_hover_rule_cache = nullptr; m_pseudo_class_rule_cache = {};
m_style_invalidation_data = nullptr; m_style_invalidation_data = nullptr;
} }

View file

@ -160,8 +160,9 @@ public:
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::Element&, Optional<CSS::PseudoElement> = {}) const; [[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::Element&, Optional<CSS::PseudoElement> = {}) const;
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::PseudoElement>) const; [[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::PseudoElement>) const;
RuleCache const& get_hover_rules() const; [[nodiscard]] RuleCache const& get_pseudo_class_rule_cache(PseudoClass) const;
[[nodiscard]] Vector<MatchingRule const*> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::PseudoElement>, bool& did_match_any_hover_rules, FlyString const& qualified_layer_name = {}) const;
[[nodiscard]] Vector<MatchingRule const*> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::PseudoElement>, PseudoClassBitmap& attempted_psuedo_class_matches, FlyString const& qualified_layer_name = {}) const;
InvalidationSet invalidation_set_for_properties(Vector<InvalidationSet::Property> const&) const; InvalidationSet invalidation_set_for_properties(Vector<InvalidationSet::Property> const&) const;
bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&) const; bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&) const;
@ -213,7 +214,7 @@ private:
struct MatchingFontCandidate; struct MatchingFontCandidate;
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const; [[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const;
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, ComputeStyleMode) const; [[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode) const;
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive); static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive); static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const; RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
@ -292,7 +293,7 @@ private:
static void collect_selector_insights(Selector const&, SelectorInsights&); static void collect_selector_insights(Selector const&, SelectorInsights&);
OwnPtr<SelectorInsights> m_selector_insights; OwnPtr<SelectorInsights> m_selector_insights;
OwnPtr<RuleCache> m_hover_rule_cache; Array<OwnPtr<RuleCache>, to_underlying(PseudoClass::__Count)> m_pseudo_class_rule_cache;
OwnPtr<StyleInvalidationData> m_style_invalidation_data; OwnPtr<StyleInvalidationData> m_style_invalidation_data;
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_author_rule_cache; OwnPtr<RuleCachesForDocumentAndShadowRoots> m_author_rule_cache;
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_user_rule_cache; OwnPtr<RuleCachesForDocumentAndShadowRoots> m_user_rule_cache;

View file

@ -1779,11 +1779,11 @@ void Document::invalidate_style_of_elements_affected_by_has()
} }
} }
void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_new_hovered_common_ancestor, GC::Ptr<Node> hovered_node) void Document::invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass pseudo_class, auto& element_slot, Node& old_new_common_ancestor, auto node)
{ {
auto const& hover_rules = style_computer().get_hover_rules(); auto const& rules = style_computer().get_pseudo_class_rule_cache(pseudo_class);
auto& root = old_new_hovered_common_ancestor.root(); auto& root = old_new_common_ancestor.root();
auto shadow_root = is<ShadowRoot>(root) ? static_cast<ShadowRoot const*>(&root) : nullptr; auto shadow_root = is<ShadowRoot>(root) ? static_cast<ShadowRoot const*>(&root) : nullptr;
auto& style_computer = this->style_computer(); auto& style_computer = this->style_computer();
@ -1814,12 +1814,12 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
return false; return false;
}; };
auto matches_different_set_of_hover_rules_after_hovered_element_change = [&](Element const& element) { auto matches_different_set_of_rules_after_state_change = [&](Element const& element) {
bool result = false; bool result = false;
hover_rules.for_each_matching_rules(element, {}, [&](auto& rules) { rules.for_each_matching_rules(element, {}, [&](auto& rules) {
for (auto& rule : rules) { for (auto& rule : rules) {
bool before = does_rule_match_on_element(element, rule); bool before = does_rule_match_on_element(element, rule);
TemporaryChange change { m_hovered_node, hovered_node }; TemporaryChange change { element_slot, node };
bool after = does_rule_match_on_element(element, rule); bool after = does_rule_match_on_element(element, rule);
if (before != after) { if (before != after) {
result = true; result = true;
@ -1831,17 +1831,17 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
return result; return result;
}; };
Function<void(Node&)> invalidate_hovered_elements_recursively = [&](Node& node) -> void { Function<void(Node&)> invalidate_affected_elements_recursively = [&](Node& node) -> void {
if (node.is_element()) { if (node.is_element()) {
auto& element = static_cast<Element&>(node); auto& element = static_cast<Element&>(node);
style_computer.push_ancestor(element); style_computer.push_ancestor(element);
if (element.affected_by_hover() && matches_different_set_of_hover_rules_after_hovered_element_change(element)) { if (element.affected_by_pseudo_class(pseudo_class) && matches_different_set_of_rules_after_state_change(element)) {
element.set_needs_style_update(true); element.set_needs_style_update(true);
} }
} }
node.for_each_child([&](auto& child) { node.for_each_child([&](auto& child) {
invalidate_hovered_elements_recursively(child); invalidate_affected_elements_recursively(child);
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
@ -1849,12 +1849,12 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
style_computer.pop_ancestor(static_cast<Element&>(node)); style_computer.pop_ancestor(static_cast<Element&>(node));
}; };
invalidate_hovered_elements_recursively(root); invalidate_affected_elements_recursively(root);
} }
void Document::set_hovered_node(Node* node) void Document::set_hovered_node(GC::Ptr<Node> node)
{ {
if (m_hovered_node.ptr() == node) if (m_hovered_node == node)
return; return;
GC::Ptr<Node> old_hovered_node = move(m_hovered_node); GC::Ptr<Node> old_hovered_node = move(m_hovered_node);
@ -1868,11 +1868,11 @@ void Document::set_hovered_node(Node* node)
new_hovered_node_root = node->root(); new_hovered_node_root = node->root();
if (old_hovered_node_root != new_hovered_node_root) { if (old_hovered_node_root != new_hovered_node_root) {
if (old_hovered_node_root) if (old_hovered_node_root)
invalidate_style_for_elements_affected_by_hover_change(*old_hovered_node_root, node); invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass::Hover, m_hovered_node, *old_hovered_node_root, node);
if (new_hovered_node_root) if (new_hovered_node_root)
invalidate_style_for_elements_affected_by_hover_change(*new_hovered_node_root, node); invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass::Hover, m_hovered_node, *new_hovered_node_root, node);
} else { } else {
invalidate_style_for_elements_affected_by_hover_change(*common_ancestor, node); invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass::Hover, m_hovered_node, *common_ancestor, node);
} }
m_hovered_node = node; m_hovered_node = node;

View file

@ -253,8 +253,9 @@ public:
virtual FlyString node_name() const override { return "#document"_fly_string; } virtual FlyString node_name() const override { return "#document"_fly_string; }
void invalidate_style_for_elements_affected_by_hover_change(Node& old_new_hovered_common_ancestor, GC::Ptr<Node> hovered_node); void invalidate_style_for_elements_affected_by_pseudo_class_change(CSS::PseudoClass, auto& element_slot, Node& old_new_common_ancestor, auto node);
void set_hovered_node(Node*);
void set_hovered_node(GC::Ptr<Node>);
Node* hovered_node() { return m_hovered_node.ptr(); } Node* hovered_node() { return m_hovered_node.ptr(); }
Node const* hovered_node() const { return m_hovered_node.ptr(); } Node const* hovered_node() const { return m_hovered_node.ptr(); }

View file

@ -1207,16 +1207,16 @@ GC::Ptr<Layout::NodeWithStyle> Element::get_pseudo_element_node(CSS::PseudoEleme
return nullptr; return nullptr;
} }
bool Element::affected_by_hover() const bool Element::affected_by_pseudo_class(CSS::PseudoClass pseudo_class) const
{ {
if (m_computed_properties && m_computed_properties->did_match_any_hover_rules()) { if (m_computed_properties && m_computed_properties->has_attempted_match_against_pseudo_class(pseudo_class)) {
return true; return true;
} }
if (m_pseudo_element_data) { if (m_pseudo_element_data) {
for (auto& pseudo_element : *m_pseudo_element_data) { for (auto& pseudo_element : *m_pseudo_element_data) {
if (!pseudo_element.computed_properties) if (!pseudo_element.computed_properties)
continue; continue;
if (pseudo_element.computed_properties->did_match_any_hover_rules()) if (pseudo_element.computed_properties->has_attempted_match_against_pseudo_class(pseudo_class))
return true; return true;
} }
} }

View file

@ -271,7 +271,7 @@ public:
static GC::Ptr<Layout::NodeWithStyle> create_layout_node_for_display_type(DOM::Document&, CSS::Display const&, GC::Ref<CSS::ComputedProperties>, Element*); static GC::Ptr<Layout::NodeWithStyle> create_layout_node_for_display_type(DOM::Document&, CSS::Display const&, GC::Ref<CSS::ComputedProperties>, Element*);
bool affected_by_hover() const; [[nodiscard]] bool affected_by_pseudo_class(CSS::PseudoClass) const;
bool includes_properties_from_invalidation_set(CSS::InvalidationSet const&) const; bool includes_properties_from_invalidation_set(CSS::InvalidationSet const&) const;
void set_pseudo_element_node(Badge<Layout::TreeBuilder>, CSS::PseudoElement, GC::Ptr<Layout::NodeWithStyle>); void set_pseudo_element_node(Badge<Layout::TreeBuilder>, CSS::PseudoElement, GC::Ptr<Layout::NodeWithStyle>);

View file

@ -60,6 +60,7 @@ enum class PseudoClass {
member_generator.appendln(" @name:titlecase@,"); member_generator.appendln(" @name:titlecase@,");
}); });
generator.append(R"~~~( generator.append(R"~~~(
__Count,
}; };
Optional<PseudoClass> pseudo_class_from_string(StringView); Optional<PseudoClass> pseudo_class_from_string(StringView);
@ -123,6 +124,8 @@ Optional<PseudoClass> pseudo_class_from_string(StringView string)
StringView pseudo_class_name(PseudoClass pseudo_class) StringView pseudo_class_name(PseudoClass pseudo_class)
{ {
switch (pseudo_class) { switch (pseudo_class) {
case PseudoClass::__Count:
VERIFY_NOT_REACHED();
)~~~"); )~~~");
pseudo_classes_data.for_each_member([&](auto& name, auto&) { pseudo_classes_data.for_each_member([&](auto& name, auto&) {
@ -144,6 +147,8 @@ StringView pseudo_class_name(PseudoClass pseudo_class)
PseudoClassMetadata pseudo_class_metadata(PseudoClass pseudo_class) PseudoClassMetadata pseudo_class_metadata(PseudoClass pseudo_class)
{ {
switch (pseudo_class) { switch (pseudo_class) {
case PseudoClass::__Count:
VERIFY_NOT_REACHED();
)~~~"); )~~~");
pseudo_classes_data.for_each_member([&](auto& name, JsonValue const& value) { pseudo_classes_data.for_each_member([&](auto& name, JsonValue const& value) {