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) {
return true;
case CSSSelector::kPseudoIs:
case CSSSelector::kPseudoWhere:
// TODO(crbug.com/1127347): Implement invalidation for :where and :is.
NOTIMPLEMENTED();
return true;
case CSSSelector::kPseudoUnknown:
case CSSSelector::kPseudoLeftPage:
......@@ -625,6 +623,23 @@ InvalidationSet* RuleFeatureSet::InvalidationSetForSimpleSelector(
}
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
// found in its selector. The first step is to extract the features from the
// rightmost compound selector (ExtractInvalidationSetFeaturesFromCompound).
......@@ -634,18 +649,21 @@ void RuleFeatureSet::UpdateInvalidationSets(const RuleData* rule_data) {
// subtree recalc, nextCompound will be the rightmost compound and we will
// addFeaturesToInvalidationSets for that one as well.
InvalidationSetFeatures features;
InvalidationSetFeatures* sibling_features = nullptr;
const CSSSelector* last_in_compound =
ExtractInvalidationSetFeaturesFromCompound(rule_data->Selector(),
features, kSubject);
ExtractInvalidationSetFeaturesFromCompound(complex, features, position,
pseudo_type);
bool was_whole_subtree_invalid =
features.invalidation_flags.WholeSubtreeInvalid();
if (features.invalidation_flags.WholeSubtreeInvalid())
features.has_features_for_rule_set_invalidation = false;
else if (!features.HasFeatures())
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.
// Add the compound features to the NthSiblingInvalidationSet. That is, for
// '#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) {
nth_set.SetInvalidatesSelf();
}
const CSSSelector* next_compound = last_in_compound
? last_in_compound->TagHistory()
: &rule_data->Selector();
if (!next_compound) {
UpdateRuleSetInvalidation(features);
return;
}
const CSSSelector* next_compound =
last_in_compound ? last_in_compound->TagHistory() : &complex;
if (!next_compound)
return kNormalInvalidation;
if (last_in_compound) {
UpdateFeaturesFromCombinator(*last_in_compound, nullptr, features,
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(
......@@ -709,22 +729,19 @@ RuleFeatureSet::ExtractInvalidationSetFeaturesFromSelectorList(
InvalidationSetFeatures any_features;
for (; sub_selector; sub_selector = CSSSelectorList::Next(*sub_selector)) {
InvalidationSetFeatures compound_features;
if (!ExtractInvalidationSetFeaturesFromCompound(
*sub_selector, compound_features, position,
simple_selector.GetPseudoType())) {
// A null selector return means the sub-selector contained a
// selector which requiresSubtreeInvalidation().
DCHECK(compound_features.invalidation_flags.WholeSubtreeInvalid());
InvalidationSetFeatures complex_features;
if (UpdateInvalidationSetsForComplex(
*sub_selector, complex_features, position,
simple_selector.GetPseudoType()) == kRequiresSubtreeInvalidation) {
features.invalidation_flags.SetWholeSubtreeInvalid(true);
return kRequiresSubtreeInvalidation;
}
if (compound_features.has_nth_pseudo)
if (complex_features.has_nth_pseudo)
features.has_nth_pseudo = true;
if (!all_sub_selectors_have_features)
continue;
if (compound_features.HasFeatures())
any_features.Add(compound_features);
if (complex_features.HasFeatures())
any_features.Add(complex_features);
else
all_sub_selectors_have_features = false;
}
......
......@@ -295,6 +295,28 @@ class CORE_EXPORT RuleFeatureSet {
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(
const CSSSelector&,
InvalidationSetFeatures&);
......
......@@ -1494,6 +1494,28 @@ struct RefTestData {
RefTestData ref_equal_test_data[] = {
// clang-format off
{".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
};
......
......@@ -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 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 ======
# ====== 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