mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-16 05:51:55 +00:00
LibWeb: Bucket hover rules using RuleCache
Analysis of selectors on modern websites shows that the `:hover` pseudo-class is mostly used in the subject position within relatively simple selectors like `.a:hover`. This suggests that we could greatly benefit from segregating them by id/class/tag name, this way reducing number of selectors tested during hover style invalidation. With this change, hover invalidation on Discord goes down from 70ms to 3ms on my machine. I also tested GMail and GitHub where this change shows nice 2x-3x speedup.
This commit is contained in:
parent
ff8826d582
commit
0ab61a94d7
Notes:
github-actions[bot]
2025-02-22 09:15:47 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: 0ab61a94d7
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3650
3 changed files with 28 additions and 23 deletions
|
@ -420,10 +420,10 @@ RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin casc
|
|||
return true;
|
||||
}
|
||||
|
||||
Vector<MatchingRule> const& StyleComputer::get_hover_rules() const
|
||||
RuleCache const& StyleComputer::get_hover_rules() const
|
||||
{
|
||||
build_rule_cache_if_needed();
|
||||
return m_hover_rules;
|
||||
return *m_hover_rule_cache;
|
||||
}
|
||||
|
||||
InvalidationSet StyleComputer::invalidation_set_for_properties(Vector<InvalidationSet::Property> const& properties) const
|
||||
|
@ -2637,7 +2637,7 @@ void StyleComputer::collect_selector_insights(Selector const& selector, Selector
|
|||
}
|
||||
}
|
||||
|
||||
void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, SelectorInsights& insights, Vector<MatchingRule>& hover_rules)
|
||||
void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, SelectorInsights& insights)
|
||||
{
|
||||
Vector<MatchingRule> matching_rules;
|
||||
size_t style_sheet_index = 0;
|
||||
|
@ -2734,9 +2734,9 @@ void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_ori
|
|||
}
|
||||
|
||||
if (selector.contains_hover_pseudo_class()) {
|
||||
hover_rules.append(matching_rule);
|
||||
// For hover rule cache we intentionally pass pseudo_element as None, because we don't want to bucket hover rules by pseudo element type
|
||||
m_hover_rule_cache->add_rule(matching_rule, {}, contains_root_pseudo_class);
|
||||
}
|
||||
|
||||
rule_cache.add_rule(matching_rule, pseudo_element, contains_root_pseudo_class);
|
||||
}
|
||||
++rule_index;
|
||||
|
@ -2864,9 +2864,10 @@ void StyleComputer::build_rule_cache()
|
|||
|
||||
build_qualified_layer_names_cache();
|
||||
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::Author, *m_selector_insights, m_hover_rules);
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::User, *m_selector_insights, m_hover_rules);
|
||||
make_rule_cache_for_cascade_origin(CascadeOrigin::UserAgent, *m_selector_insights, m_hover_rules);
|
||||
m_hover_rule_cache = make<RuleCache>();
|
||||
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::UserAgent, *m_selector_insights);
|
||||
}
|
||||
|
||||
void StyleComputer::invalidate_rule_cache()
|
||||
|
@ -2883,7 +2884,7 @@ void StyleComputer::invalidate_rule_cache()
|
|||
// If we are sure that it's safe, we could keep it as an optimization.
|
||||
m_user_agent_rule_cache = nullptr;
|
||||
|
||||
m_hover_rules.clear_with_capacity();
|
||||
m_hover_rule_cache = nullptr;
|
||||
m_style_invalidation_data = nullptr;
|
||||
}
|
||||
|
||||
|
@ -3112,7 +3113,7 @@ void RuleCache::add_rule(MatchingRule const& matching_rule, Optional<Selector::P
|
|||
}
|
||||
}
|
||||
|
||||
if (matching_rule.contains_pseudo_element) {
|
||||
if (matching_rule.contains_pseudo_element && pseudo_element.has_value()) {
|
||||
if (Selector::PseudoElement::is_known_pseudo_element_type(pseudo_element.value())) {
|
||||
rules_by_pseudo_element[to_underlying(pseudo_element.value())].append(matching_rule);
|
||||
} else {
|
||||
|
|
|
@ -161,7 +161,7 @@ public:
|
|||
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type> = {}) const;
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>) const;
|
||||
|
||||
Vector<MatchingRule> const& get_hover_rules() const;
|
||||
RuleCache const& get_hover_rules() const;
|
||||
[[nodiscard]] Vector<MatchingRule const*> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::Selector::PseudoElement::Type>, bool& did_match_any_hover_rules, FlyString const& qualified_layer_name = {}) const;
|
||||
|
||||
InvalidationSet invalidation_set_for_properties(Vector<InvalidationSet::Property> const&) const;
|
||||
|
@ -284,14 +284,14 @@ private:
|
|||
HashMap<GC::Ref<DOM::ShadowRoot const>, NonnullOwnPtr<RuleCaches>> for_shadow_roots;
|
||||
};
|
||||
|
||||
void make_rule_cache_for_cascade_origin(CascadeOrigin, SelectorInsights&, Vector<MatchingRule>& hover_rules);
|
||||
void make_rule_cache_for_cascade_origin(CascadeOrigin, SelectorInsights&);
|
||||
|
||||
[[nodiscard]] RuleCache const* rule_cache_for_cascade_origin(CascadeOrigin, FlyString const& qualified_layer_name, GC::Ptr<DOM::ShadowRoot const>) const;
|
||||
|
||||
static void collect_selector_insights(Selector const&, SelectorInsights&);
|
||||
|
||||
OwnPtr<SelectorInsights> m_selector_insights;
|
||||
Vector<MatchingRule> m_hover_rules;
|
||||
OwnPtr<RuleCache> m_hover_rule_cache;
|
||||
OwnPtr<StyleInvalidationData> m_style_invalidation_data;
|
||||
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_author_rule_cache;
|
||||
OwnPtr<RuleCachesForDocumentAndShadowRoots> m_user_rule_cache;
|
||||
|
|
|
@ -1726,8 +1726,6 @@ 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)
|
||||
{
|
||||
auto const& hover_rules = style_computer().get_hover_rules();
|
||||
if (hover_rules.is_empty())
|
||||
return;
|
||||
|
||||
auto& root = old_new_hovered_common_ancestor.root();
|
||||
auto shadow_root = is<ShadowRoot>(root) ? static_cast<ShadowRoot const*>(&root) : nullptr;
|
||||
|
@ -1761,14 +1759,20 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
|
|||
};
|
||||
|
||||
auto matches_different_set_of_hover_rules_after_hovered_element_change = [&](Element const& element) {
|
||||
for (auto const& rule : hover_rules) {
|
||||
bool before = does_rule_match_on_element(element, rule);
|
||||
TemporaryChange change { m_hovered_node, hovered_node };
|
||||
bool after = does_rule_match_on_element(element, rule);
|
||||
if (before != after)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
bool result = false;
|
||||
hover_rules.for_each_matching_rules(element, {}, [&](auto& rules) {
|
||||
for (auto& rule : rules) {
|
||||
bool before = does_rule_match_on_element(element, rule);
|
||||
TemporaryChange change { m_hovered_node, hovered_node };
|
||||
bool after = does_rule_match_on_element(element, rule);
|
||||
if (before != after) {
|
||||
result = true;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
Function<void(Node&)> invalidate_hovered_elements_recursively = [&](Node& node) -> void {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue