Coverage Report

Created: 2026-10-28 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/parcel_selectors-0.28.3/matching.rs
Line
Count
Source
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5
use crate::attr::{AttrSelectorOperation, NamespaceConstraint, ParsedAttrSelectorOperation};
6
use crate::bloom::{BloomFilter, BLOOM_HASH_MASK};
7
use crate::nth_index_cache::NthIndexCacheInner;
8
use crate::parser::{AncestorHashes, Combinator, Component, LocalName, NthType};
9
use crate::parser::{NonTSPseudoClass, Selector, SelectorImpl, SelectorIter, SelectorList};
10
use crate::tree::Element;
11
use smallvec::SmallVec;
12
use std::borrow::Borrow;
13
use std::iter;
14
15
pub use crate::context::*;
16
17
// The bloom filter for descendant CSS selectors will have a <1% false
18
// positive rate until it has this many selectors in it, then it will
19
// rapidly increase.
20
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
21
22
bitflags! {
23
    /// Set of flags that are set on either the element or its parent (depending
24
    /// on the flag) if the element could potentially match a selector.
25
    pub struct ElementSelectorFlags: usize {
26
        /// When a child is added or removed from the parent, all the children
27
        /// must be restyled, because they may match :nth-last-child,
28
        /// :last-of-type, :nth-last-of-type, or :only-of-type.
29
        const HAS_SLOW_SELECTOR = 1 << 0;
30
31
        /// When a child is added or removed from the parent, any later
32
        /// children must be restyled, because they may match :nth-child,
33
        /// :first-of-type, or :nth-of-type.
34
        const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1;
35
36
        /// When a child is added or removed from the parent, the first and
37
        /// last children must be restyled, because they may match :first-child,
38
        /// :last-child, or :only-child.
39
        const HAS_EDGE_CHILD_SELECTOR = 1 << 2;
40
41
        /// The element has an empty selector, so when a child is appended we
42
        /// might need to restyle the parent completely.
43
        const HAS_EMPTY_SELECTOR = 1 << 3;
44
    }
45
}
46
47
impl ElementSelectorFlags {
48
  /// Returns the subset of flags that apply to the element.
49
0
  pub fn for_self(self) -> ElementSelectorFlags {
50
0
    self & (ElementSelectorFlags::HAS_EMPTY_SELECTOR)
51
0
  }
52
53
  /// Returns the subset of flags that apply to the parent.
54
0
  pub fn for_parent(self) -> ElementSelectorFlags {
55
0
    self
56
0
      & (ElementSelectorFlags::HAS_SLOW_SELECTOR
57
0
        | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
58
0
        | ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR)
59
0
  }
60
}
61
62
/// Holds per-compound-selector data.
63
struct LocalMatchingContext<'a, 'b, 'i, Impl: SelectorImpl<'i>> {
64
  shared: &'a mut MatchingContext<'b, 'i, Impl>,
65
  matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk,
66
}
67
68
#[inline(always)]
69
0
pub fn matches_selector_list<'i, E>(
70
0
  selector_list: &SelectorList<'i, E::Impl>,
71
0
  element: &E,
72
0
  context: &mut MatchingContext<'_, 'i, E::Impl>,
73
0
) -> bool
74
0
where
75
0
  E: Element<'i>,
76
{
77
  // This is pretty much any(..) but manually inlined because the compiler
78
  // refuses to do so from querySelector / querySelectorAll.
79
0
  for selector in &selector_list.0 {
80
0
    let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {});
81
82
0
    if matches {
83
0
      return true;
84
0
    }
85
  }
86
87
0
  false
88
0
}
89
90
#[inline(always)]
91
0
fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool {
92
  // Check the first three hashes. Note that we can check for zero before
93
  // masking off the high bits, since if any of the first three hashes is
94
  // zero the fourth will be as well. We also take care to avoid the
95
  // special-case complexity of the fourth hash until we actually reach it,
96
  // because we usually don't.
97
  //
98
  // To be clear: this is all extremely hot.
99
0
  for i in 0..3 {
100
0
    let packed = hashes.packed_hashes[i];
101
0
    if packed == 0 {
102
      // No more hashes left - unable to fast-reject.
103
0
      return true;
104
0
    }
105
106
0
    if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) {
107
      // Hooray! We fast-rejected on this hash.
108
0
      return false;
109
0
    }
110
  }
111
112
  // Now do the slighty-more-complex work of synthesizing the fourth hash,
113
  // and check it against the filter if it exists.
114
0
  let fourth = hashes.fourth_hash();
115
0
  fourth == 0 || bf.might_contain_hash(fourth)
