Skip to content

Commit 7da3b06

Browse files
kalenikaliaksandrawesomekling
authored andcommitted
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
1 parent 8877a4f commit 7da3b06

File tree

4 files changed

+48
-37
lines changed

4 files changed

+48
-37
lines changed

Libraries/LibWeb/DOM/Document.cpp

+40
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,8 @@ void Document::update_style()
14391439
// style change event. [CSS-Transitions-2]
14401440
m_transition_generation++;
14411441

1442+
invalidate_elements_affected_by_has();
1443+
14421444
if (!needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
14431445
return;
14441446

@@ -1658,6 +1660,44 @@ static Node* find_common_ancestor(Node* a, Node* b)
16581660
return nullptr;
16591661
}
16601662

1663+
void Document::invalidate_elements_affected_by_has()
1664+
{
1665+
if (!m_needs_invalidate_elements_affected_by_has)
1666+
return;
1667+
m_needs_invalidate_elements_affected_by_has = false;
1668+
1669+
Vector<CSS::InvalidationSet::Property, 1> changed_properties;
1670+
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Has });
1671+
auto invalidation_set = document().style_computer().invalidation_set_for_properties(changed_properties);
1672+
for_each_shadow_including_inclusive_descendant([&](Node& node) {
1673+
if (!node.is_element())
1674+
return TraversalDecision::Continue;
1675+
auto& element = static_cast<Element&>(node);
1676+
bool needs_style_recalculation = false;
1677+
// There are two cases in which an element must be invalidated, depending on the position of :has() in a selector:
1678+
// 1) In the subject position, i.e., ".a:has(.b)". In that case, invalidation sets are not helpful
1679+
// for narrowing down the set of elements that need to be invalidated. Instead, we invalidate
1680+
// all elements that were tested against selectors with :has() in the subject position during
1681+
// selector matching.
1682+
// 2) In the non-subject position, i.e., ".a:has(.b) > .c". Here, invalidation sets can be used to
1683+
// determine that only elements with the "c" class have to be invalidated.
1684+
if (element.affected_by_has_pseudo_class_in_subject_position()) {
1685+
needs_style_recalculation = true;
1686+
} else if (invalidation_set.needs_invalidate_whole_subtree()) {
1687+
needs_style_recalculation = true;
1688+
} else if (element.includes_properties_from_invalidation_set(invalidation_set)) {
1689+
needs_style_recalculation = true;
1690+
}
1691+
1692+
if (needs_style_recalculation) {
1693+
element.set_needs_style_update(true);
1694+
} else {
1695+
element.set_needs_inherited_style_update(true);
1696+
}
1697+
return TraversalDecision::Continue;
1698+
});
1699+
}
1700+
16611701
void Document::invalidate_style_for_elements_affected_by_hover_change(Node& old_new_hovered_common_ancestor, GC::Ptr<Node> hovered_node)
16621702
{
16631703
auto const& hover_rules = style_computer().get_hover_rules();

Libraries/LibWeb/DOM/Document.h

+6
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,9 @@ class Document
510510
[[nodiscard]] bool needs_full_layout_tree_update() const { return m_needs_full_layout_tree_update; }
511511
void set_needs_full_layout_tree_update(bool b) { m_needs_full_layout_tree_update = b; }
512512

513+
bool needs_invalidate_elements_affected_by_has() const { return m_needs_invalidate_elements_affected_by_has; }
514+
void set_needs_invalidate_elements_affected_by_has(bool b) { m_needs_invalidate_elements_affected_by_has = b; }
515+
513516
void set_needs_to_refresh_scroll_state(bool b);
514517

515518
bool has_active_favicon() const { return m_active_favicon; }
@@ -805,6 +808,8 @@ class Document
805808
// ^HTML::GlobalEventHandlers
806809
virtual GC::Ptr<EventTarget> global_event_handlers_to_event_target(FlyString const&) final { return *this; }
807810

811+
void invalidate_elements_affected_by_has();
812+
808813
void tear_down_layout_tree();
809814

810815
void update_active_element();
@@ -945,6 +950,7 @@ class Document
945950

946951
bool m_needs_full_style_update { false };
947952
bool m_needs_full_layout_tree_update { false };
953+
bool m_needs_invalidate_elements_affected_by_has { false };
948954

949955
bool m_needs_animated_style_update { false };
950956

Libraries/LibWeb/DOM/Node.cpp

+2-36
Original file line numberDiff line numberDiff line change
@@ -401,47 +401,13 @@ GC::Ptr<HTML::Navigable> Node::navigable() const
401401
}
402402
}
403403

404-
void Node::invalidate_elements_affected_by_has()
405-
{
406-
Vector<CSS::InvalidationSet::Property, 1> changed_properties;
407-
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Has });
408-
auto invalidation_set = document().style_computer().invalidation_set_for_properties(changed_properties);
409-
for_each_shadow_including_inclusive_descendant([&](Node& node) {
410-
if (!node.is_element())
411-
return TraversalDecision::Continue;
412-
auto& element = static_cast<Element&>(node);
413-
bool needs_style_recalculation = false;
414-
// There are two cases in which an element must be invalidated, depending on the position of :has() in a selector:
415-
// 1) In the subject position, i.e., ".a:has(.b)". In that case, invalidation sets are not helpful
416-
// for narrowing down the set of elements that need to be invalidated. Instead, we invalidate
417-
// all elements that were tested against selectors with :has() in the subject position during
418-
// selector matching.
419-
// 2) In the non-subject position, i.e., ".a:has(.b) > .c". Here, invalidation sets can be used to
420-
// determine that only elements with the "c" class have to be invalidated.
421-
if (element.affected_by_has_pseudo_class_in_subject_position()) {
422-
needs_style_recalculation = true;
423-
} else if (invalidation_set.needs_invalidate_whole_subtree()) {
424-
needs_style_recalculation = true;
425-
} else if (element.includes_properties_from_invalidation_set(invalidation_set)) {
426-
needs_style_recalculation = true;
427-
}
428-
429-
if (needs_style_recalculation) {
430-
element.set_needs_style_update(true);
431-
} else {
432-
element.set_needs_inherited_style_update(true);
433-
}
434-
return TraversalDecision::Continue;
435-
});
436-
}
437-
438404
void Node::invalidate_style(StyleInvalidationReason reason)
439405
{
440406
if (is_character_data())
441407
return;
442408

443409
if (document().style_computer().may_have_has_selectors()) {
444-
document().invalidate_elements_affected_by_has();
410+
document().set_needs_invalidate_elements_affected_by_has(true);
445411
}
446412

447413
if (!needs_style_update() && !document().needs_full_style_update()) {
@@ -503,7 +469,7 @@ void Node::invalidate_style(StyleInvalidationReason, Vector<CSS::InvalidationSet
503469
properties_used_in_has_selectors |= document().style_computer().invalidation_property_used_in_has_selector(property);
504470
}
505471
if (properties_used_in_has_selectors) {
506-
document().invalidate_elements_affected_by_has();
472+
document().set_needs_invalidate_elements_affected_by_has(true);
507473
}
508474

509475
auto invalidation_set = document().style_computer().invalidation_set_for_properties(properties);

Libraries/LibWeb/DOM/Node.h

-1
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ class Node : public EventTarget {
301301
[[nodiscard]] bool entire_subtree_needs_style_update() const { return m_entire_subtree_needs_style_update; }
302302
void set_entire_subtree_needs_style_update(bool b) { m_entire_subtree_needs_style_update = b; }
303303

304-
void invalidate_elements_affected_by_has();
305304
void invalidate_style(StyleInvalidationReason);
306305
struct StyleInvalidationOptions {
307306
bool invalidate_self { false };

0 commit comments

Comments
 (0)