LibWeb: Use ancestor filters for hover style invalidation

By using ancestor filters some selectors could be early rejected
skipping selector engine invocation. According to my measurements it's
30-80% hover selectors depending on the website.
This commit is contained in:
Aliaksandr Kalenik 2025-01-28 03:04:04 +01:00 committed by Alexander Kalenik
commit db47fa3db1
Notes: github-actions[bot] 2025-01-28 17:56:41 +00:00
2 changed files with 30 additions and 20 deletions

View file

@ -187,6 +187,8 @@ public:
void absolutize_values(ComputedProperties&) const; void absolutize_values(ComputedProperties&) const;
void compute_font(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const; void compute_font(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
[[nodiscard]] bool should_reject_with_ancestor_filter(Selector const&) const;
private: private:
enum class ComputeStyleMode { enum class ComputeStyleMode {
Normal, Normal,
@ -195,8 +197,6 @@ private:
struct MatchingFontCandidate; struct MatchingFontCandidate;
[[nodiscard]] bool should_reject_with_ancestor_filter(Selector const&) const;
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, ComputeStyleMode) const; [[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, ComputeStyleMode) const;
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, 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::Selector::PseudoElement::Type>, bool& did_match_any_pseudo_element_rules, bool& did_match_any_hover_rules, 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);

View file

@ -1667,6 +1667,7 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
auto& root = old_new_hovered_common_ancestor.root(); auto& root = old_new_hovered_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 compute_hover_selectors_match_state = [&](Element const& element) { auto compute_hover_selectors_match_state = [&](Element const& element) {
auto state = MUST(AK::Bitmap::create(hover_rules.size(), 0)); auto state = MUST(AK::Bitmap::create(hover_rules.size(), 0));
for (size_t rule_index = 0; rule_index < hover_rules.size(); ++rule_index) { for (size_t rule_index = 0; rule_index < hover_rules.size(); ++rule_index) {
@ -1681,6 +1682,8 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
continue; continue;
auto const& selector = rule.selector; auto const& selector = rule.selector;
if (style_computer.should_reject_with_ancestor_filter(selector))
continue;
SelectorEngine::MatchContext context; SelectorEngine::MatchContext context;
bool selector_matched = false; bool selector_matched = false;
@ -1703,27 +1706,34 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_
return state; return state;
}; };
root.for_each_in_inclusive_subtree([&](Node& node) { Function<void(Node&)> invalidate_hovered_elements_recursively = [&](Node& node) -> void {
if (!node.is_element()) if (node.is_element()) {
return TraversalDecision::Continue; auto& element = static_cast<Element&>(node);
auto& element = static_cast<Element&>(node); style_computer.push_ancestor(element);
if (!element.affected_by_hover()) if (element.affected_by_hover()) {
return TraversalDecision::Continue; auto selectors_match_state_before = compute_hover_selectors_match_state(element);
TemporaryChange change { m_hovered_node, hovered_node };
auto selectors_match_state_after = compute_hover_selectors_match_state(element);
if (selectors_match_state_before.view() != selectors_match_state_after.view()) {
element.set_needs_style_update(true);
element.for_each_in_subtree_of_type<Element>([](auto& element) {
element.set_needs_inherited_style_update(true);
return TraversalDecision::Continue;
});
}
}
}
auto selectors_match_state_before = compute_hover_selectors_match_state(element); node.for_each_child([&](auto& child) {
TemporaryChange change { m_hovered_node, hovered_node }; invalidate_hovered_elements_recursively(child);
auto selectors_match_state_after = compute_hover_selectors_match_state(element); return IterationDecision::Continue;
if (selectors_match_state_before.view() == selectors_match_state_after.view())
return TraversalDecision::Continue;
element.set_needs_style_update(true);
element.for_each_in_subtree_of_type<Element>([](auto& element) {
element.set_needs_inherited_style_update(true);
return TraversalDecision::Continue;
}); });
return TraversalDecision::Continue; if (node.is_element())
}); style_computer.pop_ancestor(static_cast<Element&>(node));
};
invalidate_hovered_elements_recursively(root);
} }
void Document::set_hovered_node(Node* node) void Document::set_hovered_node(Node* node)