Commit b46b3410 authored by Anders Hartvoll Ruud's avatar Anders Hartvoll Ruud Committed by Commit Bot

[:is/:where] Support feature extraction for nested complex selectors

This CL modifies the extraction code for complex selectors such that
it can be called recursively from ExtractInvalidationSetFeaturesFrom-
SelectorList.

This is a step on the way towards correct invalidation behavior for
:is/:where.

Bug: 568705
Change-Id: I3ad09e09586032d409cea91b7ae457b832b49c5e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2435087
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812135}
parent 28c23371
...@@ -179,8 +179,6 @@ bool SupportsInvalidation(CSSSelector::PseudoType type) { ...@@ -179,8 +179,6 @@ bool SupportsInvalidation(CSSSelector::PseudoType type) {
return true; return true;
case CSSSelector::kPseudoIs: case CSSSelector::kPseudoIs:
case CSSSelector::kPseudoWhere: case CSSSelector::kPseudoWhere:
// TODO(crbug.com/1127347): Implement invalidation for :where and :is.
NOTIMPLEMENTED();
return true; return true;
case CSSSelector::kPseudoUnknown: case CSSSelector::kPseudoUnknown:
case CSSSelector::kPseudoLeftPage: case CSSSelector::kPseudoLeftPage:
...@@ -625,6 +623,23 @@ InvalidationSet* RuleFeatureSet::InvalidationSetForSimpleSelector( ...@@ -625,6 +623,23 @@ InvalidationSet* RuleFeatureSet::InvalidationSetForSimpleSelector(
} }
void RuleFeatureSet::UpdateInvalidationSets(const RuleData* rule_data) { void RuleFeatureSet::UpdateInvalidationSets(const RuleData* rule_data) {
InvalidationSetFeatures features;
FeatureInvalidationType feature_invalidation_type =
UpdateInvalidationSetsForComplex(rule_data->Selector(), features,
kSubject, CSSSelector::kPseudoUnknown);
if (feature_invalidation_type ==
FeatureInvalidationType::kRequiresSubtreeInvalidation) {
features.invalidation_flags.SetWholeSubtreeInvalid(true);
}
UpdateRuleSetInvalidation(features);
}
RuleFeatureSet::FeatureInvalidationType
RuleFeatureSet::UpdateInvalidationSetsForComplex(
const CSSSelector& complex,
InvalidationSetFeatures& features,
PositionType position,
CSSSelector::PseudoType pseudo_type) {
// Given a rule, update the descendant invalidation sets for the features // Given a rule, update the descendant invalidation sets for the features
// found in its selector. The first step is to extract the features from the // found in its selector. The first step is to extract the features from the
// rightmost compound selector (ExtractInvalidationSetFeaturesFromCompound). // rightmost compound selector (ExtractInvalidationSetFeaturesFromCompound).
...@@ -634,18 +649,21 @@ void RuleFeatureSet::UpdateInvalidationSets(const RuleData* rule_data) { ...@@ -634,18 +649,21 @@ void RuleFeatureSet::UpdateInvalidationSets(const RuleData* rule_data) {
// subtree recalc, nextCompound will be the rightmost compound and we will // subtree recalc, nextCompound will be the rightmost compound and we will
// addFeaturesToInvalidationSets for that one as well. // addFeaturesToInvalidationSets for that one as well.
InvalidationSetFeatures features;
InvalidationSetFeatures* sibling_features = nullptr; InvalidationSetFeatures* sibling_features = nullptr;
const CSSSelector* last_in_compound = const CSSSelector* last_in_compound =
ExtractInvalidationSetFeaturesFromCompound(rule_data->Selector(), ExtractInvalidationSetFeaturesFromCompound(complex, features, position,
features, kSubject); pseudo_type);
bool was_whole_subtree_invalid =
features.invalidation_flags.WholeSubtreeInvalid();
if (features.invalidation_flags.WholeSubtreeInvalid()) if (features.invalidation_flags.WholeSubtreeInvalid())
features.has_features_for_rule_set_invalidation = false; features.has_features_for_rule_set_invalidation = false;
else if (!features.HasFeatures()) else if (!features.HasFeatures())
features.invalidation_flags.SetWholeSubtreeInvalid(true); features.invalidation_flags.SetWholeSubtreeInvalid(true);
if (features.has_nth_pseudo) { // Only check for has_nth_pseudo if this is the top-level complex selector.
if (pseudo_type == CSSSelector::kPseudoUnknown && features.has_nth_pseudo) {
// The rightmost compound contains an :nth-* selector. // The rightmost compound contains an :nth-* selector.
// Add the compound features to the NthSiblingInvalidationSet. That is, for // Add the compound features to the NthSiblingInvalidationSet. That is, for
// '#id:nth-child(even)', add #id to the invalidation set and make sure we // '#id:nth-child(even)', add #id to the invalidation set and make sure we
...@@ -655,20 +673,22 @@ void RuleFeatureSet::UpdateInvalidationSets(const RuleData* rule_data) { ...@@ -655,20 +673,22 @@ void RuleFeatureSet::UpdateInvalidationSets(const RuleData* rule_data) {
nth_set.SetInvalidatesSelf(); nth_set.SetInvalidatesSelf();
} }
const CSSSelector* next_compound = last_in_compound const CSSSelector* next_compound =
? last_in_compound->TagHistory() last_in_compound ? last_in_compound->TagHistory() : &complex;
: &rule_data->Selector(); if (!next_compound)
if (!next_compound) { return kNormalInvalidation;
UpdateRuleSetInvalidation(features);
return;
}
if (last_in_compound) { if (last_in_compound) {
UpdateFeaturesFromCombinator(*last_in_compound, nullptr, features, UpdateFeaturesFromCombinator(*last_in_compound, nullptr, features,
sibling_features, features); sibling_features, features);
} }
AddFeaturesToInvalidationSets(*next_compound, sibling_features, features); AddFeaturesToInvalidationSets(*next_compound, sibling_features, features);
UpdateRuleSetInvalidation(features);
// We need to differentiate between no features (HasFeatures()==false)
// and RequiresSubtreeInvalidation at the callsite. Hence we reset the flag
// before returning, otherwise the distinction would be lost.
features.invalidation_flags.SetWholeSubtreeInvalid(was_whole_subtree_invalid);
return last_in_compound ? kNormalInvalidation : kRequiresSubtreeInvalidation;
} }
void RuleFeatureSet::UpdateRuleSetInvalidation( void RuleFeatureSet::UpdateRuleSetInvalidation(
...@@ -709,22 +729,19 @@ RuleFeatureSet::ExtractInvalidationSetFeaturesFromSelectorList( ...@@ -709,22 +729,19 @@ RuleFeatureSet::ExtractInvalidationSetFeaturesFromSelectorList(
InvalidationSetFeatures any_features; InvalidationSetFeatures any_features;
for (; sub_selector; sub_selector = CSSSelectorList::Next(*sub_selector)) { for (; sub_selector; sub_selector = CSSSelectorList::Next(*sub_selector)) {
InvalidationSetFeatures compound_features; InvalidationSetFeatures complex_features;
if (!ExtractInvalidationSetFeaturesFromCompound( if (UpdateInvalidationSetsForComplex(
*sub_selector, compound_features, position, *sub_selector, complex_features, position,
simple_selector.GetPseudoType())) { simple_selector.GetPseudoType()) == kRequiresSubtreeInvalidation) {
// A null selector return means the sub-selector contained a
// selector which requiresSubtreeInvalidation().
DCHECK(compound_features.invalidation_flags.WholeSubtreeInvalid());
features.invalidation_flags.SetWholeSubtreeInvalid(true); features.invalidation_flags.SetWholeSubtreeInvalid(true);
return kRequiresSubtreeInvalidation; return kRequiresSubtreeInvalidation;
} }
if (compound_features.has_nth_pseudo) if (complex_features.has_nth_pseudo)
features.has_nth_pseudo = true; features.has_nth_pseudo = true;
if (!all_sub_selectors_have_features) if (!all_sub_selectors_have_features)
continue; continue;
if (compound_features.HasFeatures()) if (complex_features.HasFeatures())
any_features.Add(compound_features); any_features.Add(complex_features);
else else
all_sub_selectors_have_features = false; all_sub_selectors_have_features = false;
} }
......
...@@ -295,6 +295,28 @@ class CORE_EXPORT RuleFeatureSet { ...@@ -295,6 +295,28 @@ class CORE_EXPORT RuleFeatureSet {
kRequiresSubtreeInvalidation kRequiresSubtreeInvalidation
}; };
// Extracts features for the given complex selector, and adds those features
// the appropriate invalidation sets.
//
// The returned InvalidationSetFeatures contain the descendant features,
// extracted from the rightmost compound selector.
//
// The PositionType indicates whether or not the complex selector resides
// in the rightmost compound (kSubject), or anything to the left of that
// (kAncestor). For example, for ':is(.a .b) :is(.c .d)', the nested
// complex selector '.c .d' should be called with kSubject, and the '.a .b'
// should be called with kAncestor.
//
// The PseudoType indicates whether or not we are inside a nested complex
// selector. For example, for :is(.a .b), this function is called with
// CSSSelector equal to '.a .b', and PseudoType equal to kPseudoIs.
// For top-level complex selectors, the PseudoType is kPseudoUnknown.
FeatureInvalidationType UpdateInvalidationSetsForComplex(
const CSSSelector&,
InvalidationSetFeatures&,
PositionType,
CSSSelector::PseudoType);
void ExtractInvalidationSetFeaturesFromSimpleSelector( void ExtractInvalidationSetFeaturesFromSimpleSelector(
const CSSSelector&, const CSSSelector&,
InvalidationSetFeatures&); InvalidationSetFeatures&);
......
...@@ -1494,6 +1494,28 @@ struct RefTestData { ...@@ -1494,6 +1494,28 @@ struct RefTestData {
RefTestData ref_equal_test_data[] = { RefTestData ref_equal_test_data[] = {
// clang-format off // clang-format off
{".a", ".a"}, {".a", ".a"},
// :is
{":is(.a)", ".a"},
{":is(.a .b)", ".a .b"},
{".a :is(.b .c)", ".a .c, .b .c"},
{".a + :is(.b .c)", ".a + .c, .b .c"},
{".a + :is(.b .c)", ".a + .c, .b .c"},
{"div + :is(.b .c)", "div + .c, .b .c"},
{":is(.a :is(.b + .c))", ".a .c, .b + .c"},
{".a + :is(.b) :is(.c)", ".a + .b .c"},
{":is(#a:nth-child(1))", "#a:nth-child(1)"},
{":is(#a:nth-child(1), #b:nth-child(1))",
"#a:nth-child(1), #b:nth-child(1)"},
{":is(#a, #b):nth-child(1)", "#a:nth-child(1), #b:nth-child(1)"},
{":is(:nth-child(1))", ":nth-child(1)"},
{".a :is(.b, .c):nth-child(1)", ".a .b:nth-child(1), .a .c:nth-child(1)"},
// TODO(andruud): We currently add _all_ rightmost features to the nth-
// sibling set, so .b is added here, since nth-child is present _somewhere_
// in the rightmost compound. Hence the unexpected '.b:nth-child(1)'
// selector in the ref.
{".a :is(.b, .c:nth-child(1))",
".a .b, .a .c:nth-child(1), .b:nth-child(1)"},
// clang-format on // clang-format on
}; };
......
...@@ -1318,9 +1318,6 @@ crbug.com/995106 external/wpt/css/css-pseudo/first-letter-exclude-inline-child-m ...@@ -1318,9 +1318,6 @@ crbug.com/995106 external/wpt/css/css-pseudo/first-letter-exclude-inline-child-m
crbug.com/929098 external/wpt/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-change-checkbox.html [ Failure ] crbug.com/929098 external/wpt/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-change-checkbox.html [ Failure ]
crbug.com/929098 virtual/dark-color-scheme/external/wpt/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-change-checkbox.html [ Pass ] crbug.com/929098 virtual/dark-color-scheme/external/wpt/css/css-color-adjust/rendering/dark-color-scheme/color-scheme-change-checkbox.html [ Pass ]
crbug.com/1127347 external/wpt/css/selectors/invalidation/is.html [ Failure ]
crbug.com/1127347 external/wpt/css/selectors/invalidation/where.html [ Failure ]
# ====== Style team owned tests to here ====== # ====== Style team owned tests to here ======
# ====== Form Controls Refresh (chrome://flags/#form-controls-refresh) failures from here ====== # ====== Form Controls Refresh (chrome://flags/#form-controls-refresh) failures from here ======
......
This is a testharness.js-based test.
PASS Preconditions.
PASS Invalidate :is() for simple selector arguments.
PASS Invalidate :is() for compound selector arguments.
PASS Invalidate :is() for complex selector arguments.
FAIL Invalidate nested :is(). assert_equals: expected "rgb(0, 0, 0)" but got "rgb(255, 0, 0)"
FAIL Test specificity of :is(). assert_equals: expected "rgb(0, 0, 255)" but got "rgb(0, 0, 0)"
Harness: the test ran to completion.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment