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