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