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. :^)
This commit is contained in:
Andreas Kling 2019-10-14 18:32:02 +02:00
parent 667b31746a
commit 735f02900b
Notes: sideshowbarker 2024-07-19 11:41:49 +09:00
8 changed files with 94 additions and 1 deletions

View file

@ -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;
}

View file

@ -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<unsigned, NonnullRefPtr<StyleValue>> m_property_values;

View file

@ -201,7 +201,12 @@ void Document::set_hovered_node(Node* node)
if (m_hovered_node == node)
return;
RefPtr<Node> 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();
}

View file

@ -1,4 +1,5 @@
#include <LibHTML/CSS/StyleResolver.h>
#include <LibHTML/DOM/Document.h>
#include <LibHTML/DOM/Element.h>
#include <LibHTML/Layout/LayoutBlock.h>
#include <LibHTML/Layout/LayoutInline.h>
@ -92,3 +93,48 @@ RefPtr<LayoutNode> 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();
}
}

View file

@ -2,6 +2,9 @@
#include <AK/String.h>
#include <LibHTML/DOM/ParentNode.h>
#include <LibHTML/Layout/LayoutNode.h>
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<LayoutNodeWithStyle*>(Node::layout_node()); }
const LayoutNodeWithStyle* layout_node() const { return static_cast<const LayoutNodeWithStyle*>(Node::layout_node()); }
private:
RefPtr<LayoutNode> create_layout_node(const StyleResolver&, const StyleProperties* parent_style) const override;

View file

@ -102,3 +102,11 @@ RefPtr<LayoutNode> Node::create_layout_node(const StyleResolver&, const StylePro
{
return nullptr;
}
void Node::invalidate_style()
{
for (auto* node = this; node; node = node->parent()) {
if (is<Element>(*node))
to<Element>(*node).recompute_style();
}
}

View file

@ -71,6 +71,8 @@ public:
virtual bool is_child_allowed(const Node&) const { return true; }
void invalidate_style();
protected:
Node(Document&, NodeType);

View file

@ -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<StyleProperties> style)