From 0f8050f0bd6eb0fb1987dcd88bbb23a3b5731f28 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 19 Feb 2025 20:07:34 +0100 Subject: [PATCH] LibWeb: Break early from rule testing in hover style invalidation Before this change, we did the following: 1. Created a bitmap with the matching state for each rule containing `:hover`. 2. Changed the actively hovered element in the document. 3. Created another bitmap with the matching state for each rule containing `:hover`. With this change, we iterate rule by rule and compare the matching state. This allows us to break early once we find the first rule whose matching state changes after the hovered element update. Additionally, this removes the need to allocate a bitmap. --- Libraries/LibWeb/DOM/Document.cpp | 73 +++++++++++++++---------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 73bf30fce72..0ecc1913c4e 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -9,7 +9,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include @@ -1732,52 +1731,50 @@ void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_ auto shadow_root = is(root) ? static_cast(&root) : nullptr; auto& style_computer = this->style_computer(); - auto compute_hover_selectors_match_state = [&](Element const& element) { - auto state = MUST(AK::Bitmap::create(hover_rules.size(), 0)); - for (size_t rule_index = 0; rule_index < hover_rules.size(); ++rule_index) { - auto const& rule = hover_rules[rule_index]; + auto does_rule_match_on_element = [&](Element const& element, CSS::MatchingRule const& rule) { + auto rule_root = rule.shadow_root; + auto from_user_agent_or_user_stylesheet = rule.cascade_origin == CSS::CascadeOrigin::UserAgent || rule.cascade_origin == CSS::CascadeOrigin::User; + bool rule_is_relevant_for_current_scope = rule_root == shadow_root + || (element.is_shadow_host() && rule_root == element.shadow_root()) + || from_user_agent_or_user_stylesheet; + if (!rule_is_relevant_for_current_scope) + return false; - auto rule_root = rule.shadow_root; - auto from_user_agent_or_user_stylesheet = rule.cascade_origin == CSS::CascadeOrigin::UserAgent || rule.cascade_origin == CSS::CascadeOrigin::User; - bool rule_is_relevant_for_current_scope = rule_root == shadow_root - || (element.is_shadow_host() && rule_root == element.shadow_root()) - || from_user_agent_or_user_stylesheet; - if (!rule_is_relevant_for_current_scope) - continue; + auto const& selector = rule.selector; + if (style_computer.should_reject_with_ancestor_filter(selector)) + return false; - auto const& selector = rule.selector; - if (style_computer.should_reject_with_ancestor_filter(selector)) - continue; - - SelectorEngine::MatchContext context; - bool selector_matched = false; - if (SelectorEngine::matches(selector, element, {}, context, {})) - selector_matched = true; - if (element.has_pseudo_element(CSS::Selector::PseudoElement::Type::Before)) { - if (SelectorEngine::matches(selector, element, {}, context, CSS::Selector::PseudoElement::Type::Before)) - selector_matched = true; - } - if (element.has_pseudo_element(CSS::Selector::PseudoElement::Type::After)) { - if (SelectorEngine::matches(selector, element, {}, context, CSS::Selector::PseudoElement::Type::After)) - selector_matched = true; - } - if (selector_matched) - state.set(rule_index, true); + SelectorEngine::MatchContext context; + if (SelectorEngine::matches(selector, element, {}, context, {})) + return true; + if (element.has_pseudo_element(CSS::Selector::PseudoElement::Type::Before)) { + if (SelectorEngine::matches(selector, element, {}, context, CSS::Selector::PseudoElement::Type::Before)) + return true; } - return state; + if (element.has_pseudo_element(CSS::Selector::PseudoElement::Type::After)) { + if (SelectorEngine::matches(selector, element, {}, context, CSS::Selector::PseudoElement::Type::After)) + return true; + } + return false; + }; + + 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; }; Function invalidate_hovered_elements_recursively = [&](Node& node) -> void { if (node.is_element()) { auto& element = static_cast(node); style_computer.push_ancestor(element); - if (element.affected_by_hover()) { - 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); - } + if (element.affected_by_hover() && matches_different_set_of_hover_rules_after_hovered_element_change(element)) { + element.set_needs_style_update(true); } }