Commit 85ad70df authored by Victoria Su's avatar Victoria Su Committed by Commit Bot

Implemented CSS Selectors 4 :matches()

Design doc: https://docs.google.com/document/d/1dfbT1mnIuwm9HTyUWDe5PS5FygZgMuQGsyw49oxBcXk/edit?usp=sharing

Intent to implement: https://groups.google.com/a/chromium.org/d/msg/blink-dev/kqD_G4sxfZE/6CJM01X2BwAJ

Bug: 568705
Change-Id: I57e619e462f8323496aad477c50c45f9537003c4
Reviewed-on: https://chromium-review.googlesource.com/879982Reviewed-by: default avatarnainar <nainar@chromium.org>
Reviewed-by: default avatarEric Willigers <ericwilligers@chromium.org>
Commit-Queue: Victoria Su <victoriaytsu@google.com>
Cr-Commit-Position: refs/heads/master@{#533969}
parent e1f1ffbf
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>CSS Selectors Invalidation: :any-link</title> <title>CSS Selectors Invalidation: :matches()</title>
<link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com"> <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com">
<link rel="help" href="https://drafts.csswg.org/selectors-4/#matches"> <link rel="help" href="https://drafts.csswg.org/selectors-4/#matches">
<meta name="assert" content="This tests that the :matches() selector is effective"> <meta name="assert" content="This tests that the :matches() selector is effective">
...@@ -9,15 +9,38 @@ ...@@ -9,15 +9,38 @@
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<style> <style>
.b { .b {
color: rgb(255, 255, 0); /* yellow */ color: yellow;
} }
/*Simple selector arguments */ /*Simple selector arguments */
.a :matches(.b, .c) { .a :matches(.b, .c) {
color: rgb(255, 0, 0); /* red */ color: red;
} }
/*Compound selector arguments */ /*Compound selector arguments */
.a :matches(.c#d, .e) { .a :matches(.c#d, .e) {
color: rgb(0, 255, 0); /* green */ color: green;
}
/* Complex selector arguments */
.a .g>.b {
color: black;
}
.a :matches(.e+.f, .g>.b, .h) {
color: blue;
}
.g>.b {
color: black;
}
.a .h {
color: black;
}
/* Nested */
.a+.c>.e {
color: black;
}
.a+:matches(.b+.f, :matches(.c>.e, .g)) {
color: red;
}
.c>.e {
color: black;
} }
</style> </style>
</head> </head>
...@@ -38,29 +61,73 @@ ...@@ -38,29 +61,73 @@
<div class="f" id="f1"> <div class="f" id="f1">
Blue Blue
</div> </div>
<div class="g">
<div class="b" id="b2">
Blue
<div class="b" id="b3">
Red
</div>
</div>
</div>
<div class="h" id="h1">
Black
</div>
</div>
<div class="c" id="c2">
<div class="e" id="e2">
Red
</div>
</div> </div>
<script> <script>
document.body.offsetTop; document.body.offsetTop;
var black = "rgb(0, 0, 0)";
var blue = "rgb(0, 0, 255)";
var green = "rgb(0, 128, 0)";
var red = "rgb(255, 0, 0)";
var yellow = "rgb(255, 255, 0)";
test(() => { test(() => {
assert_equals(getComputedStyle(b1).color, "rgb(255, 255, 0)"); assert_equals(getComputedStyle(b1).color, yellow);
assert_equals(getComputedStyle(c1).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(b2).color, black);
assert_equals(getComputedStyle(d).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(b3).color, yellow);
assert_equals(getComputedStyle(e1).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(c1).color, black);
assert_equals(getComputedStyle(f1).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(d).color, black);
assert_equals(getComputedStyle(e1).color, black);
assert_equals(getComputedStyle(e2).color, black);
assert_equals(getComputedStyle(f1).color, black);
assert_equals(getComputedStyle(h1).color, black);
}, "Preconditions."); }, "Preconditions.");
test(() => { test(() => {
a1.className = "a"; a1.className = "a";
assert_equals(getComputedStyle(b1).color, "rgb(255, 0, 0)"); assert_equals(getComputedStyle(b1).color, red);
assert_equals(getComputedStyle(c1).color, "rgb(255, 0, 0)"); assert_equals(getComputedStyle(b3).color, red);
assert_equals(getComputedStyle(c1).color, red);
}, "Invalidate :matches() for simple selector arguments."); }, "Invalidate :matches() for simple selector arguments.");
test(() => { test(() => {
a1.className = "a"; a1.className = "a";
assert_equals(getComputedStyle(d).color, "rgb(0, 255, 0)"); assert_equals(getComputedStyle(d).color, green);
}, "Invalidate :matches() for compound selector arguments."); }, "Invalidate :matches() for compound selector arguments.");
test(() => {
a1.className = "a";
assert_equals(getComputedStyle(b2).color, blue);
assert_equals(getComputedStyle(b3).color, red);
assert_equals(getComputedStyle(f1).color, blue);
}, "Invalidate :matches() for complex selector arguments.");
test(() => {
a1.className = "a";
assert_equals(getComputedStyle(e2).color, red);
}, "Invalidate nested :matches().");
test(() => {
a1.className = "a";
assert_equals(getComputedStyle(b2).color, blue);
assert_equals(getComputedStyle(h1).color, black);
}, "Test specificity of :matches().");
</script> </script>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -788,7 +788,7 @@ const CSSSelector* CSSSelector::SerializeCompound( ...@@ -788,7 +788,7 @@ const CSSSelector* CSSSelector::SerializeCompound(
if (simple_selector->SelectorList()) { if (simple_selector->SelectorList()) {
builder.Append('('); builder.Append('(');
const CSSSelector* first_sub_selector = const CSSSelector* first_sub_selector =
simple_selector->SelectorList()->First(); simple_selector->SelectorList()->FirstForCSSOM();
for (const CSSSelector* sub_selector = first_sub_selector; sub_selector; for (const CSSSelector* sub_selector = first_sub_selector; sub_selector;
sub_selector = CSSSelectorList::Next(*sub_selector)) { sub_selector = CSSSelectorList::Next(*sub_selector)) {
if (sub_selector != first_sub_selector) if (sub_selector != first_sub_selector)
...@@ -1056,6 +1056,14 @@ bool CSSSelector::NeedsUpdatedDistribution() const { ...@@ -1056,6 +1056,14 @@ bool CSSSelector::NeedsUpdatedDistribution() const {
*this); *this);
} }
bool CSSSelector::HasPseudoMatches() const {
for (const CSSSelector* s = this; s; s = s->TagHistory()) {
if (s->GetPseudoType() == CSSSelector::kPseudoMatches)
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),
......
...@@ -333,9 +333,17 @@ class CORE_EXPORT CSSSelector { ...@@ -333,9 +333,17 @@ class CORE_EXPORT CSSSelector {
} }
bool IsLastInSelectorList() const { return is_last_in_selector_list_; } bool IsLastInSelectorList() const { return is_last_in_selector_list_; }
void SetLastInSelectorList() { is_last_in_selector_list_ = true; } void SetLastInSelectorList(bool is_last) {
is_last_in_selector_list_ = is_last;
}
bool IsLastInOriginalList() const { return is_last_in_original_list_; }
void SetLastInOriginalList(bool is_last) {
is_last_in_original_list_ = is_last;
}
bool IsLastInTagHistory() const { return is_last_in_tag_history_; } bool IsLastInTagHistory() const { return is_last_in_tag_history_; }
void SetNotLastInTagHistory() { is_last_in_tag_history_ = false; } void SetLastInTagHistory(bool is_last) { is_last_in_tag_history_ = is_last; }
// http://dev.w3.org/csswg/selectors4/#compound // http://dev.w3.org/csswg/selectors4/#compound
bool IsCompound() const; bool IsCompound() const;
...@@ -363,6 +371,7 @@ class CORE_EXPORT CSSSelector { ...@@ -363,6 +371,7 @@ class CORE_EXPORT CSSSelector {
bool HasSlottedPseudo() const; bool HasSlottedPseudo() const;
bool HasDeepCombinatorOrShadowPseudo() const; bool HasDeepCombinatorOrShadowPseudo() const;
bool NeedsUpdatedDistribution() const; bool NeedsUpdatedDistribution() const;
bool HasPseudoMatches() const;
private: private:
unsigned relation_ : 4; // enum RelationType unsigned relation_ : 4; // enum RelationType
...@@ -374,6 +383,7 @@ class CORE_EXPORT CSSSelector { ...@@ -374,6 +383,7 @@ class CORE_EXPORT CSSSelector {
unsigned is_for_page_ : 1; unsigned is_for_page_ : 1;
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;
void SetPseudoType(PseudoType pseudo_type) { void SetPseudoType(PseudoType pseudo_type) {
pseudo_type_ = pseudo_type; pseudo_type_ = pseudo_type;
...@@ -474,7 +484,8 @@ inline CSSSelector::CSSSelector() ...@@ -474,7 +484,8 @@ inline CSSSelector::CSSSelector()
has_rare_data_(false), has_rare_data_(false),
is_for_page_(false), is_for_page_(false),
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) {}
inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name, inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name,
bool tag_is_implicit) bool tag_is_implicit)
...@@ -486,7 +497,8 @@ inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name, ...@@ -486,7 +497,8 @@ inline CSSSelector::CSSSelector(const QualifiedName& tag_q_name,
has_rare_data_(false), has_rare_data_(false),
is_for_page_(false), is_for_page_(false),
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) {
data_.tag_q_name_ = tag_q_name.Impl(); data_.tag_q_name_ = tag_q_name.Impl();
data_.tag_q_name_->AddRef(); data_.tag_q_name_->AddRef();
} }
...@@ -501,7 +513,8 @@ inline CSSSelector::CSSSelector(const CSSSelector& o) ...@@ -501,7 +513,8 @@ inline CSSSelector::CSSSelector(const CSSSelector& o)
is_for_page_(o.is_for_page_), is_for_page_(o.is_for_page_),
tag_is_implicit_(o.tag_is_implicit_), tag_is_implicit_(o.tag_is_implicit_),
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_) {
if (o.match_ == kTag) { if (o.match_ == kTag) {
data_.tag_q_name_ = o.data_.tag_q_name_; data_.tag_q_name_ = o.data_.tag_q_name_;
data_.tag_q_name_->AddRef(); data_.tag_q_name_->AddRef();
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "core/css/CSSSelectorList.h" #include "core/css/CSSSelectorList.h"
#include <memory> #include <memory>
#include <vector>
#include "core/css/parser/CSSParserSelector.h" #include "core/css/parser/CSSParserSelector.h"
#include "platform/wtf/allocator/Partitions.h" #include "platform/wtf/allocator/Partitions.h"
#include "platform/wtf/text/StringBuilder.h" #include "platform/wtf/text/StringBuilder.h"
...@@ -55,6 +56,167 @@ CSSSelectorList CSSSelectorList::Copy() const { ...@@ -55,6 +56,167 @@ CSSSelectorList CSSSelectorList::Copy() const {
return list; return list;
} }
CSSSelectorList CSSSelectorList::ConcatenatePseudoMatchesExpansion(
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;
}
std::vector<const CSSSelector*> SelectorBoundaries(
const CSSSelectorList& list) {
std::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,
CSSSelector::RelationType relation,
bool IsLastInTagHistory) {
for (const CSSSelector* current = begin; current != end; ++current) {
new (destination) CSSSelector(*current);
DCHECK_EQ(current + 1 == end, current->IsLastInTagHistory());
if (current->IsLastInTagHistory()) {
destination->SetRelation(relation);
if (!IsLastInTagHistory)
destination->SetLastInTagHistory(false);
}
destination->SetLastInSelectorList(false);
destination->SetLastInOriginalList(false);
destination++;
}
}
CSSSelectorList CSSSelectorList::ExpandedFirstMatchesPseudo() const {
unsigned original_length = this->ComputeLength();
std::vector<const CSSSelector*> matches_boundaries =
SelectorBoundaries(*this);
size_t i = 0;
while (!matches_boundaries[i]->HasPseudoMatches()) {
++i;
}
const CSSSelector* selector_with_matches_begin = matches_boundaries[i];
const CSSSelector* selector_with_matches_end = matches_boundaries[i + 1];
size_t selector_with_matches_length =
selector_with_matches_end - selector_with_matches_begin;
const CSSSelector* simple_matches = selector_with_matches_begin;
while (simple_matches->GetPseudoType() != CSSSelector::kPseudoMatches) {
simple_matches = simple_matches->TagHistory();
}
unsigned inner_matches_length =
simple_matches->SelectorList()->ComputeLength();
std::vector<const CSSSelector*> matches_arg_boundaries =
SelectorBoundaries(*simple_matches->SelectorList());
size_t num_matches_args = matches_arg_boundaries.size() - 1;
unsigned other_selectors_length =
original_length - selector_with_matches_length;
unsigned expanded_matches_length =
(selector_with_matches_length - 1) * num_matches_args +
inner_matches_length + other_selectors_length;
// Do not perform expansion if the selector list size is too large to create
// RuleData
if (expanded_matches_length > 8192)
return CSSSelectorList();
CSSSelectorList list;
list.selector_array_ =
reinterpret_cast<CSSSelector*>(WTF::Partitions::FastMalloc(
WTF::Partitions::ComputeAllocationSize(expanded_matches_length,
sizeof(CSSSelector)),
kCSSSelectorTypeName));
CSSSelector* destination = list.selector_array_;
AddToList(destination, matches_boundaries[0], selector_with_matches_begin);
for (size_t i = 0; i < num_matches_args; ++i) {
AddToList(destination, selector_with_matches_begin, simple_matches);
AddToList(destination, matches_arg_boundaries[i],
matches_arg_boundaries[i + 1], simple_matches->Relation(),
simple_matches->IsLastInTagHistory());
AddToList(destination, simple_matches + 1, selector_with_matches_end);
}
AddToList(destination, selector_with_matches_end, matches_boundaries.back());
DCHECK(destination == list.selector_array_ + expanded_matches_length);
list.selector_array_[expanded_matches_length - 1].SetLastInOriginalList(true);
list.selector_array_[expanded_matches_length - 1].SetLastInSelectorList(true);
return list;
}
CSSSelectorList CSSSelectorList::TransformForPseudoMatches() {
DCHECK_GT(this->ComputeLength(), 0u);
DCHECK(
this->selector_array_[this->ComputeLength() - 1].IsLastInOriginalList());
DCHECK(this->HasPseudoMatches());
// Append the expanded form of matches to the original selector list
CSSSelectorList transformed = this->Copy();
do {
transformed = transformed.ExpandedFirstMatchesPseudo();
} while (transformed.HasPseudoMatches());
if (transformed.ComputeLength() == 0)
return CSSSelectorList();
return CSSSelectorList::ConcatenatePseudoMatchesExpansion(transformed, *this);
}
bool CSSSelectorList::HasPseudoMatches() const {
for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s->HasPseudoMatches())
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;
...@@ -84,18 +246,30 @@ CSSSelectorList CSSSelectorList::AdoptSelectorVector( ...@@ -84,18 +246,30 @@ CSSSelectorList CSSSelectorList::AdoptSelectorVector(
current = current->TagHistory(); current = current->TagHistory();
DCHECK(!list.selector_array_[array_index].IsLastInSelectorList()); DCHECK(!list.selector_array_[array_index].IsLastInSelectorList());
if (current) if (current)
list.selector_array_[array_index].SetNotLastInTagHistory(); list.selector_array_[array_index].SetLastInTagHistory(false);
++array_index; ++array_index;
} }
DCHECK(list.selector_array_[array_index - 1].IsLastInTagHistory()); DCHECK(list.selector_array_[array_index - 1].IsLastInTagHistory());
} }
DCHECK_EQ(flattened_size, array_index); DCHECK_EQ(flattened_size, array_index);
list.selector_array_[array_index - 1].SetLastInSelectorList(); list.selector_array_[array_index - 1].SetLastInSelectorList(true);
list.selector_array_[array_index - 1].SetLastInOriginalList(true);
selector_vector.clear(); selector_vector.clear();
return list; return list;
} }
const CSSSelector* CSSSelectorList::FirstForCSSOM() const {
const CSSSelector* s = this->First();
if (!s)
return nullptr;
while (this->Next(*s))
s = this->Next(*s);
if (this->NextInFullList(*s))
return this->NextInFullList(*s);
return this->First();
}
unsigned CSSSelectorList::ComputeLength() const { unsigned CSSSelectorList::ComputeLength() const {
if (!selector_array_) if (!selector_array_)
return 0; return 0;
...@@ -120,8 +294,8 @@ void CSSSelectorList::DeleteSelectors() { ...@@ -120,8 +294,8 @@ void CSSSelectorList::DeleteSelectors() {
String CSSSelectorList::SelectorsText() const { String CSSSelectorList::SelectorsText() const {
StringBuilder result; StringBuilder result;
for (const CSSSelector* s = First(); s; s = Next(*s)) { for (const CSSSelector* s = FirstForCSSOM(); s; s = Next(*s)) {
if (s != First()) if (s != FirstForCSSOM())
result.Append(", "); result.Append(", ");
result.Append(s->SelectorText()); result.Append(s->SelectorText());
} }
......
...@@ -26,9 +26,10 @@ ...@@ -26,9 +26,10 @@
#ifndef CSSSelectorList_h #ifndef CSSSelectorList_h
#define CSSSelectorList_h #define CSSSelectorList_h
#include <memory>
#include <vector>
#include "core/CoreExport.h" #include "core/CoreExport.h"
#include "core/css/CSSSelector.h" #include "core/css/CSSSelector.h"
#include <memory>
namespace blink { namespace blink {
...@@ -71,7 +72,16 @@ class CORE_EXPORT CSSSelectorList { ...@@ -71,7 +72,16 @@ class CORE_EXPORT CSSSelectorList {
o.selector_array_ = nullptr; o.selector_array_ = nullptr;
} }
static CSSSelectorList ConcatenatePseudoMatchesExpansion(
const CSSSelectorList& expanded,
const CSSSelectorList& original);
CSSSelectorList ExpandedFirstMatchesPseudo() const;
CSSSelectorList TransformForPseudoMatches();
bool HasPseudoMatches() const;
CSSSelectorList& operator=(CSSSelectorList&& o) { CSSSelectorList& operator=(CSSSelectorList&& o) {
DCHECK(this != &o);
DeleteSelectorsIfNeeded(); DeleteSelectorsIfNeeded();
selector_array_ = o.selector_array_; selector_array_ = o.selector_array_;
o.selector_array_ = nullptr; o.selector_array_ = nullptr;
...@@ -86,7 +96,9 @@ class CORE_EXPORT CSSSelectorList { ...@@ -86,7 +96,9 @@ class CORE_EXPORT CSSSelectorList {
bool IsValid() const { return !!selector_array_; } bool IsValid() const { return !!selector_array_; }
const CSSSelector* First() const { return selector_array_; } const CSSSelector* First() const { return selector_array_; }
const CSSSelector* FirstForCSSOM() const;
static const CSSSelector* Next(const CSSSelector&); static const CSSSelector* Next(const CSSSelector&);
static const CSSSelector* NextInFullList(const CSSSelector&);
// The CSS selector represents a single sequence of simple selectors. // The CSS selector represents a single sequence of simple selectors.
bool HasOneSelector() const { bool HasOneSelector() const {
...@@ -133,6 +145,15 @@ class CORE_EXPORT CSSSelectorList { ...@@ -133,6 +145,15 @@ class CORE_EXPORT CSSSelectorList {
inline const CSSSelector* CSSSelectorList::Next(const CSSSelector& current) { inline const CSSSelector* CSSSelectorList::Next(const CSSSelector& current) {
// Skip subparts of compound selectors. // Skip subparts of compound selectors.
const CSSSelector* last = &current; const CSSSelector* last = &current;
while (!last->IsLastInTagHistory())
last++;
return last->IsLastInOriginalList() ? nullptr : last + 1;
}
inline const CSSSelector* CSSSelectorList::NextInFullList(
const CSSSelector& current) {
// Skip subparts of compound selectors.
const CSSSelector* last = &current;
while (!last->IsLastInTagHistory()) while (!last->IsLastInTagHistory())
last++; last++;
return last->IsLastInSelectorList() ? nullptr : last + 1; return last->IsLastInSelectorList() ? nullptr : last + 1;
......
...@@ -137,7 +137,7 @@ void CSSSelectorWatch::UpdateSelectorMatches( ...@@ -137,7 +137,7 @@ void CSSSelectorWatch::UpdateSelectorMatches(
} }
static bool AllCompound(const CSSSelectorList& selector_list) { static bool AllCompound(const CSSSelectorList& selector_list) {
for (const CSSSelector* selector = selector_list.First(); selector; for (const CSSSelector* selector = selector_list.FirstForCSSOM(); selector;
selector = selector_list.Next(*selector)) { selector = selector_list.Next(*selector)) {
if (!selector->IsCompound()) if (!selector->IsCompound())
return false; return false;
......
...@@ -63,11 +63,14 @@ RuleSet& CSSTestHelper::GetRuleSet() { ...@@ -63,11 +63,14 @@ RuleSet& CSSTestHelper::GetRuleSet() {
return rule_set; return rule_set;
} }
void CSSTestHelper::AddCSSRules(const char* css_text) { void CSSTestHelper::AddCSSRules(const char* css_text, bool is_empty_sheet) {
TextPosition position; TextPosition position;
unsigned sheet_length = style_sheet_->length(); unsigned sheet_length = style_sheet_->length();
style_sheet_->Contents()->ParseStringAtPosition(css_text, position); style_sheet_->Contents()->ParseStringAtPosition(css_text, position);
ASSERT_GT(style_sheet_->length(), sheet_length); if (!is_empty_sheet)
ASSERT_GT(style_sheet_->length(), sheet_length);
else
ASSERT_EQ(style_sheet_->length(), sheet_length);
} }
} // namespace blink } // namespace blink
...@@ -55,7 +55,7 @@ class CSSTestHelper { ...@@ -55,7 +55,7 @@ class CSSTestHelper {
const Document& GetDocument() { return *document_; }; const Document& GetDocument() { return *document_; };
void AddCSSRules(const char* rule_text); void AddCSSRules(const char* rule_text, bool is_empty_sheet = false);
RuleSet& GetRuleSet(); RuleSet& GetRuleSet();
CSSRuleList* CssRules(); CSSRuleList* CssRules();
......
...@@ -91,7 +91,6 @@ bool SupportsInvalidation(CSSSelector::PseudoType type) { ...@@ -91,7 +91,6 @@ bool SupportsInvalidation(CSSSelector::PseudoType type) {
case CSSSelector::kPseudoLink: case CSSSelector::kPseudoLink:
case CSSSelector::kPseudoVisited: case CSSSelector::kPseudoVisited:
case CSSSelector::kPseudoAny: case CSSSelector::kPseudoAny:
case CSSSelector::kPseudoMatches:
case CSSSelector::kPseudoWebkitAnyLink: case CSSSelector::kPseudoWebkitAnyLink:
case CSSSelector::kPseudoAnyLink: case CSSSelector::kPseudoAnyLink:
case CSSSelector::kPseudoAutofill: case CSSSelector::kPseudoAutofill:
...@@ -163,6 +162,7 @@ bool SupportsInvalidation(CSSSelector::PseudoType type) { ...@@ -163,6 +162,7 @@ bool SupportsInvalidation(CSSSelector::PseudoType type) {
case CSSSelector::kPseudoVideoPersistent: case CSSSelector::kPseudoVideoPersistent:
case CSSSelector::kPseudoVideoPersistentAncestor: case CSSSelector::kPseudoVideoPersistentAncestor:
return true; return true;
case CSSSelector::kPseudoMatches:
case CSSSelector::kPseudoUnknown: case CSSSelector::kPseudoUnknown:
case CSSSelector::kPseudoLeftPage: case CSSSelector::kPseudoLeftPage:
case CSSSelector::kPseudoRightPage: case CSSSelector::kPseudoRightPage:
...@@ -183,8 +183,6 @@ bool SupportsInvalidationWithSelectorList(CSSSelector::PseudoType pseudo) { ...@@ -183,8 +183,6 @@ 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::kPseudoMatches) &&
RuntimeEnabledFeatures::CSSMatchesEnabled()) ||
pseudo == CSSSelector::kPseudoNot || pseudo == CSSSelector::kPseudoNot ||
pseudo == CSSSelector::kPseudoSlotted; pseudo == CSSSelector::kPseudoSlotted;
} }
......
...@@ -325,7 +325,9 @@ void RuleSet::AddRulesFromSheet(StyleSheetContents* sheet, ...@@ -325,7 +325,9 @@ void RuleSet::AddRulesFromSheet(StyleSheetContents* sheet,
} }
void RuleSet::AddStyleRule(StyleRule* rule, AddRuleFlags add_rule_flags) { void RuleSet::AddStyleRule(StyleRule* rule, AddRuleFlags add_rule_flags) {
for (size_t selector_index = 0; selector_index != kNotFound; for (size_t selector_index =
rule->SelectorList().SelectorIndex(*rule->SelectorList().First());
selector_index != kNotFound;
selector_index = selector_index =
rule->SelectorList().IndexOfNextSelectorAfter(selector_index)) rule->SelectorList().IndexOfNextSelectorAfter(selector_index))
AddRule(rule, selector_index, add_rule_flags); AddRule(rule, selector_index, add_rule_flags);
......
...@@ -240,6 +240,49 @@ TEST(RuleSetTest, findBestRuleSetAndAdd_PlaceholderPseudo) { ...@@ -240,6 +240,49 @@ TEST(RuleSetTest, findBestRuleSetAndAdd_PlaceholderPseudo) {
ASSERT_EQ(2u, rules->size()); ASSERT_EQ(2u, rules->size());
} }
TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoMatches) {
CSSTestHelper helper;
helper.AddCSSRules(".a :matches(.b+.c, .d>:matches(.e, .f)) { }");
RuleSet& rule_set = helper.GetRuleSet();
{
AtomicString str("c");
const TerminatedArray<RuleData>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0).Selector().Value());
}
{
AtomicString str("e");
const TerminatedArray<RuleData>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0).Selector().Value());
}
{
AtomicString str("f");
const TerminatedArray<RuleData>* rules = rule_set.ClassRules(str);
ASSERT_EQ(1u, rules->size());
ASSERT_EQ(str, rules->at(0).Selector().Value());
}
}
TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoMatchesTooLarge) {
// RuleData cannot support selectors at index 8192 or beyond so the expansion
// is limited to this size
CSSTestHelper helper;
helper.AddCSSRules(
":matches(.a#a, .b#b, .c#c, .d#d) + "
":matches(.e#e, .f#f, .g#g, .h#h) + "
":matches(.i#i, .j#j, .k#k, .l#l) + "
":matches(.m#m, .n#n, .o#o, .p#p) + "
":matches(.q#q, .r#r, .s#s, .t#t) + "
":matches(.u#u, .v#v, .w#w, .x#x) { }",
true);
RuleSet& rule_set = helper.GetRuleSet();
ASSERT_EQ(0u, rule_set.RuleCount());
}
TEST(RuleSetTest, SelectorIndexLimit) { TEST(RuleSetTest, SelectorIndexLimit) {
StringBuilder builder; StringBuilder builder;
......
...@@ -883,17 +883,7 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context, ...@@ -883,17 +883,7 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context,
case CSSSelector::kPseudoMatches: { case CSSSelector::kPseudoMatches: {
if (!RuntimeEnabledFeatures::CSSMatchesEnabled()) if (!RuntimeEnabledFeatures::CSSMatchesEnabled())
return false; return false;
UseCounter::Count(context.element->GetDocument(), NOTREACHED();
WebFeature::kCSSSelectorPseudoMatches);
SelectorCheckingContext sub_context(context);
sub_context.is_sub_selector = true;
DCHECK(selector.SelectorList());
for (sub_context.selector = selector.SelectorList()->First();
sub_context.selector; sub_context.selector = CSSSelectorList::Next(
*sub_context.selector)) {
if (Match(sub_context))
return true;
}
} break; } break;
case CSSSelector::kPseudoAny: { case CSSSelector::kPseudoAny: {
SelectorCheckingContext sub_context(context); SelectorCheckingContext sub_context(context);
......
...@@ -65,7 +65,8 @@ bool CSSLazyParsingState::ShouldLazilyParseProperties( ...@@ -65,7 +65,8 @@ bool CSSLazyParsingState::ShouldLazilyParseProperties(
// list. This ensures we don't cause a collectFeatures() when we trigger // list. This ensures we don't cause a collectFeatures() when we trigger
// parsing for attr() functions which would trigger expensive invalidation // parsing for attr() functions which would trigger expensive invalidation
// propagation. // propagation.
for (const auto* s = selectors.First(); s; s = CSSSelectorList::Next(*s)) { for (const auto* s = selectors.FirstForCSSOM(); s;
s = CSSSelectorList::Next(*s)) {
for (const CSSSelector* current = s; current; for (const CSSSelector* current = s; current;
current = current->TagHistory()) { current = current->TagHistory()) {
const CSSSelector::PseudoType type(current->GetPseudoType()); const CSSSelector::PseudoType type(current->GetPseudoType());
......
...@@ -30,6 +30,11 @@ CSSSelectorList CSSSelectorParser::ParseSelector( ...@@ -30,6 +30,11 @@ CSSSelectorList CSSSelectorParser::ParseSelector(
return CSSSelectorList(); return CSSSelectorList();
parser.RecordUsageAndDeprecations(result); parser.RecordUsageAndDeprecations(result);
if (RuntimeEnabledFeatures::CSSMatchesEnabled()) {
if (result.HasPseudoMatches())
return result.TransformForPseudoMatches();
}
return result; return result;
} }
...@@ -43,6 +48,11 @@ CSSSelectorList CSSSelectorParser::ConsumeSelector( ...@@ -43,6 +48,11 @@ 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 (RuntimeEnabledFeatures::CSSMatchesEnabled()) {
if (result.HasPseudoMatches())
return result.TransformForPseudoMatches();
}
return result; return result;
} }
...@@ -528,10 +538,20 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo( ...@@ -528,10 +538,20 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
return nullptr; return nullptr;
switch (selector->GetPseudoType()) { switch (selector->GetPseudoType()) {
case CSSSelector::kPseudoMatches: case CSSSelector::kPseudoMatches: {
if (!RuntimeEnabledFeatures::CSSMatchesEnabled()) if (!RuntimeEnabledFeatures::CSSMatchesEnabled())
break; break;
FALLTHROUGH;
DisallowPseudoElementsScope scope(this);
std::unique_ptr<CSSSelectorList> selector_list =
std::make_unique<CSSSelectorList>();
*selector_list = ConsumeComplexSelectorList(block);
if (!selector_list->IsValid() || !block.AtEnd())
return nullptr;
selector->SetSelectorList(std::move(selector_list));
return selector;
}
case CSSSelector::kPseudoHost: case CSSSelector::kPseudoHost:
case CSSSelector::kPseudoHostContext: case CSSSelector::kPseudoHostContext:
case CSSSelector::kPseudoAny: case CSSSelector::kPseudoAny:
...@@ -541,7 +561,8 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo( ...@@ -541,7 +561,8 @@ 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->HasPseudoMatches())
return nullptr; return nullptr;
selector->SetSelectorList(std::move(selector_list)); selector->SetSelectorList(std::move(selector_list));
return selector; return selector;
...@@ -563,7 +584,8 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo( ...@@ -563,7 +584,8 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
std::unique_ptr<CSSParserSelector> inner_selector = std::unique_ptr<CSSParserSelector> inner_selector =
ConsumeCompoundSelector(block); ConsumeCompoundSelector(block);
block.ConsumeWhitespace(); block.ConsumeWhitespace();
if (!inner_selector || !block.AtEnd()) if (!inner_selector || !block.AtEnd() ||
inner_selector->GetPseudoType() == CSSSelector::kPseudoMatches)
return nullptr; return nullptr;
Vector<std::unique_ptr<CSSParserSelector>> selector_vector; Vector<std::unique_ptr<CSSParserSelector>> selector_vector;
selector_vector.push_back(std::move(inner_selector)); selector_vector.push_back(std::move(inner_selector));
...@@ -903,7 +925,7 @@ void CSSSelectorParser::RecordUsageAndDeprecations( ...@@ -903,7 +925,7 @@ void CSSSelectorParser::RecordUsageAndDeprecations(
if (!context_->IsUseCounterRecordingEnabled()) if (!context_->IsUseCounterRecordingEnabled())
return; return;
for (const CSSSelector* selector = selector_list.First(); selector; for (const CSSSelector* selector = selector_list.FirstForCSSOM(); selector;
selector = CSSSelectorList::Next(*selector)) { selector = CSSSelectorList::Next(*selector)) {
for (const CSSSelector* current = selector; current; for (const CSSSelector* current = selector; current;
current = current->TagHistory()) { current = current->TagHistory()) {
......
...@@ -425,6 +425,40 @@ TEST(CSSSelectorParserTest, InternalPseudo) { ...@@ -425,6 +425,40 @@ TEST(CSSSelectorParserTest, InternalPseudo) {
} }
} }
TEST(CSSSelectorParserTest, InvalidNestingPseudoMatches) {
// :matches() is currently not supported within these pseudo classes as they
// currently do not support complex selector arguments (:matches() does
// support this and the expansion of :matches() 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(:matches(.a))",
"::cue(:matches(.a))",
":cue(:matches(.a))",
":host(:matches(.a))",
":host-context(:matches(.a))",
":lang(:matches(.a))",
":not(:matches(.a))",
":nth-child(:matches(.a))",
":nth-last-child(:matches(.a))",
":nth-last-of-type(:matches(.a))",
":nth-of-type(:matches(.a))",
"::slotted(:matches(.a))"};
CSSParserContext* context = CSSParserContext::Create(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
StyleSheetContents* sheet = StyleSheetContents::Create(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());
}
}
namespace { namespace {
const auto TagLocalName = [](const CSSSelector* selector) { const auto TagLocalName = [](const CSSSelector* selector) {
......
...@@ -46,7 +46,7 @@ WebString CanonicalizeSelector(WebString web_selector, ...@@ -46,7 +46,7 @@ WebString CanonicalizeSelector(WebString web_selector,
web_selector); web_selector);
if (restriction == kWebSelectorTypeCompound) { if (restriction == kWebSelectorTypeCompound) {
for (const CSSSelector* selector = selector_list.First(); selector; for (const CSSSelector* selector = selector_list.FirstForCSSOM(); selector;
selector = selector_list.Next(*selector)) { selector = selector_list.Next(*selector)) {
if (!selector->IsCompound()) if (!selector->IsCompound())
return WebString(); return WebString();
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "core/css/CSSTestHelper.h"
#include "core/frame/Deprecation.h" #include "core/frame/Deprecation.h"
#include "core/frame/UseCounter.h" #include "core/frame/UseCounter.h"
#include "core/html/HTMLHtmlElement.h"
#include "core/page/Page.h" #include "core/page/Page.h"
#include "core/testing/DummyPageHolder.h" #include "core/testing/DummyPageHolder.h"
#include "platform/testing/HistogramTester.h" #include "platform/testing/HistogramTester.h"
...@@ -271,6 +273,17 @@ TEST_F(UseCounterTest, CSSTypedOMStylePropertyMap) { ...@@ -271,6 +273,17 @@ TEST_F(UseCounterTest, CSSTypedOMStylePropertyMap) {
EXPECT_TRUE(use_counter.IsCounted(GetDocument(), feature)); EXPECT_TRUE(use_counter.IsCounted(GetDocument(), feature));
} }
TEST_F(UseCounterTest, CSSSelectorPseudoMatches) {
std::unique_ptr<DummyPageHolder> dummy_page_holder =
DummyPageHolder::Create(IntSize(800, 600));
Document& document = dummy_page_holder->GetDocument();
WebFeature feature = WebFeature::kCSSSelectorPseudoMatches;
EXPECT_FALSE(UseCounter::IsCounted(document, feature));
document.documentElement()->SetInnerHTMLFromString(
"<style>.a+:matches(.b, .c+.d) { color: red; }</style>");
EXPECT_TRUE(UseCounter::IsCounted(document, feature));
}
TEST_F(UseCounterTest, InspectorDisablesMeasurement) { TEST_F(UseCounterTest, InspectorDisablesMeasurement) {
UseCounter use_counter; UseCounter use_counter;
......
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