tor-browser

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

element_wrapper.rs (14390B)


      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 //! A wrapper over an element and a snapshot, that allows us to selector-match
      6 //! against a past state of the element.
      7 
      8 use crate::dom::TElement;
      9 use crate::selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl};
     10 use crate::selector_parser::{Snapshot, SnapshotMap};
     11 use crate::values::AtomIdent;
     12 use crate::{CaseSensitivityExt, LocalName, Namespace, WeakAtom};
     13 use dom::ElementState;
     14 use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
     15 use selectors::bloom::BloomFilter;
     16 use selectors::matching::{ElementSelectorFlags, MatchingContext};
     17 use selectors::{Element, OpaqueElement};
     18 use std::cell::Cell;
     19 use std::fmt;
     20 
     21 /// In order to compute restyle hints, we perform a selector match against a
     22 /// list of partial selectors whose rightmost simple selector may be sensitive
     23 /// to the thing being changed. We do this matching twice, once for the element
     24 /// as it exists now and once for the element as it existed at the time of the
     25 /// last restyle. If the results of the selector match differ, that means that
     26 /// the given partial selector is sensitive to the change, and we compute a
     27 /// restyle hint based on its combinator.
     28 ///
     29 /// In order to run selector matching against the old element state, we generate
     30 /// a wrapper for the element which claims to have the old state. This is the
     31 /// ElementWrapper logic below.
     32 ///
     33 /// Gecko does this differently for element states, and passes a mask called
     34 /// mStateMask, which indicates the states that need to be ignored during
     35 /// selector matching. This saves an ElementWrapper allocation and an additional
     36 /// selector match call at the expense of additional complexity inside the
     37 /// selector matching logic. This only works for boolean states though, so we
     38 /// still need to take the ElementWrapper approach for attribute-dependent
     39 /// style. So we do it the same both ways for now to reduce complexity, but it's
     40 /// worth measuring the performance impact (if any) of the mStateMask approach.
     41 pub trait ElementSnapshot: Sized {
     42    /// The state of the snapshot, if any.
     43    fn state(&self) -> Option<ElementState>;
     44 
     45    /// If this snapshot contains attribute information.
     46    fn has_attrs(&self) -> bool;
     47 
     48    /// Gets the attribute information of the snapshot as a string.
     49    ///
     50    /// Only for debugging purposes.
     51    fn debug_list_attributes(&self) -> String {
     52        String::new()
     53    }
     54 
     55    /// The ID attribute per this snapshot. Should only be called if
     56    /// `has_attrs()` returns true.
     57    fn id_attr(&self) -> Option<&WeakAtom>;
     58 
     59    /// Whether this snapshot contains the class `name`. Should only be called
     60    /// if `has_attrs()` returns true.
     61    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool;
     62 
     63    /// Whether this snapshot represents the part named `name`. Should only be
     64    /// called if `has_attrs()` returns true.
     65    fn is_part(&self, name: &AtomIdent) -> bool;
     66 
     67    /// See Element::imported_part.
     68    fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>;
     69 
     70    /// A callback that should be called for each class of the snapshot. Should
     71    /// only be called if `has_attrs()` returns true.
     72    fn each_class<F>(&self, _: F)
     73    where
     74        F: FnMut(&AtomIdent);
     75 
     76    /// If this snapshot contains CustomStateSet information.
     77    fn has_custom_states(&self) -> bool;
     78 
     79    /// A callback that should be called for each CustomState of the snapshot.
     80    fn has_custom_state(&self, state: &AtomIdent) -> bool;
     81 
     82    /// A callback that should be called for each CustomState of the snapshot.
     83    fn each_custom_state<F>(&self, callback: F)
     84    where
     85        F: FnMut(&AtomIdent);
     86 
     87    /// The `xml:lang=""` or `lang=""` attribute value per this snapshot.
     88    fn lang_attr(&self) -> Option<AttrValue>;
     89 }
     90 
     91 /// A simple wrapper over an element and a snapshot, that allows us to
     92 /// selector-match against a past state of the element.
     93 #[derive(Clone)]
     94 pub struct ElementWrapper<'a, E>
     95 where
     96    E: TElement,
     97 {
     98    element: E,
     99    cached_snapshot: Cell<Option<&'a Snapshot>>,
    100    snapshot_map: &'a SnapshotMap,
    101 }
    102 
    103 impl<'a, E> ElementWrapper<'a, E>
    104 where
    105    E: TElement,
    106 {
    107    /// Trivially constructs an `ElementWrapper`.
    108    pub fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self {
    109        ElementWrapper {
    110            element: el,
    111            cached_snapshot: Cell::new(None),
    112            snapshot_map: snapshot_map,
    113        }
    114    }
    115 
    116    /// Gets the snapshot associated with this element, if any.
    117    pub fn snapshot(&self) -> Option<&'a Snapshot> {
    118        if !self.element.has_snapshot() {
    119            return None;
    120        }
    121 
    122        if let Some(s) = self.cached_snapshot.get() {
    123            return Some(s);
    124        }
    125 
    126        let snapshot = self.snapshot_map.get(&self.element);
    127        debug_assert!(snapshot.is_some(), "has_snapshot lied!");
    128 
    129        self.cached_snapshot.set(snapshot);
    130 
    131        snapshot
    132    }
    133 
    134    /// Returns the states that have changed since the element was snapshotted.
    135    pub fn state_changes(&self) -> ElementState {
    136        let snapshot = match self.snapshot() {
    137            Some(s) => s,
    138            None => return ElementState::empty(),
    139        };
    140 
    141        match snapshot.state() {
    142            Some(state) => state ^ self.element.state(),
    143            None => ElementState::empty(),
    144        }
    145    }
    146 
    147    /// Returns the value of the `xml:lang=""` (or, if appropriate, `lang=""`)
    148    /// attribute from this element's snapshot or the closest ancestor
    149    /// element snapshot with the attribute specified.
    150    fn get_lang(&self) -> Option<AttrValue> {
    151        let mut current = self.clone();
    152        loop {
    153            let lang = match self.snapshot() {
    154                Some(snapshot) if snapshot.has_attrs() => snapshot.lang_attr(),
    155                _ => current.element.lang_attr(),
    156            };
    157            if lang.is_some() {
    158                return lang;
    159            }
    160            current = current.parent_element()?;
    161        }
    162    }
    163 }
    164 
    165 impl<'a, E> fmt::Debug for ElementWrapper<'a, E>
    166 where
    167    E: TElement,
    168 {
    169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    170        // Ignore other fields for now, can change later if needed.
    171        self.element.fmt(f)
    172    }
    173 }
    174 
    175 impl<'a, E> Element for ElementWrapper<'a, E>
    176 where
    177    E: TElement,
    178 {
    179    type Impl = SelectorImpl;
    180 
    181    fn match_non_ts_pseudo_class(
    182        &self,
    183        pseudo_class: &NonTSPseudoClass,
    184        context: &mut MatchingContext<Self::Impl>,
    185    ) -> bool {
    186        // Some pseudo-classes need special handling to evaluate them against
    187        // the snapshot.
    188        match *pseudo_class {
    189            // For :link and :visited, we don't actually want to test the
    190            // element state directly.
    191            //
    192            // Instead, we use the `visited_handling` to determine if they
    193            // match.
    194            NonTSPseudoClass::Link => {
    195                return self.is_link() && context.visited_handling().matches_unvisited();
    196            },
    197            NonTSPseudoClass::Visited => {
    198                return self.is_link() && context.visited_handling().matches_visited();
    199            },
    200 
    201            #[cfg(feature = "gecko")]
    202            NonTSPseudoClass::MozTableBorderNonzero => {
    203                if let Some(snapshot) = self.snapshot() {
    204                    if snapshot.has_other_pseudo_class_state() {
    205                        return snapshot.mIsTableBorderNonzero();
    206                    }
    207                }
    208            },
    209 
    210            #[cfg(feature = "gecko")]
    211            NonTSPseudoClass::MozSelectListBox => {
    212                if let Some(snapshot) = self.snapshot() {
    213                    if snapshot.has_other_pseudo_class_state() {
    214                        return snapshot.mIsSelectListBox();
    215                    }
    216                }
    217            },
    218 
    219            // :lang() needs to match using the closest ancestor xml:lang="" or
    220            // lang="" attribtue from snapshots.
    221            NonTSPseudoClass::Lang(ref lang_arg) => {
    222                return self
    223                    .element
    224                    .match_element_lang(Some(self.get_lang()), lang_arg);
    225            },
    226 
    227            // :heading should match against snapshot before element
    228            #[cfg(feature = "gecko")]
    229            NonTSPseudoClass::Heading(ref levels) => {
    230                return levels.matches_state(
    231                    self.snapshot()
    232                        .and_then(|s| s.state())
    233                        .unwrap_or_else(|| self.element.state()),
    234                );
    235            },
    236 
    237            // CustomStateSet should match against the snapshot before element
    238            NonTSPseudoClass::CustomState(ref state) => return self.has_custom_state(&state.0),
    239 
    240            _ => {},
    241        }
    242 
    243        let flag = pseudo_class.state_flag();
    244        if flag.is_empty() {
    245            return self
    246                .element
    247                .match_non_ts_pseudo_class(pseudo_class, context);
    248        }
    249        match self.snapshot().and_then(|s| s.state()) {
    250            Some(snapshot_state) => snapshot_state.intersects(flag),
    251            None => self
    252                .element
    253                .match_non_ts_pseudo_class(pseudo_class, context),
    254        }
    255    }
    256 
    257    fn apply_selector_flags(&self, _flags: ElementSelectorFlags) {
    258        debug_assert!(false, "Shouldn't need selector flags for invalidation");
    259    }
    260 
    261    fn match_pseudo_element(
    262        &self,
    263        pseudo_element: &PseudoElement,
    264        context: &mut MatchingContext<Self::Impl>,
    265    ) -> bool {
    266        self.element.match_pseudo_element(pseudo_element, context)
    267    }
    268 
    269    fn is_link(&self) -> bool {
    270        match self.snapshot().and_then(|s| s.state()) {
    271            Some(state) => state.intersects(ElementState::VISITED_OR_UNVISITED),
    272            None => self.element.is_link(),
    273        }
    274    }
    275 
    276    fn opaque(&self) -> OpaqueElement {
    277        self.element.opaque()
    278    }
    279 
    280    fn parent_element(&self) -> Option<Self> {
    281        let parent = self.element.parent_element()?;
    282        Some(Self::new(parent, self.snapshot_map))
    283    }
    284 
    285    fn parent_node_is_shadow_root(&self) -> bool {
    286        self.element.parent_node_is_shadow_root()
    287    }
    288 
    289    fn containing_shadow_host(&self) -> Option<Self> {
    290        let host = self.element.containing_shadow_host()?;
    291        Some(Self::new(host, self.snapshot_map))
    292    }
    293 
    294    fn prev_sibling_element(&self) -> Option<Self> {
    295        let sibling = self.element.prev_sibling_element()?;
    296        Some(Self::new(sibling, self.snapshot_map))
    297    }
    298 
    299    fn next_sibling_element(&self) -> Option<Self> {
    300        let sibling = self.element.next_sibling_element()?;
    301        Some(Self::new(sibling, self.snapshot_map))
    302    }
    303 
    304    fn first_element_child(&self) -> Option<Self> {
    305        let child = self.element.first_element_child()?;
    306        Some(Self::new(child, self.snapshot_map))
    307    }
    308 
    309    #[inline]
    310    fn is_html_element_in_html_document(&self) -> bool {
    311        self.element.is_html_element_in_html_document()
    312    }
    313 
    314    #[inline]
    315    fn is_html_slot_element(&self) -> bool {
    316        self.element.is_html_slot_element()
    317    }
    318 
    319    #[inline]
    320    fn has_local_name(
    321        &self,
    322        local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName,
    323    ) -> bool {
    324        self.element.has_local_name(local_name)
    325    }
    326 
    327    #[inline]
    328    fn has_namespace(
    329        &self,
    330        ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl,
    331    ) -> bool {
    332        self.element.has_namespace(ns)
    333    }
    334 
    335    #[inline]
    336    fn is_same_type(&self, other: &Self) -> bool {
    337        self.element.is_same_type(&other.element)
    338    }
    339 
    340    fn attr_matches(
    341        &self,
    342        ns: &NamespaceConstraint<&Namespace>,
    343        local_name: &LocalName,
    344        operation: &AttrSelectorOperation<&AttrValue>,
    345    ) -> bool {
    346        match self.snapshot() {
    347            Some(snapshot) if snapshot.has_attrs() => {
    348                snapshot.attr_matches(ns, local_name, operation)
    349            },
    350            _ => self.element.attr_matches(ns, local_name, operation),
    351        }
    352    }
    353 
    354    fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
    355        match self.snapshot() {
    356            Some(snapshot) if snapshot.has_attrs() => snapshot
    357                .id_attr()
    358                .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)),
    359            _ => self.element.has_id(id, case_sensitivity),
    360        }
    361    }
    362 
    363    fn is_part(&self, name: &AtomIdent) -> bool {
    364        match self.snapshot() {
    365            Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name),
    366            _ => self.element.is_part(name),
    367        }
    368    }
    369 
    370    fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> {
    371        match self.snapshot() {
    372            Some(snapshot) if snapshot.has_attrs() => snapshot.imported_part(name),
    373            _ => self.element.imported_part(name),
    374        }
    375    }
    376 
    377    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
    378        match self.snapshot() {
    379            Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
    380            _ => self.element.has_class(name, case_sensitivity),
    381        }
    382    }
    383 
    384    fn has_custom_state(&self, state: &AtomIdent) -> bool {
    385        match self.snapshot() {
    386            Some(snapshot) if snapshot.has_custom_states() => snapshot.has_custom_state(state),
    387            _ => self.element.has_custom_state(state),
    388        }
    389    }
    390 
    391    fn is_empty(&self) -> bool {
    392        self.element.is_empty()
    393    }
    394 
    395    fn is_root(&self) -> bool {
    396        self.element.is_root()
    397    }
    398 
    399    fn is_pseudo_element(&self) -> bool {
    400        self.element.is_pseudo_element()
    401    }
    402 
    403    fn pseudo_element_originating_element(&self) -> Option<Self> {
    404        self.element
    405            .pseudo_element_originating_element()
    406            .map(|e| ElementWrapper::new(e, self.snapshot_map))
    407    }
    408 
    409    fn assigned_slot(&self) -> Option<Self> {
    410        self.element
    411            .assigned_slot()
    412            .map(|e| ElementWrapper::new(e, self.snapshot_map))
    413    }
    414 
    415    fn add_element_unique_hashes(&self, _filter: &mut BloomFilter) -> bool {
    416        // Should not be relevant in the context of checking past elements in invalidation.
    417        false
    418    }
    419 }