From 7daf5cdaff0fa1bba211ad40eadca5a0a52437ad Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 29 Jul 2024 15:34:21 +0100 Subject: [PATCH] LibWeb: Invalidate layout if pseudo-element style changes Pseudo-elements' style is only computed while building the layout tree. This meant that previously, they would not have their style recomputed in some cases. (Such as when :hover is applied to an ancestor.) Now, when recomputing an element's style, we also return a full invalidation if one or more pseudo-elements would exist either before or after style recomputation. This heuristic produces some false positives, but no false negatives. Because pseudo-elements' style is computed during layout building, any computation done here is then thrown away. So this approach minimises the amount of wasted style computation. Plus it's simple, until we have data on what approach would be faster. This fixes the Acid2 nose becoming blue when the .nose div is hovered. --- .../css/update-pseudo-elements-on-hover.txt | 3 ++ .../css/update-pseudo-elements-on-hover.html | 33 +++++++++++++++++++ .../Libraries/LibWeb/CSS/StyleInvalidation.h | 1 + Userland/Libraries/LibWeb/DOM/Element.cpp | 32 +++++++++++++++++- 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/css/update-pseudo-elements-on-hover.txt create mode 100644 Tests/LibWeb/Text/input/css/update-pseudo-elements-on-hover.html diff --git a/Tests/LibWeb/Text/expected/css/update-pseudo-elements-on-hover.txt b/Tests/LibWeb/Text/expected/css/update-pseudo-elements-on-hover.txt new file mode 100644 index 00000000000..4ed1a777f64 --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/update-pseudo-elements-on-hover.txt @@ -0,0 +1,3 @@ +Hi Not hovering: 16 +Hovering: 78 +Not hovering: 16 diff --git a/Tests/LibWeb/Text/input/css/update-pseudo-elements-on-hover.html b/Tests/LibWeb/Text/input/css/update-pseudo-elements-on-hover.html new file mode 100644 index 00000000000..ddebf3f40bb --- /dev/null +++ b/Tests/LibWeb/Text/input/css/update-pseudo-elements-on-hover.html @@ -0,0 +1,33 @@ + + + +
+ diff --git a/Userland/Libraries/LibWeb/CSS/StyleInvalidation.h b/Userland/Libraries/LibWeb/CSS/StyleInvalidation.h index c116781b875..4fa23d5fb9c 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleInvalidation.h +++ b/Userland/Libraries/LibWeb/CSS/StyleInvalidation.h @@ -25,6 +25,7 @@ struct RequiredInvalidationAfterStyleChange { } [[nodiscard]] bool is_none() const { return !repaint && !rebuild_stacking_context_tree && !relayout && !rebuild_layout_tree; } + [[nodiscard]] bool is_full() const { return repaint && rebuild_stacking_context_tree && relayout && rebuild_layout_tree; } static RequiredInvalidationAfterStyleChange full() { return { true, true, true, true }; } }; diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index 5816582d54d..9a21d30b64a 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -534,7 +534,8 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style() set_needs_style_update(false); VERIFY(parent()); - auto new_computed_css_values = document().style_computer().compute_style(*this); + auto& style_computer = document().style_computer(); + auto new_computed_css_values = style_computer.compute_style(*this); // Tables must not inherit -libweb-* values for text-align. // FIXME: Find the spec for this. @@ -550,6 +551,35 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style() else invalidation = CSS::RequiredInvalidationAfterStyleChange::full(); + // Any document change that can cause this element's style to change, could also affect its pseudo-elements. + // So determine if any pseudo-elements currently exist, or should now exist, and if so, invalidate everything. + // (If we're already invalidating everything, we don't need to do further checks for this.) + if (!invalidation.is_full()) { + bool pseudo_elements_dirty = false; + + if (m_pseudo_element_data) { + for (auto& pseudo_element : *m_pseudo_element_data) { + if (pseudo_element.layout_node) { + pseudo_elements_dirty = true; + break; + } + } + } + + if (!pseudo_elements_dirty) { + for (auto i = 0; i < to_underlying(CSS::Selector::PseudoElement::Type::KnownPseudoElementCount); i++) { + auto style = style_computer.compute_pseudo_element_style_if_needed(*this, static_cast(i)); + if (style) { + pseudo_elements_dirty = true; + break; + } + } + } + + if (pseudo_elements_dirty) + invalidation = CSS::RequiredInvalidationAfterStyleChange::full(); + } + if (invalidation.is_none()) return invalidation;