Skip to main content

selectors/
context.rs

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
5use crate::attr::CaseSensitivity;
6use crate::bloom::BloomFilter;
7use crate::nth_index_cache::{NthIndexCache, NthIndexCacheInner};
8use crate::parser::{Selector, SelectorImpl};
9use crate::relative_selector::cache::RelativeSelectorCache;
10use crate::relative_selector::filter::RelativeSelectorFilterMap;
11use crate::tree::{Element, OpaqueElement};
12
13/// What kind of selector matching mode we should use.
14///
15/// There are two modes of selector matching. The difference is only noticeable
16/// in presence of pseudo-elements.
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub enum MatchingMode {
19    /// Don't ignore any pseudo-element selectors.
20    Normal,
21
22    /// Ignores any stateless pseudo-element selectors in the rightmost sequence
23    /// of simple selectors.
24    ///
25    /// This is useful, for example, to match against ::before when you aren't a
26    /// pseudo-element yourself.
27    ///
28    /// For example, in presence of `::before:hover`, it would never match, but
29    /// `::before` would be ignored as in "matching".
30    ///
31    /// It's required for all the selectors you match using this mode to have a
32    /// pseudo-element.
33    ForStatelessPseudoElement,
34}
35
36/// The mode to use when matching unvisited and visited links.
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub enum VisitedHandlingMode {
39    /// All links are matched as if they are unvisted.
40    AllLinksUnvisited,
41    /// All links are matched as if they are visited and unvisited (both :link
42    /// and :visited match).
43    ///
44    /// This is intended to be used from invalidation code, to be conservative
45    /// about whether we need to restyle a link.
46    AllLinksVisitedAndUnvisited,
47    /// A element's "relevant link" is the element being matched if it is a link
48    /// or the nearest ancestor link. The relevant link is matched as though it
49    /// is visited, and all other links are matched as if they are unvisited.
50    RelevantLinkVisited,
51}
52
53impl VisitedHandlingMode {
54    #[inline]
55    pub fn matches_visited(&self) -> bool {
56        matches!(
57            *self,
58            VisitedHandlingMode::RelevantLinkVisited
59                | VisitedHandlingMode::AllLinksVisitedAndUnvisited
60        )
61    }
62
63    #[inline]
64    pub fn matches_unvisited(&self) -> bool {
65        matches!(
66            *self,
67            VisitedHandlingMode::AllLinksUnvisited
68                | VisitedHandlingMode::AllLinksVisitedAndUnvisited
69        )
70    }
71}
72
73/// Whether we need to set selector invalidation flags on elements for this
74/// match request.
75#[derive(Clone, Copy, Debug, PartialEq)]
76pub enum NeedsSelectorFlags {
77    No,
78    Yes,
79}
80
81/// Whether we're matching in the contect of invalidation.
82#[derive(Clone, Copy, PartialEq)]
83pub enum MatchingForInvalidation {
84    No,
85    Yes,
86    YesForComparison,
87}
88
89impl MatchingForInvalidation {
90    /// Are we matching for invalidation?
91    pub fn is_for_invalidation(&self) -> bool {
92        matches!(*self, Self::Yes | Self::YesForComparison)
93    }
94}
95
96/// Which quirks mode is this document in.
97///
98/// See: https://quirks.spec.whatwg.org/
99#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
100pub enum QuirksMode {
101    /// Quirks mode.
102    Quirks,
103    /// Limited quirks mode.
104    LimitedQuirks,
105    /// No quirks mode.
106    NoQuirks,
107}
108
109impl QuirksMode {
110    #[inline]
111    pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity {
112        match self {
113            QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive,
114            QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive,
115        }
116    }
117}
118
119/// Set of caches (And cache-likes) that speed up expensive selector matches.
120#[derive(Default)]
121pub struct SelectorCaches {
122    /// A cache to speed up nth-index-like selectors.
123    pub nth_index: NthIndexCache,
124    /// A cache to speed up relative selector matches. See module documentation.
125    pub relative_selector: RelativeSelectorCache,
126    /// A map of bloom filters to fast-reject relative selector matches.
127    pub relative_selector_filter_map: RelativeSelectorFilterMap,
128}
129
130/// Data associated with the matching process for a element.  This context is
131/// used across many selectors for an element, so it's not appropriate for
132/// transient data that applies to only a single selector.
133pub struct MatchingContext<'a, Impl>
134where
135    Impl: SelectorImpl,
136{
137    /// Input with the matching mode we should use when matching selectors.
138    matching_mode: MatchingMode,
139    /// Input with the bloom filter used to fast-reject selectors.
140    pub bloom_filter: Option<&'a BloomFilter>,
141    /// The element which is going to match :scope pseudo-class. It can be
142    /// either one :scope element, or the scoping element.
143    ///
144    /// Note that, although in theory there can be multiple :scope elements,
145    /// in current specs, at most one is specified, and when there is one,
146    /// scoping element is not relevant anymore, so we use a single field for
147    /// them.
148    ///
149    /// When this is None, :scope will match the root element.
150    ///
151    /// See https://drafts.csswg.org/selectors-4/#scope-pseudo
152    pub scope_element: Option<OpaqueElement>,
153
154    /// The current shadow host we're collecting :host rules for.
155    pub current_host: Option<OpaqueElement>,
156
157    /// Controls how matching for links is handled.
158    visited_handling: VisitedHandlingMode,
159
160    /// Whether we're currently matching a featureless element.
161    pub featureless: bool,
162
163    /// The current nesting level of selectors that we're matching.
164    nesting_level: usize,
165
166    /// Whether we're inside a negation or not.
167    in_negation: bool,
168
169    /// An optional hook function for checking whether a pseudo-element
170    /// should match when matching_mode is ForStatelessPseudoElement.
171    #[allow(clippy::type_complexity)]
172    pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>,
173
174    /// Extra implementation-dependent matching data.
175    pub extra_data: Impl::ExtraMatchingData<'a>,
176
177    /// The current element we're anchoring on for evaluating the relative selector.
178    current_relative_selector_anchor: Option<OpaqueElement>,
179
180    quirks_mode: QuirksMode,
181    needs_selector_flags: NeedsSelectorFlags,
182
183    /// Whether we're matching in the context of invalidation.
184    matching_for_invalidation: MatchingForInvalidation,
185
186    /// Whether we're matching in the context of revalidation.
187    matching_for_revalidation: bool,
188
189    /// Caches to speed up expensive selector matches.
190    pub selector_caches: &'a mut SelectorCaches,
191
192    classes_and_ids_case_sensitivity: CaseSensitivity,
193    _impl: ::std::marker::PhantomData<Impl>,
194}
195
196impl<'a, Impl> MatchingContext<'a, Impl>
197where
198    Impl: SelectorImpl,
199{
200    /// Constructs a new `MatchingContext`.
201    pub fn new(
202        matching_mode: MatchingMode,
203        bloom_filter: Option<&'a BloomFilter>,
204        selector_caches: &'a mut SelectorCaches,
205        quirks_mode: QuirksMode,
206        needs_selector_flags: NeedsSelectorFlags,
207        matching_for_invalidation: MatchingForInvalidation,
208    ) -> Self {
209        Self::new_internal(
210            matching_mode,
211            bloom_filter,
212            selector_caches,
213            VisitedHandlingMode::AllLinksUnvisited,
214            quirks_mode,
215            needs_selector_flags,
216            matching_for_invalidation,
217            false,
218        )
219    }
220
221    /// Constructs a new `MatchingContext` for revalidation.
222    pub fn new_for_revalidation(
223        bloom_filter: Option<&'a BloomFilter>,
224        selector_caches: &'a mut SelectorCaches,
225        quirks_mode: QuirksMode,
226        needs_selector_flags: NeedsSelectorFlags,
227    ) -> Self {
228        Self::new_internal(
229            // NB: `MatchingMode` doesn't really matter, given we don't share style
230            // between pseudos.
231            MatchingMode::Normal,
232            bloom_filter,
233            selector_caches,
234            VisitedHandlingMode::AllLinksUnvisited,
235            quirks_mode,
236            needs_selector_flags,
237            MatchingForInvalidation::No,
238            true,
239        )
240    }
241
242    /// Constructs a new `MatchingContext` for use in visited matching.
243    pub fn new_for_visited(
244        matching_mode: MatchingMode,
245        bloom_filter: Option<&'a BloomFilter>,
246        selector_caches: &'a mut SelectorCaches,
247        visited_handling: VisitedHandlingMode,
248        quirks_mode: QuirksMode,
249        needs_selector_flags: NeedsSelectorFlags,
250        matching_for_invalidation: MatchingForInvalidation,
251    ) -> Self {
252        Self::new_internal(
253            matching_mode,
254            bloom_filter,
255            selector_caches,
256            visited_handling,
257            quirks_mode,
258            needs_selector_flags,
259            matching_for_invalidation,
260            false,
261        )
262    }
263
264    #[allow(clippy::too_many_arguments)]
265    fn new_internal(
266        matching_mode: MatchingMode,
267        bloom_filter: Option<&'a BloomFilter>,
268        selector_caches: &'a mut SelectorCaches,
269        visited_handling: VisitedHandlingMode,
270        quirks_mode: QuirksMode,
271        needs_selector_flags: NeedsSelectorFlags,
272        matching_for_invalidation: MatchingForInvalidation,
273        matching_for_revalidation: bool,
274    ) -> Self {
275        Self {
276            matching_mode,
277            bloom_filter,
278            visited_handling,
279            quirks_mode,
280            classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
281            needs_selector_flags,
282            matching_for_invalidation,
283            matching_for_revalidation,
284            scope_element: None,
285            current_host: None,
286            featureless: false,
287            nesting_level: 0,
288            in_negation: false,
289            pseudo_element_matching_fn: None,
290            extra_data: Default::default(),
291            current_relative_selector_anchor: None,
292            selector_caches,
293            _impl: ::std::marker::PhantomData,
294        }
295    }
296
297    // Grab a reference to the appropriate cache.
298    #[inline]
299    pub fn nth_index_cache(
300        &mut self,
301        is_of_type: bool,
302        is_from_end: bool,
303        selectors: &[Selector<Impl>],
304    ) -> &mut NthIndexCacheInner {
305        self.selector_caches
306            .nth_index
307            .get(is_of_type, is_from_end, selectors)
308    }
309
310    /// Whether we're matching a nested selector.
311    #[inline]
312    pub fn is_nested(&self) -> bool {
313        self.nesting_level != 0
314    }
315
316    /// Whether we're matching inside a :not(..) selector.
317    #[inline]
318    pub fn in_negation(&self) -> bool {
319        self.in_negation
320    }
321
322    /// The quirks mode of the document.
323    #[inline]
324    pub fn quirks_mode(&self) -> QuirksMode {
325        self.quirks_mode
326    }
327
328    /// The matching-mode for this selector-matching operation.
329    #[inline]
330    pub fn matching_mode(&self) -> MatchingMode {
331        self.matching_mode
332    }
333
334    /// Whether we need to set selector flags.
335    #[inline]
336    pub fn needs_selector_flags(&self) -> bool {
337        self.needs_selector_flags == NeedsSelectorFlags::Yes
338    }
339
340    /// Whether or not we're matching to invalidate.
341    #[inline]
342    pub fn matching_for_invalidation(&self) -> bool {
343        self.matching_for_invalidation.is_for_invalidation()
344    }
345
346    /// Whether or not we're matching to revalidate.
347    #[inline]
348    pub fn matching_for_revalidation(&self) -> bool {
349        self.matching_for_revalidation
350    }
351
352    /// Whether or not we're comparing for invalidation, if we are matching for invalidation.
353    #[inline]
354    pub fn matching_for_invalidation_comparison(&self) -> Option<bool> {
355        match self.matching_for_invalidation {
356            MatchingForInvalidation::No => None,
357            MatchingForInvalidation::Yes => Some(false),
358            MatchingForInvalidation::YesForComparison => Some(true),
359        }
360    }
361
362    /// Run the given matching function for before/after invalidation comparison.
363    #[inline]
364    pub fn for_invalidation_comparison<F, R>(&mut self, f: F) -> R
365    where
366        F: FnOnce(&mut Self) -> R,
367    {
368        debug_assert!(
369            self.matching_for_invalidation(),
370            "Not matching for invalidation?"
371        );
372        let prev = self.matching_for_invalidation;
373        self.matching_for_invalidation = MatchingForInvalidation::YesForComparison;
374        let result = f(self);
375        self.matching_for_invalidation = prev;
376        result
377    }
378
379    /// The case-sensitivity for class and ID selectors
380    #[inline]
381    pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
382        self.classes_and_ids_case_sensitivity
383    }
384
385    /// Runs F with a deeper nesting level.
386    #[inline]
387    pub fn nest<F, R>(&mut self, f: F) -> R
388    where
389        F: FnOnce(&mut Self) -> R,
390    {
391        self.nesting_level += 1;
392        let result = f(self);
393        self.nesting_level -= 1;
394        result
395    }
396
397    /// Runs F with a deeper nesting level, and marking ourselves in a negation,
398    /// for a :not(..) selector, for example.
399    #[inline]
400    pub fn nest_for_negation<F, R>(&mut self, f: F) -> R
401    where
402        F: FnOnce(&mut Self) -> R,
403    {
404        let old_in_negation = self.in_negation;
405        self.in_negation = !self.in_negation;
406        let result = self.nest(f);
407        self.in_negation = old_in_negation;
408        result
409    }
410
411    #[inline]
412    pub fn visited_handling(&self) -> VisitedHandlingMode {
413        self.visited_handling
414    }
415
416    /// Runs F with a different featureless element flag.
417    #[inline]
418    pub fn with_featureless<F, R>(&mut self, featureless: bool, f: F) -> R
419    where
420        F: FnOnce(&mut Self) -> R,
421    {
422        let orig = self.featureless;
423        self.featureless = featureless;
424        let result = f(self);
425        self.featureless = orig;
426        result
427    }
428
429    /// Returns whether the currently matching element is acting as a featureless element (e.g.,
430    /// because we've crossed a shadow boundary). This is used to implement the :host selector
431    /// rules properly.
432    #[inline]
433    pub fn featureless(&self) -> bool {
434        self.featureless
435    }
436
437    /// Runs F with a different VisitedHandlingMode.
438    #[inline]
439    pub fn with_visited_handling_mode<F, R>(
440        &mut self,
441        handling_mode: VisitedHandlingMode,
442        f: F,
443    ) -> R
444    where
445        F: FnOnce(&mut Self) -> R,
446    {
447        let original_handling_mode = self.visited_handling;
448        self.visited_handling = handling_mode;
449        let result = f(self);
450        self.visited_handling = original_handling_mode;
451        result
452    }
453
454    /// Runs F with a given shadow host which is the root of the tree whose
455    /// rules we're matching.
456    #[inline]
457    pub fn with_shadow_host<F, E, R>(&mut self, host: Option<E>, f: F) -> R
458    where
459        E: Element,
460        F: FnOnce(&mut Self) -> R,
461    {
462        let original_host = self.current_host.take();
463        self.current_host = host.map(|h| h.opaque());
464        let result = f(self);
465        self.current_host = original_host;
466        result
467    }
468
469    /// Returns the current shadow host whose shadow root we're matching rules
470    /// against.
471    #[inline]
472    pub fn shadow_host(&self) -> Option<OpaqueElement> {
473        self.current_host
474    }
475
476    /// Runs F with a deeper nesting level, with the given element as the anchor,
477    /// for a :has(...) selector, for example.
478    #[inline]
479    pub fn nest_for_relative_selector<F, R>(&mut self, anchor: OpaqueElement, f: F) -> R
480    where
481        F: FnOnce(&mut Self) -> R,
482    {
483        debug_assert!(
484            self.current_relative_selector_anchor.is_none(),
485            "Nesting should've been rejected at parse time"
486        );
487        self.current_relative_selector_anchor = Some(anchor);
488        let result = self.nest(f);
489        self.current_relative_selector_anchor = None;
490        result
491    }
492
493    /// Runs F with a deeper nesting level, with the given element as the scope.
494    #[inline]
495    pub fn nest_for_scope<F, R>(&mut self, scope: Option<OpaqueElement>, f: F) -> R
496    where
497        F: FnOnce(&mut Self) -> R,
498    {
499        let original_scope_element = self.scope_element;
500        self.scope_element = scope;
501        let result = f(self);
502        self.scope_element = original_scope_element;
503        result
504    }
505
506    /// Runs F with a deeper nesting level, with the given element as the scope, for
507    /// matching `scope-start` and/or `scope-end` conditions.
508    #[inline]
509    pub fn nest_for_scope_condition<F, R>(&mut self, scope: Option<OpaqueElement>, f: F) -> R
510    where
511        F: FnOnce(&mut Self) -> R,
512    {
513        let original_matching_mode = self.matching_mode;
514        // We may as well be matching for a pseudo-element inside `@scope`, but
515        // the scope-defining selectors wouldn't be matching them.
516        self.matching_mode = MatchingMode::Normal;
517        let result = self.nest_for_scope(scope, f);
518        self.matching_mode = original_matching_mode;
519        result
520    }
521
522    /// Returns the current anchor element to evaluate the relative selector against.
523    #[inline]
524    pub fn relative_selector_anchor(&self) -> Option<OpaqueElement> {
525        self.current_relative_selector_anchor
526    }
527}