116
0
}
117
118
/// A result of selector matching, includes 3 failure types,
119
///
120
///   NotMatchedAndRestartFromClosestLaterSibling
121
///   NotMatchedAndRestartFromClosestDescendant
122
///   NotMatchedGlobally
123
///
124
/// When NotMatchedGlobally appears, stop selector matching completely since
125
/// the succeeding selectors never matches.
126
/// It is raised when
127
///   Child combinator cannot find the candidate element.
128
///   Descendant combinator cannot find the candidate element.
129
///
130
/// When NotMatchedAndRestartFromClosestDescendant appears, the selector
131
/// matching does backtracking and restarts from the closest Descendant
132
/// combinator.
133
/// It is raised when
134
///   NextSibling combinator cannot find the candidate element.
135
///   LaterSibling combinator cannot find the candidate element.
136
///   Child combinator doesn't match on the found element.
137
///
138
/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector
139
/// matching does backtracking and restarts from the closest LaterSibling
140
/// combinator.
141
/// It is raised when
142
///   NextSibling combinator doesn't match on the found element.
143
///
144
/// For example, when the selector "d1 d2 a" is provided and we cannot *find*
145
/// an appropriate ancestor element for "d1", this selector matching raises
146
/// NotMatchedGlobally since even if "d2" is moved to more upper element, the
147
/// candidates for "d1" becomes less than before and d1 .
148
///
149
/// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is
150
/// provided and we cannot *find* an appropriate brother element for b1,
151
/// the selector matching raises NotMatchedAndRestartFromClosestDescendant.
152
/// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1".
153
///
154
/// The additional example is child and sibling. When the selector
155
/// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on
156
/// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling.
157
/// However since the selector "c1" raises
158
/// NotMatchedAndRestartFromClosestDescendant. So the selector
159
/// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1".
160
#[derive(Clone, Copy, Eq, PartialEq)]
161
enum SelectorMatchingResult {
162
  Matched,
163
  NotMatchedAndRestartFromClosestLaterSibling,
164
  NotMatchedAndRestartFromClosestDescendant,
165
  NotMatchedGlobally,
166
}
167
168
/// Whether the :hover and :active quirk applies.
169
///
170
/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
171
#[derive(Clone, Copy, Debug, PartialEq)]
172
enum MatchesHoverAndActiveQuirk {
173
  Yes,
174
  No,
175
}
176
177
/// Matches a selector, fast-rejecting against a bloom filter.
178
///
179
/// We accept an offset to allow consumers to represent and match against
180
/// partial selectors (indexed from the right). We use this API design, rather
181
/// than having the callers pass a SelectorIter, because creating a SelectorIter
182
/// requires dereferencing the selector to get the length, which adds an
183
/// unnecessary cache miss for cases when we can fast-reject with AncestorHashes
184
/// (which the caller can store inline with the selector pointer).
185
#[inline(always)]
186
0
pub fn matches_selector<'i, E, F>(
187
0
  selector: &Selector<'i, E::Impl>,
188
0
  offset: usize,
189
0
  hashes: Option<&AncestorHashes>,
190
0
  element: &E,
191
0
  context: &mut MatchingContext<'_, 'i, E::Impl>,
192
0
  flags_setter: &mut F,
193
0
) -> bool
194
0
where
195
0
  E: Element<'i>,
196
0
  F: FnMut(&E, ElementSelectorFlags),
197
{
198
  // Use the bloom filter to fast-reject.
199
0
  if let Some(hashes) = hashes {
200
0
    if let Some(filter) = context.bloom_filter {
201
0
      if !may_match(hashes, filter) {
202
0
        return false;
203
0
      }
204
0
    }
205
0
  }
206
207
0
  matches_complex_selector(selector.iter_from(offset), element, context, flags_setter)
208
0
}
209
210
/// Whether a compound selector matched, and whether it was the rightmost
211
/// selector inside the complex selector.
212
pub enum CompoundSelectorMatchingResult {
213
  /// The selector was fully matched.
214
  FullyMatched,
215
  /// The compound selector matched, and the next combinator offset is
216
  /// `next_combinator_offset`.
217
  Matched { next_combinator_offset: usize },
218
  /// The selector didn't match.
219
  NotMatched,
220
}
221
222
/// Matches a compound selector belonging to `selector`, starting at offset
223
/// `from_offset`, matching left to right.
224
///
225
/// Requires that `from_offset` points to a `Combinator`.
226
///
227
/// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the
228
/// complex selector, but it happens to be the case we don't need it.
229
0
pub fn matches_compound_selector_from<'i, E>(
230
0
  selector: &Selector<'i, E::Impl>,
