1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
4  * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com)
5  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc.
6  * All rights reserved.
7  * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
8  * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org>
9  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved.
10  * (http://www.torchmobile.com/)
11  * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
12  * Copyright (C) Research In Motion Limited 2011. All rights reserved.
13  *
14  * This library is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU Library General Public
16  * License as published by the Free Software Foundation; either
17  * version 2 of the License, or (at your option) any later version.
18  *
19  * This library is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22  * Library General Public License for more details.
23  *
24  * You should have received a copy of the GNU Library General Public License
25  * along with this library; see the file COPYING.LIB.  If not, write to
26  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
27  * Boston, MA 02110-1301, USA.
28  */
29 
30 #include "third_party/blink/renderer/core/css/selector_checker.h"
31 
32 #include "base/auto_reset.h"
33 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
34 #include "third_party/blink/renderer/core/css/css_selector_list.h"
35 #include "third_party/blink/renderer/core/css/part_names.h"
36 #include "third_party/blink/renderer/core/css/style_engine.h"
37 #include "third_party/blink/renderer/core/dom/document.h"
38 #include "third_party/blink/renderer/core/dom/element.h"
39 #include "third_party/blink/renderer/core/dom/element_traversal.h"
40 #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
41 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
42 #include "third_party/blink/renderer/core/dom/nth_index_cache.h"
43 #include "third_party/blink/renderer/core/dom/shadow_root.h"
44 #include "third_party/blink/renderer/core/dom/text.h"
45 #include "third_party/blink/renderer/core/dom/v0_insertion_point.h"
46 #include "third_party/blink/renderer/core/editing/frame_selection.h"
47 #include "third_party/blink/renderer/core/frame/local_frame.h"
48 #include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h"
49 #include "third_party/blink/renderer/core/frame/settings.h"
50 #include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
51 #include "third_party/blink/renderer/core/html/custom/element_internals.h"
52 #include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
53 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
54 #include "third_party/blink/renderer/core/html/forms/html_option_element.h"
55 #include "third_party/blink/renderer/core/html/forms/html_select_element.h"
56 #include "third_party/blink/renderer/core/html/html_dialog_element.h"
57 #include "third_party/blink/renderer/core/html/html_document.h"
58 #include "third_party/blink/renderer/core/html/html_frame_element_base.h"
59 #include "third_party/blink/renderer/core/html/html_slot_element.h"
60 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
61 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
62 #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
63 #include "third_party/blink/renderer/core/html/track/vtt/vtt_element.h"
64 #include "third_party/blink/renderer/core/html_names.h"
65 #include "third_party/blink/renderer/core/page/focus_controller.h"
66 #include "third_party/blink/renderer/core/page/page.h"
67 #include "third_party/blink/renderer/core/page/spatial_navigation.h"
68 #include "third_party/blink/renderer/core/page/spatial_navigation_controller.h"
69 #include "third_party/blink/renderer/core/probe/core_probes.h"
70 #include "third_party/blink/renderer/core/scroll/scrollable_area.h"
71 #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
72 #include "third_party/blink/renderer/core/style/computed_style.h"
73 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
74 
75 namespace blink {
76 
IsFrameFocused(const Element & element)77 static bool IsFrameFocused(const Element& element) {
78   return element.GetDocument().GetFrame() && element.GetDocument()
79                                                  .GetFrame()
80                                                  ->Selection()
81                                                  .FrameIsFocusedAndActive();
82 }
83 
MatchesSpatialNavigationFocusPseudoClass(const Element & element)84 static bool MatchesSpatialNavigationFocusPseudoClass(const Element& element) {
85   auto* option_element = DynamicTo<HTMLOptionElement>(element);
86   return option_element && option_element->SpatialNavigationFocused() &&
87          IsFrameFocused(element);
88 }
89 
MatchesHasDatalistPseudoClass(const Element & element)90 static bool MatchesHasDatalistPseudoClass(const Element& element) {
91   auto* html_input_element = DynamicTo<HTMLInputElement>(element);
92   return html_input_element && html_input_element->list();
93 }
94 
MatchesListBoxPseudoClass(const Element & element)95 static bool MatchesListBoxPseudoClass(const Element& element) {
96   auto* html_select_element = DynamicTo<HTMLSelectElement>(element);
97   return html_select_element && !html_select_element->UsesMenuList();
98 }
99 
MatchesMultiSelectFocusPseudoClass(const Element & element)100 static bool MatchesMultiSelectFocusPseudoClass(const Element& element) {
101   auto* option_element = DynamicTo<HTMLOptionElement>(element);
102   return option_element && option_element->IsMultiSelectFocused() &&
103          IsFrameFocused(element);
104 }
105 
MatchesTagName(const Element & element,const QualifiedName & tag_q_name)106 static bool MatchesTagName(const Element& element,
107                            const QualifiedName& tag_q_name) {
108   if (tag_q_name == AnyQName())
109     return true;
110   const AtomicString& local_name = tag_q_name.LocalName();
111   if (local_name != CSSSelector::UniversalSelectorAtom() &&
112       local_name != element.localName()) {
113     if (element.IsHTMLElement() || !IsA<HTMLDocument>(element.GetDocument()))
114       return false;
115     // Non-html elements in html documents are normalized to their camel-cased
116     // version during parsing if applicable. Yet, type selectors are lower-cased
117     // for selectors in html documents. Compare the upper case converted names
118     // instead to allow matching SVG elements like foreignObject.
119     if (element.TagQName().LocalNameUpper() != tag_q_name.LocalNameUpper())
120       return false;
121   }
122   const AtomicString& namespace_uri = tag_q_name.NamespaceURI();
123   return namespace_uri == g_star_atom ||
124          namespace_uri == element.namespaceURI();
125 }
126 
ParentElement(const SelectorChecker::SelectorCheckingContext & context)127 static Element* ParentElement(
128     const SelectorChecker::SelectorCheckingContext& context) {
129   // - If context.scope is a shadow root, we should walk up to its shadow host.
130   // - If context.scope is some element in some shadow tree and querySelector
131   //   initialized the context, e.g. shadowRoot.querySelector(':host *'),
132   //   (a) context.element has the same treescope as context.scope, need to walk
133   //       up to its shadow host.
134   //   (b) Otherwise, should not walk up from a shadow root to a shadow host.
135   if (context.scope &&
136       (context.scope == context.element->ContainingShadowRoot() ||
137        context.scope->GetTreeScope() == context.element->GetTreeScope()))
138     return context.element->ParentOrShadowHostElement();
139   return context.element->parentElement();
140 }
141 
142 // If context has scope, return slot that matches the scope, otherwise return
143 // the assigned slot for scope-less matching of ::slotted pseudo element.
FindSlotElementInScope(const SelectorChecker::SelectorCheckingContext & context)144 static const HTMLSlotElement* FindSlotElementInScope(
145     const SelectorChecker::SelectorCheckingContext& context) {
146   if (!context.scope)
147     return context.element->AssignedSlot();
148 
149   for (const HTMLSlotElement* slot = context.element->AssignedSlot(); slot;
150        slot = slot->AssignedSlot()) {
151     if (slot->GetTreeScope() == context.scope->GetTreeScope())
152       return slot;
153   }
154   return nullptr;
155 }
156 
ScopeContainsLastMatchedElement(const SelectorChecker::SelectorCheckingContext & context)157 static bool ScopeContainsLastMatchedElement(
158     const SelectorChecker::SelectorCheckingContext& context) {
159   // If this context isn't scoped, skip checking.
160   if (!context.scope)
161     return true;
162 
163   if (context.scope->GetTreeScope() == context.element->GetTreeScope())
164     return true;
165 
166   // The scope-contains-last-matched-element check is only relevant for
167   // ShadowDOM V0 features (::content, ::shadow, /deep/), and the selector
168   // parser does not allow mixing ShadowDOM V0 with nested complex
169   // selectors, hence we can skip the check inside a nested complex selector.
170   if (context.in_nested_complex_selector)
171     return true;
172 
173   // Because Blink treats a shadow host's TreeScope as a separate one from its
174   // descendent shadow roots, if the last matched element is a shadow host, the
175   // condition above isn't met, even though it should be.
176   return context.element == context.scope->OwnerShadowHost() &&
177          (!context.previous_element ||
178           context.previous_element->IsInDescendantTreeOf(context.element));
179 }
180 
NextSelectorExceedsScope(const SelectorChecker::SelectorCheckingContext & context)181 static inline bool NextSelectorExceedsScope(
182     const SelectorChecker::SelectorCheckingContext& context) {
183   if (context.scope && context.scope->IsInShadowTree())
184     return context.element == context.scope->OwnerShadowHost();
185 
186   return false;
187 }
188 
ShouldMatchHoverOrActive(const SelectorChecker::SelectorCheckingContext & context)189 static bool ShouldMatchHoverOrActive(
190     const SelectorChecker::SelectorCheckingContext& context) {
191   // If we're in quirks mode, then :hover and :active should never match anchors
192   // with no href and *:hover and *:active should not match anything. This is
193   // specified in https://quirks.spec.whatwg.org/#the-:active-and-:hover-quirk
194   if (!context.element->GetDocument().InQuirksMode())
195     return true;
196   if (context.is_sub_selector)
197     return true;
198   if (context.element->IsLink())
199     return true;
200   const CSSSelector* selector = context.selector;
201   while (selector->Relation() == CSSSelector::kSubSelector &&
202          selector->TagHistory()) {
203     selector = selector->TagHistory();
204     if (selector->Match() != CSSSelector::kPseudoClass)
205       return true;
206     if (selector->GetPseudoType() != CSSSelector::kPseudoHover &&
207         selector->GetPseudoType() != CSSSelector::kPseudoActive)
208       return true;
209   }
210   return false;
211 }
212 
IsFirstChild(Element & element)213 static bool IsFirstChild(Element& element) {
214   return !ElementTraversal::PreviousSibling(element);
215 }
216 
IsLastChild(Element & element)217 static bool IsLastChild(Element& element) {
218   return !ElementTraversal::NextSibling(element);
219 }
220 
IsFirstOfType(Element & element,const QualifiedName & type)221 static bool IsFirstOfType(Element& element, const QualifiedName& type) {
222   return !ElementTraversal::PreviousSibling(element, HasTagName(type));
223 }
224 
IsLastOfType(Element & element,const QualifiedName & type)225 static bool IsLastOfType(Element& element, const QualifiedName& type) {
226   return !ElementTraversal::NextSibling(element, HasTagName(type));
227 }
228 
Match(const SelectorCheckingContext & context,MatchResult & result) const229 bool SelectorChecker::Match(const SelectorCheckingContext& context,
230                             MatchResult& result) const {
231   DCHECK(context.selector);
232 #if DCHECK_IS_ON()
233   DCHECK(!inside_match_) << "Do not re-enter Match: use MatchSelector instead";
234   base::AutoReset<bool> reset_inside_match(&inside_match_, true);
235 #endif  // DCHECK_IS_ON()
236 
237   if (UNLIKELY(context.vtt_originating_element)) {
238     // A kShadowPseudo combinator is required for VTT matching.
239     if (context.selector->IsLastInTagHistory())
240       return false;
241   }
242   return MatchSelector(context, result) == kSelectorMatches;
243 }
244 
245 // Recursive check of selectors and combinators
246 // It can return 4 different values:
247 // * SelectorMatches          - the selector matches the element e
248 // * SelectorFailsLocally     - the selector fails for the element e
249 // * SelectorFailsAllSiblings - the selector fails for e and any sibling of e
250 // * SelectorFailsCompletely  - the selector fails for e and any sibling or
251 //   ancestor of e
MatchSelector(const SelectorCheckingContext & context,MatchResult & result) const252 SelectorChecker::MatchStatus SelectorChecker::MatchSelector(
253     const SelectorCheckingContext& context,
254     MatchResult& result) const {
255   MatchResult sub_result;
256   if (!CheckOne(context, sub_result))
257     return kSelectorFailsLocally;
258 
259   if (sub_result.dynamic_pseudo != kPseudoIdNone)
260     result.dynamic_pseudo = sub_result.dynamic_pseudo;
261 
262   if (context.selector->IsLastInTagHistory()) {
263     if (ScopeContainsLastMatchedElement(context)) {
264       result.specificity += sub_result.specificity;
265       return kSelectorMatches;
266     }
267     return kSelectorFailsLocally;
268   }
269 
270   MatchStatus match;
271   if (context.selector->Relation() != CSSSelector::kSubSelector) {
272     if (NextSelectorExceedsScope(context))
273       return kSelectorFailsCompletely;
274 
275     if (context.pseudo_id != kPseudoIdNone &&
276         context.pseudo_id != result.dynamic_pseudo)
277       return kSelectorFailsCompletely;
278 
279     base::AutoReset<PseudoId> dynamic_pseudo_scope(&result.dynamic_pseudo,
280                                                    kPseudoIdNone);
281     match = MatchForRelation(context, result);
282   } else {
283     match = MatchForSubSelector(context, result);
284   }
285   if (match == kSelectorMatches)
286     result.specificity += sub_result.specificity;
287   return match;
288 }
289 
290 static inline SelectorChecker::SelectorCheckingContext
PrepareNextContextForRelation(const SelectorChecker::SelectorCheckingContext & context)291 PrepareNextContextForRelation(
292     const SelectorChecker::SelectorCheckingContext& context) {
293   SelectorChecker::SelectorCheckingContext next_context(context);
294   DCHECK(context.selector->TagHistory());
295   next_context.selector = context.selector->TagHistory();
296   return next_context;
297 }
298 
MatchForSubSelector(const SelectorCheckingContext & context,MatchResult & result) const299 SelectorChecker::MatchStatus SelectorChecker::MatchForSubSelector(
300     const SelectorCheckingContext& context,
301     MatchResult& result) const {
302   SelectorCheckingContext next_context = PrepareNextContextForRelation(context);
303 
304   PseudoId dynamic_pseudo = result.dynamic_pseudo;
305   next_context.has_scrollbar_pseudo =
306       dynamic_pseudo != kPseudoIdNone &&
307       (scrollbar_ || dynamic_pseudo == kPseudoIdScrollbarCorner ||
308        dynamic_pseudo == kPseudoIdResizer);
309 
310   // Only match pseudo classes following scrollbar pseudo elements while
311   // actually computing style for scrollbar pseudo elements. This is to
312   // avoid incorrectly setting affected-by flags on actual elements for
313   // cases like: div::-webkit-scrollbar-thumb:hover { }
314   if (context.in_rightmost_compound && dynamic_pseudo != kPseudoIdNone &&
315       dynamic_pseudo != kPseudoIdSelection &&
316       !next_context.has_scrollbar_pseudo) {
317     return kSelectorFailsCompletely;
318   }
319 
320   next_context.has_selection_pseudo = dynamic_pseudo == kPseudoIdSelection;
321   next_context.is_sub_selector = true;
322   return MatchSelector(next_context, result);
323 }
324 
IsV0ShadowRoot(const Node * node)325 static inline bool IsV0ShadowRoot(const Node* node) {
326   auto* shadow_root = DynamicTo<ShadowRoot>(node);
327   return shadow_root && shadow_root->GetType() == ShadowRootType::V0;
328 }
329 
MatchForPseudoShadow(const SelectorCheckingContext & context,const ContainerNode * node,MatchResult & result) const330 SelectorChecker::MatchStatus SelectorChecker::MatchForPseudoShadow(
331     const SelectorCheckingContext& context,
332     const ContainerNode* node,
333     MatchResult& result) const {
334   if (!IsV0ShadowRoot(node))
335     return kSelectorFailsCompletely;
336   if (!context.previous_element)
337     return kSelectorFailsCompletely;
338   return MatchSelector(context, result);
339 }
340 
ParentOrV0ShadowHostElement(const Element & element)341 static inline Element* ParentOrV0ShadowHostElement(const Element& element) {
342   if (auto* shadow_root = DynamicTo<ShadowRoot>(element.parentNode())) {
343     if (shadow_root->GetType() != ShadowRootType::V0)
344       return nullptr;
345   }
346   return element.ParentOrShadowHostElement();
347 }
348 
MatchForRelation(const SelectorCheckingContext & context,MatchResult & result) const349 SelectorChecker::MatchStatus SelectorChecker::MatchForRelation(
350     const SelectorCheckingContext& context,
351     MatchResult& result) const {
352   SelectorCheckingContext next_context = PrepareNextContextForRelation(context);
353 
354   CSSSelector::RelationType relation = context.selector->Relation();
355 
356   // Disable :visited matching when we see the first link or try to match
357   // anything else than an ancestor.
358   if ((!context.is_sub_selector || context.in_nested_complex_selector) &&
359       (context.element->IsLink() || (relation != CSSSelector::kDescendant &&
360                                      relation != CSSSelector::kChild)))
361     next_context.is_inside_visited_link = false;
362 
363   next_context.in_rightmost_compound = false;
364   next_context.is_sub_selector = false;
365   next_context.previous_element = context.element;
366   next_context.pseudo_id = kPseudoIdNone;
367 
368   switch (relation) {
369     case CSSSelector::kShadowDeepAsDescendant:
370       Deprecation::CountDeprecation(context.element->GetExecutionContext(),
371                                     WebFeature::kCSSDeepCombinator);
372       FALLTHROUGH;
373     case CSSSelector::kDescendant:
374       if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoScope) {
375         if (next_context.selector->IsLastInTagHistory()) {
376           if (context.scope->IsDocumentFragment())
377             return kSelectorMatches;
378         }
379       }
380 
381       if (context.selector->RelationIsAffectedByPseudoContent()) {
382         for (Element* element = context.element; element;
383              element = element->parentElement()) {
384           if (MatchForPseudoContent(next_context, *element, result) ==
385               kSelectorMatches)
386             return kSelectorMatches;
387         }
388         return kSelectorFailsCompletely;
389       }
390 
391       if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoShadow)
392         return MatchForPseudoShadow(
393             next_context, context.element->ContainingShadowRoot(), result);
394 
395       for (next_context.element = ParentElement(next_context);
396            next_context.element;
397            next_context.element = ParentElement(next_context)) {
398         MatchStatus match = MatchSelector(next_context, result);
399         if (match == kSelectorMatches || match == kSelectorFailsCompletely)
400           return match;
401         if (NextSelectorExceedsScope(next_context))
402           return kSelectorFailsCompletely;
403         if (next_context.element->IsLink())
404           next_context.is_inside_visited_link = false;
405       }
406       return kSelectorFailsCompletely;
407     case CSSSelector::kChild: {
408       if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoScope) {
409         if (next_context.selector->IsLastInTagHistory()) {
410           if (context.element->parentNode() == context.scope &&
411               context.scope->IsDocumentFragment())
412             return kSelectorMatches;
413         }
414       }
415 
416       if (context.selector->RelationIsAffectedByPseudoContent())
417         return MatchForPseudoContent(next_context, *context.element, result);
418 
419       if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoShadow)
420         return MatchForPseudoShadow(next_context, context.element->parentNode(),
421                                     result);
422 
423       next_context.element = ParentElement(next_context);
424       if (!next_context.element)
425         return kSelectorFailsCompletely;
426       return MatchSelector(next_context, result);
427     }
428     case CSSSelector::kDirectAdjacent:
429       // Shadow roots can't have sibling elements
430       if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoShadow)
431         return kSelectorFailsCompletely;
432 
433       if (mode_ == kResolvingStyle) {
434         if (ContainerNode* parent =
435                 context.element->ParentElementOrShadowRoot())
436           parent->SetChildrenAffectedByDirectAdjacentRules();
437       }
438       next_context.element =
439           ElementTraversal::PreviousSibling(*context.element);
440       if (!next_context.element)
441         return kSelectorFailsAllSiblings;
442       return MatchSelector(next_context, result);
443 
444     case CSSSelector::kIndirectAdjacent:
445       // Shadow roots can't have sibling elements
446       if (next_context.selector->GetPseudoType() == CSSSelector::kPseudoShadow)
447         return kSelectorFailsCompletely;
448 
449       if (mode_ == kResolvingStyle) {
450         if (ContainerNode* parent =
451                 context.element->ParentElementOrShadowRoot())
452           parent->SetChildrenAffectedByIndirectAdjacentRules();
453       }
454       next_context.element =
455           ElementTraversal::PreviousSibling(*context.element);
456       for (; next_context.element;
457            next_context.element =
458                ElementTraversal::PreviousSibling(*next_context.element)) {
459         MatchStatus match = MatchSelector(next_context, result);
460         if (match == kSelectorMatches || match == kSelectorFailsAllSiblings ||
461             match == kSelectorFailsCompletely)
462           return match;
463       }
464       return kSelectorFailsAllSiblings;
465 
466     case CSSSelector::kShadowPseudo: {
467       DCHECK(mode_ == kQueryingRules ||
468              context.selector->GetPseudoType() != CSSSelector::kPseudoShadow);
469       if (context.selector->GetPseudoType() == CSSSelector::kPseudoShadow) {
470         UseCounter::Count(context.element->GetDocument(),
471                           WebFeature::kPseudoShadowInStaticProfile);
472       }
473       // If we're in the same tree-scope as the scoping element, then following
474       // a shadow descendant combinator would escape that and thus the scope.
475       if (context.scope && context.scope->OwnerShadowHost() &&
476           context.scope->OwnerShadowHost()->GetTreeScope() ==
477               context.element->GetTreeScope())
478         return kSelectorFailsCompletely;
479 
480       Element* shadow_host = context.element->OwnerShadowHost();
481       if (!shadow_host)
482         return kSelectorFailsCompletely;
483       // Match against featureless-like Element described by spec:
484       // https://w3c.github.io/webvtt/#obtaining-css-boxes
485       if (context.vtt_originating_element)
486         shadow_host = context.vtt_originating_element;
487       next_context.element = shadow_host;
488       return MatchSelector(next_context, result);
489     }
490 
491     case CSSSelector::kShadowDeep: {
492       DCHECK(mode_ == kQueryingRules);
493       UseCounter::Count(context.element->GetDocument(),
494                         WebFeature::kDeepCombinatorInStaticProfile);
495       if (ShadowRoot* root = context.element->ContainingShadowRoot()) {
496         if (root->IsUserAgent())
497           return kSelectorFailsCompletely;
498       }
499 
500       if (context.selector->RelationIsAffectedByPseudoContent()) {
501         // TODO(kochi): closed mode tree should be handled as well for
502         // ::content.
503         for (Element* element = context.element; element;
504              element = element->ParentOrShadowHostElement()) {
505           if (MatchForPseudoContent(next_context, *element, result) ==
506               kSelectorMatches) {
507             if (context.element->IsInShadowTree()) {
508               UseCounter::Count(context.element->GetDocument(),
509                                 WebFeature::kCSSDeepCombinatorAndShadow);
510             }
511             return kSelectorMatches;
512           }
513         }
514         return kSelectorFailsCompletely;
515       }
516 
517       for (next_context.element = ParentOrV0ShadowHostElement(*context.element);
518            next_context.element;
519            next_context.element =
520                ParentOrV0ShadowHostElement(*next_context.element)) {
521         MatchStatus match = MatchSelector(next_context, result);
522         if (match == kSelectorMatches && context.element->IsInShadowTree()) {
523           UseCounter::Count(context.element->GetDocument(),
524                             WebFeature::kCSSDeepCombinatorAndShadow);
525         }
526         if (match == kSelectorMatches || match == kSelectorFailsCompletely)
527           return match;
528         if (NextSelectorExceedsScope(next_context))
529           return kSelectorFailsCompletely;
530       }
531       return kSelectorFailsCompletely;
532     }
533 
534     case CSSSelector::kShadowSlot: {
535       if (ToHTMLSlotElementIfSupportsAssignmentOrNull(*context.element))
536         return kSelectorFailsCompletely;
537       const HTMLSlotElement* slot = FindSlotElementInScope(context);
538       if (!slot)
539         return kSelectorFailsCompletely;
540 
541       next_context.element = const_cast<HTMLSlotElement*>(slot);
542       return MatchSelector(next_context, result);
543     }
544 
545     case CSSSelector::kShadowPart:
546       // We ascend through ancestor shadow host elements until we reach the host
547       // in the TreeScope associated with the style rule. We then match against
548       // that host.
549       while (next_context.element) {
550         next_context.element = next_context.element->OwnerShadowHost();
551         if (!next_context.element)
552           return kSelectorFailsCompletely;
553 
554         if (next_context.element->GetTreeScope() ==
555             context.scope->GetTreeScope())
556           return MatchSelector(next_context, result);
557       }
558       return kSelectorFailsCompletely;
559     case CSSSelector::kSubSelector:
560       break;
561   }
562   NOTREACHED();
563   return kSelectorFailsCompletely;
564 }
565 
MatchForPseudoContent(const SelectorCheckingContext & context,const Element & element,MatchResult & result) const566 SelectorChecker::MatchStatus SelectorChecker::MatchForPseudoContent(
567     const SelectorCheckingContext& context,
568     const Element& element,
569     MatchResult& result) const {
570   HeapVector<Member<V0InsertionPoint>, 8> insertion_points;
571   CollectDestinationInsertionPoints(element, insertion_points);
572   SelectorCheckingContext next_context(context);
573   for (const auto& insertion_point : insertion_points) {
574     next_context.element = insertion_point;
575     if (MatchSelector(next_context, result) == kSelectorMatches)
576       return kSelectorMatches;
577   }
578   return kSelectorFailsLocally;
579 }
580 
AttributeValueMatches(const Attribute & attribute_item,CSSSelector::MatchType match,const AtomicString & selector_value,TextCaseSensitivity case_sensitivity)581 static bool AttributeValueMatches(const Attribute& attribute_item,
582                                   CSSSelector::MatchType match,
583                                   const AtomicString& selector_value,
584                                   TextCaseSensitivity case_sensitivity) {
585   // TODO(esprehn): How do we get here with a null value?
586   const AtomicString& value = attribute_item.Value();
587   if (value.IsNull())
588     return false;
589 
590   switch (match) {
591     case CSSSelector::kAttributeExact:
592       if (case_sensitivity == kTextCaseSensitive)
593         return selector_value == value;
594       return EqualIgnoringASCIICase(selector_value, value);
595     case CSSSelector::kAttributeSet:
596       return true;
597     case CSSSelector::kAttributeList: {
598       // Ignore empty selectors or selectors containing HTML spaces
599       if (selector_value.IsEmpty() ||
600           selector_value.Find(&IsHTMLSpace<UChar>) != kNotFound)
601         return false;
602 
603       unsigned start_search_at = 0;
604       while (true) {
605         wtf_size_t found_pos =
606             value.Find(selector_value, start_search_at, case_sensitivity);
607         if (found_pos == kNotFound)
608           return false;
609         if (!found_pos || IsHTMLSpace<UChar>(value[found_pos - 1])) {
610           unsigned end_str = found_pos + selector_value.length();
611           if (end_str == value.length() || IsHTMLSpace<UChar>(value[end_str]))
612             break;  // We found a match.
613         }
614 
615         // No match. Keep looking.
616         start_search_at = found_pos + 1;
617       }
618       return true;
619     }
620     case CSSSelector::kAttributeContain:
621       if (selector_value.IsEmpty())
622         return false;
623       return value.Contains(selector_value, case_sensitivity);
624     case CSSSelector::kAttributeBegin:
625       if (selector_value.IsEmpty())
626         return false;
627       return value.StartsWith(selector_value, case_sensitivity);
628     case CSSSelector::kAttributeEnd:
629       if (selector_value.IsEmpty())
630         return false;
631       return value.EndsWith(selector_value, case_sensitivity);
632     case CSSSelector::kAttributeHyphen:
633       if (value.length() < selector_value.length())
634         return false;
635       if (!value.StartsWith(selector_value, case_sensitivity))
636         return false;
637       // It they start the same, check for exact match or following '-':
638       if (value.length() != selector_value.length() &&
639           value[selector_value.length()] != '-')
640         return false;
641       return true;
642     default:
643       NOTREACHED();
644       return false;
645   }
646 }
647 
AnyAttributeMatches(Element & element,CSSSelector::MatchType match,const CSSSelector & selector)648 static bool AnyAttributeMatches(Element& element,
649                                 CSSSelector::MatchType match,
650                                 const CSSSelector& selector) {
651   const QualifiedName& selector_attr = selector.Attribute();
652   // Should not be possible from the CSS grammar.
653   DCHECK_NE(selector_attr.LocalName(), CSSSelector::UniversalSelectorAtom());
654 
655   // Synchronize the attribute in case it is lazy-computed.
656   // Currently all lazy properties have a null namespace, so only pass
657   // localName().
658   element.SynchronizeAttribute(selector_attr.LocalName());
659 
660   const AtomicString& selector_value = selector.Value();
661   TextCaseSensitivity case_sensitivity =
662       (selector.AttributeMatch() ==
663        CSSSelector::AttributeMatchType::kCaseInsensitive)
664           ? kTextCaseASCIIInsensitive
665           : kTextCaseSensitive;
666 
667   AttributeCollection attributes = element.AttributesWithoutUpdate();
668   for (const auto& attribute_item : attributes) {
669     if (!attribute_item.Matches(selector_attr)) {
670       if (element.IsHTMLElement() || !IsA<HTMLDocument>(element.GetDocument()))
671         continue;
672       // Non-html attributes in html documents are normalized to their camel-
673       // cased version during parsing if applicable. Yet, attribute selectors
674       // are lower-cased for selectors in html documents. Compare the selector
675       // and the attribute local name insensitively to e.g. allow matching SVG
676       // attributes like viewBox.
677       if (!attribute_item.MatchesCaseInsensitive(selector_attr))
678         continue;
679     }
680 
681     if (AttributeValueMatches(attribute_item, match, selector_value,
682                               case_sensitivity))
683       return true;
684 
685     if (case_sensitivity == kTextCaseASCIIInsensitive) {
686       if (selector_attr.NamespaceURI() != g_star_atom)
687         return false;
688       continue;
689     }
690 
691     // Legacy dictates that values of some attributes should be compared in
692     // a case-insensitive manner regardless of whether the case insensitive
693     // flag is set or not.
694     bool legacy_case_insensitive =
695         IsA<HTMLDocument>(element.GetDocument()) &&
696         !HTMLDocument::IsCaseSensitiveAttribute(selector_attr);
697 
698     // If case-insensitive, re-check, and count if result differs.
699     // See http://code.google.com/p/chromium/issues/detail?id=327060
700     if (legacy_case_insensitive &&
701         AttributeValueMatches(attribute_item, match, selector_value,
702                               kTextCaseASCIIInsensitive)) {
703       UseCounter::Count(element.GetDocument(),
704                         WebFeature::kCaseInsensitiveAttrSelectorMatch);
705       return true;
706     }
707     if (selector_attr.NamespaceURI() != g_star_atom)
708       return false;
709   }
710 
711   return false;
712 }
713 
CheckOne(const SelectorCheckingContext & context,MatchResult & result) const714 bool SelectorChecker::CheckOne(const SelectorCheckingContext& context,
715                                MatchResult& result) const {
716   DCHECK(context.element);
717   Element& element = *context.element;
718   DCHECK(context.selector);
719   const CSSSelector& selector = *context.selector;
720 
721   // Only :host and :host-context() should match the host:
722   // http://drafts.csswg.org/css-scoping/#host-element
723   if (context.scope && context.scope->OwnerShadowHost() == element &&
724       (!selector.IsHostPseudoClass() &&
725        selector.GetPseudoType() != CSSSelector::kPseudoScope &&
726        !context.treat_shadow_host_as_normal_scope &&
727        selector.Match() != CSSSelector::kPseudoElement))
728     return false;
729 
730   switch (selector.Match()) {
731     case CSSSelector::kTag:
732       return MatchesTagName(element, selector.TagQName());
733     case CSSSelector::kClass:
734       return element.HasClass() &&
735              element.ClassNames().Contains(selector.Value());
736     case CSSSelector::kId:
737       return element.HasID() &&
738              element.IdForStyleResolution() == selector.Value();
739 
740     // Attribute selectors
741     case CSSSelector::kAttributeExact:
742     case CSSSelector::kAttributeSet:
743     case CSSSelector::kAttributeHyphen:
744     case CSSSelector::kAttributeList:
745     case CSSSelector::kAttributeContain:
746     case CSSSelector::kAttributeBegin:
747     case CSSSelector::kAttributeEnd:
748       return AnyAttributeMatches(element, selector.Match(), selector);
749 
750     case CSSSelector::kPseudoClass:
751       return CheckPseudoClass(context, result);
752     case CSSSelector::kPseudoElement:
753       return CheckPseudoElement(context, result);
754 
755     default:
756       NOTREACHED();
757       return false;
758   }
759 }
760 
CheckPseudoNot(const SelectorCheckingContext & context,MatchResult & result) const761 bool SelectorChecker::CheckPseudoNot(const SelectorCheckingContext& context,
762                                      MatchResult& result) const {
763   const CSSSelector& selector = *context.selector;
764   DCHECK(selector.SelectorList());
765   SelectorCheckingContext sub_context(context);
766   sub_context.is_sub_selector = true;
767   sub_context.in_nested_complex_selector =
768       !selector.SelectorList()->TreatAsNonComplexArgumentToNot();
769   sub_context.pseudo_id = kPseudoIdNone;
770   for (sub_context.selector = selector.SelectorList()->First();
771        sub_context.selector;
772        sub_context.selector = CSSSelectorList::Next(*sub_context.selector)) {
773     MatchResult sub_result;
774     if (MatchSelector(sub_context, sub_result) == kSelectorMatches)
775       return false;
776   }
777   return true;
778 }
779 
CheckPseudoClass(const SelectorCheckingContext & context,MatchResult & result) const780 bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context,
781                                        MatchResult& result) const {
782   Element& element = *context.element;
783   const CSSSelector& selector = *context.selector;
784   bool force_pseudo_state = false;
785 
786   if (context.has_scrollbar_pseudo) {
787     // CSS scrollbars match a specific subset of pseudo classes, and they have
788     // specialized rules for each
789     // (since there are no elements involved).
790     return CheckScrollbarPseudoClass(context, result);
791   }
792 
793   switch (selector.GetPseudoType()) {
794     case CSSSelector::kPseudoNot:
795       return CheckPseudoNot(context, result);
796     case CSSSelector::kPseudoEmpty: {
797       bool is_empty = true;
798       bool has_whitespace = false;
799       for (Node* n = element.firstChild(); n; n = n->nextSibling()) {
800         if (n->IsElementNode()) {
801           is_empty = false;
802           break;
803         }
804         if (auto* text_node = DynamicTo<Text>(n)) {
805           if (!text_node->data().IsEmpty()) {
806             if (text_node->ContainsOnlyWhitespaceOrEmpty()) {
807               has_whitespace = true;
808             } else {
809               is_empty = false;
810               break;
811             }
812           }
813         }
814       }
815       if (is_empty && has_whitespace) {
816         UseCounter::Count(context.element->GetDocument(),
817                           WebFeature::kCSSSelectorEmptyWhitespaceOnlyFail);
818         is_empty = false;
819       }
820       if (mode_ == kResolvingStyle)
821         element.SetStyleAffectedByEmpty();
822       return is_empty;
823     }
824     case CSSSelector::kPseudoFirstChild:
825       if (mode_ == kResolvingStyle) {
826         if (ContainerNode* parent = element.ParentElementOrDocumentFragment())
827           parent->SetChildrenAffectedByFirstChildRules();
828         element.SetAffectedByFirstChildRules();
829       }
830       return IsFirstChild(element);
831     case CSSSelector::kPseudoFirstOfType:
832       if (mode_ == kResolvingStyle) {
833         if (ContainerNode* parent = element.ParentElementOrDocumentFragment())
834           parent->SetChildrenAffectedByForwardPositionalRules();
835       }
836       return IsFirstOfType(element, element.TagQName());
837     case CSSSelector::kPseudoLastChild: {
838       ContainerNode* parent = element.ParentElementOrDocumentFragment();
839       if (mode_ == kResolvingStyle) {
840         if (parent)
841           parent->SetChildrenAffectedByLastChildRules();
842         element.SetAffectedByLastChildRules();
843       }
844       if (mode_ != kQueryingRules && parent &&
845           !parent->IsFinishedParsingChildren())
846         return false;
847       return IsLastChild(element);
848     }
849     case CSSSelector::kPseudoLastOfType: {
850       ContainerNode* parent = element.ParentElementOrDocumentFragment();
851       if (mode_ == kResolvingStyle) {
852         if (parent)
853           parent->SetChildrenAffectedByBackwardPositionalRules();
854       }
855       if (mode_ != kQueryingRules && parent &&
856           !parent->IsFinishedParsingChildren())
857         return false;
858       return IsLastOfType(element, element.TagQName());
859     }
860     case CSSSelector::kPseudoOnlyChild: {
861       ContainerNode* parent = element.ParentElementOrDocumentFragment();
862       if (mode_ == kResolvingStyle) {
863         if (parent) {
864           parent->SetChildrenAffectedByFirstChildRules();
865           parent->SetChildrenAffectedByLastChildRules();
866         }
867         element.SetAffectedByFirstChildRules();
868         element.SetAffectedByLastChildRules();
869       }
870       if (mode_ != kQueryingRules && parent &&
871           !parent->IsFinishedParsingChildren())
872         return false;
873       return IsFirstChild(element) && IsLastChild(element);
874     }
875     case CSSSelector::kPseudoOnlyOfType: {
876       // FIXME: This selector is very slow.
877       ContainerNode* parent = element.ParentElementOrDocumentFragment();
878       if (mode_ == kResolvingStyle && parent) {
879         parent->SetChildrenAffectedByForwardPositionalRules();
880         parent->SetChildrenAffectedByBackwardPositionalRules();
881       }
882       if (mode_ != kQueryingRules && parent &&
883           !parent->IsFinishedParsingChildren())
884         return false;
885       return IsFirstOfType(element, element.TagQName()) &&
886              IsLastOfType(element, element.TagQName());
887     }
888     case CSSSelector::kPseudoPlaceholderShown:
889       if (auto* text_control = ToTextControlOrNull(element))
890         return text_control->IsPlaceholderVisible();
891       break;
892     case CSSSelector::kPseudoNthChild:
893       if (mode_ == kResolvingStyle) {
894         if (ContainerNode* parent = element.ParentElementOrDocumentFragment())
895           parent->SetChildrenAffectedByForwardPositionalRules();
896       }
897       return selector.MatchNth(NthIndexCache::NthChildIndex(element));
898     case CSSSelector::kPseudoNthOfType:
899       if (mode_ == kResolvingStyle) {
900         if (ContainerNode* parent = element.ParentElementOrDocumentFragment())
901           parent->SetChildrenAffectedByForwardPositionalRules();
902       }
903       return selector.MatchNth(NthIndexCache::NthOfTypeIndex(element));
904     case CSSSelector::kPseudoNthLastChild: {
905       ContainerNode* parent = element.ParentElementOrDocumentFragment();
906       if (mode_ == kResolvingStyle && parent)
907         parent->SetChildrenAffectedByBackwardPositionalRules();
908       if (mode_ != kQueryingRules && parent &&
909           !parent->IsFinishedParsingChildren())
910         return false;
911       return selector.MatchNth(NthIndexCache::NthLastChildIndex(element));
912     }
913     case CSSSelector::kPseudoNthLastOfType: {
914       ContainerNode* parent = element.ParentElementOrDocumentFragment();
915       if (mode_ == kResolvingStyle && parent)
916         parent->SetChildrenAffectedByBackwardPositionalRules();
917       if (mode_ != kQueryingRules && parent &&
918           !parent->IsFinishedParsingChildren())
919         return false;
920       return selector.MatchNth(NthIndexCache::NthLastOfTypeIndex(element));
921     }
922     case CSSSelector::kPseudoTarget:
923       return element == element.GetDocument().CssTarget();
924     case CSSSelector::kPseudoIs:
925     case CSSSelector::kPseudoWhere:
926     case CSSSelector::kPseudoAny: {
927       SelectorCheckingContext sub_context(context);
928       sub_context.is_sub_selector = true;
929       sub_context.in_nested_complex_selector = true;
930       sub_context.pseudo_id = kPseudoIdNone;
931       if (!selector.SelectorList())
932         break;
933       for (sub_context.selector = selector.SelectorList()->First();
934            sub_context.selector; sub_context.selector = CSSSelectorList::Next(
935                                      *sub_context.selector)) {
936         MatchResult sub_result;
937         if (MatchSelector(sub_context, sub_result) == kSelectorMatches)
938           return true;
939       }
940     } break;
941     case CSSSelector::kPseudoAutofill: {
942       auto* html_form_element = DynamicTo<HTMLFormControlElement>(&element);
943       return html_form_element && html_form_element->IsAutofilled();
944     }
945     case CSSSelector::kPseudoAutofillPreviewed: {
946       auto* html_form_element = DynamicTo<HTMLFormControlElement>(&element);
947       return html_form_element && html_form_element->GetAutofillState() ==
948                                       WebAutofillState::kPreviewed;
949     }
950     case CSSSelector::kPseudoAutofillSelected: {
951       auto* html_form_element = DynamicTo<HTMLFormControlElement>(&element);
952       return html_form_element && html_form_element->GetAutofillState() ==
953                                       WebAutofillState::kAutofilled;
954     }
955     case CSSSelector::kPseudoAnyLink:
956     case CSSSelector::kPseudoWebkitAnyLink:
957       return element.IsLink();
958     case CSSSelector::kPseudoLink:
959       return element.IsLink() && !context.is_inside_visited_link;
960     case CSSSelector::kPseudoVisited:
961       return element.IsLink() && context.is_inside_visited_link;
962     case CSSSelector::kPseudoDrag:
963       if (mode_ == kResolvingStyle) {
964         if (context.in_rightmost_compound)
965           element_style_->SetAffectedByDrag();
966         else
967           element.SetChildrenOrSiblingsAffectedByDrag();
968       }
969       return element.IsDragged();
970     case CSSSelector::kPseudoFocus:
971       if (mode_ == kResolvingStyle && !context.in_rightmost_compound)
972         element.SetChildrenOrSiblingsAffectedByFocus();
973       return MatchesFocusPseudoClass(element);
974     case CSSSelector::kPseudoFocusVisible:
975       if (mode_ == kResolvingStyle && !context.in_rightmost_compound)
976         element.SetChildrenOrSiblingsAffectedByFocusVisible();
977       return MatchesFocusVisiblePseudoClass(element);
978     case CSSSelector::kPseudoFocusWithin:
979       if (mode_ == kResolvingStyle) {
980         if (context.in_rightmost_compound)
981           element_style_->SetAffectedByFocusWithin();
982         else
983           element.SetChildrenOrSiblingsAffectedByFocusWithin();
984       }
985       probe::ForcePseudoState(&element, CSSSelector::kPseudoFocusWithin,
986                               &force_pseudo_state);
987       if (force_pseudo_state)
988         return true;
989       return element.HasFocusWithin();
990     case CSSSelector::kPseudoHover:
991       if (mode_ == kResolvingStyle) {
992         if (context.in_rightmost_compound)
993           element_style_->SetAffectedByHover();
994         else
995           element.SetChildrenOrSiblingsAffectedByHover();
996       }
997       if (!ShouldMatchHoverOrActive(context))
998         return false;
999       probe::ForcePseudoState(&element, CSSSelector::kPseudoHover,
1000                               &force_pseudo_state);
1001       if (force_pseudo_state)
1002         return true;
1003       return element.IsHovered();
1004     case CSSSelector::kPseudoActive:
1005       if (mode_ == kResolvingStyle) {
1006         if (context.in_rightmost_compound)
1007           element_style_->SetAffectedByActive();
1008         else
1009           element.SetChildrenOrSiblingsAffectedByActive();
1010       }
1011       if (!ShouldMatchHoverOrActive(context))
1012         return false;
1013       probe::ForcePseudoState(&element, CSSSelector::kPseudoActive,
1014                               &force_pseudo_state);
1015       if (force_pseudo_state)
1016         return true;
1017       return element.IsActive();
1018     case CSSSelector::kPseudoEnabled:
1019       return element.MatchesEnabledPseudoClass();
1020     case CSSSelector::kPseudoFullPageMedia:
1021       return element.GetDocument().IsMediaDocument();
1022     case CSSSelector::kPseudoDefault:
1023       return element.MatchesDefaultPseudoClass();
1024     case CSSSelector::kPseudoDisabled:
1025       return element.IsDisabledFormControl();
1026     case CSSSelector::kPseudoReadOnly:
1027       return element.MatchesReadOnlyPseudoClass();
1028     case CSSSelector::kPseudoReadWrite:
1029       return element.MatchesReadWritePseudoClass();
1030     case CSSSelector::kPseudoOptional:
1031       return element.IsOptionalFormControl();
1032     case CSSSelector::kPseudoRequired:
1033       return element.IsRequiredFormControl();
1034     case CSSSelector::kPseudoValid:
1035       return element.MatchesValidityPseudoClasses() && element.IsValidElement();
1036     case CSSSelector::kPseudoInvalid:
1037       return element.MatchesValidityPseudoClasses() &&
1038              !element.IsValidElement();
1039     case CSSSelector::kPseudoChecked: {
1040       if (auto* input_element = DynamicTo<HTMLInputElement>(element)) {
1041         // Even though WinIE allows checked and indeterminate to
1042         // co-exist, the CSS selector spec says that you can't be
1043         // both checked and indeterminate. We will behave like WinIE
1044         // behind the scenes and just obey the CSS spec here in the
1045         // test for matching the pseudo.
1046         if (input_element->ShouldAppearChecked() &&
1047             !input_element->ShouldAppearIndeterminate())
1048           return true;
1049       } else if (auto* option_element = DynamicTo<HTMLOptionElement>(element)) {
1050         if (option_element->Selected()) {
1051           return true;
1052         }
1053       }
1054       break;
1055     }
1056     case CSSSelector::kPseudoIndeterminate:
1057       return element.ShouldAppearIndeterminate();
1058     case CSSSelector::kPseudoRoot:
1059       return element == element.GetDocument().documentElement();
1060     case CSSSelector::kPseudoLang: {
1061       auto* vtt_element = DynamicTo<VTTElement>(element);
1062       AtomicString value = vtt_element ? vtt_element->Language()
1063                                        : element.ComputeInheritedLanguage();
1064       const AtomicString& argument = selector.Argument();
1065       if (value.IsEmpty() ||
1066           !value.StartsWith(argument, kTextCaseASCIIInsensitive))
1067         break;
1068       if (value.length() != argument.length() &&
1069           value[argument.length()] != '-')
1070         break;
1071       return true;
1072     }
1073     case CSSSelector::kPseudoFullscreen:
1074     // fall through
1075     case CSSSelector::kPseudoFullScreen:
1076       return Fullscreen::IsFullscreenElement(element);
1077     case CSSSelector::kPseudoFullScreenAncestor:
1078       return element.ContainsFullScreenElement();
1079     case CSSSelector::kPseudoPictureInPicture:
1080       return PictureInPictureController::IsElementInPictureInPicture(&element);
1081     case CSSSelector::kPseudoVideoPersistent: {
1082       DCHECK(is_ua_rule_);
1083       auto* video_element = DynamicTo<HTMLVideoElement>(element);
1084       return video_element && video_element->IsPersistent();
1085     }
1086     case CSSSelector::kPseudoVideoPersistentAncestor:
1087       DCHECK(is_ua_rule_);
1088       return element.ContainsPersistentVideo();
1089     case CSSSelector::kPseudoXrOverlay:
1090       // In immersive AR overlay mode, apply a pseudostyle to the DOM Overlay
1091       // element. This is the same as the fullscreen element in the current
1092       // implementation, but could be different for AR headsets.
1093       return element.GetDocument().IsXrOverlay() &&
1094              Fullscreen::IsFullscreenElement(element);
1095     case CSSSelector::kPseudoInRange:
1096       return element.IsInRange();
1097     case CSSSelector::kPseudoOutOfRange:
1098       return element.IsOutOfRange();
1099     case CSSSelector::kPseudoFutureCue: {
1100       auto* vtt_element = DynamicTo<VTTElement>(element);
1101       return vtt_element && !vtt_element->IsPastNode();
1102     }
1103     case CSSSelector::kPseudoPastCue: {
1104       auto* vtt_element = DynamicTo<VTTElement>(element);
1105       return vtt_element && vtt_element->IsPastNode();
1106     }
1107     case CSSSelector::kPseudoScope:
1108       if (!context.scope)
1109         return false;
1110       if (context.scope == &element.GetDocument())
1111         return element == element.GetDocument().documentElement();
1112       if (auto* shadow_root = DynamicTo<ShadowRoot>(context.scope))
1113         return element == shadow_root->host();
1114       return context.scope == &element;
1115     case CSSSelector::kPseudoUnresolved:
1116       return !element.IsDefined() && element.IsUnresolvedV0CustomElement();
1117     case CSSSelector::kPseudoDefined:
1118       return element.IsDefined() || element.IsUpgradedV0CustomElement();
1119     case CSSSelector::kPseudoHostContext:
1120       UseCounter::Count(
1121           context.element->GetDocument(),
1122           mode_ == kQueryingRules
1123               ? WebFeature::kCSSSelectorHostContextInSnapshotProfile
1124               : WebFeature::kCSSSelectorHostContextInLiveProfile);
1125       FALLTHROUGH;
1126     case CSSSelector::kPseudoHost:
1127       return CheckPseudoHost(context, result);
1128     case CSSSelector::kPseudoSpatialNavigationFocus:
1129       DCHECK(is_ua_rule_);
1130       return MatchesSpatialNavigationFocusPseudoClass(element);
1131     case CSSSelector::kPseudoSpatialNavigationInterest:
1132       DCHECK(is_ua_rule_);
1133       return MatchesSpatialNavigationInterestPseudoClass(element);
1134     case CSSSelector::kPseudoHasDatalist:
1135       DCHECK(is_ua_rule_);
1136       return MatchesHasDatalistPseudoClass(element);
1137     case CSSSelector::kPseudoIsHtml:
1138       DCHECK(is_ua_rule_);
1139       return IsA<HTMLDocument>(element.GetDocument());
1140     case CSSSelector::kPseudoListBox:
1141       DCHECK(is_ua_rule_);
1142       return MatchesListBoxPseudoClass(element);
1143     case CSSSelector::kPseudoMultiSelectFocus:
1144       DCHECK(is_ua_rule_);
1145       return MatchesMultiSelectFocusPseudoClass(element);
1146     case CSSSelector::kPseudoHostHasAppearance:
1147       DCHECK(is_ua_rule_);
1148       if (ShadowRoot* root = element.ContainingShadowRoot()) {
1149         if (!root->IsUserAgent())
1150           return false;
1151         const ComputedStyle* style = root->host().GetComputedStyle();
1152         return style && style->HasEffectiveAppearance();
1153       }
1154       return false;
1155     case CSSSelector::kPseudoWindowInactive:
1156       if (!context.has_selection_pseudo)
1157         return false;
1158       return !element.GetDocument().GetPage()->GetFocusController().IsActive();
1159     case CSSSelector::kPseudoState: {
1160       return element.DidAttachInternals() &&
1161              element.EnsureElementInternals().HasState(selector.Argument());
1162     }
1163     case CSSSelector::kPseudoHorizontal:
1164     case CSSSelector::kPseudoVertical:
1165     case CSSSelector::kPseudoDecrement:
1166     case CSSSelector::kPseudoIncrement:
1167     case CSSSelector::kPseudoStart:
1168     case CSSSelector::kPseudoEnd:
1169     case CSSSelector::kPseudoDoubleButton:
1170     case CSSSelector::kPseudoSingleButton:
1171     case CSSSelector::kPseudoNoButton:
1172     case CSSSelector::kPseudoCornerPresent:
1173       return false;
1174     case CSSSelector::kPseudoModal:
1175       DCHECK(is_ua_rule_);
1176       if (const auto* dialog_element = DynamicTo<HTMLDialogElement>(element))
1177         return dialog_element->IsModal();
1178       return false;
1179     case CSSSelector::kPseudoUnknown:
1180     default:
1181       NOTREACHED();
1182       break;
1183   }
1184   return false;
1185 }
1186 
CheckPseudoElement(const SelectorCheckingContext & context,MatchResult & result) const1187 bool SelectorChecker::CheckPseudoElement(const SelectorCheckingContext& context,
1188                                          MatchResult& result) const {
1189   const CSSSelector& selector = *context.selector;
1190   Element& element = *context.element;
1191 
1192   switch (selector.GetPseudoType()) {
1193     case CSSSelector::kPseudoCue: {
1194       SelectorCheckingContext sub_context(context);
1195       sub_context.is_sub_selector = true;
1196       sub_context.scope = nullptr;
1197       sub_context.treat_shadow_host_as_normal_scope = false;
1198 
1199       for (sub_context.selector = selector.SelectorList()->First();
1200            sub_context.selector; sub_context.selector = CSSSelectorList::Next(
1201                                      *sub_context.selector)) {
1202         MatchResult sub_result;
1203         if (MatchSelector(sub_context, sub_result) == kSelectorMatches)
1204           return true;
1205       }
1206       return false;
1207     }
1208     case CSSSelector::kPseudoPart:
1209       DCHECK(part_names_);
1210       for (const auto& part_name : *selector.PartNames()) {
1211         if (!part_names_->Contains(part_name))
1212           return false;
1213       }
1214       return true;
1215     case CSSSelector::kPseudoPlaceholder:
1216       if (ShadowRoot* root = element.ContainingShadowRoot()) {
1217         return root->IsUserAgent() &&
1218                element.ShadowPseudoId() ==
1219                    shadow_element_names::kPseudoInputPlaceholder;
1220       }
1221       return false;
1222     case CSSSelector::kPseudoWebKitCustomElement: {
1223       if (ShadowRoot* root = element.ContainingShadowRoot()) {
1224         if (!root->IsUserAgent())
1225           return false;
1226         if (element.ShadowPseudoId() != selector.Value())
1227           return false;
1228         if (!is_ua_rule_ &&
1229             selector.Value() ==
1230                 shadow_element_names::kPseudoWebKitDetailsMarker) {
1231           UseCounter::Count(element.GetDocument(),
1232                             WebFeature::kCSSSelectorPseudoWebKitDetailsMarker);
1233         }
1234         return true;
1235       }
1236       return false;
1237     }
1238     case CSSSelector::kPseudoBlinkInternalElement:
1239       DCHECK(is_ua_rule_);
1240       if (ShadowRoot* root = element.ContainingShadowRoot())
1241         return root->IsUserAgent() &&
1242                element.ShadowPseudoId() == selector.Value();
1243       return false;
1244     case CSSSelector::kPseudoSlotted: {
1245       SelectorCheckingContext sub_context(context);
1246       sub_context.is_sub_selector = true;
1247       sub_context.scope = nullptr;
1248       sub_context.treat_shadow_host_as_normal_scope = false;
1249 
1250       // ::slotted() only allows one compound selector.
1251       DCHECK(selector.SelectorList()->First());
1252       DCHECK(!CSSSelectorList::Next(*selector.SelectorList()->First()));
1253       sub_context.selector = selector.SelectorList()->First();
1254       MatchResult sub_result;
1255       if (MatchSelector(sub_context, sub_result) != kSelectorMatches)
1256         return false;
1257       return true;
1258     }
1259     case CSSSelector::kPseudoContent:
1260       return element.IsInShadowTree() && element.IsV0InsertionPoint();
1261     case CSSSelector::kPseudoShadow:
1262       return element.IsInShadowTree() && context.previous_element;
1263     default:
1264       DCHECK_NE(mode_, kQueryingRules);
1265       result.dynamic_pseudo =
1266           CSSSelector::GetPseudoId(selector.GetPseudoType());
1267       DCHECK_NE(result.dynamic_pseudo, kPseudoIdNone);
1268       return true;
1269   }
1270 }
1271 
CheckPseudoHost(const SelectorCheckingContext & context,MatchResult & result) const1272 bool SelectorChecker::CheckPseudoHost(const SelectorCheckingContext& context,
1273                                       MatchResult& result) const {
1274   const CSSSelector& selector = *context.selector;
1275   Element& element = *context.element;
1276 
1277   // :host only matches a shadow host when :host is in a shadow tree of the
1278   // shadow host.
1279   if (!context.scope)
1280     return false;
1281   const ContainerNode* shadow_host = context.scope->OwnerShadowHost();
1282   if (!shadow_host || shadow_host != element)
1283     return false;
1284   DCHECK(IsShadowHost(element));
1285   DCHECK(element.GetShadowRoot());
1286   bool is_v1_shadow = element.GetShadowRoot()->IsV1();
1287 
1288   // For the case with no parameters, i.e. just :host.
1289   if (!selector.SelectorList()) {
1290     if (is_v1_shadow)
1291       result.specificity += CSSSelector::kClassLikeSpecificity;
1292     return true;
1293   }
1294 
1295   SelectorCheckingContext sub_context(context);
1296   sub_context.is_sub_selector = true;
1297 
1298   bool matched = false;
1299   unsigned max_specificity = 0;
1300 
1301   // If one of simple selectors matches an element, returns SelectorMatches.
1302   // Just "OR".
1303   for (sub_context.selector = selector.SelectorList()->First();
1304        sub_context.selector;
1305        sub_context.selector = CSSSelectorList::Next(*sub_context.selector)) {
1306     sub_context.treat_shadow_host_as_normal_scope = true;
1307     sub_context.scope = context.scope;
1308     // Use FlatTreeTraversal to traverse a composed ancestor list of a given
1309     // element.
1310     Element* next_element = &element;
1311     SelectorCheckingContext host_context(sub_context);
1312     do {
1313       MatchResult sub_result;
1314       host_context.element = next_element;
1315       if (MatchSelector(host_context, sub_result) == kSelectorMatches) {
1316         matched = true;
1317         // Consider div:host(div:host(div:host(div:host...))).
1318         max_specificity =
1319             std::max(max_specificity, host_context.selector->Specificity() +
1320                                           sub_result.specificity);
1321         break;
1322       }
1323       host_context.treat_shadow_host_as_normal_scope = false;
1324       host_context.scope = nullptr;
1325 
1326       if (selector.GetPseudoType() == CSSSelector::kPseudoHost)
1327         break;
1328 
1329       host_context.in_rightmost_compound = false;
1330       next_element = FlatTreeTraversal::ParentElement(*next_element);
1331     } while (next_element);
1332   }
1333   if (matched) {
1334     result.specificity += max_specificity;
1335     if (is_v1_shadow)
1336       result.specificity += CSSSelector::kClassLikeSpecificity;
1337 
1338     if (result.specificity !=
1339         selector.Specificity(
1340             CSSSelector::SpecificityMode::kIncludeHostPseudos)) {
1341       UseCounter::Count(context.element->GetDocument(),
1342                         WebFeature::kCSSPseudoHostDynamicSpecificity);
1343     }
1344 
1345     return true;
1346   }
1347 
1348   // FIXME: this was a fallthrough condition.
1349   return false;
1350 }
1351 
CheckScrollbarPseudoClass(const SelectorCheckingContext & context,MatchResult & result) const1352 bool SelectorChecker::CheckScrollbarPseudoClass(
1353     const SelectorCheckingContext& context,
1354     MatchResult& result) const {
1355   const CSSSelector& selector = *context.selector;
1356 
1357   if (selector.GetPseudoType() == CSSSelector::kPseudoNot)
1358     return CheckPseudoNot(context, result);
1359 
1360   // FIXME: This is a temporary hack for resizers and scrollbar corners.
1361   // Eventually :window-inactive should become a real
1362   // pseudo class and just apply to everything.
1363   if (selector.GetPseudoType() == CSSSelector::kPseudoWindowInactive)
1364     return !context.element->GetDocument()
1365                 .GetPage()
1366                 ->GetFocusController()
1367                 .IsActive();
1368 
1369   if (!scrollbar_)
1370     return false;
1371 
1372   switch (selector.GetPseudoType()) {
1373     case CSSSelector::kPseudoEnabled:
1374       return scrollbar_->Enabled();
1375     case CSSSelector::kPseudoDisabled:
1376       return !scrollbar_->Enabled();
1377     case CSSSelector::kPseudoHover: {
1378       ScrollbarPart hovered_part = scrollbar_->HoveredPart();
1379       if (scrollbar_part_ == kScrollbarBGPart)
1380         return hovered_part != kNoPart;
1381       if (scrollbar_part_ == kTrackBGPart)
1382         return hovered_part == kBackTrackPart ||
1383                hovered_part == kForwardTrackPart || hovered_part == kThumbPart;
1384       return scrollbar_part_ == hovered_part;
1385     }
1386     case CSSSelector::kPseudoActive: {
1387       ScrollbarPart pressed_part = scrollbar_->PressedPart();
1388       if (scrollbar_part_ == kScrollbarBGPart)
1389         return pressed_part != kNoPart;
1390       if (scrollbar_part_ == kTrackBGPart)
1391         return pressed_part == kBackTrackPart ||
1392                pressed_part == kForwardTrackPart || pressed_part == kThumbPart;
1393       return scrollbar_part_ == pressed_part;
1394     }
1395     case CSSSelector::kPseudoHorizontal:
1396       return scrollbar_->Orientation() == kHorizontalScrollbar;
1397     case CSSSelector::kPseudoVertical:
1398       return scrollbar_->Orientation() == kVerticalScrollbar;
1399     case CSSSelector::kPseudoDecrement:
1400       return scrollbar_part_ == kBackButtonStartPart ||
1401              scrollbar_part_ == kBackButtonEndPart ||
1402              scrollbar_part_ == kBackTrackPart;
1403     case CSSSelector::kPseudoIncrement:
1404       return scrollbar_part_ == kForwardButtonStartPart ||
1405              scrollbar_part_ == kForwardButtonEndPart ||
1406              scrollbar_part_ == kForwardTrackPart;
1407     case CSSSelector::kPseudoStart:
1408       return scrollbar_part_ == kBackButtonStartPart ||
1409              scrollbar_part_ == kForwardButtonStartPart ||
1410              scrollbar_part_ == kBackTrackPart;
1411     case CSSSelector::kPseudoEnd:
1412       return scrollbar_part_ == kBackButtonEndPart ||
1413              scrollbar_part_ == kForwardButtonEndPart ||
1414              scrollbar_part_ == kForwardTrackPart;
1415     case CSSSelector::kPseudoDoubleButton:
1416       // :double-button matches nothing on all platforms.
1417       return false;
1418     case CSSSelector::kPseudoSingleButton:
1419       if (!scrollbar_->GetTheme().NativeThemeHasButtons())
1420         return false;
1421       return scrollbar_part_ == kBackButtonStartPart ||
1422              scrollbar_part_ == kForwardButtonEndPart ||
1423              scrollbar_part_ == kBackTrackPart ||
1424              scrollbar_part_ == kForwardTrackPart;
1425     case CSSSelector::kPseudoNoButton:
1426       if (scrollbar_->GetTheme().NativeThemeHasButtons())
1427         return false;
1428       return scrollbar_part_ == kBackTrackPart ||
1429              scrollbar_part_ == kForwardTrackPart;
1430     case CSSSelector::kPseudoCornerPresent:
1431       return scrollbar_->GetScrollableArea() &&
1432              scrollbar_->GetScrollableArea()->IsScrollCornerVisible();
1433     default:
1434       return false;
1435   }
1436 }
1437 
MatchesFocusPseudoClass(const Element & element)1438 bool SelectorChecker::MatchesFocusPseudoClass(const Element& element) {
1439   bool force_pseudo_state = false;
1440   probe::ForcePseudoState(const_cast<Element*>(&element),
1441                           CSSSelector::kPseudoFocus, &force_pseudo_state);
1442   if (force_pseudo_state)
1443     return true;
1444   return element.IsFocused() && IsFrameFocused(element);
1445 }
1446 
MatchesFocusVisiblePseudoClass(const Element & element)1447 bool SelectorChecker::MatchesFocusVisiblePseudoClass(const Element& element) {
1448   bool force_pseudo_state = false;
1449   probe::ForcePseudoState(const_cast<Element*>(&element),
1450                           CSSSelector::kPseudoFocusVisible,
1451                           &force_pseudo_state);
1452   if (force_pseudo_state)
1453     return true;
1454 
1455   if (!element.IsFocused() || !IsFrameFocused(element))
1456     return false;
1457 
1458   const Document& document = element.GetDocument();
1459   const Settings* settings = document.GetSettings();
1460   bool always_show_focus = settings->GetAccessibilityAlwaysShowFocus();
1461   bool is_text_input = element.MayTriggerVirtualKeyboard();
1462   bool last_focus_from_mouse =
1463       document.GetFrame() &&
1464       document.GetFrame()->Selection().FrameIsFocusedAndActive() &&
1465       document.LastFocusType() == mojom::blink::FocusType::kMouse;
1466   bool had_keyboard_event = document.HadKeyboardEvent();
1467 
1468   return (always_show_focus || is_text_input || !last_focus_from_mouse ||
1469           had_keyboard_event);
1470 }
1471 
1472 // static
MatchesSpatialNavigationInterestPseudoClass(const Element & element)1473 bool SelectorChecker::MatchesSpatialNavigationInterestPseudoClass(
1474     const Element& element) {
1475   if (!IsSpatialNavigationEnabled(element.GetDocument().GetFrame()))
1476     return false;
1477 
1478   if (!RuntimeEnabledFeatures::FocuslessSpatialNavigationEnabled())
1479     return false;
1480 
1481   DCHECK(element.GetDocument().GetPage());
1482   Element* interested_element = element.GetDocument()
1483                                     .GetPage()
1484                                     ->GetSpatialNavigationController()
1485                                     .GetInterestedElement();
1486   return interested_element && *interested_element == element;
1487 }
1488 
1489 }  // namespace blink
1490