Commit c2ab413e authored by Eric Willigers's avatar Eric Willigers Committed by Commit Bot

CSS: Retire expansion-based implementation of :is and :where

We no longer attempt to expand :is and :where during style sheet loading.

Correct support for these selectors remains to be implemented.

In particular, we need:
- invalidation - RuleFeatureSet
- support for empty selector lists
- error recovery - CSSSelectorParser

Bug: 1127347
Change-Id: I28912cf5cb9b373c423d488b47c20251917329fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2405034
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Reviewed-by: default avatarAnders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/master@{#808207}
parent 546c3a67
...@@ -50,6 +50,25 @@ ...@@ -50,6 +50,25 @@
namespace blink { namespace blink {
namespace {
unsigned MaximumSpecificity(const CSSSelectorList* list) {
if (!list)
return 0;
unsigned result = 0;
const CSSSelector* selector;
for (selector = list->First(); selector;
selector = CSSSelectorList::Next(*selector)) {
unsigned specificity = selector->Specificity();
if (result < specificity)
result = specificity;
}
return result;
}
} // namespace
struct SameSizeAsCSSSelector { struct SameSizeAsCSSSelector {
unsigned bitfields; unsigned bitfields;
void* pointers[1]; void* pointers[1];
...@@ -103,13 +122,13 @@ inline unsigned CSSSelector::SpecificityForOneSelector() const { ...@@ -103,13 +122,13 @@ inline unsigned CSSSelector::SpecificityForOneSelector() const {
// FIXME: Pseudo-elements and pseudo-classes do not have the same specificity. // FIXME: Pseudo-elements and pseudo-classes do not have the same specificity.
// This function isn't quite correct. // This function isn't quite correct.
// http://www.w3.org/TR/selectors/#specificity // http://www.w3.org/TR/selectors/#specificity
if (ignore_specificity_)
return 0;
switch (match_) { switch (match_) {
case kId: case kId:
return kIdSpecificity; return kIdSpecificity;
case kPseudoClass: case kPseudoClass:
switch (GetPseudoType()) { switch (GetPseudoType()) {
case kPseudoWhere:
return 0;
case kPseudoHost: case kPseudoHost:
case kPseudoHostContext: case kPseudoHostContext:
// We dynamically compute the specificity of :host and :host-context // We dynamically compute the specificity of :host and :host-context
...@@ -118,10 +137,11 @@ inline unsigned CSSSelector::SpecificityForOneSelector() const { ...@@ -118,10 +137,11 @@ inline unsigned CSSSelector::SpecificityForOneSelector() const {
case kPseudoNot: case kPseudoNot:
DCHECK(SelectorList()); DCHECK(SelectorList());
return SelectorList()->First()->Specificity(); return SelectorList()->First()->Specificity();
case kPseudoIs:
return MaximumSpecificity(SelectorList());
// FIXME: PseudoAny should base the specificity on the sub-selectors. // FIXME: PseudoAny should base the specificity on the sub-selectors.
// See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html // See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html
case kPseudoAny: case kPseudoAny:
case kPseudoIs:
default: default:
break; break;
} }
...@@ -1173,22 +1193,6 @@ bool CSSSelector::NeedsUpdatedDistribution() const { ...@@ -1173,22 +1193,6 @@ bool CSSSelector::NeedsUpdatedDistribution() const {
*this); *this);
} }
bool CSSSelector::HasPseudoIs() const {
for (const CSSSelector* s = this; s; s = s->TagHistory()) {
if (s->GetPseudoType() == CSSSelector::kPseudoIs)
return true;
}
return false;
}
bool CSSSelector::HasPseudoWhere() const {
for (const CSSSelector* s = this; s; s = s->TagHistory()) {
if (s->GetPseudoType() == CSSSelector::kPseudoWhere)
return true;
}
return false;
}
CSSSelector::RareData::RareData(const AtomicString& value) CSSSelector::RareData::RareData(const AtomicString& value)
: matching_value_(value), : matching_value_(value),
serializing_value_(value), serializing_value_(value),
......
...@@ -368,9 +368,6 @@ class CORE_EXPORT CSSSelector { ...@@ -368,9 +368,6 @@ class CORE_EXPORT CSSSelector {
bool IsLastInTagHistory() const { return is_last_in_tag_history_; } bool IsLastInTagHistory() const { return is_last_in_tag_history_; }
void SetLastInTagHistory(bool is_last) { is_last_in_tag_history_ = is_last; } void SetLastInTagHistory(bool is_last) { is_last_in_tag_history_ = is_last; }
bool IgnoreSpecificity() const { return ignore_specificity_; }
void SetIgnoreSpecificity(bool ignore) { ignore_specificity_ = ignore; }
// https://drafts.csswg.org/selectors/#compound // https://drafts.csswg.org/selectors/#compound
bool IsCompound() const; bool IsCompound() const;
...@@ -401,8 +398,6 @@ class CORE_EXPORT CSSSelector { ...@@ -401,8 +398,6 @@ class CORE_EXPORT CSSSelector {
// Returns true if the immediately preceeding simple selector is ::part. // Returns true if the immediately preceeding simple selector is ::part.
bool FollowsPart() const; bool FollowsPart() const;
bool NeedsUpdatedDistribution() const; bool NeedsUpdatedDistribution() const;
bool HasPseudoIs() const;
bool HasPseudoWhere() const;
private: private:
unsigned relation_ : 4; // enum RelationType unsigned relation_ : 4; // enum RelationType
...@@ -415,7 +410,6 @@ class CORE_EXPORT CSSSelector { ...@@ -415,7 +410,6 @@ class CORE_EXPORT CSSSelector {
unsigned tag_is_implicit_ : 1; unsigned tag_is_implicit_ : 1;
unsigned relation_is_affected_by_pseudo_content_ : 1; unsigned relation_is_affected_by_pseudo_content_ : 1;
unsigned is_last_in_original_list_ : 1; unsigned is_last_in_original_list_ : 1;
unsigned ignore_specificity_ : 1;
void SetPseudoType(PseudoType pseudo_type) { void SetPseudoType(PseudoType pseudo_type) {
pseudo_type_ = pseudo_type; pseudo_type_ = pseudo_type;
...@@ -542,7 +536,6 @@ inline CSSSelector::CSSSelector() ...@@ -542,7 +536,6 @@ inline CSSSelector::CSSSelector()
tag_is_implicit_(false), tag_is_implicit_(false),
relation_is_affected_by_pseudo_content_(false), relation_is_affected_by_pseudo_content_(false),
is_last_in_original_list_(false), is_last_in_original_list_(false),
ignore_specificity_(false),
data_(DataUnion::kConstructEmptyValue) {} data_(DataUnion::kConstructEmptyValue) {}
inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name, inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name,
...@@ -557,7 +550,6 @@ inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name, ...@@ -557,7 +550,6 @@ inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name,
tag_is_implicit_(tag_is_implicit), tag_is_implicit_(tag_is_implicit),
relation_is_affected_by_pseudo_content_(false), relation_is_affected_by_pseudo_content_(false),
is_last_in_original_list_(false), is_last_in_original_list_(false),
ignore_specificity_(false),
data_(tag_q_name) {} data_(tag_q_name) {}
inline CSSSelector::CSSSelector(const CSSSelector& o) inline CSSSelector::CSSSelector(const CSSSelector& o)
...@@ -572,7 +564,6 @@ inline CSSSelector::CSSSelector(const CSSSelector& o) ...@@ -572,7 +564,6 @@ inline CSSSelector::CSSSelector(const CSSSelector& o)
relation_is_affected_by_pseudo_content_( relation_is_affected_by_pseudo_content_(
o.relation_is_affected_by_pseudo_content_), o.relation_is_affected_by_pseudo_content_),
is_last_in_original_list_(o.is_last_in_original_list_), is_last_in_original_list_(o.is_last_in_original_list_),
ignore_specificity_(o.ignore_specificity_),
data_(DataUnion::kConstructUninitialized) { data_(DataUnion::kConstructUninitialized) {
if (o.match_ == kTag) { if (o.match_ == kTag) {
new (&data_.tag_q_name_) QualifiedName(o.data_.tag_q_name_); new (&data_.tag_q_name_) QualifiedName(o.data_.tag_q_name_);
......
...@@ -57,188 +57,6 @@ CSSSelectorList CSSSelectorList::Copy() const { ...@@ -57,188 +57,6 @@ CSSSelectorList CSSSelectorList::Copy() const {
return list; return list;
} }
CSSSelectorList CSSSelectorList::ConcatenateListExpansion(
const CSSSelectorList& expanded,
const CSSSelectorList& original) {
unsigned expanded_length = expanded.ComputeLength();
unsigned original_length = original.ComputeLength();
unsigned total_length = expanded_length + original_length;
CSSSelectorList list;
list.selector_array_ = reinterpret_cast<CSSSelector*>(
WTF::Partitions::FastMalloc(WTF::Partitions::ComputeAllocationSize(
total_length, sizeof(CSSSelector)),
kCSSSelectorTypeName));
unsigned list_index = 0;
for (unsigned i = 0; i < expanded_length; ++i) {
new (&list.selector_array_[list_index])
CSSSelector(expanded.selector_array_[i]);
++list_index;
}
DCHECK(list.selector_array_[list_index - 1].IsLastInOriginalList());
DCHECK(list.selector_array_[list_index - 1].IsLastInSelectorList());
list.selector_array_[list_index - 1].SetLastInSelectorList(false);
for (unsigned i = 0; i < original_length; ++i) {
new (&list.selector_array_[list_index])
CSSSelector(original.selector_array_[i]);
++list_index;
}
DCHECK(list.selector_array_[list_index - 1].IsLastInOriginalList());
DCHECK(list.selector_array_[list_index - 1].IsLastInSelectorList());
return list;
}
Vector<const CSSSelector*> SelectorBoundaries(const CSSSelectorList& list) {
Vector<const CSSSelector*> result;
for (const CSSSelector* s = list.First(); s; s = list.Next(*s)) {
result.push_back(s);
}
result.push_back(list.First() + list.ComputeLength());
return result;
}
void AddToList(CSSSelector*& destination,
const CSSSelector* begin,
const CSSSelector* end) {
for (const CSSSelector* current = begin; current != end; ++current) {
new (destination) CSSSelector(*current);
destination->SetLastInSelectorList(false);
destination->SetLastInOriginalList(false);
destination++;
}
}
void AddToList(CSSSelector*& destination,
const CSSSelector* begin,
const CSSSelector* end,
const CSSSelector* selector_to_expand) {
for (const CSSSelector* current = begin; current != end; ++current) {
new (destination) CSSSelector(*current);
DCHECK_EQ(current + 1 == end, current->IsLastInTagHistory());
if (current->IsLastInTagHistory()) {
destination->SetRelation(selector_to_expand->Relation());
if (!selector_to_expand->IsLastInTagHistory())
destination->SetLastInTagHistory(false);
}
if (selector_to_expand->GetPseudoType() == CSSSelector::kPseudoWhere ||
selector_to_expand->IgnoreSpecificity())
destination->SetIgnoreSpecificity(true);
destination->SetLastInSelectorList(false);
destination->SetLastInOriginalList(false);
destination++;
}
}
CSSSelectorList CSSSelectorList::ExpandedFirstPseudoClass() const {
DCHECK(this->RequiresExpansion());
unsigned original_length = this->ComputeLength();
Vector<const CSSSelector*> selector_boundaries = SelectorBoundaries(*this);
size_t begin = 0;
CSSSelectorList transformed = this->Copy();
while (!selector_boundaries[begin]->HasPseudoIs() &&
!selector_boundaries[begin]->HasPseudoWhere())
++begin;
const CSSSelector* selector_to_expand_begin = selector_boundaries[begin];
const CSSSelector* selector_to_expand_end = selector_boundaries[begin + 1];
unsigned selector_to_expand_length =
static_cast<unsigned>(selector_to_expand_end - selector_to_expand_begin);
const CSSSelector* simple_selector = selector_to_expand_begin;
while (simple_selector->GetPseudoType() != CSSSelector::kPseudoIs &&
simple_selector->GetPseudoType() != CSSSelector::kPseudoWhere) {
simple_selector = simple_selector->TagHistory();
}
unsigned inner_selector_length =
simple_selector->SelectorList()->ComputeLength();
Vector<const CSSSelector*> selector_arg_boundaries =
SelectorBoundaries(*simple_selector->SelectorList());
wtf_size_t num_args =
SafeCast<wtf_size_t>(selector_arg_boundaries.size()) - 1;
unsigned other_selectors_length = original_length - selector_to_expand_length;
wtf_size_t expanded_selector_list_length =
(selector_to_expand_length - 1) * num_args + inner_selector_length +
other_selectors_length;
// Do not perform expansion if the selector list size is too large to create
// RuleData
if (expanded_selector_list_length > 8192)
return CSSSelectorList();
CSSSelectorList list;
list.selector_array_ =
reinterpret_cast<CSSSelector*>(WTF::Partitions::FastMalloc(
WTF::Partitions::ComputeAllocationSize(expanded_selector_list_length,
sizeof(CSSSelector)),
kCSSSelectorTypeName));
CSSSelector* destination = list.selector_array_;
AddToList(destination, selector_boundaries[0], selector_to_expand_begin);
for (wtf_size_t i = 0; i < num_args; ++i) {
AddToList(destination, selector_to_expand_begin, simple_selector);
AddToList(destination, selector_arg_boundaries[i],
selector_arg_boundaries[i + 1], simple_selector);
AddToList(destination, simple_selector + 1, selector_to_expand_end);
}
AddToList(destination, selector_to_expand_end, selector_boundaries.back());
DCHECK(destination == list.selector_array_ + expanded_selector_list_length);
list.selector_array_[expanded_selector_list_length - 1].SetLastInOriginalList(
true);
list.selector_array_[expanded_selector_list_length - 1].SetLastInSelectorList(
true);
return list;
}
CSSSelectorList CSSSelectorList::TransformForListExpansion() {
DCHECK_GT(this->ComputeLength(), 0u);
DCHECK(
this->selector_array_[this->ComputeLength() - 1].IsLastInOriginalList());
DCHECK(this->RequiresExpansion());
// Append the expanded form of matches to the original selector list
CSSSelectorList transformed = this->Copy();
do {
transformed = transformed.ExpandedFirstPseudoClass();
} while (transformed.RequiresExpansion());
if (transformed.ComputeLength() == 0)
return CSSSelectorList();
return CSSSelectorList::ConcatenateListExpansion(transformed, *this);
}
bool CSSSelectorList::HasPseudoIs() const {
for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s->HasPseudoIs())
return true;
}
return false;
}
bool CSSSelectorList::HasPseudoWhere() const {
for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s->HasPseudoWhere())
return true;
}
return false;
}
bool CSSSelectorList::RequiresExpansion() const {
for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s->HasPseudoIs() || s->HasPseudoWhere())
return true;
}
return false;
}
CSSSelectorList CSSSelectorList::AdoptSelectorVector( CSSSelectorList CSSSelectorList::AdoptSelectorVector(
Vector<std::unique_ptr<CSSParserSelector>>& selector_vector) { Vector<std::unique_ptr<CSSParserSelector>>& selector_vector) {
size_t flattened_size = 0; size_t flattened_size = 0;
......
...@@ -71,16 +71,6 @@ class CORE_EXPORT CSSSelectorList { ...@@ -71,16 +71,6 @@ class CORE_EXPORT CSSSelectorList {
o.selector_array_ = nullptr; o.selector_array_ = nullptr;
} }
static CSSSelectorList ConcatenateListExpansion(
const CSSSelectorList& expanded,
const CSSSelectorList& original);
CSSSelectorList ExpandedFirstPseudoClass() const;
CSSSelectorList TransformForListExpansion();
bool HasPseudoIs() const;
bool HasPseudoWhere() const;
bool RequiresExpansion() const;
CSSSelectorList& operator=(CSSSelectorList&& o) { CSSSelectorList& operator=(CSSSelectorList&& o) {
DCHECK(this != &o); DCHECK(this != &o);
DeleteSelectorsIfNeeded(); DeleteSelectorsIfNeeded();
......
...@@ -3,12 +3,29 @@ ...@@ -3,12 +3,29 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "third_party/blink/renderer/core/css/css_test_helpers.h" #include "third_party/blink/renderer/core/css/css_test_helpers.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/rule_set.h" #include "third_party/blink/renderer/core/css/rule_set.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include <iostream>
namespace blink { namespace blink {
namespace {
unsigned Specificity(const String& selector_text) {
CSSSelectorList selector_list = CSSParser::ParseSelector(
StrictCSSParserContext(SecureContextMode::kInsecureContext), nullptr,
selector_text);
const CSSSelector* selector = selector_list.First();
return selector->Specificity();
}
} // namespace
TEST(CSSSelector, Representations) { TEST(CSSSelector, Representations) {
css_test_helpers::TestStyleSheet sheet; css_test_helpers::TestStyleSheet sheet;
...@@ -76,4 +93,31 @@ TEST(CSSSelector, OverflowRareDataMatchNth) { ...@@ -76,4 +93,31 @@ TEST(CSSSelector, OverflowRareDataMatchNth) {
EXPECT_FALSE(selector.MatchNth(2)); EXPECT_FALSE(selector.MatchNth(2));
} }
TEST(CSSSelector, Specificity_Is) {
EXPECT_EQ(Specificity(".a :is(.b, div.c)"), Specificity(".a div.c"));
EXPECT_EQ(Specificity(".a :is(.c#d, .e)"), Specificity(".a .c#d"));
EXPECT_EQ(Specificity(":is(.e+.f, .g>.b, .h)"), Specificity(".e+.f"));
EXPECT_EQ(Specificity(".a :is(.e+.f, .g>.b, .h#i)"), Specificity(".a .h#i"));
EXPECT_EQ(Specificity(".a+:is(.b+span.f, :is(.c>.e, .g))"),
Specificity(".a+.b+span.f"));
EXPECT_EQ(Specificity("div > :is(div:where(span:where(.b ~ .c)))"),
Specificity("div > div"));
EXPECT_EQ(Specificity(":is(.c + .c + .c, .b + .c:not(span), .b + .c + .e)"),
Specificity(".c + .c + .c"));
}
TEST(CSSSelector, Specificity_Where) {
EXPECT_EQ(Specificity(".a :where(.b, div.c)"), Specificity(".a"));
EXPECT_EQ(Specificity(".a :where(.c#d, .e)"), Specificity(".a"));
EXPECT_EQ(Specificity(":where(.e+.f, .g>.b, .h)"), Specificity("*"));
EXPECT_EQ(Specificity(".a :where(.e+.f, .g>.b, .h#i)"), Specificity(".a"));
EXPECT_EQ(Specificity("div > :where(.b+span.f, :where(.c>.e, .g))"),
Specificity("div"));
EXPECT_EQ(Specificity("div > :where(div:is(span:is(.b ~ .c)))"),
Specificity("div"));
EXPECT_EQ(
Specificity(":where(.c + .c + .c, .b + .c:not(span), .b + .c + .e)"),
Specificity("*"));
}
} // namespace blink } // namespace blink
...@@ -30,9 +30,6 @@ CSSSelectorList CSSSelectorParser::ParseSelector( ...@@ -30,9 +30,6 @@ CSSSelectorList CSSSelectorParser::ParseSelector(
return CSSSelectorList(); return CSSSelectorList();
parser.RecordUsageAndDeprecations(result); parser.RecordUsageAndDeprecations(result);
if (result.RequiresExpansion())
return result.TransformForListExpansion();
return result; return result;
} }
...@@ -46,9 +43,6 @@ CSSSelectorList CSSSelectorParser::ConsumeSelector( ...@@ -46,9 +43,6 @@ CSSSelectorList CSSSelectorParser::ConsumeSelector(
stream.ConsumeWhitespace(); stream.ConsumeWhitespace();
CSSSelectorList result = parser.ConsumeComplexSelectorList(stream, observer); CSSSelectorList result = parser.ConsumeComplexSelectorList(stream, observer);
parser.RecordUsageAndDeprecations(result); parser.RecordUsageAndDeprecations(result);
if (result.RequiresExpansion())
return result.TransformForListExpansion();
return result; return result;
} }
...@@ -618,8 +612,7 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo( ...@@ -618,8 +612,7 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
std::unique_ptr<CSSSelectorList> selector_list = std::unique_ptr<CSSSelectorList> selector_list =
std::make_unique<CSSSelectorList>(); std::make_unique<CSSSelectorList>();
*selector_list = ConsumeCompoundSelectorList(block); *selector_list = ConsumeCompoundSelectorList(block);
if (!selector_list->IsValid() || !block.AtEnd() || if (!selector_list->IsValid() || !block.AtEnd())
selector_list->HasPseudoIs() || selector_list->HasPseudoWhere())
return nullptr; return nullptr;
selector->SetSelectorList(std::move(selector_list)); selector->SetSelectorList(std::move(selector_list));
return selector; return selector;
......
...@@ -386,40 +386,6 @@ TEST(CSSSelectorParserTest, InternalPseudo) { ...@@ -386,40 +386,6 @@ TEST(CSSSelectorParserTest, InternalPseudo) {
} }
} }
TEST(CSSSelectorParserTest, InvalidNestingPseudoIs) {
// :is() is currently not supported within these pseudo classes as they
// currently do not support complex selector arguments (:is() does
// support this and the expansion of :is() may provide complex selector
// arguments to these pseudo classes). Most of these test cases should
// eventually be removed once they support complex selector arguments.
const char* test_cases[] = {":-webkit-any(:is(.a))",
"::cue(:is(.a))",
":cue(:is(.a))",
":host(:is(.a))",
":host-context(:is(.a))",
":lang(:is(.a))",
":not(:is(.a))",
":nth-child(:is(.a))",
":nth-last-child(:is(.a))",
":nth-last-of-type(:is(.a))",
":nth-of-type(:is(.a))",
"::slotted(:is(.a))"};
auto* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
auto* sheet = MakeGarbageCollected<StyleSheetContents>(context);
for (auto* test_case : test_cases) {
SCOPED_TRACE(test_case);
CSSTokenizer tokenizer(test_case);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorList list =
CSSSelectorParser::ParseSelector(range, context, sheet);
EXPECT_FALSE(list.IsValid());
}
}
TEST(CSSSelectorParserTest, InvalidPseudoIsArguments) { TEST(CSSSelectorParserTest, InvalidPseudoIsArguments) {
// Pseudo-elements are not valid within :is() as per the spec: // Pseudo-elements are not valid within :is() as per the spec:
// https://drafts.csswg.org/selectors-4/#matches // https://drafts.csswg.org/selectors-4/#matches
...@@ -460,40 +426,6 @@ TEST(CSSSelectorParserTest, InvalidPseudoIsArguments) { ...@@ -460,40 +426,6 @@ TEST(CSSSelectorParserTest, InvalidPseudoIsArguments) {
} }
} }
TEST(CSSSelectorParserTest, InvalidNestingPseudoWhere) {
// :where() is currently not supported within these pseudo classes as they
// currently do not support complex selector arguments (:where() does support
// this and the expansion of :where() may provide complex selector arguments
// to these pseudo classes). Most of these test cases should eventually be
// removed once they support complex selector arguments.
const char* test_cases[] = {":-webkit-any(:where(.a))",
"::cue(:where(.a))",
":cue(:where(.a))",
":host(:where(.a))",
":host-context(:where(.a))",
":lang(:where(.a))",
":not(:where(.a))",
":nth-child(:where(.a))",
":nth-last-child(:where(.a))",
":nth-last-of-type(:where(.a))",
":nth-of-type(:where(.a))",
"::slotted(:where(.a))"};
auto* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
auto* sheet = MakeGarbageCollected<StyleSheetContents>(context);
for (const char* test_case : test_cases) {
SCOPED_TRACE(test_case);
CSSTokenizer tokenizer(test_case);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorList list =
CSSSelectorParser::ParseSelector(range, context, sheet);
EXPECT_FALSE(list.IsValid());
}
}
namespace { namespace {
const auto TagLocalName = [](const CSSSelector* selector) { const auto TagLocalName = [](const CSSSelector* selector) {
......
...@@ -176,6 +176,9 @@ bool SupportsInvalidation(CSSSelector::PseudoType type) { ...@@ -176,6 +176,9 @@ 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;
case CSSSelector::kPseudoUnknown: case CSSSelector::kPseudoUnknown:
case CSSSelector::kPseudoLeftPage: case CSSSelector::kPseudoLeftPage:
case CSSSelector::kPseudoRightPage: case CSSSelector::kPseudoRightPage:
...@@ -196,8 +199,10 @@ bool SupportsInvalidationWithSelectorList(CSSSelector::PseudoType pseudo) { ...@@ -196,8 +199,10 @@ bool SupportsInvalidationWithSelectorList(CSSSelector::PseudoType pseudo) {
pseudo == CSSSelector::kPseudoCue || pseudo == CSSSelector::kPseudoCue ||
pseudo == CSSSelector::kPseudoHost || pseudo == CSSSelector::kPseudoHost ||
pseudo == CSSSelector::kPseudoHostContext || pseudo == CSSSelector::kPseudoHostContext ||
pseudo == CSSSelector::kPseudoIs ||
pseudo == CSSSelector::kPseudoNot || pseudo == CSSSelector::kPseudoNot ||
pseudo == CSSSelector::kPseudoSlotted; pseudo == CSSSelector::kPseudoSlotted ||
pseudo == CSSSelector::kPseudoWhere;
} }
bool RequiresSubtreeInvalidation(const CSSSelector& selector) { bool RequiresSubtreeInvalidation(const CSSSelector& selector) {
......
...@@ -1256,23 +1256,6 @@ TEST_F(RuleFeatureSetTest, pseudoIsNested) { ...@@ -1256,23 +1256,6 @@ TEST_F(RuleFeatureSetTest, pseudoIsNested) {
ExpectNoInvalidation(invalidation_lists.siblings); ExpectNoInvalidation(invalidation_lists.siblings);
} }
TEST_F(RuleFeatureSetTest, pseudoIsTooLarge) {
// RuleData cannot support selectors at index 8192 or beyond so the expansion
// is limited to this size
EXPECT_EQ(RuleFeatureSet::kSelectorNeverMatches,
CollectFeatures(":is(.a#a, .b#b, .c#c, .d#d) + "
":is(.e#e, .f#f, .g#g, .h#h) + "
":is(.i#i, .j#j, .k#k, .l#l) + "
":is(.m#m, .n#n, .o#o, .p#p) + "
":is(.q#q, .r#r, .s#s, .t#t) + "
":is(.u#u, .v#v, .w#w, .x#x)"));
InvalidationLists invalidation_lists;
CollectInvalidationSetsForClass(invalidation_lists, "a");
ExpectNoInvalidation(invalidation_lists.descendants);
ExpectNoInvalidation(invalidation_lists.siblings);
}
TEST_F(RuleFeatureSetTest, pseudoWhere) { TEST_F(RuleFeatureSetTest, pseudoWhere) {
EXPECT_EQ(RuleFeatureSet::kSelectorMayMatch, EXPECT_EQ(RuleFeatureSet::kSelectorMayMatch,
CollectFeatures(":where(.w, .x)")); CollectFeatures(":where(.w, .x)"));
...@@ -1372,23 +1355,6 @@ TEST_F(RuleFeatureSetTest, pseudoWhereNested) { ...@@ -1372,23 +1355,6 @@ TEST_F(RuleFeatureSetTest, pseudoWhereNested) {
ExpectNoInvalidation(invalidation_lists.siblings); ExpectNoInvalidation(invalidation_lists.siblings);
} }
TEST_F(RuleFeatureSetTest, pseudoWhereTooLarge) {
// RuleData cannot support selectors at index 8192 or beyond so the expansion
// is limited to this size
EXPECT_EQ(RuleFeatureSet::kSelectorNeverMatches,
CollectFeatures(":where(.a#a, .b#b, .c#c, .d#d) + "
":where(.e#e, .f#f, .g#g, .h#h) + "
":where(.i#i, .j#j, .k#k, .l#l) + "
":where(.m#m, .n#n, .o#o, .p#p) + "
":where(.q#q, .r#r, .s#s, .t#t) + "
":where(.u#u, .v#v, .w#w, .x#x)"));
InvalidationLists invalidation_lists;
CollectInvalidationSetsForClass(invalidation_lists, "a");
ExpectNoInvalidation(invalidation_lists.descendants);
ExpectNoInvalidation(invalidation_lists.siblings);
}
TEST_F(RuleFeatureSetTest, invalidatesParts) { TEST_F(RuleFeatureSetTest, invalidatesParts) {
EXPECT_EQ(RuleFeatureSet::kSelectorMayMatch, EXPECT_EQ(RuleFeatureSet::kSelectorMayMatch,
CollectFeatures(".a .b::part(partname)")); CollectFeatures(".a .b::part(partname)"));
......
...@@ -257,56 +257,6 @@ TEST(RuleSetTest, findBestRuleSetAndAdd_PlaceholderPseudo) { ...@@ -257,56 +257,6 @@ TEST(RuleSetTest, findBestRuleSetAndAdd_PlaceholderPseudo) {
ASSERT_EQ(2u, rules->size()); ASSERT_EQ(2u, rules->size());
} }
TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoIs) {
css_test_helpers::TestStyleSheet sheet;
sheet.AddCSSRules(".a :is(.b+.c, .d>:is(.e, .f)) { }");
RuleSet& rule_set = sheet.GetRuleSet();
{
AtomicString str("c");
const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0)->Selector().Value());
}
{
AtomicString str("e");
const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0)->Selector().Value());
}
{
AtomicString str("f");
const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0)->Selector().Value());
}
}
TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoWhere) {
css_test_helpers::TestStyleSheet sheet;
sheet.AddCSSRules(".a :where(.b+.c, .d>:where(.e, .f)) { }");
RuleSet& rule_set = sheet.GetRuleSet();
{
AtomicString str("c");
const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0)->Selector().Value());
}
{
AtomicString str("e");
const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0)->Selector().Value());
}
{
AtomicString str("f");
const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0)->Selector().Value());
}
}
TEST(RuleSetTest, findBestRuleSetAndAdd_PartPseudoElements) { TEST(RuleSetTest, findBestRuleSetAndAdd_PartPseudoElements) {
css_test_helpers::TestStyleSheet sheet; css_test_helpers::TestStyleSheet sheet;
...@@ -316,39 +266,6 @@ TEST(RuleSetTest, findBestRuleSetAndAdd_PartPseudoElements) { ...@@ -316,39 +266,6 @@ TEST(RuleSetTest, findBestRuleSetAndAdd_PartPseudoElements) {
ASSERT_EQ(2u, rules->size()); ASSERT_EQ(2u, rules->size());
} }
TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoIsTooLarge) {
// RuleData cannot support selectors at index 8192 or beyond so the expansion
// is limited to this size
css_test_helpers::TestStyleSheet sheet;
sheet.AddCSSRules(
":is(.a#a, .b#b, .c#c, .d#d) + "
":is(.e#e, .f#f, .g#g, .h#h) + "
":is(.i#i, .j#j, .k#k, .l#l) + "
":is(.m#m, .n#n, .o#o, .p#p) + "
":is(.q#q, .r#r, .s#s, .t#t) + "
":is(.u#u, .v#v, .w#w, .x#x) { }",
true);
RuleSet& rule_set = sheet.GetRuleSet();
ASSERT_EQ(0u, rule_set.RuleCount());
}
TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoWhereTooLarge) {
// RuleData cannot support selectors at index 8192 or beyond so the expansion
// is limited to this size
css_test_helpers::TestStyleSheet sheet;
sheet.AddCSSRules(
":where(.a#a, .b#b, .c#c, .d#d) + :where(.e#e, .f#f, .g#g, .h#h) + "
":where(.i#i, .j#j, .k#k, .l#l) + :where(.m#m, .n#n, .o#o, .p#p) + "
":where(.q#q, .r#r, .s#s, .t#t) + :where(.u#u, .v#v, .w#w, .x#x) { }",
true);
RuleSet& rule_set = sheet.GetRuleSet();
ASSERT_EQ(0u, rule_set.RuleCount());
}
TEST(RuleSetTest, SelectorIndexLimit) { TEST(RuleSetTest, SelectorIndexLimit) {
// It's not feasible to run this test for a large number of bits. If the // It's not feasible to run this test for a large number of bits. If the
// number of bits have increased to a large number, consider removing this // number of bits have increased to a large number, consider removing this
......
...@@ -1096,10 +1096,13 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context, ...@@ -1096,10 +1096,13 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context,
} }
case CSSSelector::kPseudoTarget: case CSSSelector::kPseudoTarget:
return element == element.GetDocument().CssTarget(); return element == element.GetDocument().CssTarget();
case CSSSelector::kPseudoIs:
case CSSSelector::kPseudoWhere:
case CSSSelector::kPseudoAny: { case CSSSelector::kPseudoAny: {
SelectorCheckingContext sub_context(context); SelectorCheckingContext sub_context(context);
sub_context.is_sub_selector = true; sub_context.is_sub_selector = true;
DCHECK(selector.SelectorList()); if (!selector.SelectorList())
break;
for (sub_context.selector = selector.SelectorList()->First(); for (sub_context.selector = selector.SelectorList()->First();
sub_context.selector; sub_context.selector = CSSSelectorList::Next( sub_context.selector; sub_context.selector = CSSSelectorList::Next(
*sub_context.selector)) { *sub_context.selector)) {
...@@ -1342,8 +1345,6 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context, ...@@ -1342,8 +1345,6 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context,
case CSSSelector::kPseudoCornerPresent: case CSSSelector::kPseudoCornerPresent:
return false; return false;
case CSSSelector::kPseudoUnknown: case CSSSelector::kPseudoUnknown:
case CSSSelector::kPseudoIs:
case CSSSelector::kPseudoWhere:
default: default:
NOTREACHED(); NOTREACHED();
break; break;
......
...@@ -1338,6 +1338,9 @@ crbug.com/1058822 external/wpt/css/css-color-adjust/rendering/dark-color-scheme/ ...@@ -1338,6 +1338,9 @@ crbug.com/1058822 external/wpt/css/css-color-adjust/rendering/dark-color-scheme/
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 ======
......
...@@ -2,8 +2,8 @@ This is a testharness.js-based test. ...@@ -2,8 +2,8 @@ This is a testharness.js-based test.
PASS Multiple selectors with combinators PASS Multiple selectors with combinators
PASS Nested :is PASS Nested :is
PASS Nested :where PASS Nested :where
FAIL Nested inside :host, without combinators assert_equals: Nested inside :host, without combinators: :host(:is(div)) expected ":host(:is(div))" but got "random-selector" PASS Nested inside :host, without combinators
PASS Nested inside :host, with combinators FAIL Nested inside :host, with combinators assert_not_equals: Nested inside :host, with combinators: :host(:is(div .foo)) got disallowed value ":host(:is(div .foo))"
PASS Pseudo-classes inside PASS Pseudo-classes inside
PASS Pseudo-classes after PASS Pseudo-classes after
PASS Pseudo-elements after PASS Pseudo-elements after
......
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Selectors Level 4: query using :is()</title>
<link rel="help" href="https://drafts.csswg.org/selectors/#matches">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/css/support/query-testcommon.js"></script>
</head>
<body>
<div id="a1" class="a">
<div class="b" id="b1"></div>
<div class="c" id="c1"></div>
<div class="c" id="d"></div>
<div class="e" id="e1"></div>
<div class="f" id="f1"></div>
<div class="g">
<div class="b" id="b2">
<div class="b" id="b3"></div>
</div>
</div>
<div class="h" id="h1"></div>
</div>
<div class="c" id="c2">
<div id="a2" class="a"></div>
<div class="e" id="e2"></div>
</div>
<script>
'use strict';
// Simple selector arguments are supported by :is
test_query_selector(document, '.a :is(.b, .c)',
['b1', 'c1', 'd', 'b2', 'b3']);
// Compound selector arguments are supported by :is
test_query_selector(document, '.a :is(.c#d, .e)',
['d', 'e1']);
// Complex selector arguments are supported by :is
test_query_selector(document, '.a :is(.e+.f, .g>.b, .h)',
['f1', 'b2', 'h1']);
// Nested selector arguments are supported by :is
test_query_selector(document, '.a+:is(.b+.f, :is(.c>.e, .g))',
'e2');
// Nested :where selector arguments are supported by :is
test_query_selector(document, '.a :is(:where(:where(.b ~ .c)))',
['c1', 'd']);
// Nested :not selector arguments are supported by :is
test_query_selector(document, '.b + :is(.c + .c + .c, .b + .c:not(span), .b + .c + .e) ~ .h',
['h1']);
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Selectors Level 4: query using :where()</title>
<link rel="help" href="https://drafts.csswg.org/selectors/#zero-matches">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/css/support/query-testcommon.js"></script>
</head>
<body>
<div id="a1" class="a">
<div class="b" id="b1"></div>
<div class="c" id="c1"></div>
<div class="c" id="d"></div>
<div class="e" id="e1"></div>
<div class="f" id="f1"></div>
<div class="g">
<div class="b" id="b2">
<div class="b" id="b3"></div>
</div>
</div>
<div class="h" id="h1"></div>
</div>
<div class="c" id="c2">
<div id="a2" class="a"></div>
<div class="e" id="e2"></div>
</div>
<script>
'use strict';
// Simple selector arguments are supported by :where
test_query_selector(document, '.a :where(.b, .c)',
['b1', 'c1', 'd', 'b2', 'b3']);
// Compound selector arguments are supported by :where
test_query_selector(document, '.a :where(.c#d, .e)',
['d', 'e1']);
// Complex selector arguments are supported by :where
test_query_selector(document, '.a :where(.e+.f, .g>.b, .h)',
['f1', 'b2', 'h1']);
// Nested selector arguments are supported by :where
test_query_selector(document, '.a+:where(.b+.f, :where(.c>.e, .g))',
['e2']);
// Nested :is selector arguments are supported by :where
test_query_selector(document, '.a :where(:is(:is(.b ~ .c)))',
['c1', 'd']);
// Nested :not selector arguments are supported by :where
test_query_selector(document, '.b + :where(.c + .c + .c, .b + .c:not(span), .b + .c + .e) ~ .h',
['h1']);
</script>
</body>
</html>
'use strict';
function test_query_selector(parentNode, selector, expected) {
if (!Array.isArray(expected))
expected = [ expected ];
test(function(){
const elementList = parentNode.querySelectorAll(selector);
assert_equals(elementList.length, expected.length);
for (let i = 0; i < elementList.length; ++i) {
if (typeof expected[i] === 'string')
assert_equals(elementList[i].id, expected[i]);
else
assert_equals(elementList[i], expected[i]);
}
}, "Selector '" + selector + '" should find the expected elements');
}
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