tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

context.rs (16592B)


      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::CaseSensitivity;
      6 use crate::bloom::BloomFilter;
      7 use crate::nth_index_cache::{NthIndexCache, NthIndexCacheInner};
      8 use crate::parser::{Selector, SelectorImpl};
      9 use crate::relative_selector::cache::RelativeSelectorCache;
     10 use crate::relative_selector::filter::RelativeSelectorFilterMap;
     11 use 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)]
     18 pub 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)]
     38 pub 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 
     53 impl 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)]
     76 pub 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)]
     89 pub enum NeedsSelectorFlags {
     90    No,
     91    Yes,
     92 }
     93 
     94 /// Whether we're matching in the contect of invalidation.
     95 #[derive(Clone, Copy, PartialEq)]
     96 pub enum MatchingForInvalidation {
     97    No,
     98    Yes,
     99    YesForComparison,
    100 }
    101 
    102 impl 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)]
    113 pub enum QuirksMode {
    114    /// Quirks mode.
    115    Quirks,
    116    /// Limited quirks mode.
    117    LimitedQuirks,
    118    /// No quirks mode.
    119    NoQuirks,
    120 }
    121 
    122 impl 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)]
    134 pub 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.
    146 pub struct MatchingContext<'a, Impl>
    147 where
    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 
    211 impl<'a, Impl> MatchingContext<'a, Impl>
    212 where
    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 }