Selaa lähdekoodia

LibWeb: Avoid layout invalidation for some CSS property changes

Use the new CSS::property_affects_layout() helper to figure out if we
actually need to perform a full relayout after recomputing style.

There are three tiers of required invalidation after an element receives
new style: none, repaint only, or full relayout.

This avoids the need to rebuild the layout tree (and perform layout on
it) when trivial properties like "color" etc are changed.
Andreas Kling 3 vuotta sitten
vanhempi
commit
f1711a562a

+ 10 - 6
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -595,17 +595,20 @@ void Document::update_layout()
     m_layout_update_timer->stop();
     m_layout_update_timer->stop();
 }
 }
 
 
-static void update_style_recursively(DOM::Node& node)
+static bool update_style_recursively(DOM::Node& node)
 {
 {
-    if (is<Element>(node))
-        static_cast<Element&>(node).recompute_style();
+    bool needs_relayout = false;
+
+    if (is<Element>(node)) {
+        needs_relayout |= static_cast<Element&>(node).recompute_style() == Element::NeedsRelayout::Yes;
+    }
     node.set_needs_style_update(false);
     node.set_needs_style_update(false);
 
 
     if (node.child_needs_style_update()) {
     if (node.child_needs_style_update()) {
         if (node.is_element()) {
         if (node.is_element()) {
             if (auto* shadow_root = static_cast<DOM::Element&>(node).shadow_root()) {
             if (auto* shadow_root = static_cast<DOM::Element&>(node).shadow_root()) {
                 if (shadow_root->needs_style_update() || shadow_root->child_needs_style_update())
                 if (shadow_root->needs_style_update() || shadow_root->child_needs_style_update())
-                    update_style_recursively(*shadow_root);
+                    needs_relayout |= update_style_recursively(*shadow_root);
             }
             }
         }
         }
         node.for_each_child([&](auto& child) {
         node.for_each_child([&](auto& child) {
@@ -616,6 +619,7 @@ static void update_style_recursively(DOM::Node& node)
     }
     }
 
 
     node.set_child_needs_style_update(false);
     node.set_child_needs_style_update(false);
+    return needs_relayout;
 }
 }
 
 
 void Document::update_style()
 void Document::update_style()
@@ -624,9 +628,9 @@ void Document::update_style()
         return;
         return;
     if (!needs_style_update() && !child_needs_style_update())
     if (!needs_style_update() && !child_needs_style_update())
         return;
         return;
-    update_style_recursively(*this);
+    if (update_style_recursively(*this))
+        invalidate_layout();
     m_style_update_timer->stop();
     m_style_update_timer->stop();
-    set_needs_layout();
 }
 }
 
 
 void Document::set_link_color(Color color)
 void Document::set_link_color(Color color)

+ 45 - 3
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -267,18 +267,60 @@ void Element::parse_attribute(const FlyString& name, const String& value)
     }
     }
 }
 }
 
 
-void Element::recompute_style()
+enum class RequiredInvalidation {
+    None,
+    RepaintOnly,
+    Relayout,
+};
+
+static RequiredInvalidation compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style)
+{
+    bool requires_repaint = false;
+    for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) {
+        auto property_id = static_cast<CSS::PropertyID>(i);
+        auto const& old_value = old_style.properties()[i];
+        auto const& new_value = new_style.properties()[i];
+        if (!old_value && !new_value)
+            continue;
+        if (!old_value || !new_value)
+            return RequiredInvalidation::Relayout;
+        if (*old_value == *new_value)
+            continue;
+        if (CSS::property_affects_layout(property_id))
+            return RequiredInvalidation::Relayout;
+        requires_repaint = true;
+    }
+    if (requires_repaint)
+        return RequiredInvalidation::RepaintOnly;
+    return RequiredInvalidation::None;
+}
+
+Element::NeedsRelayout Element::recompute_style()
 {
 {
     set_needs_style_update(false);
     set_needs_style_update(false);
     VERIFY(parent());
     VERIFY(parent());
     auto new_computed_css_values = document().style_computer().compute_style(*this);
     auto new_computed_css_values = document().style_computer().compute_style(*this);
 
 
-    if (m_computed_css_values && *m_computed_css_values == *new_computed_css_values)
-        return;
+    auto required_invalidation = RequiredInvalidation::Relayout;
+
+    if (m_computed_css_values)
+        required_invalidation = compute_required_invalidation(*m_computed_css_values, *new_computed_css_values);
+
+    if (required_invalidation == RequiredInvalidation::None)
+        return NeedsRelayout::No;
 
 
     m_computed_css_values = move(new_computed_css_values);
     m_computed_css_values = move(new_computed_css_values);
 
 
+    if (required_invalidation == RequiredInvalidation::RepaintOnly && layout_node()) {
+        layout_node()->apply_style(*m_computed_css_values);
+        layout_node()->set_needs_display();
+        return NeedsRelayout::No;
+    }
+
+    // FIXME: Get rid of this layout invalidation and let Document take care of it.
+    //        There seems to be some missing invalidation somewhere else that this is papering over.
     document().invalidate_layout();
     document().invalidate_layout();
+    return NeedsRelayout::Yes;
 }
 }
 
 
 NonnullRefPtr<CSS::StyleProperties> Element::resolved_css_values()
 NonnullRefPtr<CSS::StyleProperties> Element::resolved_css_values()

+ 5 - 1
Userland/Libraries/LibWeb/DOM/Element.h

@@ -87,7 +87,11 @@ public:
     virtual void parse_attribute(const FlyString& name, const String& value);
     virtual void parse_attribute(const FlyString& name, const String& value);
     virtual void did_remove_attribute(FlyString const&) { }
     virtual void did_remove_attribute(FlyString const&) { }
 
 
-    void recompute_style();
+    enum class NeedsRelayout {
+        No = 0,
+        Yes = 1,
+    };
+    NeedsRelayout recompute_style();
 
 
     Layout::NodeWithStyle* layout_node() { return static_cast<Layout::NodeWithStyle*>(Node::layout_node()); }
     Layout::NodeWithStyle* layout_node() { return static_cast<Layout::NodeWithStyle*>(Node::layout_node()); }
     const Layout::NodeWithStyle* layout_node() const { return static_cast<const Layout::NodeWithStyle*>(Node::layout_node()); }
     const Layout::NodeWithStyle* layout_node() const { return static_cast<const Layout::NodeWithStyle*>(Node::layout_node()); }