231
0
  mut from_offset: usize,
232
0
  context: &mut MatchingContext<'_, 'i, E::Impl>,
233
0
  element: &E,
234
0
) -> CompoundSelectorMatchingResult
235
0
where
236
0
  E: Element<'i>,
237
{
238
0
  if cfg!(debug_assertions) && from_offset != 0 {
239
0
    selector.combinator_at_parse_order(from_offset - 1); // This asserts.
240
0
  }
241
242
0
  let mut local_context = LocalMatchingContext {
243
0
    shared: context,
244
0
    matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk::No,
245
0
  };
246
247
  // Find the end of the selector or the next combinator, then match
248
  // backwards, so that we match in the same order as
249
  // matches_complex_selector, which is usually faster.
250
0
  let start_offset = from_offset;
251
0
  for component in selector.iter_raw_parse_order_from(from_offset) {
252
0
    if matches!(*component, Component::Combinator(..)) {
253
0
      debug_assert_ne!(from_offset, 0, "Selector started with a combinator?");
254
0
      break;
255
0
    }
256
257
0
    from_offset += 1;
258
  }
259
260
0
  debug_assert!(from_offset >= 1);
261
0
  debug_assert!(from_offset <= selector.len());
262
263
0
  let iter = selector.iter_from(selector.len() - from_offset);
264
0
  debug_assert!(
265
0
    iter.clone().next().is_some()
266
0
      || (from_offset != selector.len()
267
0
        && matches!(
268
0
          selector.combinator_at_parse_order(from_offset),
269
          Combinator::SlotAssignment | Combinator::PseudoElement
270
        )),
271
    "Got the math wrong: {:?} | {:?} | {} {}",
272
    selector,
273
0
    selector.iter_raw_match_order().as_slice(),
274
    from_offset,
275
    start_offset
276
  );
277
278
0
  for component in iter {
279
0
    if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) {
280
0
      return CompoundSelectorMatchingResult::NotMatched;
281
0
    }
282
  }
283
284
0
  if from_offset != selector.len() {
285
0
    return CompoundSelectorMatchingResult::Matched {
286
0
      next_combinator_offset: from_offset,
287
0
    };
288
0
  }
289
290
0
  CompoundSelectorMatchingResult::FullyMatched
291
0
}
292
293
/// Matches a complex selector.
294
#[inline(always)]
295
0
pub fn matches_complex_selector<'i, E, F>(
296
0
  mut iter: SelectorIter<'_, 'i, E::Impl>,
297
0
  element: &E,
298
0
  context: &mut MatchingContext<'_, 'i, E::Impl>,
299
0
  flags_setter: &mut F,
300
0
) -> bool
301
0
where
302
0
  E: Element<'i>,
303
0
  F: FnMut(&E, ElementSelectorFlags),
304
{
305
  // If this is the special pseudo-element mode, consume the ::pseudo-element
306
  // before proceeding, since the caller has already handled that part.
307
0
  if context.matching_mode() == MatchingMode::ForStatelessPseudoElement && !context.is_nested() {
308
    // Consume the pseudo.
309
0
    match *iter.next().unwrap() {
310
0
      Component::PseudoElement(ref pseudo) => {
311
0
        if let Some(ref f) = context.pseudo_element_matching_fn {
312
0
          if !f(pseudo) {
313
0
            return false;
314
0
          }
315
0
        }
316
      }
317
      _ => {
318
0
        debug_assert!(
319
0
          false,
320
          "Used MatchingMode::ForStatelessPseudoElement \
321
                     in a non-pseudo selector"
322
        );
323
      }
324
    }
325
326
0
    if !iter.matches_for_stateless_pseudo_element() {
327
0
      return false;
328
0
    }
329
330
    // Advance to the non-pseudo-element part of the selector.
331
0
    let next_sequence = iter.next_sequence().unwrap();
332
0
    debug_assert_eq!(next_sequence, Combinator::PseudoElement);
333
0
  }
334
335
0
  let result = matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes);
336
337
0
  matches!(result, SelectorMatchingResult::Matched)
