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    pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>,
172
173    /// Extra implementation-dependent matching data.
174    pub extra_data: Impl::ExtraMatchingData<'a>,
175
176    /// The current element we're anchoring on for evaluating the relative selector.
177    current_relative_selector_anchor: Option<OpaqueElement>,
178
179    quirks_mode: QuirksMode,
180    needs_selector_flags: NeedsSelectorFlags,
181
182    /// Whether we're matching in the context of invalidation.
183    matching_for_invalidation: MatchingForInvalidation,
184
185    /// Whether we're matching in the context of revalidation.
186    matching_for_revalidation: bool,
187
188    /// Caches to speed up expensive selector matches.
189    pub selector_caches: &'a mut SelectorCaches,
190
191    classes_and_ids_case_sensitivity: CaseSensitivity,
192    _impl: ::std::marker::PhantomData<Impl>,
193}
194
195impl<'a, Impl> MatchingContext<'a, Impl>
196where
197    Impl: SelectorImpl,
198{
199    /// Constructs a new `MatchingContext`.
200    pub fn new(
201        matching_mode: MatchingMode,
202        bloom_filter: Option<&'a BloomFilter>,
203        selector_caches: &'a mut SelectorCaches,
204        quirks_mode: QuirksMode,
205        needs_selector_flags: NeedsSelectorFlags,
206        matching_for_invalidation: MatchingForInvalidation,
207    ) -> Self {
208        Self::new_internal(
209            matching_mode,
210            bloom_filter,
211            selector_caches,
212            VisitedHandlingMode::AllLinksUnvisited,
213            quirks_mode,
214            needs_selector_flags,
215            matching_for_invalidation,
216            false,
217        )
218    }
219
220    /// Constructs a new `MatchingContext` for revalidation.
221    pub fn new_for_revalidation(
222        bloom_filter: Option<&'a BloomFilter>,
223        selector_caches: &'a mut SelectorCaches,
224        quirks_mode: QuirksMode,
225        needs_selector_flags: NeedsSelectorFlags,
226    ) -> Self {
227        Self::new_internal(
228            // NB: `MatchingMode` doesn't really matter, given we don't share style
229            // between pseudos.
230            MatchingMode::Normal,
231            bloom_filter,
232            selector_caches,
233            VisitedHandlingMode::AllLinksUnvisited,
234            quirks_mode,
235            needs_selector_flags,
236            MatchingForInvalidation::No,
237            true,
238        )
239    }
240
241    /// Constructs a new `MatchingContext` for use in visited matching.
242    pub fn new_for_visited(
243        matching_mode: MatchingMode,
244        bloom_filter: Option<&'a BloomFilter>,
245        selector_caches: &'a mut SelectorCaches,
246        visited_handling: VisitedHandlingMode,
247        quirks_mode: QuirksMode,
248        needs_selector_flags: NeedsSelectorFlags,
249        matching_for_invalidation: MatchingForInvalidation,
250    ) -> Self {
251        Self::new_internal(
252            matching_mode,
253            bloom_filter,
254            selector_caches,
255            visited_handling,
256            quirks_mode,
257            needs_selector_flags,
258            matching_for_invalidation,
259            false,
260        )
261    }
262
263    fn new_internal(
264        matching_mode: MatchingMode,
265        bloom_filter: Option<&'a BloomFilter>,
266        selector_caches: &'a mut SelectorCaches,
267        visited_handling: VisitedHandlingMode,
268        quirks_mode: QuirksMode,
269        needs_selector_flags: NeedsSelectorFlags,
270        matching_for_invalidation: MatchingForInvalidation,
271        matching_for_revalidation: bool,
272    ) -> Self {
273        Self {
274            matching_mode,
275            bloom_filter,
276            visited_handling,
277            quirks_mode,
278            classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
279            needs_selector_flags,
280            matching_for_invalidation,
281            matching_for_revalidation,
282            scope_element: None,
283            current_host: None,
284            featureless: false,
285            nesting_level: 0,
286            in_negation: false,
287            pseudo_element_matching_fn: None,
288            extra_data: Default::default(),
289            current_relative_selector_anchor: None,
290            selector_caches,
291            _impl: ::std::marker::PhantomData,
292        }
293    }
294
295    // Grab a reference to the appropriate cache.
296    #[inline]
297    pub fn nth_index_cache(
298        &mut self,
299        is_of_type: bool,
300        is_from_end: bool,
301        selectors: &[Selector<Impl>],
302    ) -> &mut NthIndexCacheInner {
303        self.selector_caches
304            .nth_index
305            .get(is_of_type, is_from_end, selectors)
306    }
307
308    /// Whether we're matching a nested selector.
309    #[inline]
310    pub fn is_nested(&self) -> bool {
311        self.nesting_level != 0
312    }
313
314    /// Whether we're matching inside a :not(..) selector.
315    #[inline]
316    pub fn in_negation(&self) -> bool {
317        self.in_negation
318    }
319
320    /// The quirks mode of the document.
321    #[inline]
322    pub fn quirks_mode(&self) -> QuirksMode {
323        self.quirks_mode
324    }
325
326    /// The matching-mode for this selector-matching operation.
327    #[inline]
328    pub fn matching_mode(&self) -> MatchingMode {
329        self.matching_mode
330    }
331
332    /// Whether we need to set selector flags.
333    #[inline]
334    pub fn needs_selector_flags(&self) -> bool {
335        self.needs_selector_flags == NeedsSelectorFlags::Yes
336    }
337
338    /// Whether or not we're matching to invalidate.
339    #[inline]
340    pub fn matching_for_invalidation(&self) -> bool {
341        self.matching_for_invalidation.is_for_invalidation()
342    }
343
344    /// Whether or not we're matching to revalidate.
345    #[inline]
346    pub fn matching_for_revalidation(&self) -> bool {
347        self.matching_for_revalidation
348    }
349
350    /// Whether or not we're comparing for invalidation, if we are matching for invalidation.
351    #[inline]
352    pub fn matching_for_invalidation_comparison(&self) -> Option<bool> {
353        match self.matching_for_invalidation {
354            MatchingForInvalidation::No => None,
355            MatchingForInvalidation::Yes => Some(false),
356            MatchingForInvalidation::YesForComparison => Some(true),
357        }
358    }
359
360    /// Run the given matching function for before/after invalidation comparison.
361    #[inline]
362    pub fn for_invalidation_comparison<F, R>(&mut self, f: F) -> R
363    where
364        F: FnOnce(&mut Self) -> R,
365    {
366        debug_assert!(
367            self.matching_for_invalidation(),
368            "Not matching for invalidation?"
369        );
370        let prev = self.matching_for_invalidation;
371        self.matching_for_invalidation = MatchingForInvalidation::YesForComparison;
372        let result = f(self);
373        self.matching_for_invalidation = prev;
374        result
375    }
376
377    /// The case-sensitivity for class and ID selectors
378    #[inline]
379    pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
380        self.classes_and_ids_case_sensitivity
381    }
382
383    /// Runs F with a deeper nesting level.
384    #[inline]
385    pub fn nest<F, R>(&mut self, f: F) -> R
386    where
387        F: FnOnce(&mut Self) -> R,
388    {
389        self.nesting_level += 1;
390        let result = f(self);
391        self.nesting_level -= 1;
392        result
393    }
394
395    /// Runs F with a deeper nesting level, and marking ourselves in a negation,
396    /// for a :not(..) selector, for example.
397    #[inline]
398    pub fn nest_for_negation<F, R>(&mut self, f: F) -> R
399    where
400        F: FnOnce(&mut Self) -> R,
401    {
402        let old_in_negation = self.in_negation;
403        self.in_negation = !self.in_negation;
404        let result = self.nest(f);
405        self.in_negation = old_in_negation;
406        result
407    }
408
409    #[inline]
410    pub fn visited_handling(&self) -> VisitedHandlingMode {
411        self.visited_handling
412    }
413
414    /// Runs F with a different featureless element flag.
415    #[inline]
416    pub fn with_featureless<F, R>(&mut self, featureless: bool, f: F) -> R
417    where
418        F: FnOnce(&mut Self) -> R,
419    {
420        let orig = self.featureless;
421        self.featureless = featureless;
422        let result = f(self);
423        self.featureless = orig;
424        result
425    }
426
427    /// Returns whether the currently matching element is acting as a featureless element (e.g.,
428    /// because we've crossed a shadow boundary). This is used to implement the :host selector
429    /// rules properly.
430    #[inline]
431    pub fn featureless(&self) -> bool {
432        self.featureless
433    }
434
435    /// Runs F with a different VisitedHandlingMode.
436    #[inline]
437    pub fn with_visited_handling_mode<F, R>(
438        &mut self,
439        handling_mode: VisitedHandlingMode,
440        f: F,
441    ) -> R
442    where
443        F: FnOnce(&mut Self) -> R,
444    {
445        let original_handling_mode = self.visited_handling;
446        self.visited_handling = handling_mode;
447        let result = f(self);
448        self.visited_handling = original_handling_mode;
449        result
450    }
451
452    /// Runs F with a given shadow host which is the root of the tree whose
453    /// rules we're matching.
454    #[inline]
455    pub fn with_shadow_host<F, E, R>(&mut self, host: Option<E>, f: F) -> R
456    where
457        E: Element,
458        F: FnOnce(&mut Self) -> R,
459    {
460        let original_host = self.current_host.take();
461        self.current_host = host.map(|h| h.opaque());
462        let result = f(self);
463        self.current_host = original_host;
464        result
465    }
466
467    /// Returns the current shadow host whose shadow root we're matching rules
468    /// against.
469    #[inline]
470    pub fn shadow_host(&self) -> Option<OpaqueElement> {
471        self.current_host
472    }
473
474    /// Runs F with a deeper nesting level, with the given element as the anchor,
475    /// for a :has(...) selector, for example.
476    #[inline]
477    pub fn nest_for_relative_selector<F, R>(&mut self, anchor: OpaqueElement, f: F) -> R
478    where
479        F: FnOnce(&mut Self) -> R,
480    {
481        debug_assert!(
482            self.current_relative_selector_anchor.is_none(),
483            "Nesting should've been rejected at parse time"
484        );
485        self.current_relative_selector_anchor = Some(anchor);
486        let result = self.nest(f);
487        self.current_relative_selector_anchor = None;
488        result
489    }
490
491    /// Runs F with a deeper nesting level, with the given element as the scope.
492    #[inline]
493    pub fn nest_for_scope<F, R>(&mut self, scope: Option<OpaqueElement>, f: F) -> R
494    where
495        F: FnOnce(&mut Self) -> R,
496    {
497        let original_scope_element = self.scope_element;
498        self.scope_element = scope;
499        let result = f(self);
500        self.scope_element = original_scope_element;
501        result
502    }
503
504    /// Runs F with a deeper nesting level, with the given element as the scope, for
505    /// matching `scope-start` and/or `scope-end` conditions.
506    #[inline]
507    pub fn nest_for_scope_condition<F, R>(&mut self, scope: Option<OpaqueElement>, f: F) -> R
508    where
509        F: FnOnce(&mut Self) -> R,
510    {
511        let original_matching_mode = self.matching_mode;
512        // We may as well be matching for a pseudo-element inside `@scope`, but
513        // the scope-defining selectors wouldn't be matching them.
514        self.matching_mode = MatchingMode::Normal;
515        let result = self.nest_for_scope(scope, f);
516        self.matching_mode = original_matching_mode;
517        result
518    }
519
520    /// Returns the current anchor element to evaluate the relative selector against.
521    #[inline]
522    pub fn relative_selector_anchor(&self) -> Option<OpaqueElement> {
523        self.current_relative_selector_anchor
524    }
525}