Skip to content

Commit

Permalink
LibWeb: Fix underinvalidation of :nth-child using invalidation sets
Browse files Browse the repository at this point in the history
For all invalidation properties nested into nth-child argument list we
need to invalidate whole subtree to make sure style of sibling elements
will be recalculated.
  • Loading branch information
kalenikaliaksandr committed Jan 25, 2025
1 parent 86c4a02 commit 719678b
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 10 deletions.
39 changes: 30 additions & 9 deletions Libraries/LibWeb/CSS/StyleInvalidationData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,14 @@ enum class ExcludePropertiesNestedInNotPseudoClass : bool {
Yes,
};

static void build_invalidation_sets_for_simple_selector(Selector::SimpleSelector const& selector, InvalidationSet& invalidation_set, ExcludePropertiesNestedInNotPseudoClass exclude_properties_nested_in_not_pseudo_class, StyleInvalidationData& rule_invalidation_data)
enum class InsideNthChildPseudoClass {
No,
Yes
};

static InvalidationSet build_invalidation_sets_for_selector_impl(StyleInvalidationData& style_invalidation_data, Selector const& selector, InsideNthChildPseudoClass inside_nth_child_pseudo_class);

static void build_invalidation_sets_for_simple_selector(Selector::SimpleSelector const& selector, InvalidationSet& invalidation_set, ExcludePropertiesNestedInNotPseudoClass exclude_properties_nested_in_not_pseudo_class, StyleInvalidationData& style_invalidation_data, InsideNthChildPseudoClass inside_nth_child_selector)
{
switch (selector.type) {
case Selector::SimpleSelector::Type::Class:
Expand Down Expand Up @@ -126,8 +133,12 @@ static void build_invalidation_sets_for_simple_selector(Selector::SimpleSelector
break;
if (exclude_properties_nested_in_not_pseudo_class == ExcludePropertiesNestedInNotPseudoClass::Yes && pseudo_class.type == PseudoClass::Not)
break;
InsideNthChildPseudoClass inside_nth_child_pseudo_class_for_nested = inside_nth_child_selector;
if (AK::first_is_one_of(pseudo_class.type, PseudoClass::NthChild, PseudoClass::NthLastChild, PseudoClass::NthOfType, PseudoClass::NthLastOfType)) {
inside_nth_child_pseudo_class_for_nested = InsideNthChildPseudoClass::Yes;
}
for (auto const& nested_selector : pseudo_class.argument_selector_list) {
auto rightmost_invalidation_set_for_selector = rule_invalidation_data.build_invalidation_sets_for_selector(*nested_selector);
auto rightmost_invalidation_set_for_selector = build_invalidation_sets_for_selector_impl(style_invalidation_data, *nested_selector, inside_nth_child_pseudo_class_for_nested);
invalidation_set.include_all_from(rightmost_invalidation_set_for_selector);
}
break;
Expand All @@ -137,7 +148,7 @@ static void build_invalidation_sets_for_simple_selector(Selector::SimpleSelector
}
}

InvalidationSet StyleInvalidationData::build_invalidation_sets_for_selector(Selector const& selector)
static InvalidationSet build_invalidation_sets_for_selector_impl(StyleInvalidationData& style_invalidation_data, Selector const& selector, InsideNthChildPseudoClass inside_nth_child_pseudo_class)
{
auto const& compound_selectors = selector.compound_selectors();
int compound_selector_index = compound_selectors.size() - 1;
Expand All @@ -155,7 +166,7 @@ InvalidationSet StyleInvalidationData::build_invalidation_sets_for_selector(Sele
if (pseudo_class.type == PseudoClass::Has)
in_has = true;
}
collect_properties_used_in_has(simple_selector, *this, in_has);
collect_properties_used_in_has(simple_selector, style_invalidation_data, in_has);
}

if (is_rightmost) {
Expand All @@ -169,23 +180,28 @@ InvalidationSet StyleInvalidationData::build_invalidation_sets_for_selector(Sele
// which means invalidation_set_for_rightmost_selector should be empty
for (auto const& simple_selector : simple_selectors) {
InvalidationSet s;
build_invalidation_sets_for_simple_selector(simple_selector, s, ExcludePropertiesNestedInNotPseudoClass::No, *this);
build_invalidation_sets_for_simple_selector(simple_selector, s, ExcludePropertiesNestedInNotPseudoClass::No, style_invalidation_data, inside_nth_child_pseudo_class);
s.for_each_property([&](auto const& invalidation_property) {
auto& descendant_invalidation_set = descendant_invalidation_sets.ensure(invalidation_property, [] { return InvalidationSet {}; });
auto& descendant_invalidation_set = style_invalidation_data.descendant_invalidation_sets.ensure(invalidation_property, [] { return InvalidationSet {}; });
descendant_invalidation_set.set_needs_invalidate_self();
if (inside_nth_child_pseudo_class == InsideNthChildPseudoClass::Yes) {
// When invalidation property is nested in nth-child selector like p:nth-child(even of #t1, #t2, #t3)
// we need to make all siblings are invalidated.
descendant_invalidation_set.set_needs_invalidate_whole_subtree();
}
});
}

for (auto const& simple_selector : simple_selectors) {
build_invalidation_sets_for_simple_selector(simple_selector, invalidation_set_for_rightmost_selector, ExcludePropertiesNestedInNotPseudoClass::Yes, *this);
build_invalidation_sets_for_simple_selector(simple_selector, invalidation_set_for_rightmost_selector, ExcludePropertiesNestedInNotPseudoClass::Yes, style_invalidation_data, inside_nth_child_pseudo_class);
}
} else {
VERIFY(previous_compound_combinator != Selector::Combinator::None);
for (auto const& simple_selector : simple_selectors) {
InvalidationSet s;
build_invalidation_sets_for_simple_selector(simple_selector, s, ExcludePropertiesNestedInNotPseudoClass::No, *this);
build_invalidation_sets_for_simple_selector(simple_selector, s, ExcludePropertiesNestedInNotPseudoClass::No, style_invalidation_data, inside_nth_child_pseudo_class);
s.for_each_property([&](auto const& invalidation_property) {
auto& descendant_invalidation_set = descendant_invalidation_sets.ensure(invalidation_property, [] {
auto& descendant_invalidation_set = style_invalidation_data.descendant_invalidation_sets.ensure(invalidation_property, [] {
return InvalidationSet {};
});
// If the rightmost selector's invalidation set is empty, it means there's no
Expand All @@ -209,4 +225,9 @@ InvalidationSet StyleInvalidationData::build_invalidation_sets_for_selector(Sele
return invalidation_set_for_rightmost_selector;
}

void StyleInvalidationData::build_invalidation_sets_for_selector(Selector const& selector)
{
(void)build_invalidation_sets_for_selector_impl(*this, selector, InsideNthChildPseudoClass::No);
}

}
2 changes: 1 addition & 1 deletion Libraries/LibWeb/CSS/StyleInvalidationData.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct StyleInvalidationData {
HashTable<FlyString> tag_names_used_in_has_selectors;
HashTable<PseudoClass> pseudo_classes_used_in_has_selectors;

InvalidationSet build_invalidation_sets_for_selector(Selector const& selector);
void build_invalidation_sets_for_selector(Selector const& selector);
};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<title>CSS Selectors Invalidation: :nth-child(... of IDs)</title>
<link rel="author" title="Zach Hoffman" href="mailto:[email protected]">
<link rel="match" href="../../../../../expected/wpt-import/css/selectors/invalidation/nth-child-of-class-ref.html">
<link rel="help" href="https://drafts.csswg.org/selectors-4/#child-index">
<style>
p:nth-child(even of #t1, #t2, #t3, #t4, #t5) {
color: green;
}
</style>
<!--
This test is modified version of nth-child-of-ids.html that does not contain empty text node
between <div> and <script> elements. This is important, because insertion of this empty text node
causes style invalidation that does not use invalidation sets (at the time of writing this comment),
so we cannot check if modification of id on #t2 correctly invalidates style of sibling <p> elements.
-->
<div>
<p>Ignored</p>
<p>Ignored</p>
<p id="t1">Not ignored</p>
<p id="t2">Selectively ignored</p>
<p id="t3">Not ignored</p>
<p id="t4">Not ignored</p>
<p id="t5">Not ignored</p>
<p>Ignored</p>
</div><script>
document.documentElement.offsetTop;
t2.id = "new-id";
</script>

0 comments on commit 719678b

Please sign in to comment.