338
0
}
339
340
#[inline]
341
0
fn matches_hover_and_active_quirk<'i, Impl: SelectorImpl<'i>>(
342
0
  selector_iter: &SelectorIter<'_, 'i, Impl>,
343
0
  context: &MatchingContext<'_, 'i, Impl>,
344
0
  rightmost: Rightmost,
345
0
) -> MatchesHoverAndActiveQuirk {
346
0
  if context.quirks_mode() != QuirksMode::Quirks {
347
0
    return MatchesHoverAndActiveQuirk::No;
348
0
  }
349
350
0
  if context.is_nested() {
351
0
    return MatchesHoverAndActiveQuirk::No;
352
0
  }
353
354
  // This compound selector had a pseudo-element to the right that we
355
  // intentionally skipped.
356
0
  if rightmost == Rightmost::Yes && context.matching_mode() == MatchingMode::ForStatelessPseudoElement {
357
0
    return MatchesHoverAndActiveQuirk::No;
358
0
  }
359
360
0
  let all_match = selector_iter.clone().all(|simple| match *simple {
361
    Component::LocalName(_)
362
    | Component::AttributeInNoNamespaceExists { .. }
363
    | Component::AttributeInNoNamespace { .. }
364
    | Component::AttributeOther(_)
365
    | Component::ID(_)
366
    | Component::Class(_)
367
    | Component::PseudoElement(_)
368
    | Component::Negation(_)
369
    | Component::Nth(_)
370
    | Component::NthOf(..)
371
0
    | Component::Empty => false,
372
0
    Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(),
373
0
    _ => true,
374
0
  });
375
376
0
  if all_match {
377
0
    MatchesHoverAndActiveQuirk::Yes
378
  } else {
379
0
    MatchesHoverAndActiveQuirk::No
380
  }
381
0
}
382
383
#[derive(Clone, Copy, PartialEq)]
384
enum Rightmost {
385
  Yes,
386
  No,
387
}
388
389
#[inline(always)]
390
0
fn next_element_for_combinator<'i, E>(
391
0
  element: &E,
392
0
  combinator: Combinator,
393
0
  selector: &SelectorIter<'_, 'i, E::Impl>,
394
0
  context: &MatchingContext<'_, 'i, E::Impl>,
395
0
) -> Option<E>
396
0
where
397
0
  E: Element<'i>,
398
{
399
0
  match combinator {
400
0
    Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(),
401
    Combinator::Child | Combinator::Descendant | Combinator::Deep | Combinator::DeepDescendant => {
402
0
      match element.parent_element() {
403
0
        Some(e) => return Some(e),
404
0
        None => {}
405
      }
406
407
0
      if !element.parent_node_is_shadow_root() {
408
0
        return None;
409
0
      }
410
411
      // https://drafts.csswg.org/css-scoping/#host-element-in-tree:
412
      //
413
      //   For the purpose of Selectors, a shadow host also appears in
414
      //   its shadow tree, with the contents of the shadow tree treated
415
      //   as its children. (In other words, the shadow host is treated as
416
      //   replacing the shadow root node.)
417
      //
418
      // and also:
419
      //
420
      //   When considered within its own shadow trees, the shadow host is
421
      //   featureless. Only the :host, :host(), and :host-context()
422
      //   pseudo-classes are allowed to match it.
423
      //
424
      // Since we know that the parent is a shadow root, we necessarily
425
      // are in a shadow tree of the host, and the next selector will only
426
      // match if the selector is a featureless :host selector.
427
0
      if !selector.clone().is_featureless_host_selector() {
428
0
        return None;
429
0
      }
430
431
0
      element.containing_shadow_host()
432
    }
433
0
    Combinator::Part => element.containing_shadow_host(),
434
    Combinator::SlotAssignment => {
435
0
      debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element()));
436
0
      let scope = context.current_host?;
437
0
      let mut current_slot = element.assigned_slot()?;
438
0
      while current_slot.containing_shadow_host().unwrap().opaque() != scope {
439
0
        current_slot = current_slot.assigned_slot()?;
440
      }
441
0
      Some(current_slot)
442
    }
443
0
    Combinator::PseudoElement => element.pseudo_element_originating_element(),
444
  }
445
0
}
446
447
0
fn matches_complex_selector_internal<'i, E, F>(
448
0
  mut selector_iter: SelectorIter<'_, 'i, E::Impl>,
449
0
  element: &E,
450
0
  context: &mut MatchingContext<'_, 'i, E::Impl>,
451
0
  flags_setter: &mut F,
452
0
  rightmost: Rightmost,
453
0
) -> SelectorMatchingResult
454
0
where
455
0
  E: Element<'i>,
456
0
  F: FnMut(&E, ElementSelectorFlags),
457
{
458
0
  debug!("Matching complex selector {:?} for {:?}", selector_iter, element);
459
460
0
  let matches_compound_selector =
461
0
    matches_compound_selector(&mut selector_iter, element, context, flags_setter, rightmost);
462
463
0
  let combinator = selector_iter.next_sequence();
464
0
  if combinator.map_or(false, |c| c.is_sibling()) {
465
0
    flags_setter(element, ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS);
466
0
  }
467
468
0
  if !matches_compound_selector {
469
0
    return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling;
470
0
  }
471
472
0
  let combinator = match combinator {
473
0
    None => return SelectorMatchingResult::Matched,
474
0
    Some(c) => c,
475
  };
476
477
0
  let candidate_not_found = match combinator {
478
    Combinator::NextSibling | Combinator::LaterSibling => {
479
0
      SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant
480
    }
481
    Combinator::Child
482
    | Combinator::Deep
483
    | Combinator::DeepDescendant
484
    | Combinator::Descendant
485
    | Combinator::SlotAssignment
486
    | Combinator::Part
487
0
    | Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally,
488
  };
489
490
0
  let mut next_element = next_element_for_combinator(element, combinator, &selector_iter, &context);
491
492
  // Stop matching :visited as soon as we find a link, or a combinator for
493
  // something that isn't an ancestor.
494
0
  let mut visited_handling = if element.is_link() || combinator.is_sibling() {
495
0
    VisitedHandlingMode::AllLinksUnvisited
496
  } else {
497
0
    context.visited_handling()
498
  };
499
500
  loop {
501
0
    let element = match next_element {
502
0
      None => return candidate_not_found,
503
0
      Some(next_element) => next_element,
504
    };
505
506
0
    let result = context.with_visited_handling_mode(visited_handling, |context| {
507
0
      matches_complex_selector_internal(selector_iter.clone(), &element, context, flags_setter, Rightmost::No)
508
0
    });
509
510
0
    match (result, combinator) {
511
      // Return the status immediately.
512
      (SelectorMatchingResult::Matched, _)
513
      | (SelectorMatchingResult::NotMatchedGlobally, _)
514
      | (_, Combinator::NextSibling) => {
515
0
        return result;
516
      }
517
518
      // Upgrade the failure status to
519
      // NotMatchedAndRestartFromClosestDescendant.
520
      (_, Combinator::PseudoElement) | (_, Combinator::Child) => {
521
0
        return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant;
522
      }
523
524
      // If the failure status is
525
      // NotMatchedAndRestartFromClosestDescendant and combinator is
526
      // Combinator::LaterSibling, give up this Combinator::LaterSibling
527
      // matching and restart from the closest descendant combinator.
528
      (SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, Combinator::LaterSibling) => {
529
0
        return result;
530
      }
531
532
      // The Combinator::Descendant combinator and the status is
533
      // NotMatchedAndRestartFromClosestLaterSibling or
534
      // NotMatchedAndRestartFromClosestDescendant, or the
535
      // Combinator::LaterSibling combinator and the status is
536
      // NotMatchedAndRestartFromClosestDescendant, we can continue to
537
      // matching on the next candidate element.
538
0
      _ => {}
539
    }
540
541
0
    if element.is_link() {
542
0
      visited_handling = VisitedHandlingMode::AllLinksUnvisited;
543
0
    }
544
545
0
    next_element = next_element_for_combinator(&element, combinator, &selector_iter, &context);
546
  }
547
0
}
548
549
#[inline]
550
0
fn matches_local_name<'i, E>(element: &E, local_name: &LocalName<'i, E::Impl>) -> bool
551
0
where
552
0
  E: Element<'i>,
553
{
554
0
  let name = select_name(
555
0
    element.is_html_element_in_html_document(),
556
0
    &local_name.name,
557
0
    &local_name.lower_name,
558
0
  )
559
0
  .borrow();
560
0
  element.has_local_name(name)
561
0
}
562
563
/// Determines whether the given element matches the given compound selector.
564
#[inline]
565
0
fn matches_compound_selector<'i, E, F>(
566
0
  selector_iter: &mut SelectorIter<'_, 'i, E::Impl>,
567
0
  element: &E,
568
0
  context: &mut MatchingContext<'_, 'i, E::Impl>,
569
0
  flags_setter: &mut F,
570
0
  rightmost: Rightmost,
571
0
) -> bool
572
0
where
573
0
  E: Element<'i>,
574
0
  F: FnMut(&E, ElementSelectorFlags),
