mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-03 09:48:47 +00:00
LibWeb: Limit sibling style invalidation by max distance
If an element is affected only by selectors using the direct sibling combinator `+`, we can calculate the maximum invalidation distance and use it to limit style invalidation. For example, the selector `.a + .b + .c` has a maximum invalidation distance of 2, meaning we can skip invalidating any element affected by this selector if it's more than two siblings away from the element that triggered the style invalidation. This change results in visible performance improvement when hovering PR list on GitHub.
This commit is contained in:
parent
46abdd1126
commit
84ecaaa75c
Notes:
github-actions[bot]
2025-03-10 17:57:49 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: 84ecaaa75c
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3891
6 changed files with 61 additions and 10 deletions
|
@ -765,6 +765,36 @@ Optional<Selector::SimpleSelector> Selector::SimpleSelector::absolutized(Selecto
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
size_t Selector::sibling_invalidation_distance() const
|
||||
{
|
||||
if (m_sibling_invalidation_distance.has_value())
|
||||
return *m_sibling_invalidation_distance;
|
||||
|
||||
m_sibling_invalidation_distance = 0;
|
||||
size_t current_distance = 0;
|
||||
for (auto const& compound_selector : compound_selectors()) {
|
||||
if (compound_selector.combinator == Combinator::None)
|
||||
continue;
|
||||
|
||||
if (compound_selector.combinator == Combinator::SubsequentSibling) {
|
||||
m_sibling_invalidation_distance = NumericLimits<size_t>::max();
|
||||
return *m_sibling_invalidation_distance;
|
||||
}
|
||||
|
||||
if (compound_selector.combinator == Combinator::NextSibling) {
|
||||
current_distance++;
|
||||
} else {
|
||||
m_sibling_invalidation_distance = max(*m_sibling_invalidation_distance, current_distance);
|
||||
current_distance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_distance > 0) {
|
||||
m_sibling_invalidation_distance = max(*m_sibling_invalidation_distance, current_distance);
|
||||
}
|
||||
return *m_sibling_invalidation_distance;
|
||||
}
|
||||
|
||||
SelectorList adapt_nested_relative_selector_list(SelectorList const& selectors)
|
||||
{
|
||||
// "Nested style rules differ from non-nested rules in the following ways:
|
||||
|
|
|
@ -271,12 +271,15 @@ public:
|
|||
bool can_use_fast_matches() const { return m_can_use_fast_matches; }
|
||||
bool can_use_ancestor_filter() const { return m_can_use_ancestor_filter; }
|
||||
|
||||
size_t sibling_invalidation_distance() const;
|
||||
|
||||
private:
|
||||
explicit Selector(Vector<CompoundSelector>&&);
|
||||
|
||||
Vector<CompoundSelector> m_compound_selectors;
|
||||
mutable Optional<u32> m_specificity;
|
||||
Optional<Selector::PseudoElement> m_pseudo_element;
|
||||
mutable Optional<size_t> m_sibling_invalidation_distance;
|
||||
bool m_can_use_fast_matches { false };
|
||||
bool m_can_use_ancestor_filter { false };
|
||||
bool m_contains_the_nesting_selector { false };
|
||||
|
|
|
@ -978,7 +978,9 @@ bool matches(CSS::Selector const& selector, int component_list_index, DOM::Eleme
|
|||
}
|
||||
case CSS::Selector::Combinator::NextSibling:
|
||||
if (context.collect_per_element_selector_involvement_metadata) {
|
||||
const_cast<DOM::Element&>(element).set_affected_by_sibling_combinator(true);
|
||||
const_cast<DOM::Element&>(element).set_affected_by_direct_sibling_combinator(true);
|
||||
auto new_sibling_invalidation_distance = max(selector.sibling_invalidation_distance(), element.sibling_invalidation_distance());
|
||||
const_cast<DOM::Element&>(element).set_sibling_invalidation_distance(new_sibling_invalidation_distance);
|
||||
}
|
||||
VERIFY(component_list_index != 0);
|
||||
if (auto* sibling = element.previous_element_sibling())
|
||||
|
@ -986,7 +988,7 @@ bool matches(CSS::Selector const& selector, int component_list_index, DOM::Eleme
|
|||
return false;
|
||||
case CSS::Selector::Combinator::SubsequentSibling:
|
||||
if (context.collect_per_element_selector_involvement_metadata) {
|
||||
const_cast<DOM::Element&>(element).set_affected_by_sibling_combinator(true);
|
||||
const_cast<DOM::Element&>(element).set_affected_by_indirect_sibling_combinator(true);
|
||||
}
|
||||
VERIFY(component_list_index != 0);
|
||||
for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) {
|
||||
|
|
|
@ -509,9 +509,11 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
|
|||
m_affected_by_has_pseudo_class_in_subject_position = false;
|
||||
m_affected_by_has_pseudo_class_in_non_subject_position = false;
|
||||
m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator = false;
|
||||
m_affected_by_sibling_combinator = false;
|
||||
m_affected_by_direct_sibling_combinator = false;
|
||||
m_affected_by_indirect_sibling_combinator = false;
|
||||
m_affected_by_first_or_last_child_pseudo_class = false;
|
||||
m_affected_by_nth_child_pseudo_class = false;
|
||||
m_sibling_invalidation_distance = 0;
|
||||
|
||||
auto& style_computer = document().style_computer();
|
||||
auto new_computed_properties = style_computer.compute_style(*this);
|
||||
|
|
|
@ -437,8 +437,11 @@ public:
|
|||
bool affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator() const { return m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator; }
|
||||
void set_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator(bool value) { m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator = value; }
|
||||
|
||||
bool affected_by_sibling_combinator() const { return m_affected_by_sibling_combinator; }
|
||||
void set_affected_by_sibling_combinator(bool value) { m_affected_by_sibling_combinator = value; }
|
||||
bool affected_by_direct_sibling_combinator() const { return m_affected_by_direct_sibling_combinator; }
|
||||
void set_affected_by_direct_sibling_combinator(bool value) { m_affected_by_direct_sibling_combinator = value; }
|
||||
|
||||
bool affected_by_indirect_sibling_combinator() const { return m_affected_by_indirect_sibling_combinator; }
|
||||
void set_affected_by_indirect_sibling_combinator(bool value) { m_affected_by_indirect_sibling_combinator = value; }
|
||||
|
||||
bool affected_by_first_or_last_child_pseudo_class() const { return m_affected_by_first_or_last_child_pseudo_class; }
|
||||
void set_affected_by_first_or_last_child_pseudo_class(bool value) { m_affected_by_first_or_last_child_pseudo_class = value; }
|
||||
|
@ -446,9 +449,12 @@ public:
|
|||
bool affected_by_nth_child_pseudo_class() const { return m_affected_by_nth_child_pseudo_class; }
|
||||
void set_affected_by_nth_child_pseudo_class(bool value) { m_affected_by_nth_child_pseudo_class = value; }
|
||||
|
||||
size_t sibling_invalidation_distance() const { return m_sibling_invalidation_distance; }
|
||||
void set_sibling_invalidation_distance(size_t value) { m_sibling_invalidation_distance = value; }
|
||||
|
||||
bool style_affected_by_structural_changes() const
|
||||
{
|
||||
return affected_by_sibling_combinator() || affected_by_first_or_last_child_pseudo_class() || affected_by_nth_child_pseudo_class();
|
||||
return affected_by_direct_sibling_combinator() || affected_by_indirect_sibling_combinator() || affected_by_first_or_last_child_pseudo_class() || affected_by_nth_child_pseudo_class();
|
||||
}
|
||||
|
||||
size_t number_of_owned_list_items() const;
|
||||
|
@ -547,11 +553,14 @@ private:
|
|||
bool m_style_uses_css_custom_properties { false };
|
||||
bool m_affected_by_has_pseudo_class_in_subject_position : 1 { false };
|
||||
bool m_affected_by_has_pseudo_class_in_non_subject_position : 1 { false };
|
||||
bool m_affected_by_sibling_combinator : 1 { false };
|
||||
bool m_affected_by_direct_sibling_combinator : 1 { false };
|
||||
bool m_affected_by_indirect_sibling_combinator : 1 { false };
|
||||
bool m_affected_by_first_or_last_child_pseudo_class : 1 { false };
|
||||
bool m_affected_by_nth_child_pseudo_class : 1 { false };
|
||||
bool m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator : 1 { false };
|
||||
|
||||
size_t m_sibling_invalidation_distance { 0 };
|
||||
|
||||
OwnPtr<CSS::CountersSet> m_counters_set;
|
||||
|
||||
GC::Ptr<DOM::Element> m_aria_active_descendant_element;
|
||||
|
|
|
@ -460,17 +460,22 @@ void Node::invalidate_style(StyleInvalidationReason reason)
|
|||
}
|
||||
}
|
||||
|
||||
size_t current_sibling_distance = 1;
|
||||
for (auto* sibling = next_sibling(); sibling; sibling = sibling->next_sibling()) {
|
||||
if (auto* element = as_if<Element>(sibling)) {
|
||||
bool needs_to_invalidate = false;
|
||||
if (reason == StyleInvalidationReason::NodeInsertBefore || reason == StyleInvalidationReason::NodeRemove) {
|
||||
needs_to_invalidate = element->style_affected_by_structural_changes();
|
||||
} else {
|
||||
needs_to_invalidate = element->affected_by_sibling_combinator() || element->affected_by_nth_child_pseudo_class();
|
||||
} else if (element->affected_by_indirect_sibling_combinator() || element->affected_by_nth_child_pseudo_class()) {
|
||||
needs_to_invalidate = true;
|
||||
} else if (element->affected_by_direct_sibling_combinator() && current_sibling_distance <= element->sibling_invalidation_distance()) {
|
||||
needs_to_invalidate = true;
|
||||
}
|
||||
if (needs_to_invalidate)
|
||||
if (needs_to_invalidate) {
|
||||
element->set_entire_subtree_needs_style_update(true);
|
||||
}
|
||||
current_sibling_distance++;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* ancestor = parent_or_shadow_host(); ancestor; ancestor = ancestor->parent_or_shadow_host())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue