mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 11:49:44 +00:00
LibWeb: Postpone invalidating style of elements affected by :has()
...until Document::update_style(). This allows to avoid doing full document DOM tree traversal on each Node::invalidate_style() call. Fixes performance regression on wpt.fyi
This commit is contained in:
parent
8877a4f691
commit
7da3b06e3e
Notes:
github-actions[bot]
2025-01-29 16:22:40 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: 7da3b06e3e
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3401
4 changed files with 48 additions and 37 deletions
|
@ -1439,6 +1439,8 @@ void Document::update_style()
|
||||||
// style change event. [CSS-Transitions-2]
|
// style change event. [CSS-Transitions-2]
|
||||||
m_transition_generation++;
|
m_transition_generation++;
|
||||||
|
|
||||||
|
invalidate_elements_affected_by_has();
|
||||||
|
|
||||||
if (!needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
|
if (!needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1658,6 +1660,44 @@ static Node* find_common_ancestor(Node* a, Node* b)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Document::invalidate_elements_affected_by_has()
|
||||||
|
{
|
||||||
|
if (!m_needs_invalidate_elements_affected_by_has)
|
||||||
|
return;
|
||||||
|
m_needs_invalidate_elements_affected_by_has = false;
|
||||||
|
|
||||||
|
Vector<CSS::InvalidationSet::Property, 1> changed_properties;
|
||||||
|
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Has });
|
||||||
|
auto invalidation_set = document().style_computer().invalidation_set_for_properties(changed_properties);
|
||||||
|
for_each_shadow_including_inclusive_descendant([&](Node& node) {
|
||||||
|
if (!node.is_element())
|
||||||
|
return TraversalDecision::Continue;
|
||||||
|
auto& element = static_cast<Element&>(node);
|
||||||
|
bool needs_style_recalculation = false;
|
||||||
|
// There are two cases in which an element must be invalidated, depending on the position of :has() in a selector:
|
||||||
|
// 1) In the subject position, i.e., ".a:has(.b)". In that case, invalidation sets are not helpful
|
||||||
|
// for narrowing down the set of elements that need to be invalidated. Instead, we invalidate
|
||||||
|
// all elements that were tested against selectors with :has() in the subject position during
|
||||||
|
// selector matching.
|
||||||
|
// 2) In the non-subject position, i.e., ".a:has(.b) > .c". Here, invalidation sets can be used to
|
||||||
|
// determine that only elements with the "c" class have to be invalidated.
|
||||||
|
if (element.affected_by_has_pseudo_class_in_subject_position()) {
|
||||||
|
needs_style_recalculation = true;
|
||||||
|
} else if (invalidation_set.needs_invalidate_whole_subtree()) {
|
||||||
|
needs_style_recalculation = true;
|
||||||
|
} else if (element.includes_properties_from_invalidation_set(invalidation_set)) {
|
||||||
|
needs_style_recalculation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_style_recalculation) {
|
||||||
|
element.set_needs_style_update(true);
|
||||||
|
} else {
|
||||||
|
element.set_needs_inherited_style_update(true);
|
||||||
|
}
|
||||||
|
return TraversalDecision::Continue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_new_hovered_common_ancestor, GC::Ptr<Node> hovered_node)
|
void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_new_hovered_common_ancestor, GC::Ptr<Node> hovered_node)
|
||||||
{
|
{
|
||||||
auto const& hover_rules = style_computer().get_hover_rules();
|
auto const& hover_rules = style_computer().get_hover_rules();
|
||||||
|
|
|
@ -510,6 +510,9 @@ public:
|
||||||
[[nodiscard]] bool needs_full_layout_tree_update() const { return m_needs_full_layout_tree_update; }
|
[[nodiscard]] bool needs_full_layout_tree_update() const { return m_needs_full_layout_tree_update; }
|
||||||
void set_needs_full_layout_tree_update(bool b) { m_needs_full_layout_tree_update = b; }
|
void set_needs_full_layout_tree_update(bool b) { m_needs_full_layout_tree_update = b; }
|
||||||
|
|
||||||
|
bool needs_invalidate_elements_affected_by_has() const { return m_needs_invalidate_elements_affected_by_has; }
|
||||||
|
void set_needs_invalidate_elements_affected_by_has(bool b) { m_needs_invalidate_elements_affected_by_has = b; }
|
||||||
|
|
||||||
void set_needs_to_refresh_scroll_state(bool b);
|
void set_needs_to_refresh_scroll_state(bool b);
|
||||||
|
|
||||||
bool has_active_favicon() const { return m_active_favicon; }
|
bool has_active_favicon() const { return m_active_favicon; }
|
||||||
|
@ -805,6 +808,8 @@ private:
|
||||||
// ^HTML::GlobalEventHandlers
|
// ^HTML::GlobalEventHandlers
|
||||||
virtual GC::Ptr<EventTarget> global_event_handlers_to_event_target(FlyString const&) final { return *this; }
|
virtual GC::Ptr<EventTarget> global_event_handlers_to_event_target(FlyString const&) final { return *this; }
|
||||||
|
|
||||||
|
void invalidate_elements_affected_by_has();
|
||||||
|
|
||||||
void tear_down_layout_tree();
|
void tear_down_layout_tree();
|
||||||
|
|
||||||
void update_active_element();
|
void update_active_element();
|
||||||
|
@ -945,6 +950,7 @@ private:
|
||||||
|
|
||||||
bool m_needs_full_style_update { false };
|
bool m_needs_full_style_update { false };
|
||||||
bool m_needs_full_layout_tree_update { false };
|
bool m_needs_full_layout_tree_update { false };
|
||||||
|
bool m_needs_invalidate_elements_affected_by_has { false };
|
||||||
|
|
||||||
bool m_needs_animated_style_update { false };
|
bool m_needs_animated_style_update { false };
|
||||||
|
|
||||||
|
|
|
@ -401,47 +401,13 @@ GC::Ptr<HTML::Navigable> Node::navigable() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::invalidate_elements_affected_by_has()
|
|
||||||
{
|
|
||||||
Vector<CSS::InvalidationSet::Property, 1> changed_properties;
|
|
||||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Has });
|
|
||||||
auto invalidation_set = document().style_computer().invalidation_set_for_properties(changed_properties);
|
|
||||||
for_each_shadow_including_inclusive_descendant([&](Node& node) {
|
|
||||||
if (!node.is_element())
|
|
||||||
return TraversalDecision::Continue;
|
|
||||||
auto& element = static_cast<Element&>(node);
|
|
||||||
bool needs_style_recalculation = false;
|
|
||||||
// There are two cases in which an element must be invalidated, depending on the position of :has() in a selector:
|
|
||||||
// 1) In the subject position, i.e., ".a:has(.b)". In that case, invalidation sets are not helpful
|
|
||||||
// for narrowing down the set of elements that need to be invalidated. Instead, we invalidate
|
|
||||||
// all elements that were tested against selectors with :has() in the subject position during
|
|
||||||
// selector matching.
|
|
||||||
// 2) In the non-subject position, i.e., ".a:has(.b) > .c". Here, invalidation sets can be used to
|
|
||||||
// determine that only elements with the "c" class have to be invalidated.
|
|
||||||
if (element.affected_by_has_pseudo_class_in_subject_position()) {
|
|
||||||
needs_style_recalculation = true;
|
|
||||||
} else if (invalidation_set.needs_invalidate_whole_subtree()) {
|
|
||||||
needs_style_recalculation = true;
|
|
||||||
} else if (element.includes_properties_from_invalidation_set(invalidation_set)) {
|
|
||||||
needs_style_recalculation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needs_style_recalculation) {
|
|
||||||
element.set_needs_style_update(true);
|
|
||||||
} else {
|
|
||||||
element.set_needs_inherited_style_update(true);
|
|
||||||
}
|
|
||||||
return TraversalDecision::Continue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::invalidate_style(StyleInvalidationReason reason)
|
void Node::invalidate_style(StyleInvalidationReason reason)
|
||||||
{
|
{
|
||||||
if (is_character_data())
|
if (is_character_data())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (document().style_computer().may_have_has_selectors()) {
|
if (document().style_computer().may_have_has_selectors()) {
|
||||||
document().invalidate_elements_affected_by_has();
|
document().set_needs_invalidate_elements_affected_by_has(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needs_style_update() && !document().needs_full_style_update()) {
|
if (!needs_style_update() && !document().needs_full_style_update()) {
|
||||||
|
@ -503,7 +469,7 @@ void Node::invalidate_style(StyleInvalidationReason, Vector<CSS::InvalidationSet
|
||||||
properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property);
|
properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property);
|
||||||
}
|
}
|
||||||
if (properties_used_in_has_selectors) {
|
if (properties_used_in_has_selectors) {
|
||||||
document().invalidate_elements_affected_by_has();
|
document().set_needs_invalidate_elements_affected_by_has(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto invalidation_set = document().style_computer().invalidation_set_for_properties(properties);
|
auto invalidation_set = document().style_computer().invalidation_set_for_properties(properties);
|
||||||
|
|
|
@ -301,7 +301,6 @@ public:
|
||||||
[[nodiscard]] bool entire_subtree_needs_style_update() const { return m_entire_subtree_needs_style_update; }
|
[[nodiscard]] bool entire_subtree_needs_style_update() const { return m_entire_subtree_needs_style_update; }
|
||||||
void set_entire_subtree_needs_style_update(bool b) { m_entire_subtree_needs_style_update = b; }
|
void set_entire_subtree_needs_style_update(bool b) { m_entire_subtree_needs_style_update = b; }
|
||||||
|
|
||||||
void invalidate_elements_affected_by_has();
|
|
||||||
void invalidate_style(StyleInvalidationReason);
|
void invalidate_style(StyleInvalidationReason);
|
||||||
struct StyleInvalidationOptions {
|
struct StyleInvalidationOptions {
|
||||||
bool invalidate_self { false };
|
bool invalidate_self { false };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue