From 6e703b76caa719fc03267ac9a4bdb49e0e7e19e2 Mon Sep 17 00:00:00 2001 From: Byungwoo Lee Date: Wed, 4 Aug 2021 12:41:07 +0000 Subject: [PATCH] Support relative selector to update the :has tests Support the relative selector grammar starting with combinator. - https://www.w3.org/TR/selectors-4/#typedef-relative-selector To simplify matching operation, some relation types are added. - kRelativeDescendant : Leftmost descendant combinator - kRelativeChild : Leftmost child combinator - kRelativeDirectAdjacent : Leftmost next-sibling combinator - kRelativeIndirectAdjacent : Leftmost subsequent-sibling combinator The ':scope' dependency in definition creates too much confusion especially with ':has' as the CSSWG issue describes. - https://github.com/w3c/csswg-drafts/issues/6399 1. ':scope' behavior in ':has' argument is different with usual ':scope' behavior. 2. Explicit ':scope' in a ':has' argument can create performance issues or increase complexity when the ':scope' is not leftmost or compounded with other simple selectors. 3. Absolutizing a relative selector with ':scope' doesn't make sense when the ':has' argument already has explicit ':scope' (e.g. ':has(~ .a :scope .b)' -> ':has(:scope ~ .a :scope .b)' To skip those complexity and ambiguity, this CL removed some logic related with the 'explicit :scope in :has argument', and added TODO comment to handle it later separately. As suggested in the CSSWG issue, this CL always absolutize the with a dummy pseudo class. - kPseudoRelativeLeftmost The added pseudo class represents any elements that is at the relative position that matches with the leftmost combinator of the relative selector. This CL also includes tentative tests for some cases involving the ':scope' inside ':has' to show the result of the suggestion. By removing the ':scope' dependency from the relative selector, most of the ':scope' inside ':has' will be meaningless. (It will not match or can be changed more simple/efficient expression) Change-Id: I1e0ccf0c190d04b9636d86cb15e1bbb175b7cc30 Bug: 669058 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2972189 Reviewed-by: Rune Lillesveen Commit-Queue: Byungwoo Lee Cr-Commit-Position: refs/heads/master@{#908421} NOKEYCHECK=True GitOrigin-RevId: 4913bff09fee113fddaeef2aaeed95a527a1201a --- blink/renderer/core/css/css_selector.cc | 18 ++ blink/renderer/core/css/css_selector.h | 14 ++ blink/renderer/core/css/css_selector_test.cc | 18 ++ .../core/css/has_argument_match_context.cc | 119 ++++----- .../core/css/has_argument_match_context.h | 20 +- .../core/css/parser/css_selector_parser.cc | 76 +++++- .../core/css/parser/css_selector_parser.h | 23 ++ blink/renderer/core/css/selector_checker.cc | 237 ++++++------------ blink/renderer/core/css/selector_checker.h | 1 + blink/renderer/core/css/selector_filter.cc | 6 + .../core/inspector/inspector_trace_events.cc | 1 + ...rgument-with-explicit-scope.tentative.html | 69 +++++ .../wpt/css/selectors/has-basic-expected.txt | 26 -- .../external/wpt/css/selectors/has-basic.html | 7 +- .../css/selectors/has-relative-argument.html | 164 +++--------- .../selectors/parsing/parse-has-expected.txt | 42 ---- .../wpt/css/selectors/parsing/parse-has.html | 13 - .../dom/nodes/Element-closest-expected.txt | 32 --- 18 files changed, 388 insertions(+), 498 deletions(-) create mode 100644 blink/web_tests/external/wpt/css/selectors/has-argument-with-explicit-scope.tentative.html delete mode 100644 blink/web_tests/external/wpt/css/selectors/has-basic-expected.txt delete mode 100644 blink/web_tests/external/wpt/css/selectors/parsing/parse-has-expected.txt delete mode 100644 blink/web_tests/external/wpt/dom/nodes/Element-closest-expected.txt diff --git a/blink/renderer/core/css/css_selector.cc b/blink/renderer/core/css/css_selector.cc index 28055ed4a06..8a6222dd3c3 100644 --- a/blink/renderer/core/css/css_selector.cc +++ b/blink/renderer/core/css/css_selector.cc @@ -143,6 +143,10 @@ inline unsigned CSSSelector::SpecificityForOneSelector() const { FALLTHROUGH; case kPseudoIs: return MaximumSpecificity(SelectorList()); + case kPseudoHas: + return MaximumSpecificity(SelectorList()); + case kPseudoRelativeLeftmost: + return 0; // FIXME: PseudoAny should base the specificity on the sub-selectors. // See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html case kPseudoAny: @@ -342,6 +346,7 @@ PseudoId CSSSelector::GetPseudoId(PseudoType type) { case kPseudoXrOverlay: case kPseudoModal: case kPseudoHas: + case kPseudoRelativeLeftmost: return kPseudoIdNone; } @@ -369,6 +374,7 @@ const static NameToPseudoStruct kPseudoTypeWithoutArgumentsMap[] = { {"-internal-modal", CSSSelector::kPseudoModal}, {"-internal-multi-select-focus", CSSSelector::kPseudoMultiSelectFocus}, {"-internal-popup-open", CSSSelector::kPseudoPopupOpen}, + {"-internal-relative-leftmost", CSSSelector::kPseudoRelativeLeftmost}, {"-internal-shadow-host-has-appearance", CSSSelector::kPseudoHostHasAppearance}, {"-internal-spatial-navigation-focus", @@ -727,6 +733,7 @@ void CSSSelector::UpdatePseudoType(const AtomicString& value, case kPseudoPastCue: case kPseudoReadOnly: case kPseudoReadWrite: + case kPseudoRelativeLeftmost: case kPseudoRequired: case kPseudoRoot: case kPseudoScope: @@ -870,6 +877,9 @@ const CSSSelector* CSSSelector::SerializeCompound( case kPseudoIs: case kPseudoWhere: break; + case kPseudoRelativeLeftmost: + NOTREACHED(); + return nullptr; default: break; } @@ -993,6 +1003,14 @@ String CSSSelector::SelectorText() const { case kShadowSlot: result = builder.ToString() + result; break; + case kRelativeDescendant: + return builder.ToString() + result; + case kRelativeChild: + return "> " + builder.ToString() + result; + case kRelativeDirectAdjacent: + return "+ " + builder.ToString() + result; + case kRelativeIndirectAdjacent: + return "~ " + builder.ToString() + result; } } NOTREACHED(); diff --git a/blink/renderer/core/css/css_selector.h b/blink/renderer/core/css/css_selector.h index 9bb7bdd2fb8..e850dc4e11f 100644 --- a/blink/renderer/core/css/css_selector.h +++ b/blink/renderer/core/css/css_selector.h @@ -156,6 +156,15 @@ class CORE_EXPORT CSSSelector { // matching a ::part in shadow-including descendant tree for #host in // "#host::part(button)". kShadowPart, + + // leftmost "Space" combinator of relative selector + kRelativeDescendant, + // leftmost > combinator of relative selector + kRelativeChild, + // leftmost + combinator of relative selector + kRelativeDirectAdjacent, + // leftmost ~ combinator of relative selector + kRelativeIndirectAdjacent }; enum PseudoType { @@ -277,6 +286,11 @@ class CORE_EXPORT CSSSelector { kPseudoSpellingError, kPseudoGrammarError, kPseudoHas, + // TODO(blee@igalia.com) Need to clarify the :scope dependency in relative + // selector definition. + // - spec : https://www.w3.org/TR/selectors-4/#relative + // - csswg issue : https://github.com/w3c/csswg-drafts/issues/6399 + kPseudoRelativeLeftmost, }; enum class AttributeMatchType { diff --git a/blink/renderer/core/css/css_selector_test.cc b/blink/renderer/core/css/css_selector_test.cc index a376702b8e8..fa4bbff759b 100644 --- a/blink/renderer/core/css/css_selector_test.cc +++ b/blink/renderer/core/css/css_selector_test.cc @@ -152,6 +152,24 @@ TEST(CSSSelector, Specificity_Not) { Specificity(":is(.c + .c + .c, .b + .c:not(span), .b + .c + .e)")); } +TEST(CSSSelector, Specificity_Has) { + EXPECT_EQ(Specificity(":has(div)"), Specificity("div")); + EXPECT_EQ(Specificity(":has(div)"), Specificity("* div")); + EXPECT_EQ(Specificity(":has(~ div)"), Specificity("* ~ div")); + EXPECT_EQ(Specificity(":has(> .a)"), Specificity("* > .a")); + EXPECT_EQ(Specificity(":has(+ div.a)"), Specificity("* + div.a")); + EXPECT_EQ(Specificity(".a :has(.b, div.c)"), Specificity(".a div.c")); + EXPECT_EQ(Specificity(".a :has(.c#d, .e)"), Specificity(".a .c#d")); + EXPECT_EQ(Specificity(":has(.e+.f, .g>.b, .h)"), Specificity(".e+.f")); + EXPECT_EQ(Specificity(".a :has(.e+.f, .g>.b, .h#i)"), Specificity(".a .h#i")); + EXPECT_EQ(Specificity(".a+:has(.b+span.f, :has(.c>.e, .g))"), + Specificity(".a+.b+span.f")); + EXPECT_EQ(Specificity("div > :has(div:where(span:where(.b ~ .c)))"), + Specificity("div > div")); + EXPECT_EQ(Specificity(":has(.c + .c + .c, .b + .c:not(span), .b + .c + .e)"), + Specificity(".c + .c + .c")); +} + TEST(CSSSelector, HasLinkOrVisited) { EXPECT_FALSE(HasLinkOrVisited("tag")); EXPECT_FALSE(HasLinkOrVisited("visited")); diff --git a/blink/renderer/core/css/has_argument_match_context.cc b/blink/renderer/core/css/has_argument_match_context.cc index 8a0fd9746d7..07c14bd2094 100644 --- a/blink/renderer/core/css/has_argument_match_context.cc +++ b/blink/renderer/core/css/has_argument_match_context.cc @@ -8,6 +8,7 @@ namespace { // anonymous namespace for file-local method and constant +using blink::CSSSelector; using blink::Element; using blink::To; using blink::Traversal; @@ -34,98 +35,82 @@ inline Element* LastDescendantOf(const Element& element, return last_descendant; } +inline const CSSSelector* GetCurrentRelationAndNextCompound( + const CSSSelector* compound_selector, + CSSSelector::RelationType& relation) { + DCHECK(compound_selector); + for (; compound_selector; + compound_selector = compound_selector->TagHistory()) { + relation = compound_selector->Relation(); + if (relation != CSSSelector::kSubSelector) + return compound_selector->TagHistory(); + } + return nullptr; +} + } // namespace namespace blink { -HasArgumentMatchContext::HasArgumentMatchContext(const CSSSelector* selector) { - const CSSSelector* leftmost_compound = selector; - const CSSSelector* leftmost_compound_containing_scope = nullptr; - - while (leftmost_compound) { - const CSSSelector* simple_selector = leftmost_compound; - CSSSelector::RelationType relation; - while (simple_selector) { - if (leftmost_compound_containing_scope) - contains_compounded_scope_selector_ = true; - if (simple_selector->GetPseudoType() == CSSSelector::kPseudoScope) { - if (leftmost_compound_containing_scope && - leftmost_compound_containing_scope != leftmost_compound) { - // Selectors that contains multiple :scope pseudo classes separated - // by combinators will never match. - // (e.g. :has(:scope > .a > :scope)) - SetNeverMatch(); - return; - } - if (simple_selector != leftmost_compound) - contains_compounded_scope_selector_ = true; - leftmost_compound_containing_scope = leftmost_compound; - } - relation = simple_selector->Relation(); - if (relation != CSSSelector::kSubSelector) - break; - simple_selector = simple_selector->TagHistory(); - } - - if (!simple_selector) - break; - - if (leftmost_compound_containing_scope) { - // Skip to update the context if it already found the :scope - leftmost_compound = simple_selector->TagHistory(); - DCHECK(leftmost_compound); - continue; - } - +HasArgumentMatchContext::HasArgumentMatchContext(const CSSSelector* selector) + : leftmost_relation_(CSSSelector::kSubSelector), + adjacent_traversal_distance_(0), + descendant_traversal_depth_(0) { + CSSSelector::RelationType relation = CSSSelector::kSubSelector; + // The explicit ':scope' in ':has' argument selector is not considered + // for getting the depth and adjacent distance. + // TODO(blee@igalia.com) Need to clarify the :scope dependency in relative + // selector definition. + // - spec : https://www.w3.org/TR/selectors-4/#relative + // - csswg issue : https://github.com/w3c/csswg-drafts/issues/6399 + for (selector = GetCurrentRelationAndNextCompound(selector, relation); + selector; + selector = GetCurrentRelationAndNextCompound(selector, relation)) { switch (relation) { + case CSSSelector::kRelativeDescendant: + leftmost_relation_ = relation; + FALLTHROUGH; case CSSSelector::kDescendant: descendant_traversal_depth_ = kInfiniteDepth; adjacent_traversal_distance_ = 0; - leftmost_relation_ = relation; break; + + case CSSSelector::kRelativeChild: + leftmost_relation_ = relation; + FALLTHROUGH; case CSSSelector::kChild: if (descendant_traversal_depth_ != kInfiniteDepth) { descendant_traversal_depth_++; adjacent_traversal_distance_ = 0; } - leftmost_relation_ = relation; break; + + case CSSSelector::kRelativeDirectAdjacent: + leftmost_relation_ = relation; + FALLTHROUGH; case CSSSelector::kDirectAdjacent: if (adjacent_traversal_distance_ != kInfiniteAdjacentDistance) adjacent_traversal_distance_++; - leftmost_relation_ = relation; break; + + case CSSSelector::kRelativeIndirectAdjacent: + leftmost_relation_ = relation; + FALLTHROUGH; case CSSSelector::kIndirectAdjacent: adjacent_traversal_distance_ = kInfiniteAdjacentDistance; - leftmost_relation_ = relation; break; + case CSSSelector::kUAShadow: case CSSSelector::kShadowSlot: case CSSSelector::kShadowPart: // TODO(blee@igalia.com) Need to check how to handle the shadow tree // (e.g. ':has(::slotted(img))', ':has(component::part(my-part))') - SetNeverMatch(); return; default: NOTREACHED(); break; } - - leftmost_compound = simple_selector->TagHistory(); - DCHECK(leftmost_compound); - } - - if (!leftmost_compound_containing_scope) { - // Always set descendant relative selector because the relative selector - // spec is not supported yet. - leftmost_relation_ = CSSSelector::kDescendant; - descendant_traversal_depth_ = kInfiniteDepth; - adjacent_traversal_distance_ = 0; - return; } - - contains_no_leftmost_scope_selector_ = - leftmost_compound_containing_scope != leftmost_compound; } CSSSelector::RelationType HasArgumentMatchContext::GetLeftMostRelation() const { @@ -140,22 +125,6 @@ bool HasArgumentMatchContext::GetAdjacentDistanceFixed() const { return adjacent_traversal_distance_ != kInfiniteAdjacentDistance; } -bool HasArgumentMatchContext::WillNeverMatch() const { - return leftmost_relation_ == CSSSelector::kSubSelector; -} - -bool HasArgumentMatchContext::ContainsCompoundedScopeSelector() const { - return contains_compounded_scope_selector_; -} - -bool HasArgumentMatchContext::ContainsNoLeftmostScopeSelector() const { - return contains_no_leftmost_scope_selector_; -} - -void HasArgumentMatchContext::SetNeverMatch() { - leftmost_relation_ = CSSSelector::kSubSelector; -} - HasArgumentSubtreeIterator::HasArgumentSubtreeIterator( Element& scope_element, HasArgumentMatchContext& context) diff --git a/blink/renderer/core/css/has_argument_match_context.h b/blink/renderer/core/css/has_argument_match_context.h index fcd3baea31d..1d84cdc18ac 100644 --- a/blink/renderer/core/css/has_argument_match_context.h +++ b/blink/renderer/core/css/has_argument_match_context.h @@ -20,13 +20,8 @@ class HasArgumentMatchContext { CSSSelector::RelationType GetLeftMostRelation() const; bool GetDepthFixed() const; bool GetAdjacentDistanceFixed() const; - bool WillNeverMatch() const; - bool ContainsCompoundedScopeSelector() const; - bool ContainsNoLeftmostScopeSelector() const; private: - void SetNeverMatch(); - // Indicate the :has argument relative type and subtree traversal scope. // If 'adjacent_traversal_distance_' is greater than 0, then it means that // it is enough to traverse the adjacent subtree at that distance. @@ -144,18 +139,9 @@ class HasArgumentMatchContext { // - Traverse the depth m elements of the distance n sibling subtree of // the :has scope element. (elements at depth m of the descendant subtree // of the sibling element at distance n) - CSSSelector::RelationType leftmost_relation_{CSSSelector::kSubSelector}; - int adjacent_traversal_distance_{0}; - int descendant_traversal_depth_{0}; - - // Indicate that the argument selector has a ':scope' in a compound which - // contains other simple selectors. - // (e.g. ':has(.a:scope .b)', ':has(.a .b:scope .c') - bool contains_compounded_scope_selector_{false}; - - // Indicate that the argument selector has a ':scope' which is not leftmost - // (e.g. ':has(.a :scope .b)', ':has(.a .b:scope .c)') - bool contains_no_leftmost_scope_selector_{false}; + CSSSelector::RelationType leftmost_relation_; + int adjacent_traversal_distance_; + int descendant_traversal_depth_; friend class HasArgumentSubtreeIterator; }; diff --git a/blink/renderer/core/css/parser/css_selector_parser.cc b/blink/renderer/core/css/parser/css_selector_parser.cc index 6c5aa21f056..1c28688a302 100644 --- a/blink/renderer/core/css/parser/css_selector_parser.cc +++ b/blink/renderer/core/css/parser/css_selector_parser.cc @@ -226,6 +226,27 @@ CSSSelectorList CSSSelectorParser::ConsumeForgivingCompoundSelectorList( return CSSSelectorList::AdoptSelectorVector(selector_list); } +CSSSelectorList CSSSelectorParser::ConsumeRelativeSelectorList( + CSSParserTokenRange& range) { + Vector> selector_list; + std::unique_ptr selector = ConsumeRelativeSelector(range); + if (!selector) + return CSSSelectorList(); + selector_list.push_back(std::move(selector)); + while (!range.AtEnd() && range.Peek().GetType() == kCommaToken) { + range.ConsumeIncludingWhitespace(); + selector = ConsumeRelativeSelector(range); + if (!selector) + return CSSSelectorList(); + selector_list.push_back(std::move(selector)); + } + + if (failed_parsing_) + return CSSSelectorList(); + + return CSSSelectorList::AdoptSelectorVector(selector_list); +} + namespace { enum CompoundSelectorFlags { @@ -249,6 +270,41 @@ unsigned ExtractCompoundFlags(const CSSParserSelector& simple_selector, } // namespace +std::unique_ptr CSSSelectorParser::ConsumeRelativeSelector( + CSSParserTokenRange& range) { + std::unique_ptr selector = + std::make_unique(); + selector->SetMatch(CSSSelector::kPseudoClass); + selector->UpdatePseudoType("-internal-relative-leftmost", *context_, + false /*has_arguments*/, context_->Mode()); + DCHECK_EQ(selector->GetPseudoType(), CSSSelector::kPseudoRelativeLeftmost); + + CSSSelector::RelationType combinator = ConsumeCombinator(range); + switch (combinator) { + case CSSSelector::kSubSelector: + case CSSSelector::kDescendant: + combinator = CSSSelector::kRelativeDescendant; + break; + case CSSSelector::kChild: + combinator = CSSSelector::kRelativeChild; + break; + case CSSSelector::kDirectAdjacent: + combinator = CSSSelector::kRelativeDirectAdjacent; + break; + case CSSSelector::kIndirectAdjacent: + combinator = CSSSelector::kRelativeIndirectAdjacent; + break; + default: + NOTREACHED(); + return nullptr; + } + + unsigned previous_compound_flags = 0; + + return ConsumePartialComplexSelector(range, combinator, std::move(selector), + previous_compound_flags); +} + std::unique_ptr CSSSelectorParser::ConsumeComplexSelector( CSSParserTokenRange& range) { std::unique_ptr selector = ConsumeCompoundSelector(range); @@ -261,7 +317,21 @@ std::unique_ptr CSSSelectorParser::ConsumeComplexSelector( simple && !previous_compound_flags; simple = simple->TagHistory()) previous_compound_flags |= ExtractCompoundFlags(*simple, context_->Mode()); - while (CSSSelector::RelationType combinator = ConsumeCombinator(range)) { + if (CSSSelector::RelationType combinator = ConsumeCombinator(range)) { + return ConsumePartialComplexSelector(range, combinator, std::move(selector), + previous_compound_flags); + } + + return selector; +} + +std::unique_ptr +CSSSelectorParser::ConsumePartialComplexSelector( + CSSParserTokenRange& range, + CSSSelector::RelationType& combinator, + std::unique_ptr selector, + unsigned& previous_compound_flags) { + do { std::unique_ptr next_selector = ConsumeCompoundSelector(range); if (!next_selector) @@ -280,7 +350,7 @@ std::unique_ptr CSSSelectorParser::ConsumeComplexSelector( end->SetTagHistory(std::move(selector)); selector = std::move(next_selector); - } + } while ((combinator = ConsumeCombinator(range))); return selector; } @@ -806,7 +876,7 @@ std::unique_ptr CSSSelectorParser::ConsumePseudo( std::unique_ptr selector_list = std::make_unique(); - *selector_list = ConsumeNestedSelectorList(block); + *selector_list = ConsumeRelativeSelectorList(block); if (!selector_list->IsValid() || !block.AtEnd()) return nullptr; diff --git a/blink/renderer/core/css/parser/css_selector_parser.h b/blink/renderer/core/css/parser/css_selector_parser.h index 589f8ef2c66..b2047bd21f8 100644 --- a/blink/renderer/core/css/parser/css_selector_parser.h +++ b/blink/renderer/core/css/parser/css_selector_parser.h @@ -61,9 +61,32 @@ class CORE_EXPORT CSSSelectorParser { // https://drafts.csswg.org/selectors/#typedef-forgiving-selector-list CSSSelectorList ConsumeForgivingComplexSelectorList(CSSParserTokenRange&); CSSSelectorList ConsumeForgivingCompoundSelectorList(CSSParserTokenRange&); + // https://drafts.csswg.org/selectors/#typedef-relative-selector-list + CSSSelectorList ConsumeRelativeSelectorList(CSSParserTokenRange&); + std::unique_ptr ConsumeRelativeSelector( + CSSParserTokenRange&); std::unique_ptr ConsumeComplexSelector( CSSParserTokenRange&); + + // ConsumePartialComplexSelector() method provides the common logic of + // consuming a complex selector and consuming a relative selector. + // + // After consuming the left-most combinator of a relative selector, we can + // consume the remaining selectors with the common logic. + // For example, after consuming the left-most combinator '~' of the relative + // selector '~ .a ~ .b', we can consume remaining selectors '.a ~ .b' + // with this method. + // + // After consuming the left-most compound selector and a combinator of a + // complex selector, we can also use this method to consume the remaining + // selectors of the complex selector. + std::unique_ptr ConsumePartialComplexSelector( + CSSParserTokenRange&, + CSSSelector::RelationType& /* current combinator */, + std::unique_ptr /* previous compound selector */, + unsigned& /* previous compound flags */); + std::unique_ptr ConsumeCompoundSelector( CSSParserTokenRange&); // This doesn't include element names, since they're handled specially diff --git a/blink/renderer/core/css/selector_checker.cc b/blink/renderer/core/css/selector_checker.cc index 3a94fbaf474..4244f2e1259 100644 --- a/blink/renderer/core/css/selector_checker.cc +++ b/blink/renderer/core/css/selector_checker.cc @@ -217,6 +217,7 @@ bool SelectorChecker::Match(const SelectorCheckingContext& context, if (context.selector->IsLastInTagHistory()) return false; } + HasMatchedCacheScope has_matched_cache_scope(&context.element->GetDocument()); return MatchSelector(context, result) == kSelectorMatches; } @@ -237,18 +238,8 @@ SelectorChecker::MatchStatus SelectorChecker::MatchSelector( if (sub_result.dynamic_pseudo != kPseudoIdNone) result.dynamic_pseudo = sub_result.dynamic_pseudo; - // Fix the perf test regression : https://crbug.com/1216100 - // Place the UNLIKELY conditional branch early to separate the - // ':has' argument matching sequence. - if (UNLIKELY(result.has_argument_leftmost_compound_matches)) { - if (context.selector->IsLastInTagHistory()) { - result.has_argument_leftmost_compound_matches->push_back(context.element); - return kSelectorMatches; - } - } else { - if (context.selector->IsLastInTagHistory()) - return kSelectorMatches; - } + if (context.selector->IsLastInTagHistory()) + return kSelectorMatches; MatchStatus match; if (context.selector->Relation() != CSSSelector::kSubSelector) { @@ -323,20 +314,15 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation( next_context.pseudo_id = kPseudoIdNone; switch (relation) { + case CSSSelector::kRelativeDescendant: + DCHECK(result.has_argument_leftmost_compound_matches); + result.has_argument_leftmost_compound_matches->push_back(context.element); + FALLTHROUGH; case CSSSelector::kDescendant: if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoScope) { if (next_context.selector->IsLastInTagHistory()) { - // Fix the perf test regression : https://crbug.com/1216100 - // Place the UNLIKELY conditional branch early to separate the - // ':has' argument matching sequence. - if (UNLIKELY(result.has_argument_leftmost_compound_matches)) { - result.has_argument_leftmost_compound_matches->push_back( - context.element); + if (context.scope->IsDocumentFragment()) return kSelectorMatches; - } else { - if (context.scope->IsDocumentFragment()) - return kSelectorMatches; - } } } for (next_context.element = ParentElement(next_context); @@ -351,21 +337,16 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation( next_context.is_inside_visited_link = false; } return kSelectorFailsCompletely; + case CSSSelector::kRelativeChild: + DCHECK(result.has_argument_leftmost_compound_matches); + result.has_argument_leftmost_compound_matches->push_back(context.element); + FALLTHROUGH; case CSSSelector::kChild: { if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoScope) { if (next_context.selector->IsLastInTagHistory()) { - // Place the UNLIKELY conditional branch early to separate the - // ':has' argument matching sequence. - if (UNLIKELY(result.has_argument_leftmost_compound_matches)) { - result.has_argument_leftmost_compound_matches->push_back( - context.element); - if (context.element->parentNode() == context.scope) - return kSelectorMatches; - } else { - if (context.element->parentNode() == context.scope && - context.scope->IsDocumentFragment()) - return kSelectorMatches; - } + if (context.element->parentNode() == context.scope && + context.scope->IsDocumentFragment()) + return kSelectorMatches; } } @@ -374,15 +355,11 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation( return kSelectorFailsCompletely; return MatchSelector(next_context, result); } + case CSSSelector::kRelativeDirectAdjacent: + DCHECK(result.has_argument_leftmost_compound_matches); + result.has_argument_leftmost_compound_matches->push_back(context.element); + FALLTHROUGH; case CSSSelector::kDirectAdjacent: - if (UNLIKELY(result.has_argument_leftmost_compound_matches)) { - if (next_context.selector->GetPseudoType() == - CSSSelector::kPseudoScope && - next_context.selector->IsLastInTagHistory()) { - result.has_argument_leftmost_compound_matches->push_back( - context.element); - } - } if (mode_ == kResolvingStyle) { if (ContainerNode* parent = context.element->ParentElementOrShadowRoot()) @@ -393,16 +370,11 @@ SelectorChecker::MatchStatus SelectorChecker::MatchForRelation( if (!next_context.element) return kSelectorFailsAllSiblings; return MatchSelector(next_context, result); - + case CSSSelector::kRelativeIndirectAdjacent: + DCHECK(result.has_argument_leftmost_compound_matches); + result.has_argument_leftmost_compound_matches->push_back(context.element); + FALLTHROUGH; case CSSSelector::kIndirectAdjacent: - if (UNLIKELY(result.has_argument_leftmost_compound_matches)) { - if (next_context.selector->GetPseudoType() == - CSSSelector::kPseudoScope && - next_context.selector->IsLastInTagHistory()) { - result.has_argument_leftmost_compound_matches->push_back( - context.element); - } - } if (mode_ == kResolvingStyle) { if (ContainerNode* parent = context.element->ParentElementOrShadowRoot()) @@ -683,6 +655,11 @@ bool SelectorChecker::CheckPseudoHas(const SelectorCheckingContext& context, DCHECK(document.GetHasMatchedCacheScope()); Element* element = context.element; SelectorCheckingContext sub_context(element); + // TODO(blee@igalia.com) Need to clarify the :scope dependency in relative + // selector definition. + // - spec : https://www.w3.org/TR/selectors-4/#relative + // - csswg issue : https://github.com/w3c/csswg-drafts/issues/6399 + sub_context.scope = context.scope; // sub_context.is_inside_visited_link is false (by default) to disable // :visited matching when it is in the :has argument @@ -710,105 +687,52 @@ bool SelectorChecker::CheckPseudoHas(const SelectorCheckingContext& context, sub_context.selector = selector; HasArgumentMatchContext has_argument_match_context(selector); - if (has_argument_match_context.WillNeverMatch()) - continue; - bool depth_fixed = has_argument_match_context.GetDepthFixed(); - // In case of some argument selectors containing :scope pseudo class - // compounded with other simple selectors or containing :scope pseudo - // which is not at leftmost, it is hard to get possibly matched elements - // from the argument selector matching result. - // In this case, it only mark the matched :has scope element. - bool mark_only_matched_scope_element = - has_argument_match_context.ContainsCompoundedScopeSelector() || - has_argument_match_context.ContainsNoLeftmostScopeSelector(); - - // Change the :scope in the :has argument selector. This is based on the - // selector4 spec. - // - :has : https://www.w3.org/TR/selectors-4/#relational - // - absolutizing : https://www.w3.org/TR/selectors-4/#absolutize - // > ':has represents an element if any of the relative selectors, - // when absolutized and evaluated with the element as the :scope - // elements, would match at least one element. - // But the :scope in the :has argument is a bit confused and arguable - // when we think about the current :scope usage with other selectors. + // To prevent incorrect 'NotChecked' status while matching ':has' pseudo + // class, change the argument matching context scope when the ':has' + // argument matching traversal cannot be fixed with a certain depth and + // adjacent distance. // - // Currently, :scope will be :root in CSS, but it will be the virtual - // scoping root in JS according to the following spec. - // - https://www.w3.org/TR/selectors-4/#scope-element - // - https://www.w3.org/TR/selectors-4/#virtual-scoping-root - // (e.g. :scope is root for the style rule ':scope .a {...}', but the - // :scope is 'a' element for the js comment 'a.querySelector(':scope .a')') + // For example, When we tries to match '.a:has(.b .c) .d' on below DOM, + //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // the ':has(.b .c)' selector will be checked on the #d3 element first + // because the selector '.a:has(.b .c) .d' will be matched upward from + // the #d5 element. + // 1) '.d' will be matched first on #d5 + // 2) move to the #d3 until the '.a' matched + // 3) match the ':has(.b .c)' on the #d3 + // 3.1) match the argument selector '.b .c' on the descendants of #d3 + // 4) move to the #d1 until the '.a' matched + // 5) match the ':has(.b .c)' on the #d1 + // 5.1) match the argument selector '.b .c' on the descendants of #d1 // - // By following the :has spec strictly, the '.a:has(> .b)' can be - // interpreted as 'select .a element that has .b child because the selector - // will be absolutized to '.a:has(:scope > .b)' before matching, and the - // :scope represents the .a element. This is intuitive and easily matches - // the simple :has definition of 'selecting ancestors or previous siblings' - // But this definition also has some ambiguous cases when we use the :scope - // in the middle of the argument selector or compounding a :scope with some - // other simple selectors. (e.g. '.a:has(.b :scope > .c)' is actually - // equivalent to the '.b .a:has(:scope > .c)'. And the ':has(:scope.a > .b)' - // is equivalent to the '.a:has(:scope > .b)) + // The argument selector '.b .c' will not be matched on the #d4 at this + // step if the argument matching scope is limited to #d3. But the '.b .c' + // can be matched on the #d4 if the argument matching scope is #d1. + // To prevent duplicated argument matching operation, the #d1 should be + // marked as 'Matched' at the step 3. // - // But by following the current :scope definition in the spec, this can be - // interpreted differently as follows. - // - The style rule '.a:has(:scope > .b) {...}' is interpreted as, 'style - // .a element which has .b element as its descendant, and the .b element - // should have :root as its parent. - // - The javascript call 'main.querySelector('.a:has(:scope > .b')' can - // be interpreted as 'Among the descendants of the main element, select - // .a element which has .b element as it's descendants, and the .b - // element should have main element as it's parent' - // This interpretation is too complex and hard to understand. And it - // is difficult to match the simple :has definition at above. - // - // Current implementation followed the :has definition for the :scope. - // TODO(blee@igalia.com) Need to clarify the spec related with :scope or - // absolutizing. - if (mark_only_matched_scope_element) { - sub_context.scope = element; + // TODO(blee@igalia.com) Need to clarify the :scope dependency in relative + // selector definition. + // - spec : https://www.w3.org/TR/selectors-4/#relative + // - csswg issue : https://github.com/w3c/csswg-drafts/issues/6399 + if (!depth_fixed) { + sub_context.relative_leftmost_element = + &element->ContainingTreeScope().RootNode(); + } else if (has_argument_match_context.GetAdjacentDistanceFixed()) { + sub_context.relative_leftmost_element = + Traversal::FirstChild(*element->parentNode()); } else { - // To prevent incorrect 'NotChecked' status while matching ':has' pseudo - // class, change the argument matching context scope when the ':has' - // argument matching traversal cannot be fixed with a certain depth and - // adjacent distance. - // - // For example, When we tries to match '.a:has(.b .c) .d' on below DOM, - //
- //
- //
- //
- //
- //
- //
- //
- //
- // the ':has(.b .c)' selector will be checked on the #d3 element first - // because the selector '.a:has(.b .c) .d' will be matched upward from - // the #d5 element. - // 1) '.d' will be matched first on #d5 - // 2) move to the #d3 until the '.a' matched - // 3) match the ':has(.b .c)' on the #d3 - // 3.1) match the argument selector '.b .c' on the descendants of #d3 - // 4) move to the #d1 until the '.a' matched - // 5) match the ':has(.b .c)' on the #d1 - // 5.1) match the argument selector '.b .c' on the descendants of #d1 - // - // The argument selector '.b .c' will not be matched on the #d4 at this - // step if the argument matching scope is limited to #d3. But the '.b .c' - // can be matched on the #d4 if the argument matching scope is #d1. - // To prevent duplicated argument matching operation, the #d1 should be - // marked as 'Matched' at the step 3. - if (!depth_fixed) { - sub_context.scope = &element->ContainingTreeScope().RootNode(); - } else if (has_argument_match_context.GetAdjacentDistanceFixed()) { - sub_context.scope = - Traversal::FirstChild(*element->parentNode()); - } else { - sub_context.scope = element; - } + sub_context.relative_leftmost_element = element; } bool selector_matched = false; @@ -823,18 +747,10 @@ bool SelectorChecker::CheckPseudoHas(const SelectorCheckingContext& context, sub_result.has_argument_leftmost_compound_matches = &has_argument_leftmost_compound_matches; - if (UNLIKELY(mark_only_matched_scope_element)) { - if (MatchSelector(sub_context, sub_result) != kSelectorMatches) - continue; - - map.Set(element, true); - return true; - } - MatchSelector(sub_context, sub_result); switch (has_argument_match_context.GetLeftMostRelation()) { - case CSSSelector::kDescendant: + case CSSSelector::kRelativeDescendant: map.insert(iterator.Get(), false); // Mark as checked if (!has_argument_leftmost_compound_matches.IsEmpty()) { sub_context.element = @@ -848,7 +764,7 @@ bool SelectorChecker::CheckPseudoHas(const SelectorCheckingContext& context, } } break; - case CSSSelector::kChild: + case CSSSelector::kRelativeChild: for (auto leftmost : has_argument_leftmost_compound_matches) { Element* parent = leftmost->parentElement(); map.Set(parent, true); // Mark as matched @@ -856,7 +772,7 @@ bool SelectorChecker::CheckPseudoHas(const SelectorCheckingContext& context, selector_matched = true; } break; - case CSSSelector::kDirectAdjacent: + case CSSSelector::kRelativeDirectAdjacent: if (!depth_fixed && !iterator.IsAtSiblingOfHasScope()) map.insert(iterator.Get(), false); // Mark as checked for (auto leftmost : has_argument_leftmost_compound_matches) { @@ -868,7 +784,7 @@ bool SelectorChecker::CheckPseudoHas(const SelectorCheckingContext& context, } } break; - case CSSSelector::kIndirectAdjacent: + case CSSSelector::kRelativeIndirectAdjacent: if (!depth_fixed) map.insert(iterator.Get(), false); // Mark as checked for (auto leftmost : has_argument_leftmost_compound_matches) { @@ -909,8 +825,6 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context, switch (selector.GetPseudoType()) { case CSSSelector::kPseudoNot: return CheckPseudoNot(context, result); - case CSSSelector::kPseudoHas: - return CheckPseudoHas(context, result); case CSSSelector::kPseudoEmpty: { bool is_empty = true; bool has_whitespace = false; @@ -1259,8 +1173,6 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context, return vtt_element && vtt_element->IsPastNode(); } case CSSSelector::kPseudoScope: - if (UNLIKELY(result.has_argument_leftmost_compound_matches)) - return context.scope == &element; if (!context.scope) return false; if (context.scope == &element.GetDocument()) @@ -1330,6 +1242,11 @@ bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context, if (const auto* dialog_element = DynamicTo(element)) return dialog_element->IsModal(); return false; + case CSSSelector::kPseudoHas: + return CheckPseudoHas(context, result); + case CSSSelector::kPseudoRelativeLeftmost: + DCHECK(context.relative_leftmost_element); + return context.relative_leftmost_element == &element; case CSSSelector::kPseudoUnknown: default: NOTREACHED(); diff --git a/blink/renderer/core/css/selector_checker.h b/blink/renderer/core/css/selector_checker.h index 329b7a629a3..5c8504034f5 100644 --- a/blink/renderer/core/css/selector_checker.h +++ b/blink/renderer/core/css/selector_checker.h @@ -121,6 +121,7 @@ class CORE_EXPORT SelectorChecker { Element* vtt_originating_element = nullptr; bool in_nested_complex_selector = false; bool is_inside_visited_link = false; + const ContainerNode* relative_leftmost_element = nullptr; }; struct MatchResult { diff --git a/blink/renderer/core/css/selector_filter.cc b/blink/renderer/core/css/selector_filter.cc index 0c67f267db3..67eb6277d18 100644 --- a/blink/renderer/core/css/selector_filter.cc +++ b/blink/renderer/core/css/selector_filter.cc @@ -208,6 +208,12 @@ void SelectorFilter::CollectIdentifierHashes( skip_over_subselectors = false; CollectDescendantSelectorIdentifierHashes(*current, hash); break; + case CSSSelector::kRelativeDescendant: + case CSSSelector::kRelativeChild: + case CSSSelector::kRelativeDirectAdjacent: + case CSSSelector::kRelativeIndirectAdjacent: + NOTREACHED(); + break; } if (hash == end) return; diff --git a/blink/renderer/core/inspector/inspector_trace_events.cc b/blink/renderer/core/inspector/inspector_trace_events.cc index df595151f0d..f6bc8c36f28 100644 --- a/blink/renderer/core/inspector/inspector_trace_events.cc +++ b/blink/renderer/core/inspector/inspector_trace_events.cc @@ -392,6 +392,7 @@ const char* PseudoTypeToString(CSSSelector::PseudoType pseudo_type) { DEFINE_STRING_MAPPING(PseudoSpellingError) DEFINE_STRING_MAPPING(PseudoGrammarError) DEFINE_STRING_MAPPING(PseudoHas) + DEFINE_STRING_MAPPING(PseudoRelativeLeftmost) #undef DEFINE_STRING_MAPPING } diff --git a/blink/web_tests/external/wpt/css/selectors/has-argument-with-explicit-scope.tentative.html b/blink/web_tests/external/wpt/css/selectors/has-argument-with-explicit-scope.tentative.html new file mode 100644 index 00000000000..b5773988cc1 --- /dev/null +++ b/blink/web_tests/external/wpt/css/selectors/has-argument-with-explicit-scope.tentative.html @@ -0,0 +1,69 @@ + + +:has pseudo class behavior with explicit ':scope' in its argument + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/blink/web_tests/external/wpt/css/selectors/has-basic-expected.txt b/blink/web_tests/external/wpt/css/selectors/has-basic-expected.txt deleted file mode 100644 index 190834b9cad..00000000000 --- a/blink/web_tests/external/wpt/css/selectors/has-basic-expected.txt +++ /dev/null @@ -1,26 +0,0 @@ -This is a testharness.js-based test. -PASS :has(#a) matches expected elements -PASS :has(.ancestor) matches expected elements -PASS :has(.target) matches expected elements -PASS :has(.descendant) matches expected elements -PASS .parent:has(.target) matches expected elements -PASS :has(.sibling ~ .target) matches expected elements -PASS .parent:has(.sibling ~ .target) matches expected elements -PASS :has(:is(.target ~ .sibling .descendant)) matches expected elements -PASS .parent:has(:is(.target ~ .sibling .descendant)) matches expected elements -PASS .sibling:has(.descendant) ~ .target matches expected elements -PASS :has(.sibling:has(.descendant) ~ .target) matches expected elements -PASS :has(.sibling:has(.descendant) ~ .target) ~ .parent > .descendant matches expected elements -PASS :has(:scope .target) matches expected elements -PASS :has(:scope > .parent) matches expected elements -FAIL :has(> .target) matches expected elements Failed to execute 'querySelectorAll' on 'Element': ':has(> .target)' is not a valid selector. -PASS :has(:scope > .target) matches expected elements -FAIL :has(+ #h) matches expected elements Failed to execute 'querySelectorAll' on 'Element': ':has(+ #h)' is not a valid selector. -PASS :has(:scope + #h) matches expected elements -FAIL .parent:has(~ #h) matches expected elements Failed to execute 'querySelectorAll' on 'Element': '.parent:has(~ #h)' is not a valid selector. -PASS .parent:has(:scope ~ #h) matches expected elements -PASS .sibling:has(.descendant) matches expected element -PASS closest(.ancestor:has(.descendant)) returns expected element -PASS :has(.target ~ .sibling .descendant) matches expectedly -Harness: the test ran to completion. - diff --git a/blink/web_tests/external/wpt/css/selectors/has-basic.html b/blink/web_tests/external/wpt/css/selectors/has-basic.html index 1cb1ec6bcb8..b346af6768a 100644 --- a/blink/web_tests/external/wpt/css/selectors/has-basic.html +++ b/blink/web_tests/external/wpt/css/selectors/has-basic.html @@ -74,14 +74,11 @@ test_selector_all( ':has(.sibling:has(.descendant) ~ .target) ~ .parent > .descendant', [g, i, j]); - test_selector_all(':has(:scope .target)', [a, b, f, h]); - test_selector_all(':has(:scope > .parent)', [a]); + test_selector_all(':has(> .parent)', [a]); test_selector_all(':has(> .target)', [b, f, h]); - test_selector_all(':has(:scope > .target)', [b, f, h]); + test_selector_all(':has(> .parent, > .target)', [a, b, f, h]); test_selector_all(':has(+ #h)', [f]); - test_selector_all(':has(:scope + #h)', [f]); test_selector_all('.parent:has(~ #h)', [b, f]); - test_selector_all('.parent:has(:scope ~ #h)', [b, f]); test_selector('.sibling:has(.descendant)', c); test_closest(k, '.ancestor:has(.descendant)', h); test_matches(h, ':has(.target ~ .sibling .descendant)', true); diff --git a/blink/web_tests/external/wpt/css/selectors/has-relative-argument.html b/blink/web_tests/external/wpt/css/selectors/has-relative-argument.html index 2ce3fd9124d..b4ba20f664a 100644 --- a/blink/web_tests/external/wpt/css/selectors/has-relative-argument.html +++ b/blink/web_tests/external/wpt/css/selectors/has-relative-argument.html @@ -138,139 +138,53 @@ }, `${selector} matches expected elements`); } - test_selector_all('.x:has(:scope .a)', [d02, d06, d07, d09, d12]); - test_selector_all('.x:has(:scope .a > .b)', [d09]); - test_selector_all('.x:has(:scope .a .b)', [d09, d12]); - test_selector_all('.x:has(:scope .a + .b)', [d12]); - test_selector_all('.x:has(:scope .a ~ .b)', [d02, d12]); - test_selector_all(':has(.x:scope .a)', [d02, d06, d07, d09, d12]); - test_selector_all(':has(.x:scope .a > .b)', [d09]); - test_selector_all(':has(.x:scope .a .b)', [d09, d12]); - test_selector_all(':has(.x:scope .a + .b)', [d12]); - test_selector_all(':has(.x:scope .a ~ .b)', [d02, d12]); - test_selector_all(':has(:scope.x .a)', [d02, d06, d07, d09, d12]); - test_selector_all(':has(:scope.x .a > .b)', [d09]); - test_selector_all(':has(:scope.x .a .b)', [d09, d12]); - test_selector_all(':has(:scope.x .a + .b)', [d12]); - test_selector_all(':has(:scope.x .a ~ .b)', [d02, d12]); + test_selector_all('.x:has(.a)', [d02, d06, d07, d09, d12]); + test_selector_all('.x:has(.a > .b)', [d09]); + test_selector_all('.x:has(.a .b)', [d09, d12]); + test_selector_all('.x:has(.a + .b)', [d12]); + test_selector_all('.x:has(.a ~ .b)', [d02, d12]); - test_selector_all('.x:has(:scope > .a)', [d02, d07, d09, d12]); - test_selector_all('.x:has(:scope > .a > .b)', [d09]); - test_selector_all('.x:has(:scope > .a .b)', [d09, d12]); - test_selector_all('.x:has(:scope > .a + .b)', [d12]); - test_selector_all('.x:has(:scope > .a ~ .b)', [d02, d12]); - test_selector_all(':has(.x:scope > .a)', [d02, d07, d09, d12]); - test_selector_all(':has(.x:scope > .a > .b)', [d09]); - test_selector_all(':has(.x:scope > .a .b)', [d09, d12]); - test_selector_all(':has(.x:scope > .a + .b)', [d12]); - test_selector_all(':has(.x:scope > .a ~ .b)', [d02, d12]); - test_selector_all(':has(:scope.x > .a)', [d02, d07, d09, d12]); - test_selector_all(':has(:scope.x > .a > .b)', [d09]); - test_selector_all(':has(:scope.x > .a .b)', [d09, d12]); - test_selector_all(':has(:scope.x > .a + .b)', [d12]); - test_selector_all(':has(:scope.x > .a ~ .b)', [d02, d12]); + test_selector_all('.x:has(> .a)', [d02, d07, d09, d12]); + test_selector_all('.x:has(> .a > .b)', [d09]); + test_selector_all('.x:has(> .a .b)', [d09, d12]); + test_selector_all('.x:has(> .a + .b)', [d12]); + test_selector_all('.x:has(> .a ~ .b)', [d02, d12]); - test_selector_all('.x:has(:scope + .a)', [d19, d21, d24, d28, d32, d37, d40, d46]); - test_selector_all('.x:has(:scope + .a > .b)', [d21]); - test_selector_all('.x:has(:scope + .a .b)', [d21, d24]); - test_selector_all('.x:has(:scope + .a + .b)', [d28, d32, d37]); - test_selector_all('.x:has(:scope + .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]); - test_selector_all(':has(.x:scope + .a)', [d19, d21, d24, d28, d32, d37, d40, d46]); - test_selector_all(':has(.x:scope + .a > .b)', [d21]); - test_selector_all(':has(.x:scope + .a .b)', [d21, d24]); - test_selector_all(':has(.x:scope + .a + .b)', [d28, d32, d37]); - test_selector_all(':has(.x:scope + .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]); - test_selector_all(':has(:scope.x + .a)', [d19, d21, d24, d28, d32, d37, d40, d46]); - test_selector_all(':has(:scope.x + .a > .b)', [d21]); - test_selector_all(':has(:scope.x + .a .b)', [d21, d24]); - test_selector_all(':has(:scope.x + .a + .b)', [d28, d32, d37]); - test_selector_all(':has(:scope.x + .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]); + test_selector_all('.x:has(+ .a)', [d19, d21, d24, d28, d32, d37, d40, d46]); + test_selector_all('.x:has(+ .a > .b)', [d21]); + test_selector_all('.x:has(+ .a .b)', [d21, d24]); + test_selector_all('.x:has(+ .a + .b)', [d28, d32, d37]); + test_selector_all('.x:has(+ .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]); - test_selector_all('.x:has(:scope ~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]); - test_selector_all('.x:has(:scope ~ .a > .b)', [d18, d19, d21]); - test_selector_all('.x:has(:scope ~ .a .b)', [d18, d19, d21, d24]); - test_selector_all('.x:has(:scope ~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]); - test_selector_all('.x:has(:scope ~ .a + .b > .c)', [d18, d19, d21, d24, d28]); - test_selector_all('.x:has(:scope ~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]); - test_selector_all(':has(.x:scope ~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]); - test_selector_all(':has(.x:scope ~ .a > .b)', [d18, d19, d21]); - test_selector_all(':has(.x:scope ~ .a .b)', [d18, d19, d21, d24]); - test_selector_all(':has(.x:scope ~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]); - test_selector_all(':has(.x:scope ~ .a + .b > .c)', [d18, d19, d21, d24, d28]); - test_selector_all(':has(.x:scope ~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]); - test_selector_all(':has(:scope.x ~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]); - test_selector_all(':has(:scope.x ~ .a > .b)', [d18, d19, d21]); - test_selector_all(':has(:scope.x ~ .a .b)', [d18, d19, d21, d24]); - test_selector_all(':has(:scope.x ~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]); - test_selector_all(':has(:scope.x ~ .a + .b > .c)', [d18, d19, d21, d24, d28]); - test_selector_all(':has(:scope.x ~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]); + test_selector_all('.x:has(~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]); + test_selector_all('.x:has(~ .a > .b)', [d18, d19, d21]); + test_selector_all('.x:has(~ .a .b)', [d18, d19, d21, d24]); + test_selector_all('.x:has(~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]); + test_selector_all('.x:has(~ .a + .b > .c)', [d18, d19, d21, d24, d28]); + test_selector_all('.x:has(~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]); test_selector_all('.x:has(.d .e)', [d48, d49, d50]); test_selector_all('.x:has(.d .e) .f', [d54]); - test_selector_all('.x:has(:scope .d .e)', [d48, d49, d50]); - test_selector_all('.x:has(:scope .d .e) .f', [d54]); - test_selector_all('.x:has(:scope > .d)', [d49, d50]); - test_selector_all('.x:has(:scope > .d) .f', [d54]); - test_selector_all('.x:has(:scope ~ .d ~ .e)', [d48, d55, d56]); - test_selector_all('.x:has(:scope ~ .d ~ .e) ~ .f', [d60]); - test_selector_all('.x:has(:scope + .d ~ .e)', [d55, d56]); - test_selector_all('.x:has(:scope + .d ~ .e) ~ .f', [d60]); - test_selector_all(':has(.x:scope .d .e)', [d48, d49, d50]); - test_selector_all(':has(.x:scope .d .e) .f', [d54]); - test_selector_all(':has(.x:scope > .d)', [d49, d50]); - test_selector_all(':has(.x:scope > .d) .f', [d54]); - test_selector_all(':has(.x:scope ~ .d ~ .e)', [d48, d55, d56]); - test_selector_all(':has(.x:scope ~ .d ~ .e) ~ .f', [d60]); - test_selector_all(':has(.x:scope + .d ~ .e)', [d55, d56]); - test_selector_all(':has(.x:scope + .d ~ .e) ~ .f', [d60]); - test_selector_all(':has(:scope.x .d .e)', [d48, d49, d50]); - test_selector_all(':has(:scope.x .d .e) .f', [d54]); - test_selector_all(':has(:scope.x > .d)', [d49, d50]); - test_selector_all(':has(:scope.x > .d) .f', [d54]); - test_selector_all(':has(:scope.x ~ .d ~ .e)', [d48, d55, d56]); - test_selector_all(':has(:scope.x ~ .d ~ .e) ~ .f', [d60]); - test_selector_all(':has(:scope.x + .d ~ .e)', [d55, d56]); - test_selector_all(':has(:scope.x + .d ~ .e) ~ .f', [d60]); + test_selector_all('.x:has(> .d)', [d49, d50]); + test_selector_all('.x:has(> .d) .f', [d54]); + test_selector_all('.x:has(~ .d ~ .e)', [d48, d55, d56]); + test_selector_all('.x:has(~ .d ~ .e) ~ .f', [d60]); + test_selector_all('.x:has(+ .d ~ .e)', [d55, d56]); + test_selector_all('.x:has(+ .d ~ .e) ~ .f', [d60]); - test_selector_all('.y:has(:scope > .g .h)', [d63, d71]) - test_selector_all('.y:has(:scope .g .h)', [d63, d68, d71]) - test_selector_all('.y:has(:scope > .g .h) .i', [d67, d75]) - test_selector_all('.y:has(:scope .g .h) .i', [d67, d75]) - test_selector_all('.x:has(:scope + .y:has(:scope > .g .h) .i)', [d62, d70]) - test_selector_all('.x:has(:scope + .y:has(:scope .g .h) .i)', [d62, d63, d70]) - test_selector_all('.x:has(:scope + .y:has(:scope > .g .h) .i) ~ .j', [d77, d80]) - test_selector_all('.x:has(:scope + .y:has(:scope .g .h) .i) ~ .j', [d77, d80]) - test_selector_all('.x:has(:scope ~ .y:has(:scope > .g .h) .i)', [d61, d62, d69, d70]) - test_selector_all('.x:has(:scope ~ .y:has(:scope .g .h) .i)', [d61, d62, d63, d69, d70]) - test_selector_all(':has(.y:scope > .g .h)', [d63, d71]) - test_selector_all(':has(.y:scope .g .h)', [d63, d68, d71]) - test_selector_all(':has(.y:scope > .g .h) .i', [d67, d75]) - test_selector_all(':has(.y:scope .g .h) .i', [d67, d75]) - test_selector_all(':has(.x:scope + :has(.y:scope > .g .h) .i)', [d62, d70]) - test_selector_all(':has(.x:scope + :has(.y:scope .g .h) .i)', [d62, d63, d70]) - test_selector_all(':has(.x:scope + :has(.y:scope > .g .h) .i) ~ .j', [d77, d80]) - test_selector_all(':has(.x:scope + :has(.y:scope .g .h) .i) ~ .j', [d77, d80]) - test_selector_all(':has(.x:scope ~ :has(.y:scope > .g .h) .i)', [d61, d62, d69, d70]) - test_selector_all(':has(.x:scope ~ :has(.y:scope .g .h) .i)', [d61, d62, d63, d69, d70]) - test_selector_all(':has(:scope.y > .g .h)', [d63, d71]) - test_selector_all(':has(:scope.y .g .h)', [d63, d68, d71]) - test_selector_all(':has(:scope.y > .g .h) .i', [d67, d75]) - test_selector_all(':has(:scope.y .g .h) .i', [d67, d75]) - test_selector_all(':has(:scope.x + :has(:scope.y > .g .h) .i)', [d62, d70]) - test_selector_all(':has(:scope.x + :has(:scope.y .g .h) .i)', [d62, d63, d70]) - test_selector_all(':has(:scope.x + :has(:scope.y > .g .h) .i) ~ .j', [d77, d80]) - test_selector_all(':has(:scope.x + :has(:scope.y .g .h) .i) ~ .j', [d77, d80]) - test_selector_all(':has(:scope.x ~ :has(:scope.y > .g .h) .i)', [d61, d62, d69, d70]) - test_selector_all(':has(:scope.x ~ :has(:scope.y .g .h) .i)', [d61, d62, d63, d69, d70]) + test_selector_all('.y:has(> .g .h)', [d63, d71]) + test_selector_all('.y:has(.g .h)', [d63, d68, d71]) + test_selector_all('.y:has(> .g .h) .i', [d67, d75]) + test_selector_all('.y:has(.g .h) .i', [d67, d75]) + test_selector_all('.x:has(+ .y:has(> .g .h) .i)', [d62, d70]) + test_selector_all('.x:has(+ .y:has(.g .h) .i)', [d62, d63, d70]) + test_selector_all('.x:has(+ .y:has(> .g .h) .i) ~ .j', [d77, d80]) + test_selector_all('.x:has(+ .y:has(.g .h) .i) ~ .j', [d77, d80]) + test_selector_all('.x:has(~ .y:has(> .g .h) .i)', [d61, d62, d69, d70]) + test_selector_all('.x:has(~ .y:has(.g .h) .i)', [d61, d62, d63, d69, d70]) - test_selector_all('.x:has(.d :scope .e)', [d51, d52]) - test_selector_all(':has(.d .x:scope .e)', [d51, d52]) - test_selector_all(':has(.d :scope.x .e)', [d51, d52]) + test_selector_all('.d .x:has(.e)', [d51, d52]) - test_selector_all('.x:has(.d ~ :scope ~ .e)', [d57, d58]) - test_selector_all(':has(.d ~ .x:scope ~ .e)', [d57, d58]) - test_selector_all(':has(.d ~ :scope.x ~ .e)', [d57, d58]) + test_selector_all('.d ~ .x:has(~ .e)', [d57, d58]) - test_selector_all(':has(:scope .d :scope)', []) - test_selector_all(':has(:scope ~ .d ~ :scope)', []) diff --git a/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-expected.txt b/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-expected.txt deleted file mode 100644 index 726a34e2d86..00000000000 --- a/blink/web_tests/external/wpt/css/selectors/parsing/parse-has-expected.txt +++ /dev/null @@ -1,42 +0,0 @@ -This is a testharness.js-based test. -PASS ":has(a)" should be a valid selector -PASS ":has(#a)" should be a valid selector -PASS ":has(.a)" should be a valid selector -PASS ":has([a])" should be a valid selector -PASS ":has([a=\"b\"])" should be a valid selector -PASS ":has([a|=\"b\"])" should be a valid selector -PASS ":has(:hover)" should be a valid selector -PASS "*:has(.a)" should be a valid selector -PASS ".a:has(.b)" should be a valid selector -PASS ".a:has(:scope .b)" should be a valid selector -PASS ":has(.a:scope .b)" should be a valid selector -PASS ":has(.a .b:scope)" should be a valid selector -FAIL ".a:has(> .b)" should be a valid selector Failed to execute 'querySelector' on 'Document': '.a:has(> .b)' is not a valid selector. -PASS ".a:has(:scope > .b)" should be a valid selector -PASS ":has(.a:scope > .b)" should be a valid selector -FAIL ":has(> .a .b:scope)" should be a valid selector Failed to execute 'querySelector' on 'Document': ':has(> .a .b:scope)' is not a valid selector. -FAIL ".a:has(~ .b)" should be a valid selector Failed to execute 'querySelector' on 'Document': '.a:has(~ .b)' is not a valid selector. -PASS ".a:has(:scope ~ .b)" should be a valid selector -PASS ":has(.a:scope ~ .b)" should be a valid selector -FAIL ":has(~ .a .b:scope)" should be a valid selector Failed to execute 'querySelector' on 'Document': ':has(~ .a .b:scope)' is not a valid selector. -FAIL ".a:has(+ .b)" should be a valid selector Failed to execute 'querySelector' on 'Document': '.a:has(+ .b)' is not a valid selector. -PASS ".a:has(:scope + .b)" should be a valid selector -PASS ":has(.a:scope + .b)" should be a valid selector -FAIL ":has(+ .a .b:scope)" should be a valid selector Failed to execute 'querySelector' on 'Document': ':has(+ .a .b:scope)' is not a valid selector. -PASS ".a:has(:scope .b :scope)" should be a valid selector -PASS ".a:has(.b) .c" should be a valid selector -PASS ".a .b:has(.c)" should be a valid selector -PASS ".a .b:has(.c .d)" should be a valid selector -PASS ".a .b:has(.c .d) .e" should be a valid selector -PASS ".a:has(.b:has(.c))" should be a valid selector -PASS ".a:has(.b:is(.c .d))" should be a valid selector -PASS ".a:has(.b:is(.c:has(.d) .e))" should be a valid selector -PASS ".a:is(.b:has(.c) .d)" should be a valid selector -PASS ".a:not(:has(.b))" should be a valid selector -PASS ".a:has(:not(.b))" should be a valid selector -PASS ".a:has(.b):has(.c)" should be a valid selector -PASS "*|*:has(*)" should be a valid selector -PASS ":has(*|*)" should be a valid selector -PASS ".a:has()" should be an invalid selector -Harness: the test ran to completion. - diff --git a/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html b/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html index 88366c3429e..1db87c4c008 100644 --- a/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html +++ b/blink/web_tests/external/wpt/css/selectors/parsing/parse-has.html @@ -16,22 +16,9 @@ test_valid_selector(':has(:hover)'); test_valid_selector('*:has(.a)', ['*:has(.a)', ':has(.a)']); test_valid_selector('.a:has(.b)'); - test_valid_selector('.a:has(:scope .b)'); - test_valid_selector(':has(.a:scope .b)'); - test_valid_selector(':has(.a .b:scope)'); test_valid_selector('.a:has(> .b)'); - test_valid_selector('.a:has(:scope > .b)'); - test_valid_selector(':has(.a:scope > .b)'); - test_valid_selector(':has(> .a .b:scope)'); test_valid_selector('.a:has(~ .b)'); - test_valid_selector('.a:has(:scope ~ .b)'); - test_valid_selector(':has(.a:scope ~ .b)'); - test_valid_selector(':has(~ .a .b:scope)'); test_valid_selector('.a:has(+ .b)'); - test_valid_selector('.a:has(:scope + .b)'); - test_valid_selector(':has(.a:scope + .b)'); - test_valid_selector(':has(+ .a .b:scope)'); - test_valid_selector('.a:has(:scope .b :scope)'); test_valid_selector('.a:has(.b) .c'); test_valid_selector('.a .b:has(.c)'); test_valid_selector('.a .b:has(.c .d)'); diff --git a/blink/web_tests/external/wpt/dom/nodes/Element-closest-expected.txt b/blink/web_tests/external/wpt/dom/nodes/Element-closest-expected.txt deleted file mode 100644 index f9f5b3bdd62..00000000000 --- a/blink/web_tests/external/wpt/dom/nodes/Element-closest-expected.txt +++ /dev/null @@ -1,32 +0,0 @@ -This is a testharness.js-based test. -PASS Element.closest with context node 'test12' and selector 'select' -PASS Element.closest with context node 'test13' and selector 'fieldset' -PASS Element.closest with context node 'test13' and selector 'div' -PASS Element.closest with context node 'test3' and selector 'body' -PASS Element.closest with context node 'test4' and selector '[default]' -PASS Element.closest with context node 'test4' and selector '[selected]' -PASS Element.closest with context node 'test11' and selector '[selected]' -PASS Element.closest with context node 'test12' and selector '[name="form-a"]' -PASS Element.closest with context node 'test13' and selector 'form[name="form-a"]' -PASS Element.closest with context node 'test9' and selector 'input[required]' -PASS Element.closest with context node 'test9' and selector 'select[required]' -PASS Element.closest with context node 'test13' and selector 'div:not(.div1)' -PASS Element.closest with context node 'test6' and selector 'div.div3' -PASS Element.closest with context node 'test1' and selector 'div#test7' -PASS Element.closest with context node 'test12' and selector '.div3 > .div2' -PASS Element.closest with context node 'test12' and selector '.div3 > .div1' -PASS Element.closest with context node 'test9' and selector 'form > input[required]' -PASS Element.closest with context node 'test12' and selector 'fieldset > select[required]' -PASS Element.closest with context node 'test6' and selector 'input + fieldset' -PASS Element.closest with context node 'test3' and selector 'form + form' -PASS Element.closest with context node 'test5' and selector 'form + form' -PASS Element.closest with context node 'test10' and selector ':empty' -PASS Element.closest with context node 'test11' and selector ':last-child' -PASS Element.closest with context node 'test12' and selector ':first-child' -PASS Element.closest with context node 'test11' and selector ':invalid' -PASS Element.closest with context node 'test4' and selector ':scope' -PASS Element.closest with context node 'test4' and selector 'select > :scope' -PASS Element.closest with context node 'test4' and selector 'div > :scope' -FAIL Element.closest with context node 'test4' and selector ':has(> :scope)' Failed to execute 'closest' on 'Element': ':has(> :scope)' is not a valid selector. -Harness: the test ran to completion. -