diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 38dfa052c95c..f4cda2663e62 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -1439,6 +1439,8 @@ void Document::update_style() // style change event. [CSS-Transitions-2] m_transition_generation++; + invalidate_elements_affected_by_has(); + if (!needs_full_style_update() && !needs_style_update() && !child_needs_style_update()) return; @@ -1658,6 +1660,44 @@ static Node* find_common_ancestor(Node* a, Node* b) return nullptr; } +void Document::invalidate_elements_affected_by_has() +{ + if (!m_needs_invalidate_elements_affected_by_has) + return; + m_needs_invalidate_elements_affected_by_has = false; + + Vector changed_properties; + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Has }); + auto invalidation_set = document().style_computer().invalidation_set_for_properties(changed_properties); + for_each_shadow_including_inclusive_descendant([&](Node& node) { + if (!node.is_element()) + return TraversalDecision::Continue; + auto& element = static_cast(node); + bool needs_style_recalculation = false; + // There are two cases in which an element must be invalidated, depending on the position of :has() in a selector: + // 1) In the subject position, i.e., ".a:has(.b)". In that case, invalidation sets are not helpful + // for narrowing down the set of elements that need to be invalidated. Instead, we invalidate + // all elements that were tested against selectors with :has() in the subject position during + // selector matching. + // 2) In the non-subject position, i.e., ".a:has(.b) > .c". Here, invalidation sets can be used to + // determine that only elements with the "c" class have to be invalidated. + if (element.affected_by_has_pseudo_class_in_subject_position()) { + needs_style_recalculation = true; + } else if (invalidation_set.needs_invalidate_whole_subtree()) { + needs_style_recalculation = true; + } else if (element.includes_properties_from_invalidation_set(invalidation_set)) { + needs_style_recalculation = true; + } + + if (needs_style_recalculation) { + element.set_needs_style_update(true); + } else { + element.set_needs_inherited_style_update(true); + } + return TraversalDecision::Continue; + }); +} + void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_new_hovered_common_ancestor, GC::Ptr hovered_node) { auto const& hover_rules = style_computer().get_hover_rules(); diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index 72affa78d2f6..13bbda26c264 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -510,6 +510,9 @@ class Document [[nodiscard]] bool needs_full_layout_tree_update() const { return m_needs_full_layout_tree_update; } void set_needs_full_layout_tree_update(bool b) { m_needs_full_layout_tree_update = b; } + bool needs_invalidate_elements_affected_by_has() const { return m_needs_invalidate_elements_affected_by_has; } + void set_needs_invalidate_elements_affected_by_has(bool b) { m_needs_invalidate_elements_affected_by_has = b; } + void set_needs_to_refresh_scroll_state(bool b); bool has_active_favicon() const { return m_active_favicon; } @@ -805,6 +808,8 @@ class Document // ^HTML::GlobalEventHandlers virtual GC::Ptr global_event_handlers_to_event_target(FlyString const&) final { return *this; } + void invalidate_elements_affected_by_has(); + void tear_down_layout_tree(); void update_active_element(); @@ -945,6 +950,7 @@ class Document bool m_needs_full_style_update { false }; bool m_needs_full_layout_tree_update { false }; + bool m_needs_invalidate_elements_affected_by_has { false }; bool m_needs_animated_style_update { false }; diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 7df5b736fca6..96c76d510ae6 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -401,47 +401,13 @@ GC::Ptr Node::navigable() const } } -void Node::invalidate_elements_affected_by_has() -{ - Vector changed_properties; - changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Has }); - auto invalidation_set = document().style_computer().invalidation_set_for_properties(changed_properties); - for_each_shadow_including_inclusive_descendant([&](Node& node) { - if (!node.is_element()) - return TraversalDecision::Continue; - auto& element = static_cast(node); - bool needs_style_recalculation = false; - // There are two cases in which an element must be invalidated, depending on the position of :has() in a selector: - // 1) In the subject position, i.e., ".a:has(.b)". In that case, invalidation sets are not helpful - // for narrowing down the set of elements that need to be invalidated. Instead, we invalidate - // all elements that were tested against selectors with :has() in the subject position during - // selector matching. - // 2) In the non-subject position, i.e., ".a:has(.b) > .c". Here, invalidation sets can be used to - // determine that only elements with the "c" class have to be invalidated. - if (element.affected_by_has_pseudo_class_in_subject_position()) { - needs_style_recalculation = true; - } else if (invalidation_set.needs_invalidate_whole_subtree()) { - needs_style_recalculation = true; - } else if (element.includes_properties_from_invalidation_set(invalidation_set)) { - needs_style_recalculation = true; - } - - if (needs_style_recalculation) { - element.set_needs_style_update(true); - } else { - element.set_needs_inherited_style_update(true); - } - return TraversalDecision::Continue; - }); -} - void Node::invalidate_style(StyleInvalidationReason reason) { if (is_character_data()) return; if (document().style_computer().may_have_has_selectors()) { - document().invalidate_elements_affected_by_has(); + document().set_needs_invalidate_elements_affected_by_has(true); } if (!needs_style_update() && !document().needs_full_style_update()) { @@ -503,7 +469,7 @@ void Node::invalidate_style(StyleInvalidationReason, Vector