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