575
{
576
0
  let matches_hover_and_active_quirk = matches_hover_and_active_quirk(&selector_iter, context, rightmost);
577
578
  // Handle some common cases first.
579
  // We may want to get rid of this at some point if we can make the
580
  // generic case fast enough.
581
0
  let mut selector = selector_iter.next();
582
0
  if let Some(&Component::LocalName(ref local_name)) = selector {
583
0
    if !matches_local_name(element, local_name) {
584
0
      return false;
585
0
    }
586
0
    selector = selector_iter.next();
587
0
  }
588
0
  let class_and_id_case_sensitivity = context.classes_and_ids_case_sensitivity();
589
0
  if let Some(&Component::ID(ref id)) = selector {
590
0
    if !element.has_id(id, class_and_id_case_sensitivity) {
591
0
      return false;
592
0
    }
593
0
    selector = selector_iter.next();
594
0
  }
595
0
  while let Some(&Component::Class(ref class)) = selector {
596
0
    if !element.has_class(class, class_and_id_case_sensitivity) {
597
0
      return false;
598
0
    }
599
0
    selector = selector_iter.next();
600
  }
601
0
  let selector = match selector {
602
0
    Some(s) => s,
603
0
    None => return true,
604
  };
605
606
0
  let mut local_context = LocalMatchingContext {
607
0
    shared: context,
608
0
    matches_hover_and_active_quirk,
609
0
  };
610
0
  iter::once(selector)
611
0
    .chain(selector_iter)
612
0
    .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter))
613
0
}
614
615
/// Determines whether the given element matches the given single selector.
616
0
fn matches_simple_selector<'i, E, F>(
617
0
  selector: &Component<'i, E::Impl>,
618
0
  element: &E,
619
0
  context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>,
620
0
  flags_setter: &mut F,
621
0
) -> bool
622
0
where
623
0
  E: Element<'i>,
624
0
  F: FnMut(&E, ElementSelectorFlags),
625
{
626
0
  debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
627
628
0
  match *selector {
629
0
    Component::Combinator(_) => unreachable!(),
630
0
    Component::Part(ref parts) => {
631
0
      let mut hosts = SmallVec::<[E; 4]>::new();
632
633
0
      let mut host = match element.containing_shadow_host() {
634
0
        Some(h) => h,
635
0
        None => return false,
636
      };
637
638
0
      let current_host = context.shared.current_host;
639
0
      if current_host != Some(host.opaque()) {
640
        loop {
641
0
          let outer_host = host.containing_shadow_host();
642
0
          if outer_host.as_ref().map(|h| h.opaque()) == current_host {
643
0
            break;
644
0
          }
645
0
          let outer_host = match outer_host {
646
0
            Some(h) => h,
647
0
            None => return false,
648
          };
649
          // TODO(emilio): if worth it, we could early return if
650
          // host doesn't have the exportparts attribute.
651
0
          hosts.push(host);
652
0
          host = outer_host;
653
        }
654
0
      }
655
656
      // Translate the part into the right scope.
657
0
      parts.iter().all(|part| {
658
0
        let mut part = part.clone();
659
0
        for host in hosts.iter().rev() {
660
0
          part = match host.imported_part(&part) {
661
0
            Some(p) => p,
662
0
            None => return false,
663
          };
664
        }
665
0
        element.is_part(&part)
666
0
      })
667
    }
668
0
    Component::Slotted(ref selector) => {
669
      // <slots> are never flattened tree slottables.
670
0
      !element.is_html_slot_element()
671
0
        && context
672
0
          .shared
673
0
          .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter))
674
    }
675
0
    Component::PseudoElement(ref pseudo) => element.match_pseudo_element(pseudo, context.shared),
676
0
    Component::LocalName(ref local_name) => matches_local_name(element, local_name),
677
0
    Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => true,
678
0
    Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => {
679
0
      element.has_namespace(&url.borrow())
680
    }
681
    Component::ExplicitNoNamespace => {
682
0
      let ns = crate::parser::namespace_empty_string::<E::Impl>();
683
0
      element.has_namespace(&ns.borrow())
684
    }
685
0
    Component::ID(ref id) => element.has_id(id, context.shared.classes_and_ids_case_sensitivity()),
686
0
    Component::Class(ref class) => element.has_class(class, context.shared.classes_and_ids_case_sensitivity()),
687
    Component::AttributeInNoNamespaceExists {
688
0
      ref local_name,
689
0
      ref local_name_lower,
690
    } => {
691
0
      let is_html = element.is_html_element_in_html_document();
692
0
      element.attr_matches(
693
0
        &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()),
694
0
        select_name(is_html, local_name, local_name_lower),
695
0
        &AttrSelectorOperation::Exists,
696
      )
697
    }
