From 735f02900bb3e7e272ff1b96a54e9fa6ba6afb66 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 14 Oct 2019 18:32:02 +0200 Subject: [PATCH] LibHTML: Implement basic partial style invalidation This patch makes it possible to call Node::invalidate_style() and have that node and all of its ancestors recompute their style. We then figure out if the new style is visually different from the old style, and if so do a paint invalidation with set_needs_display(). Note that the "are they visually different" code is very incomplete! Use this to make hover effects a lot more efficient. They no longer cause a full relayout+repaint, but only a style invalidation. Style invalidations are still quite heavy though, and there's a lot of room for improvement there. :^) --- Libraries/LibHTML/CSS/StyleProperties.cpp | 20 ++++++++++ Libraries/LibHTML/CSS/StyleProperties.h | 3 ++ Libraries/LibHTML/DOM/Document.cpp | 7 +++- Libraries/LibHTML/DOM/Element.cpp | 46 +++++++++++++++++++++++ Libraries/LibHTML/DOM/Element.h | 8 ++++ Libraries/LibHTML/DOM/Node.cpp | 8 ++++ Libraries/LibHTML/DOM/Node.h | 2 + Libraries/LibHTML/Layout/LayoutNode.h | 1 + 8 files changed, 94 insertions(+), 1 deletion(-) diff --git a/Libraries/LibHTML/CSS/StyleProperties.cpp b/Libraries/LibHTML/CSS/StyleProperties.cpp index e0386e63b0..ff3528f7d7 100644 --- a/Libraries/LibHTML/CSS/StyleProperties.cpp +++ b/Libraries/LibHTML/CSS/StyleProperties.cpp @@ -97,3 +97,23 @@ int StyleProperties::line_height() const // FIXME: Allow overriding the line-height. We currently default to 140% which seems to look nice. return (int)(font().glyph_height() * 1.4f); } + +bool StyleProperties::operator==(const StyleProperties& other) const +{ + if (m_property_values.size() != other.m_property_values.size()) + return false; + + for (auto& it : m_property_values) { + auto jt = other.m_property_values.find(it.key); + if (jt == other.m_property_values.end()) + return false; + auto& my_value = *it.value; + auto& other_value = *jt->value; + if (my_value.type() != other_value.type()) + return false; + if (my_value.to_string() != other_value.to_string()) + return false; + } + + return true; +} diff --git a/Libraries/LibHTML/CSS/StyleProperties.h b/Libraries/LibHTML/CSS/StyleProperties.h index c18b87ef93..4f0221bd39 100644 --- a/Libraries/LibHTML/CSS/StyleProperties.h +++ b/Libraries/LibHTML/CSS/StyleProperties.h @@ -34,6 +34,9 @@ public: int line_height() const; + bool operator==(const StyleProperties&) const; + bool operator!=(const StyleProperties& other) const { return !(*this == other); } + private: HashMap> m_property_values; diff --git a/Libraries/LibHTML/DOM/Document.cpp b/Libraries/LibHTML/DOM/Document.cpp index 48bbeec6e5..cd3c97715b 100644 --- a/Libraries/LibHTML/DOM/Document.cpp +++ b/Libraries/LibHTML/DOM/Document.cpp @@ -201,7 +201,12 @@ void Document::set_hovered_node(Node* node) if (m_hovered_node == node) return; + RefPtr old_hovered_node = move(m_hovered_node); m_hovered_node = node; - update_style(); + + if (old_hovered_node) + old_hovered_node->invalidate_style(); + if (m_hovered_node) + m_hovered_node->invalidate_style(); } diff --git a/Libraries/LibHTML/DOM/Element.cpp b/Libraries/LibHTML/DOM/Element.cpp index 2627e628f8..67b00abcb1 100644 --- a/Libraries/LibHTML/DOM/Element.cpp +++ b/Libraries/LibHTML/DOM/Element.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -92,3 +93,48 @@ RefPtr Element::create_layout_node(const StyleResolver& resolver, co void Element::parse_attribute(const String&, const String&) { } + +enum class StyleDifference { + None, + NeedsRepaint, + NeedsRelayout, +}; + +static StyleDifference compute_style_difference(const StyleProperties& old_style, const StyleProperties& new_style, const Document& document) +{ + if (old_style == new_style) + return StyleDifference::None; + + bool needs_repaint = false; + bool needs_relayout = false; + + if (new_style.color_or_fallback(CSS::PropertyID::Color, document, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::Color, document, Color::Black)) + needs_repaint = true; + else if (new_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document, Color::Black) != old_style.color_or_fallback(CSS::PropertyID::BackgroundColor, document, Color::Black)) + needs_repaint = true; + + if (needs_relayout) + return StyleDifference::NeedsRelayout; + if (needs_repaint) + return StyleDifference::NeedsRepaint; + return StyleDifference::None; +} + +void Element::recompute_style() +{ + ASSERT(parent()); + auto* parent_layout_node = parent()->layout_node(); + ASSERT(parent_layout_node); + auto style = document().style_resolver().resolve_style(*this, &parent_layout_node->style()); + ASSERT(layout_node()); + auto diff = compute_style_difference(layout_node()->style(), *style, document()); + if (diff == StyleDifference::None) + return; + layout_node()->set_style(*style); + if (diff == StyleDifference::NeedsRelayout) { + ASSERT_NOT_REACHED(); + } + if (diff == StyleDifference::NeedsRepaint) { + layout_node()->set_needs_display(); + } +} diff --git a/Libraries/LibHTML/DOM/Element.h b/Libraries/LibHTML/DOM/Element.h index db3e260ed8..1c354a6af0 100644 --- a/Libraries/LibHTML/DOM/Element.h +++ b/Libraries/LibHTML/DOM/Element.h @@ -2,6 +2,9 @@ #include #include +#include + +class LayoutNodeWithStyle; class Attribute { public: @@ -45,6 +48,11 @@ public: virtual void apply_presentational_hints(StyleProperties&) const {} virtual void parse_attribute(const String& name, const String& value); + void recompute_style(); + + LayoutNodeWithStyle* layout_node() { return static_cast(Node::layout_node()); } + const LayoutNodeWithStyle* layout_node() const { return static_cast(Node::layout_node()); } + private: RefPtr create_layout_node(const StyleResolver&, const StyleProperties* parent_style) const override; diff --git a/Libraries/LibHTML/DOM/Node.cpp b/Libraries/LibHTML/DOM/Node.cpp index 14c7a38a29..54857808eb 100644 --- a/Libraries/LibHTML/DOM/Node.cpp +++ b/Libraries/LibHTML/DOM/Node.cpp @@ -102,3 +102,11 @@ RefPtr Node::create_layout_node(const StyleResolver&, const StylePro { return nullptr; } + +void Node::invalidate_style() +{ + for (auto* node = this; node; node = node->parent()) { + if (is(*node)) + to(*node).recompute_style(); + } +} diff --git a/Libraries/LibHTML/DOM/Node.h b/Libraries/LibHTML/DOM/Node.h index 9efd4bf962..1154d0466d 100644 --- a/Libraries/LibHTML/DOM/Node.h +++ b/Libraries/LibHTML/DOM/Node.h @@ -71,6 +71,8 @@ public: virtual bool is_child_allowed(const Node&) const { return true; } + void invalidate_style(); + protected: Node(Document&, NodeType); diff --git a/Libraries/LibHTML/Layout/LayoutNode.h b/Libraries/LibHTML/Layout/LayoutNode.h index b412e59370..19c4c50127 100644 --- a/Libraries/LibHTML/Layout/LayoutNode.h +++ b/Libraries/LibHTML/Layout/LayoutNode.h @@ -111,6 +111,7 @@ public: virtual ~LayoutNodeWithStyle() override {} const StyleProperties& style() const { return m_style; } + void set_style(const StyleProperties& style) { m_style = style; } protected: explicit LayoutNodeWithStyle(const Node* node, NonnullRefPtr style)