diff --git a/Libraries/LibWeb/CSS/Invalidation/ElementStateInvalidator.cpp b/Libraries/LibWeb/CSS/Invalidation/ElementStateInvalidator.cpp index 807d1e9afc..31e97fd54f 100644 --- a/Libraries/LibWeb/CSS/Invalidation/ElementStateInvalidator.cpp +++ b/Libraries/LibWeb/CSS/Invalidation/ElementStateInvalidator.cpp @@ -5,14 +5,36 @@ */ #include +#include +#include #include #include namespace Web::CSS::Invalidation { +static void invalidate_style_after_pseudo_class_state_change(DOM::Element& element, DOM::StyleInvalidationReason reason, + PseudoClass pseudo_class) +{ + bool may_affect_has_selectors = false; + element.for_each_style_scope_which_may_observe_the_node([&](StyleScope& scope) { + if (element_has_feature_used_in_has_selector(element, pseudo_class, scope)) + may_affect_has_selectors = true; + }); + if (may_affect_has_selectors) { + element.invalidate_style(reason); + return; + } + + Vector pseudo_class_properties { + { .type = InvalidationSet::Property::Type::PseudoClass, .value = pseudo_class }, + }; + element.invalidate_style(reason, pseudo_class_properties, { .invalidate_self = true }); +} + void invalidate_style_after_active_state_change(DOM::Element& element) { - element.invalidate_style(DOM::StyleInvalidationReason::ElementSetActive); + invalidate_style_after_pseudo_class_state_change(element, DOM::StyleInvalidationReason::ElementSetActive, + PseudoClass::Active); } void invalidate_style_after_modal_state_change(DOM::Element& element) @@ -22,9 +44,8 @@ void invalidate_style_after_modal_state_change(DOM::Element& element) void invalidate_style_after_open_state_change(DOM::Element& element) { - // The :open pseudo-class can affect sibling selectors (e.g. dialog:open + sibling), so keep the existing broad - // subtree and sibling invalidation. - element.invalidate_style(DOM::StyleInvalidationReason::HTMLDetailsOrDialogOpenAttributeChange); + invalidate_style_after_pseudo_class_state_change(element, + DOM::StyleInvalidationReason::HTMLDetailsOrDialogOpenAttributeChange, PseudoClass::Open); } void invalidate_style_after_option_selected_state_change(DOM::Element& element) diff --git a/Libraries/LibWeb/CSS/Invalidation/HasMutationFeatureCollector.cpp b/Libraries/LibWeb/CSS/Invalidation/HasMutationFeatureCollector.cpp index 87548147ff..2bb04a2053 100644 --- a/Libraries/LibWeb/CSS/Invalidation/HasMutationFeatureCollector.cpp +++ b/Libraries/LibWeb/CSS/Invalidation/HasMutationFeatureCollector.cpp @@ -87,6 +87,27 @@ bool HasMutationFeatureCollector::subtree_has_feature_used_in_has_selector(DOM:: return found; } +bool element_has_feature_used_in_has_selector(DOM::Element const& element, PseudoClass changed_pseudo_class, + StyleScope const& style_scope) +{ + if (!style_scope.may_have_has_selectors()) + return false; + + auto const* data = style_scope.m_rule_cache ? &style_scope.m_rule_cache->style_invalidation_data : nullptr; + if (!data) + return true; + + HasMutationFeatureCollector collector { *data }; + if (!collector.has_any_metadata()) + return true; + if (data->has_selectors_sensitive_to_featureless_subtree_changes) + return true; + if (data->pseudo_classes_used_in_has_selectors.contains(changed_pseudo_class)) + return true; + + return collector.element_has_feature_used_in_has_selector(element); +} + bool subtree_has_feature_used_in_has_selector(DOM::Node& node, StyleScope const& style_scope) { auto const* data = style_scope.m_rule_cache ? &style_scope.m_rule_cache->style_invalidation_data : nullptr; diff --git a/Libraries/LibWeb/CSS/Invalidation/HasMutationFeatureCollector.h b/Libraries/LibWeb/CSS/Invalidation/HasMutationFeatureCollector.h index e35ae396b5..5272978a5e 100644 --- a/Libraries/LibWeb/CSS/Invalidation/HasMutationFeatureCollector.h +++ b/Libraries/LibWeb/CSS/Invalidation/HasMutationFeatureCollector.h @@ -15,6 +15,7 @@ class Node; namespace Web::CSS { +enum class PseudoClass; struct StyleInvalidationData; class StyleScope; @@ -25,14 +26,14 @@ public: explicit HasMutationFeatureCollector(StyleInvalidationData const&); [[nodiscard]] bool has_any_metadata() const; + [[nodiscard]] bool element_has_feature_used_in_has_selector(DOM::Element const&) const; [[nodiscard]] bool subtree_has_feature_used_in_has_selector(DOM::Node&) const; private: - [[nodiscard]] bool element_has_feature_used_in_has_selector(DOM::Element const&) const; - StyleInvalidationData const& m_data; }; +[[nodiscard]] bool element_has_feature_used_in_has_selector(DOM::Element const&, PseudoClass, StyleScope const&); [[nodiscard]] bool subtree_has_feature_used_in_has_selector(DOM::Node&, StyleScope const&); } diff --git a/Tests/LibWeb/Text/expected/css/style-invalidation/hover-non-matching-element-skips-self-recompute.txt b/Tests/LibWeb/Text/expected/css/style-invalidation/user-action-pseudo-class-targeted-invalidation.txt similarity index 76% rename from Tests/LibWeb/Text/expected/css/style-invalidation/hover-non-matching-element-skips-self-recompute.txt rename to Tests/LibWeb/Text/expected/css/style-invalidation/user-action-pseudo-class-targeted-invalidation.txt index e7bb6bf979..620a60e239 100644 --- a/Tests/LibWeb/Text/expected/css/style-invalidation/hover-non-matching-element-skips-self-recompute.txt +++ b/Tests/LibWeb/Text/expected/css/style-invalidation/user-action-pseudo-class-targeted-invalidation.txt @@ -1,3 +1,4 @@ plain text hover: styleInvalidations=0, elementStyleRecomputations=0, elementStyleNoopRecomputations=0 link hover: styleInvalidations=1, elementStyleRecomputations=2, elementStyleNoopRecomputations=1 link color: rgb(0, 128, 0) +active state: fullStyleInvalidations=0, elementStyleRecomputations=8 diff --git a/Tests/LibWeb/Text/input/css/style-invalidation/hover-non-matching-element-skips-self-recompute.html b/Tests/LibWeb/Text/input/css/style-invalidation/hover-non-matching-element-skips-self-recompute.html deleted file mode 100644 index fc9f312c80..0000000000 --- a/Tests/LibWeb/Text/input/css/style-invalidation/hover-non-matching-element-skips-self-recompute.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - -
plain text
-link text - diff --git a/Tests/LibWeb/Text/input/css/style-invalidation/user-action-pseudo-class-targeted-invalidation.html b/Tests/LibWeb/Text/input/css/style-invalidation/user-action-pseudo-class-targeted-invalidation.html new file mode 100644 index 0000000000..5405e469aa --- /dev/null +++ b/Tests/LibWeb/Text/input/css/style-invalidation/user-action-pseudo-class-targeted-invalidation.html @@ -0,0 +1,104 @@ + + + + +
plain text
+link text +
+ +
sibling
+
ancestor descendant
+
+