698
    Component::AttributeInNoNamespace {
699
0
      ref local_name,
700
0
      ref value,
701
0
      operator,
702
0
      case_sensitivity,
703
0
      never_matches,
704
    } => {
705
0
      if never_matches {
706
0
        return false;
707
0
      }
708
0
      let is_html = element.is_html_element_in_html_document();
709
0
      element.attr_matches(
710
0
        &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()),
711
0
        local_name,
712
0
        &AttrSelectorOperation::WithValue {
713
0
          operator,
714
0
          case_sensitivity: case_sensitivity.to_unconditional(is_html),
715
0
          expected_value: value,
716
0
        },
717
      )
718
    }
719
0
    Component::AttributeOther(ref attr_sel) => {
720
0
      if attr_sel.never_matches {
721
0
        return false;
722
0
      }
723
0
      let is_html = element.is_html_element_in_html_document();
724
      let empty_string;
725
0
      let namespace = match attr_sel.namespace() {
726
0
        Some(ns) => ns,
727
        None => {
728
0
          empty_string = crate::parser::namespace_empty_string::<E::Impl>();
729
0
          NamespaceConstraint::Specific(&empty_string)
730
        }
731
      };
732
0
      element.attr_matches(
733
0
        &namespace,
734
0
        select_name(is_html, &attr_sel.local_name, &attr_sel.local_name_lower),
735
0
        &match attr_sel.operation {
736
0
          ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists,
737
          ParsedAttrSelectorOperation::WithValue {
738
0
            operator,
739
0
            case_sensitivity,
740
0
            ref expected_value,
741
0
          } => AttrSelectorOperation::WithValue {
742
0
            operator,
743
0
            case_sensitivity: case_sensitivity.to_unconditional(is_html),
744
0
            expected_value,
745
0
          },
746
        },
747
      )
748
    }
749
0
    Component::NonTSPseudoClass(ref pc) => {
750
0
      if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes
751
0
        && !context.shared.is_nested()
752
0
        && pc.is_active_or_hover()
753
0
        && !element.is_link()
754
      {
755
0
        return false;
756
0
      }
757
758
0
      element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter)
759
    }
760
0
    Component::Root => element.is_root(),
761
    Component::Empty => {
762
0
      flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
763
0
      element.is_empty()
764
    }
765
0
    Component::Host(ref selector) => {
766
0
      context.shared.shadow_host().map_or(false, |host| host == element.opaque())
767
0
        && selector.as_ref().map_or(true, |selector| {
768
0
          context
769
0
            .shared
770
0
            .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter))
771
0
        })
772
    }
773
0
    Component::Scope => match context.shared.scope_element {
774
0
      Some(ref scope_element) => element.opaque() == *scope_element,
775
0
      None => element.is_root(),
776
    },
777
0
    Component::Nth(data) => match data.ty {
778
0
      NthType::Child => match (data.a, data.b) {
779
0
        (0, 1) => matches_first_child(element, flags_setter),
780
0
        (a, b) => matches_generic_nth_child(element, context, a, b, false, false, flags_setter),
781
      },
782
0
      NthType::LastChild => match (data.a, data.b) {
783
0
        (0, 1) => matches_last_child(element, flags_setter),
784
0
        (a, b) => matches_generic_nth_child(element, context, a, b, false, true, flags_setter),
785
      },
786
      NthType::OnlyChild => {
787
0
        matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter)
788
      }
789
0
      NthType::OfType => matches_generic_nth_child(element, context, data.a, data.b, true, false, flags_setter),
790
0
      NthType::LastOfType => matches_generic_nth_child(element, context, data.a, data.b, true, true, flags_setter),
791
      NthType::OnlyOfType => {
792
0
        matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter)
793
0
          && matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter)
794
      }
795
0
      _ => todo!(),
796
    },
797
0
    Component::NthOf(..) => todo!(),
798
0
    Component::Is(ref list) | Component::Where(ref list) | Component::Any(_, ref list) => {
799
0
      context.shared.nest(|context| {
800
0
        for selector in &**list {
801
0
          if matches_complex_selector(selector.iter(), element, context, flags_setter) {
802
0
            return true;
803
0
          }
804
        }
805
0
        false
806
0
      })
807
    }
808
0
    Component::Negation(ref list) => context.shared.nest_for_negation(|context| {
809
0
      for selector in &**list {
810
0
        if matches_complex_selector(selector.iter(), element, context, flags_setter) {
811
0
          return false;
812
0
        }
813
      }
814
0
      true
815
0
    }),
816
0
    Component::Nesting | Component::Has(..) => unreachable!(),
817
  }
818
0
}
819
820
#[inline(always)]
821
0
fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T {
822
0
  if is_html {
823
0
    local_name_lower
824
  } else {
825
0
    local_name
826
  }
827
0
}
828
829
#[inline]
830
0
fn matches_generic_nth_child<'i, E, F>(
831
0
  element: &E,
