Jelajahi Sumber

LibWeb: Compute pseudo-element style when computing element style

Previously, pseudo-elements had their style computed while the layout
tree was being built. Instead, do so inside Element::recompute_style(),
using the same invalidation mechanism that the element itself uses.

This also has the effect of invalidating the layout much less often.
Sam Atkins 1 tahun lalu
induk
melakukan
3abd3ef5e2

+ 49 - 26
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -551,41 +551,31 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
     else
         invalidation = CSS::RequiredInvalidationAfterStyleChange::full();
 
+    if (!invalidation.is_none())
+        set_computed_css_values(move(new_computed_css_values));
+
     // 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;
-                }
-            }
-        }
+    for (auto i = 0; i < to_underlying(CSS::Selector::PseudoElement::Type::KnownPseudoElementCount); i++) {
+        style_computer.push_ancestor(*this);
 
-        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<CSS::Selector::PseudoElement::Type>(i));
-                if (style) {
-                    pseudo_elements_dirty = true;
-                    break;
-                }
-            }
-        }
+        auto pseudo_element = static_cast<CSS::Selector::PseudoElement::Type>(i);
+        auto pseudo_element_style = pseudo_element_computed_css_values(pseudo_element);
+        auto new_pseudo_element_style = style_computer.compute_pseudo_element_style_if_needed(*this, pseudo_element);
 
-        if (pseudo_elements_dirty)
+        // TODO: Can we be smarter about invalidation?
+        if (pseudo_element_style && new_pseudo_element_style) {
+            invalidation |= compute_required_invalidation(*pseudo_element_style, *new_pseudo_element_style);
+        } else if (pseudo_element_style || new_pseudo_element_style) {
             invalidation = CSS::RequiredInvalidationAfterStyleChange::full();
+        }
+
+        set_pseudo_element_computed_css_values(pseudo_element, move(new_pseudo_element_style));
+        style_computer.pop_ancestor(*this);
     }
 
     if (invalidation.is_none())
         return invalidation;
 
-    m_computed_css_values = move(new_computed_css_values);
-    computed_css_values_changed();
-
     if (invalidation.repaint)
         document().set_needs_to_resolve_paint_only_properties();
 
@@ -594,6 +584,24 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
         layout_node()->apply_style(*m_computed_css_values);
         if (invalidation.repaint && paintable())
             paintable()->set_needs_display();
+
+        // Do the same for pseudo-elements.
+        for (auto i = 0; i < to_underlying(CSS::Selector::PseudoElement::Type::KnownPseudoElementCount); i++) {
+            auto pseudo_element_type = static_cast<CSS::Selector::PseudoElement::Type>(i);
+            auto pseudo_element = get_pseudo_element(pseudo_element_type);
+            if (!pseudo_element.has_value() || !pseudo_element->layout_node)
+                continue;
+
+            auto pseudo_element_style = pseudo_element_computed_css_values(pseudo_element_type);
+            if (!pseudo_element_style)
+                continue;
+
+            if (auto* node_with_style = dynamic_cast<Layout::NodeWithStyle*>(pseudo_element->layout_node.ptr())) {
+                node_with_style->apply_style(*pseudo_element_style);
+                if (invalidation.repaint && node_with_style->paintable())
+                    node_with_style->paintable()->set_needs_display();
+            }
+        }
     }
 
     return invalidation;
@@ -2253,6 +2261,21 @@ void Element::set_computed_css_values(RefPtr<CSS::StyleProperties> style)
     computed_css_values_changed();
 }
 
+void Element::set_pseudo_element_computed_css_values(CSS::Selector::PseudoElement::Type pseudo_element, RefPtr<CSS::StyleProperties> style)
+{
+    if (!m_pseudo_element_data && !style)
+        return;
+    ensure_pseudo_element(pseudo_element).computed_css_values = move(style);
+}
+
+RefPtr<CSS::StyleProperties> Element::pseudo_element_computed_css_values(CSS::Selector::PseudoElement::Type type)
+{
+    auto pseudo_element = get_pseudo_element(type);
+    if (pseudo_element.has_value())
+        return pseudo_element->computed_css_values;
+    return nullptr;
+}
+
 Optional<Element::PseudoElement&> Element::get_pseudo_element(CSS::Selector::PseudoElement::Type type) const
 {
     if (!m_pseudo_element_data)

+ 4 - 0
Userland/Libraries/LibWeb/DOM/Element.h

@@ -188,6 +188,9 @@ public:
     void set_computed_css_values(RefPtr<CSS::StyleProperties>);
     NonnullRefPtr<CSS::StyleProperties> resolved_css_values();
 
+    void set_pseudo_element_computed_css_values(CSS::Selector::PseudoElement::Type, RefPtr<CSS::StyleProperties>);
+    RefPtr<CSS::StyleProperties> pseudo_element_computed_css_values(CSS::Selector::PseudoElement::Type);
+
     void reset_animated_css_properties();
 
     JS::GCPtr<CSS::ElementInlineCSSStyleDeclaration const> inline_style() const { return m_inline_style; }
@@ -451,6 +454,7 @@ private:
 
     struct PseudoElement {
         JS::GCPtr<Layout::Node> layout_node;
+        RefPtr<CSS::StyleProperties> computed_css_values;
         HashMap<FlyString, CSS::StyleProperty> custom_properties;
     };
     // TODO: CSS::Selector::PseudoElement::Type includes a lot of pseudo-elements that exist in shadow trees,

+ 1 - 2
Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp

@@ -191,9 +191,8 @@ void TreeBuilder::insert_node_into_inline_or_block_ancestor(Layout::Node& node,
 void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Selector::PseudoElement::Type pseudo_element, AppendOrPrepend mode)
 {
     auto& document = element.document();
-    auto& style_computer = document.style_computer();
 
-    auto pseudo_element_style = style_computer.compute_pseudo_element_style_if_needed(element, pseudo_element);
+    auto pseudo_element_style = element.pseudo_element_computed_css_values(pseudo_element);
     if (!pseudo_element_style)
         return;