diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index f806dc8aaa4..961eb0f7956 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -508,8 +508,12 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla // :has() cannot be nested in a :has() if (selector_kind == SelectorKind::Relative) return false; - if (context.collect_per_element_selector_involvement_metadata && &element == context.subject) { - const_cast(element).set_affected_by_has_pseudo_class_in_subject_position(true); + if (context.collect_per_element_selector_involvement_metadata) { + if (&element == context.subject) { + const_cast(element).set_affected_by_has_pseudo_class_in_subject_position(true); + } else { + const_cast(element).set_affected_by_has_pseudo_class_in_non_subject_position(true); + } } // These selectors should be relative selectors (https://drafts.csswg.org/selectors-4/#relative-selector) for (auto& selector : pseudo_class.argument_selector_list) { diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index c9ebd87448b..fdd1b6a4261 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -1679,30 +1679,25 @@ void Document::invalidate_style_of_elements_affected_by_has() for (auto const& node : m_pending_nodes_for_style_invalidation_due_to_presence_of_has) { if (node.is_null()) continue; - node->invalidate_ancestors_affected_by_has_in_subject_position(); - } + for (auto* ancestor = node.ptr(); ancestor; ancestor = ancestor->parent_or_shadow_host()) { + if (!ancestor->is_element()) + continue; + auto& element = static_cast(*ancestor); + element.invalidate_style_if_affected_by_has(); - // Take care of elements that affected by :has() in non-subject position, i.e., ".a:has(.b) > .c". - // Elements affected by :has() in subject position, i.e., ".a:has(.b)", are handled by - // Node::invalidate_style() and should already be marked for style update by now. - Vector changed_properties; - changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Has }); - auto invalidation_set = 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; - 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; + auto* parent = ancestor->parent_or_shadow_host(); + if (!parent) + return; + + // If any ancestor's sibling was tested against selectors like ".a:has(+ .b)" or ".a:has(~ .b)" + // its style might be affected by the change in descendant node. + parent->for_each_child_of_type([&](auto& ancestor_sibling_element) { + if (ancestor_sibling_element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator()) + ancestor_sibling_element.invalidate_style_if_affected_by_has(); + return IterationDecision::Continue; + }); } - - if (needs_style_recalculation) - element.set_needs_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) diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 0a140ea92a2..79a376b6851 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -498,6 +498,8 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style() VERIFY(parent()); m_affected_by_has_pseudo_class_in_subject_position = false; + m_affected_by_has_pseudo_class_in_non_subject_position = false; + m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator = false; m_affected_by_sibling_combinator = false; m_affected_by_first_or_last_child_pseudo_class = false; m_affected_by_nth_child_pseudo_class = false; @@ -1312,6 +1314,16 @@ bool Element::includes_properties_from_invalidation_set(CSS::InvalidationSet con return includes_any; } +void Element::invalidate_style_if_affected_by_has() +{ + if (affected_by_has_pseudo_class_in_subject_position()) { + set_needs_style_update(true); + } + if (affected_by_has_pseudo_class_in_non_subject_position()) { + invalidate_style(StyleInvalidationReason::Other, { { CSS::InvalidationSet::Property::Type::PseudoClass, CSS::PseudoClass::Has } }, {}); + } +} + bool Element::has_pseudo_elements() const { if (m_pseudo_element_data) { diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index b014b2f883d..fad2b788f01 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -428,9 +428,14 @@ public: bool matches_link_pseudo_class() const; bool matches_local_link_pseudo_class() const; + void invalidate_style_if_affected_by_has(); + bool affected_by_has_pseudo_class_in_subject_position() const { return m_affected_by_has_pseudo_class_in_subject_position; } void set_affected_by_has_pseudo_class_in_subject_position(bool value) { m_affected_by_has_pseudo_class_in_subject_position = value; } + bool affected_by_has_pseudo_class_in_non_subject_position() const { return m_affected_by_has_pseudo_class_in_non_subject_position; } + void set_affected_by_has_pseudo_class_in_non_subject_position(bool value) { m_affected_by_has_pseudo_class_in_non_subject_position = value; } + bool affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator() const { return m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator; } void set_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator(bool value) { m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator = value; } @@ -539,6 +544,7 @@ private: bool m_rendered_in_top_layer : 1 { false }; bool m_style_uses_css_custom_properties { false }; bool m_affected_by_has_pseudo_class_in_subject_position : 1 { false }; + bool m_affected_by_has_pseudo_class_in_non_subject_position : 1 { false }; bool m_affected_by_sibling_combinator : 1 { false }; bool m_affected_by_first_or_last_child_pseudo_class : 1 { false }; bool m_affected_by_nth_child_pseudo_class : 1 { false }; diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 5f44098f822..4ad49e47cd3 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -397,34 +397,6 @@ GC::Ptr Node::navigable() const } } -void Node::invalidate_ancestors_affected_by_has_in_subject_position() -{ - // This call only takes care of invalidating `:has()` in subject position (e.g., ".a:has(.b)"). - // Non-subject position (e.g., ".a:has(.b) + .c") is still handled by another call that uses - // invalidation sets and requires whole document traversal. - // Here we assume that :has() invalidation scope is limited to ancestors and sibling ancestors. - for (auto* ancestor = this; ancestor; ancestor = ancestor->parent_or_shadow_host()) { - if (!ancestor->is_element()) - continue; - auto& element = static_cast(*ancestor); - if (element.affected_by_has_pseudo_class_in_subject_position()) { - element.set_needs_style_update(true); - } - - auto* parent = ancestor->parent_or_shadow_host(); - if (!parent) - return; - - // If any ancestor's sibling was tested against selectors like ".a:has(+ .b)" or ".a:has(~ .b)" - // its style might be affected by the change in descendant node. - parent->for_each_child_of_type([&](auto& element) { - if (element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator()) - element.set_needs_style_update(true); - return IterationDecision::Continue; - }); - } -} - void Node::invalidate_style(StyleInvalidationReason reason) { if (is_character_data()) @@ -436,7 +408,7 @@ void Node::invalidate_style(StyleInvalidationReason reason) document().schedule_ancestors_style_invalidation_due_to_presence_of_has(*parent); parent->for_each_child_of_type([&](auto& element) { if (element.affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator()) - element.set_needs_style_update(true); + element.invalidate_style_if_affected_by_has(); return IterationDecision::Continue; }); } diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index 21999ba6eb2..aed5012e337 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -309,7 +309,6 @@ public: [[nodiscard]] bool entire_subtree_needs_style_update() const { return m_entire_subtree_needs_style_update; } void set_entire_subtree_needs_style_update(bool b) { m_entire_subtree_needs_style_update = b; } - void invalidate_ancestors_affected_by_has_in_subject_position(); void invalidate_style(StyleInvalidationReason); struct StyleInvalidationOptions { bool invalidate_self { false };