832
0
  context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>,
833
0
  a: i32,
834
0
  b: i32,
835
0
  is_of_type: bool,
836
0
  is_from_end: bool,
837
0
  flags_setter: &mut F,
838
0
) -> bool
839
0
where
840
0
  E: Element<'i>,
841
0
  F: FnMut(&E, ElementSelectorFlags),
842
{
843
0
  if element.ignores_nth_child_selectors() {
844
0
    return false;
845
0
  }
846
847
0
  flags_setter(
848
0
    element,
849
0
    if is_from_end {
850
0
      ElementSelectorFlags::HAS_SLOW_SELECTOR
851
    } else {
852
0
      ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
853
    },
854
  );
855
856
  // Grab a reference to the appropriate cache.
857
0
  let mut cache = context.shared.nth_index_cache.as_mut().map(|c| c.get(is_of_type, is_from_end));
858
859
  // Lookup or compute the index.
860
0
  let index = if let Some(i) = cache.as_mut().and_then(|c| c.lookup(element.opaque())) {
861
0
    i
862
  } else {
863
0
    let i = nth_child_index(element, is_of_type, is_from_end, cache.as_deref_mut());
864
0
    if let Some(c) = cache.as_mut() {
865
0
      c.insert(element.opaque(), i)
866
0
    }
867
0
    i
868
  };
869
0
  debug_assert_eq!(
870
    index,
871
0
    nth_child_index(element, is_of_type, is_from_end, None),
872
    "invalid cache"
873
  );
874
875
  // Is there a non-negative integer n such that An+B=index?
876
0
  match index.checked_sub(b) {
877
0
    None => false,
878
0
    Some(an) => match an.checked_div(a) {
879
0
            Some(n) => n >= 0 && a * n == an,
880
0
            None /* a == 0 */ => an == 0,
881
        },
882
  }
883
0
}
884
885
#[inline]
886
0
fn nth_child_index<'i, E>(
887
0
  element: &E,
888
0
  is_of_type: bool,
889
0
  is_from_end: bool,
890
0
  mut cache: Option<&mut NthIndexCacheInner>,
891
0
) -> i32
892
0
where
893
0
  E: Element<'i>,
894
{
895
  // The traversal mostly processes siblings left to right. So when we walk
896
  // siblings to the right when computing NthLast/NthLastOfType we're unlikely
897
  // to get cache hits along the way. As such, we take the hit of walking the
898
  // siblings to the left checking the cache in the is_from_end case (this
899
  // matches what Gecko does). The indices-from-the-left is handled during the
900
  // regular look further below.
901
0
  if let Some(ref mut c) = cache {
902
0
    if is_from_end && !c.is_empty() {
903
0
      let mut index: i32 = 1;
904
0
      let mut curr = element.clone();
905
0
      while let Some(e) = curr.prev_sibling_element() {
906
0
        curr = e;
907
0
        if !is_of_type || element.is_same_type(&curr) {
908
0
          if let Some(i) = c.lookup(curr.opaque()) {
909
0
            return i - index;
910
0
          }
911
0
          index += 1;
912
0
        }
913
      }
914
0
    }
915
0
  }
916
917
0
  let mut index: i32 = 1;
918
0
  let mut curr = element.clone();
919
0
  let next = |e: E| {
920
0
    if is_from_end {
921
0
      e.next_sibling_element()
922
    } else {
923
0
      e.prev_sibling_element()
924
    }
925
0
  };
926
0
  while let Some(e) = next(curr) {
927
0
    curr = e;
928
0
    if !is_of_type || element.is_same_type(&curr) {
929
      // If we're computing indices from the left, check each element in the
930
      // cache. We handle the indices-from-the-right case at the top of this
931
      // function.
932
0
      if !is_from_end {
933
0
        if let Some(i) = cache.as_mut().and_then(|c| c.lookup(curr.opaque())) {
934
0
          return i + index;
935
0
        }
936
0
      }
937
0
      index += 1;
938
0
    }
939
  }
940
941
0
  index
942
0
}
943
944
#[inline]
945
0
fn matches_first_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool
946
0
where
947
0
  E: Element<'i>,
948
0
  F: FnMut(&E, ElementSelectorFlags),
949
{
950
0
  flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
951
0
  element.prev_sibling_element().is_none()
952
0
}
953
954
#[inline]
955
0
fn matches_last_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool
956
0
where
957
0
  E: Element<'i>,
958
0
  F: FnMut(&E, ElementSelectorFlags),
959
{
960
0
  flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
961
0
  element.next_sibling_element().is_none()
962
0
}