tor-browser

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

mod.rs (34901B)


      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 //! Code related to the style sharing cache, an optimization that allows similar
      6 //! nodes to share style without having to run selector matching twice.
      7 //!
      8 //! The basic setup is as follows.  We have an LRU cache of style sharing
      9 //! candidates.  When we try to style a target element, we first check whether
     10 //! we can quickly determine that styles match something in this cache, and if
     11 //! so we just use the cached style information.  This check is done with a
     12 //! StyleBloom filter set up for the target element, which may not be a correct
     13 //! state for the cached candidate element if they're cousins instead of
     14 //! siblings.
     15 //!
     16 //! The complicated part is determining that styles match.  This is subject to
     17 //! the following constraints:
     18 //!
     19 //! 1) The target and candidate must be inheriting the same styles.
     20 //! 2) The target and candidate must have exactly the same rules matching them.
     21 //! 3) The target and candidate must have exactly the same non-selector-based
     22 //!    style information (inline styles, presentation hints).
     23 //! 4) The target and candidate must have exactly the same rules matching their
     24 //!    pseudo-elements, because an element's style data points to the style
     25 //!    data for its pseudo-elements.
     26 //!
     27 //! These constraints are satisfied in the following ways:
     28 //!
     29 //! * We check that the parents of the target and the candidate have the same
     30 //!   computed style.  This addresses constraint 1.
     31 //!
     32 //! * We check that the target and candidate have the same inline style and
     33 //!   presentation hint declarations.  This addresses constraint 3.
     34 //!
     35 //! * We ensure that a target matches a candidate only if they have the same
     36 //!   matching result for all selectors that target either elements or the
     37 //!   originating elements of pseudo-elements.  This addresses constraint 4
     38 //!   (because it prevents a target that has pseudo-element styles from matching
     39 //!   a candidate that has different pseudo-element styles) as well as
     40 //!   constraint 2.
     41 //!
     42 //! The actual checks that ensure that elements match the same rules are
     43 //! conceptually split up into two pieces.  First, we do various checks on
     44 //! elements that make sure that the set of possible rules in all selector maps
     45 //! in the stylist (for normal styling and for pseudo-elements) that might match
     46 //! the two elements is the same.  For example, we enforce that the target and
     47 //! candidate must have the same localname and namespace.  Second, we have a
     48 //! selector map of "revalidation selectors" that the stylist maintains that we
     49 //! actually match against the target and candidate and then check whether the
     50 //! two sets of results were the same.  Due to the up-front selector map checks,
     51 //! we know that the target and candidate will be matched against the same exact
     52 //! set of revalidation selectors, so the match result arrays can be compared
     53 //! directly.
     54 //!
     55 //! It's very important that a selector be added to the set of revalidation
     56 //! selectors any time there are two elements that could pass all the up-front
     57 //! checks but match differently against some ComplexSelector in the selector.
     58 //! If that happens, then they can have descendants that might themselves pass
     59 //! the up-front checks but would have different matching results for the
     60 //! selector in question.  In this case, "descendants" includes pseudo-elements,
     61 //! so there is a single selector map of revalidation selectors that includes
     62 //! both selectors targeting elements and selectors targeting pseudo-element
     63 //! originating elements.  We ensure that the pseudo-element parts of all these
     64 //! selectors are effectively stripped off, so that matching them all against
     65 //! elements makes sense.
     66 
     67 use crate::applicable_declarations::ApplicableDeclarationBlock;
     68 use crate::bloom::StyleBloom;
     69 use crate::computed_value_flags::ComputedValueFlags;
     70 use crate::context::{SharedStyleContext, StyleContext};
     71 use crate::dom::{SendElement, TElement, TShadowRoot};
     72 use crate::properties::ComputedValues;
     73 use crate::rule_tree::StrongRuleNode;
     74 use crate::selector_map::RelevantAttributes;
     75 use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
     76 use crate::stylist::Stylist;
     77 use crate::values::AtomIdent;
     78 use atomic_refcell::{AtomicRefCell, AtomicRefMut};
     79 use selectors::matching::{NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode};
     80 use smallbitvec::SmallBitVec;
     81 use smallvec::SmallVec;
     82 use std::marker::PhantomData;
     83 use std::mem;
     84 use std::ops::Deref;
     85 use std::ptr::NonNull;
     86 use uluru::LRUCache;
     87 
     88 mod checks;
     89 
     90 /// The amount of nodes that the style sharing candidate cache should hold at
     91 /// most.
     92 ///
     93 /// The cache size was chosen by measuring style sharing and resulting
     94 /// performance on a few pages; sizes up to about 32 were giving good sharing
     95 /// improvements (e.g. 3x fewer styles having to be resolved than at size 8) and
     96 /// slight performance improvements.  Sizes larger than 32 haven't really been
     97 /// tested.
     98 pub const SHARING_CACHE_SIZE: usize = 32;
     99 
    100 /// Opaque pointer type to compare ComputedValues identities.
    101 #[derive(Clone, Debug, Eq, PartialEq)]
    102 pub struct OpaqueComputedValues(NonNull<()>);
    103 
    104 unsafe impl Send for OpaqueComputedValues {}
    105 unsafe impl Sync for OpaqueComputedValues {}
    106 
    107 impl OpaqueComputedValues {
    108    fn from(cv: &ComputedValues) -> Self {
    109        let p =
    110            unsafe { NonNull::new_unchecked(cv as *const ComputedValues as *const () as *mut ()) };
    111        OpaqueComputedValues(p)
    112    }
    113 
    114    fn eq(&self, cv: &ComputedValues) -> bool {
    115        Self::from(cv) == *self
    116    }
    117 }
    118 
    119 /// The results from the revalidation step.
    120 ///
    121 /// Rather than either:
    122 ///
    123 ///  * Plainly rejecting sharing for elements with different attributes (which would be unfortunate
    124 ///    because a lot of elements have different attributes yet those attributes are not
    125 ///    style-relevant).
    126 ///
    127 ///  * Having to give up on per-attribute bucketing, which would be unfortunate because it
    128 ///    increases the cost of revalidation for pages with lots of global attribute selectors (see
    129 ///    bug 1868316).
    130 ///
    131 ///  * We also store the style-relevant attributes for these elements, in order to guarantee that
    132 ///    we end up looking at the same selectors.
    133 ///
    134 #[derive(Debug, Default)]
    135 pub struct RevalidationResult {
    136    /// A bit for each selector matched. This is sound because we guarantee we look up into the
    137    /// same buckets via the pre-revalidation checks and relevant_attributes.
    138    pub selectors_matched: SmallBitVec,
    139    /// The set of attributes of this element that were relevant for its style.
    140    pub relevant_attributes: RelevantAttributes,
    141 }
    142 
    143 /// The results from trying to revalidate scopes this element is in.
    144 #[derive(Debug, Default, PartialEq)]
    145 pub struct ScopeRevalidationResult {
    146    /// A bit for each scope activated.
    147    pub scopes_matched: SmallBitVec,
    148 }
    149 
    150 impl PartialEq for RevalidationResult {
    151    fn eq(&self, other: &Self) -> bool {
    152        if self.relevant_attributes != other.relevant_attributes {
    153            return false;
    154        }
    155 
    156        // This assert "ensures", to some extent, that the two candidates have matched the
    157        // same rulehash buckets, and as such, that the bits we're comparing represent the
    158        // same set of selectors.
    159        debug_assert_eq!(self.selectors_matched.len(), other.selectors_matched.len());
    160        self.selectors_matched == other.selectors_matched
    161    }
    162 }
    163 
    164 /// Some data we want to avoid recomputing all the time while trying to share
    165 /// style.
    166 #[derive(Debug, Default)]
    167 pub struct ValidationData {
    168    /// The class list of this element.
    169    ///
    170    /// TODO(emilio): Maybe check whether rules for these classes apply to the
    171    /// element?
    172    class_list: Option<SmallVec<[AtomIdent; 5]>>,
    173 
    174    /// The part list of this element.
    175    ///
    176    /// TODO(emilio): Maybe check whether rules with these part names apply to
    177    /// the element?
    178    part_list: Option<SmallVec<[AtomIdent; 5]>>,
    179 
    180    /// The list of presentational attributes of the element.
    181    pres_hints: Option<SmallVec<[ApplicableDeclarationBlock; 5]>>,
    182 
    183    /// The pointer identity of the parent ComputedValues.
    184    parent_style_identity: Option<OpaqueComputedValues>,
    185 
    186    /// The cached result of matching this entry against the revalidation
    187    /// selectors.
    188    revalidation_match_results: Option<RevalidationResult>,
    189 }
    190 
    191 impl ValidationData {
    192    /// Move the cached data to a new instance, and return it.
    193    pub fn take(&mut self) -> Self {
    194        mem::replace(self, Self::default())
    195    }
    196 
    197    /// Get or compute the list of presentational attributes associated with
    198    /// this element.
    199    pub fn pres_hints<E>(&mut self, element: E) -> &[ApplicableDeclarationBlock]
    200    where
    201        E: TElement,
    202    {
    203        self.pres_hints.get_or_insert_with(|| {
    204            let mut pres_hints = SmallVec::new();
    205            element.synthesize_presentational_hints_for_legacy_attributes(
    206                VisitedHandlingMode::AllLinksUnvisited,
    207                &mut pres_hints,
    208            );
    209            pres_hints
    210        })
    211    }
    212 
    213    /// Get or compute the part-list associated with this element.
    214    pub fn part_list<E>(&mut self, element: E) -> &[AtomIdent]
    215    where
    216        E: TElement,
    217    {
    218        if !element.has_part_attr() {
    219            return &[];
    220        }
    221        self.part_list.get_or_insert_with(|| {
    222            let mut list = SmallVec::<[_; 5]>::new();
    223            element.each_part(|p| list.push(p.clone()));
    224            // See below for the reasoning.
    225            if !list.spilled() {
    226                list.sort_unstable_by_key(|a| a.get_hash());
    227            }
    228            list
    229        })
    230    }
    231 
    232    /// Get or compute the class-list associated with this element.
    233    pub fn class_list<E>(&mut self, element: E) -> &[AtomIdent]
    234    where
    235        E: TElement,
    236    {
    237        self.class_list.get_or_insert_with(|| {
    238            let mut list = SmallVec::<[_; 5]>::new();
    239            element.each_class(|c| list.push(c.clone()));
    240            // Assuming there are a reasonable number of classes (we use the
    241            // inline capacity as "reasonable number"), sort them to so that
    242            // we don't mistakenly reject sharing candidates when one element
    243            // has "foo bar" and the other has "bar foo".
    244            if !list.spilled() {
    245                list.sort_unstable_by_key(|a| a.get_hash());
    246            }
    247            list
    248        })
    249    }
    250 
    251    /// Get or compute the parent style identity.
    252    pub fn parent_style_identity<E>(&mut self, el: E) -> OpaqueComputedValues
    253    where
    254        E: TElement,
    255    {
    256        self.parent_style_identity
    257            .get_or_insert_with(|| {
    258                let parent = el.inheritance_parent().unwrap();
    259                let values =
    260                    OpaqueComputedValues::from(parent.borrow_data().unwrap().styles.primary());
    261                values
    262            })
    263            .clone()
    264    }
    265 
    266    /// Computes the revalidation results if needed, and returns it.
    267    /// Inline so we know at compile time what bloom_known_valid is.
    268    #[inline]
    269    fn revalidation_match_results<E>(
    270        &mut self,
    271        element: E,
    272        stylist: &Stylist,
    273        bloom: &StyleBloom<E>,
    274        selector_caches: &mut SelectorCaches,
    275        bloom_known_valid: bool,
    276        needs_selector_flags: NeedsSelectorFlags,
    277    ) -> &RevalidationResult
    278    where
    279        E: TElement,
    280    {
    281        self.revalidation_match_results.get_or_insert_with(|| {
    282            // The bloom filter may already be set up for our element.
    283            // If it is, use it.  If not, we must be in a candidate
    284            // (i.e. something in the cache), and the element is one
    285            // of our cousins, not a sibling.  In that case, we'll
    286            // just do revalidation selector matching without a bloom
    287            // filter, to avoid thrashing the filter.
    288            let bloom_to_use = if bloom_known_valid {
    289                debug_assert_eq!(bloom.current_parent(), element.traversal_parent());
    290                Some(bloom.filter())
    291            } else {
    292                if bloom.current_parent() == element.traversal_parent() {
    293                    Some(bloom.filter())
    294                } else {
    295                    None
    296                }
    297            };
    298            stylist.match_revalidation_selectors(
    299                element,
    300                bloom_to_use,
    301                selector_caches,
    302                needs_selector_flags,
    303            )
    304        })
    305    }
    306 }
    307 
    308 /// Information regarding a style sharing candidate, that is, an entry in the
    309 /// style sharing cache.
    310 ///
    311 /// Note that this information is stored in TLS and cleared after the traversal,
    312 /// and once here, the style information of the element is immutable, so it's
    313 /// safe to access.
    314 ///
    315 /// Important: If you change the members/layout here, You need to do the same for
    316 /// FakeCandidate below.
    317 #[derive(Debug)]
    318 pub struct StyleSharingCandidate<E: TElement> {
    319    /// The element.
    320    element: E,
    321    validation_data: ValidationData,
    322    considered_nontrivial_scoped_style: bool,
    323 }
    324 
    325 struct FakeCandidate {
    326    _element: usize,
    327    _validation_data: ValidationData,
    328    _may_contain_scoped_style: bool,
    329 }
    330 
    331 impl<E: TElement> Deref for StyleSharingCandidate<E> {
    332    type Target = E;
    333 
    334    fn deref(&self) -> &Self::Target {
    335        &self.element
    336    }
    337 }
    338 
    339 impl<E: TElement> StyleSharingCandidate<E> {
    340    /// Get the classlist of this candidate.
    341    fn class_list(&mut self) -> &[AtomIdent] {
    342        self.validation_data.class_list(self.element)
    343    }
    344 
    345    /// Get the part list of this candidate.
    346    fn part_list(&mut self) -> &[AtomIdent] {
    347        self.validation_data.part_list(self.element)
    348    }
    349 
    350    /// Get the pres hints of this candidate.
    351    fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] {
    352        self.validation_data.pres_hints(self.element)
    353    }
    354 
    355    /// Get the parent style identity.
    356    fn parent_style_identity(&mut self) -> OpaqueComputedValues {
    357        self.validation_data.parent_style_identity(self.element)
    358    }
    359 
    360    /// Compute the bit vector of revalidation selector match results
    361    /// for this candidate.
    362    fn revalidation_match_results(
    363        &mut self,
    364        stylist: &Stylist,
    365        bloom: &StyleBloom<E>,
    366        selector_caches: &mut SelectorCaches,
    367    ) -> &RevalidationResult {
    368        self.validation_data.revalidation_match_results(
    369            self.element,
    370            stylist,
    371            bloom,
    372            selector_caches,
    373            /* bloom_known_valid = */ false,
    374            // The candidate must already have the right bits already, if
    375            // needed.
    376            NeedsSelectorFlags::No,
    377        )
    378    }
    379 
    380    fn scope_revalidation_results(
    381        &mut self,
    382        stylist: &Stylist,
    383        selector_caches: &mut SelectorCaches,
    384    ) -> ScopeRevalidationResult {
    385        stylist.revalidate_scopes(&self.element, selector_caches, NeedsSelectorFlags::No)
    386    }
    387 }
    388 
    389 impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<E> {
    390    fn eq(&self, other: &Self) -> bool {
    391        self.element == other.element
    392    }
    393 }
    394 
    395 /// An element we want to test against the style sharing cache.
    396 pub struct StyleSharingTarget<E: TElement> {
    397    element: E,
    398    validation_data: ValidationData,
    399 }
    400 
    401 impl<E: TElement> Deref for StyleSharingTarget<E> {
    402    type Target = E;
    403 
    404    fn deref(&self) -> &Self::Target {
    405        &self.element
    406    }
    407 }
    408 
    409 impl<E: TElement> StyleSharingTarget<E> {
    410    /// Trivially construct a new StyleSharingTarget to test against the cache.
    411    pub fn new(element: E) -> Self {
    412        Self {
    413            element: element,
    414            validation_data: ValidationData::default(),
    415        }
    416    }
    417 
    418    fn class_list(&mut self) -> &[AtomIdent] {
    419        self.validation_data.class_list(self.element)
    420    }
    421 
    422    fn part_list(&mut self) -> &[AtomIdent] {
    423        self.validation_data.part_list(self.element)
    424    }
    425 
    426    /// Get the pres hints of this candidate.
    427    fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] {
    428        self.validation_data.pres_hints(self.element)
    429    }
    430 
    431    /// Get the parent style identity.
    432    fn parent_style_identity(&mut self) -> OpaqueComputedValues {
    433        self.validation_data.parent_style_identity(self.element)
    434    }
    435 
    436    fn revalidation_match_results(
    437        &mut self,
    438        stylist: &Stylist,
    439        bloom: &StyleBloom<E>,
    440        selector_caches: &mut SelectorCaches,
    441    ) -> &RevalidationResult {
    442        // It's important to set the selector flags. Otherwise, if we succeed in
    443        // sharing the style, we may not set the slow selector flags for the
    444        // right elements (which may not necessarily be |element|), causing
    445        // missed restyles after future DOM mutations.
    446        //
    447        // Gecko's test_bug534804.html exercises this. A minimal testcase is:
    448        // <style> #e:empty + span { ... } </style>
    449        // <span id="e">
    450        //   <span></span>
    451        // </span>
    452        // <span></span>
    453        //
    454        // The style sharing cache will get a hit for the second span. When the
    455        // child span is subsequently removed from the DOM, missing selector
    456        // flags would cause us to miss the restyle on the second span.
    457        self.validation_data.revalidation_match_results(
    458            self.element,
    459            stylist,
    460            bloom,
    461            selector_caches,
    462            /* bloom_known_valid = */ true,
    463            NeedsSelectorFlags::Yes,
    464        )
    465    }
    466 
    467    fn scope_revalidation_results(
    468        &mut self,
    469        stylist: &Stylist,
    470        selector_caches: &mut SelectorCaches,
    471    ) -> ScopeRevalidationResult {
    472        stylist.revalidate_scopes(&self.element, selector_caches, NeedsSelectorFlags::Yes)
    473    }
    474 
    475    /// Attempts to share a style with another node.
    476    pub fn share_style_if_possible(
    477        &mut self,
    478        context: &mut StyleContext<E>,
    479    ) -> Option<ResolvedElementStyles> {
    480        let cache = &mut context.thread_local.sharing_cache;
    481        let shared_context = &context.shared;
    482        let bloom_filter = &context.thread_local.bloom_filter;
    483        let selector_caches = &mut context.thread_local.selector_caches;
    484 
    485        if cache.dom_depth != bloom_filter.matching_depth() {
    486            debug!(
    487                "Can't share style, because DOM depth changed from {:?} to {:?}, element: {:?}",
    488                cache.dom_depth,
    489                bloom_filter.matching_depth(),
    490                self.element
    491            );
    492            return None;
    493        }
    494        debug_assert_eq!(
    495            bloom_filter.current_parent(),
    496            self.element.traversal_parent()
    497        );
    498 
    499        cache.share_style_if_possible(shared_context, bloom_filter, selector_caches, self)
    500    }
    501 
    502    /// Gets the validation data used to match against this target, if any.
    503    pub fn take_validation_data(&mut self) -> ValidationData {
    504        self.validation_data.take()
    505    }
    506 }
    507 
    508 struct SharingCacheBase<Candidate> {
    509    entries: LRUCache<Candidate, SHARING_CACHE_SIZE>,
    510 }
    511 
    512 impl<Candidate> Default for SharingCacheBase<Candidate> {
    513    fn default() -> Self {
    514        Self {
    515            entries: LRUCache::default(),
    516        }
    517    }
    518 }
    519 
    520 impl<Candidate> SharingCacheBase<Candidate> {
    521    fn clear(&mut self) {
    522        self.entries.clear();
    523    }
    524 
    525    fn is_empty(&self) -> bool {
    526        self.entries.len() == 0
    527    }
    528 }
    529 
    530 impl<E: TElement> SharingCache<E> {
    531    fn insert(
    532        &mut self,
    533        element: E,
    534        validation_data_holder: Option<&mut StyleSharingTarget<E>>,
    535        considered_nontrivial_scoped_style: bool,
    536    ) {
    537        let validation_data = match validation_data_holder {
    538            Some(v) => v.take_validation_data(),
    539            None => ValidationData::default(),
    540        };
    541        self.entries.insert(StyleSharingCandidate {
    542            element,
    543            validation_data,
    544            considered_nontrivial_scoped_style,
    545        });
    546    }
    547 }
    548 
    549 /// Style sharing caches are are large allocations, so we store them in thread-local
    550 /// storage such that they can be reused across style traversals. Ideally, we'd just
    551 /// stack-allocate these buffers with uninitialized memory, but right now rustc can't
    552 /// avoid memmoving the entire cache during setup, which gets very expensive. See
    553 /// issues like [1] and [2].
    554 ///
    555 /// Given that the cache stores entries of type TElement, we transmute to usize
    556 /// before storing in TLS. This is safe as long as we make sure to empty the cache
    557 /// before we let it go.
    558 ///
    559 /// [1] https://github.com/rust-lang/rust/issues/42763
    560 /// [2] https://github.com/rust-lang/rust/issues/13707
    561 type SharingCache<E> = SharingCacheBase<StyleSharingCandidate<E>>;
    562 type TypelessSharingCache = SharingCacheBase<FakeCandidate>;
    563 
    564 thread_local! {
    565    // See the comment on bloom.rs about why do we leak this.
    566    static SHARING_CACHE_KEY: &'static AtomicRefCell<TypelessSharingCache> =
    567        Box::leak(Default::default());
    568 }
    569 
    570 /// An LRU cache of the last few nodes seen, so that we can aggressively try to
    571 /// reuse their styles.
    572 ///
    573 /// Note that this cache is flushed every time we steal work from the queue, so
    574 /// storing nodes here temporarily is safe.
    575 pub struct StyleSharingCache<E: TElement> {
    576    /// The LRU cache, with the type cast away to allow persisting the allocation.
    577    cache_typeless: AtomicRefMut<'static, TypelessSharingCache>,
    578    /// Bind this structure to the lifetime of E, since that's what we effectively store.
    579    marker: PhantomData<SendElement<E>>,
    580    /// The DOM depth we're currently at.  This is used as an optimization to
    581    /// clear the cache when we change depths, since we know at that point
    582    /// nothing in the cache will match.
    583    dom_depth: usize,
    584 }
    585 
    586 impl<E: TElement> Drop for StyleSharingCache<E> {
    587    fn drop(&mut self) {
    588        self.clear();
    589    }
    590 }
    591 
    592 impl<E: TElement> StyleSharingCache<E> {
    593    #[allow(dead_code)]
    594    fn cache(&self) -> &SharingCache<E> {
    595        let base: &TypelessSharingCache = &*self.cache_typeless;
    596        unsafe { mem::transmute(base) }
    597    }
    598 
    599    fn cache_mut(&mut self) -> &mut SharingCache<E> {
    600        let base: &mut TypelessSharingCache = &mut *self.cache_typeless;
    601        unsafe { mem::transmute(base) }
    602    }
    603 
    604    /// Create a new style sharing candidate cache.
    605 
    606    // Forced out of line to limit stack frame sizes after extra inlining from
    607    // https://github.com/rust-lang/rust/pull/43931
    608    //
    609    // See https://github.com/servo/servo/pull/18420#issuecomment-328769322
    610    #[inline(never)]
    611    pub fn new() -> Self {
    612        assert_eq!(
    613            mem::size_of::<SharingCache<E>>(),
    614            mem::size_of::<TypelessSharingCache>()
    615        );
    616        assert_eq!(
    617            mem::align_of::<SharingCache<E>>(),
    618            mem::align_of::<TypelessSharingCache>()
    619        );
    620        let cache = SHARING_CACHE_KEY.with(|c| c.borrow_mut());
    621        debug_assert!(cache.is_empty());
    622 
    623        StyleSharingCache {
    624            cache_typeless: cache,
    625            marker: PhantomData,
    626            dom_depth: 0,
    627        }
    628    }
    629 
    630    /// Tries to insert an element in the style sharing cache.
    631    ///
    632    /// Fails if we know it should never be in the cache.
    633    ///
    634    /// NB: We pass a source for the validation data, rather than the data itself,
    635    /// to avoid memmoving at each function call. See rust issue #42763.
    636    pub fn insert_if_possible(
    637        &mut self,
    638        element: &E,
    639        style: &PrimaryStyle,
    640        validation_data_holder: Option<&mut StyleSharingTarget<E>>,
    641        dom_depth: usize,
    642        shared_context: &SharedStyleContext,
    643    ) {
    644        let parent = match element.traversal_parent() {
    645            Some(element) => element,
    646            None => {
    647                debug!("Failing to insert to the cache: no parent element");
    648                return;
    649            },
    650        };
    651 
    652        if !element.matches_user_and_content_rules() {
    653            debug!("Failing to insert into the cache: no tree rules:");
    654            return;
    655        }
    656 
    657        // If the element has running animations, we can't share style.
    658        //
    659        // This is distinct from the specifies_{animations,transitions} check below,
    660        // because:
    661        //   * Animations can be triggered directly via the Web Animations API.
    662        //   * Our computed style can still be affected by animations after we no
    663        //     longer match any animation rules, since removing animations involves
    664        //     a sequential task and an additional traversal.
    665        if element.has_animations(shared_context) {
    666            debug!("Failing to insert to the cache: running animations");
    667            return;
    668        }
    669 
    670        if element.smil_override().is_some() {
    671            debug!("Failing to insert to the cache: SMIL");
    672            return;
    673        }
    674 
    675        debug!(
    676            "Inserting into cache: {:?} with parent {:?}",
    677            element, parent
    678        );
    679 
    680        if self.dom_depth != dom_depth {
    681            debug!(
    682                "Clearing cache because depth changed from {:?} to {:?}, element: {:?}",
    683                self.dom_depth, dom_depth, element
    684            );
    685            self.clear();
    686            self.dom_depth = dom_depth;
    687        }
    688        self.cache_mut().insert(
    689            *element,
    690            validation_data_holder,
    691            style
    692                .style()
    693                .flags
    694                .intersects(ComputedValueFlags::CONSIDERED_NONTRIVIAL_SCOPED_STYLE),
    695        );
    696    }
    697 
    698    /// Clear the style sharing candidate cache.
    699    pub fn clear(&mut self) {
    700        self.cache_mut().clear();
    701    }
    702 
    703    /// Attempts to share a style with another node.
    704    fn share_style_if_possible(
    705        &mut self,
    706        shared_context: &SharedStyleContext,
    707        bloom_filter: &StyleBloom<E>,
    708        selector_caches: &mut SelectorCaches,
    709        target: &mut StyleSharingTarget<E>,
    710    ) -> Option<ResolvedElementStyles> {
    711        if shared_context.options.disable_style_sharing_cache {
    712            debug!(
    713                "{:?} Cannot share style: style sharing cache disabled",
    714                target.element
    715            );
    716            return None;
    717        }
    718 
    719        if target.inheritance_parent().is_none() {
    720            debug!(
    721                "{:?} Cannot share style: element has no parent",
    722                target.element
    723            );
    724            return None;
    725        }
    726 
    727        if !target.matches_user_and_content_rules() {
    728            debug!("{:?} Cannot share style: content rules", target.element);
    729            return None;
    730        }
    731 
    732        self.cache_mut().entries.lookup(|candidate| {
    733            Self::test_candidate(
    734                target,
    735                candidate,
    736                &shared_context,
    737                bloom_filter,
    738                selector_caches,
    739                shared_context,
    740            )
    741        })
    742    }
    743 
    744    fn test_candidate(
    745        target: &mut StyleSharingTarget<E>,
    746        candidate: &mut StyleSharingCandidate<E>,
    747        shared: &SharedStyleContext,
    748        bloom: &StyleBloom<E>,
    749        selector_caches: &mut SelectorCaches,
    750        shared_context: &SharedStyleContext,
    751    ) -> Option<ResolvedElementStyles> {
    752        debug_assert!(target.matches_user_and_content_rules());
    753 
    754        // Check that we have the same parent, or at least that the parents
    755        // share styles and permit sharing across their children. The latter
    756        // check allows us to share style between cousins if the parents
    757        // shared style.
    758        if !checks::parents_allow_sharing(target, candidate) {
    759            trace!("Miss: Parent");
    760            return None;
    761        }
    762 
    763        if target.local_name() != candidate.element.local_name() {
    764            trace!("Miss: Local Name");
    765            return None;
    766        }
    767 
    768        if target.namespace() != candidate.element.namespace() {
    769            trace!("Miss: Namespace");
    770            return None;
    771        }
    772 
    773        // We do not ignore visited state here, because Gecko needs to store
    774        // extra bits on visited styles, so these contexts cannot be shared.
    775        if target.element.state() != candidate.state() {
    776            trace!("Miss: User and Author State");
    777            return None;
    778        }
    779 
    780        if target.is_link() != candidate.element.is_link() {
    781            trace!("Miss: Link");
    782            return None;
    783        }
    784 
    785        // If two elements belong to different shadow trees, different rules may
    786        // apply to them, from the respective trees.
    787        if target.element.containing_shadow() != candidate.element.containing_shadow() {
    788            trace!("Miss: Different containing shadow roots");
    789            return None;
    790        }
    791 
    792        // If the elements are not assigned to the same slot they could match
    793        // different ::slotted() rules in the slot scope.
    794        //
    795        // If two elements are assigned to different slots, even within the same
    796        // shadow root, they could match different rules, due to the slot being
    797        // assigned to yet another slot in another shadow root.
    798        if target.element.assigned_slot() != candidate.element.assigned_slot() {
    799            // TODO(emilio): We could have a look at whether the shadow roots
    800            // actually have slotted rules and such.
    801            trace!("Miss: Different assigned slots");
    802            return None;
    803        }
    804 
    805        if target.implemented_pseudo_element() != candidate.implemented_pseudo_element() {
    806            trace!("Miss: Element backed pseudo-element");
    807            return None;
    808        }
    809 
    810        // Shadow hosts can share style when they have matching CascadeData pointers, which
    811        // ensures they match the same :host rules.
    812        match (
    813            target.element.shadow_root().and_then(|s| s.style_data()),
    814            candidate.element.shadow_root().and_then(|s| s.style_data()),
    815        ) {
    816            (Some(td), Some(cd)) if std::ptr::eq(td, cd) => {},
    817            (None, None) => {},
    818            _ => {
    819                trace!("Miss: Different shadow root style data");
    820                return None;
    821            },
    822        }
    823 
    824        if target.element.has_animations(shared_context)
    825            || candidate.element.has_animations(shared_context)
    826        {
    827            trace!("Miss: Has Animations");
    828            return None;
    829        }
    830 
    831        if target.element.smil_override().is_some() {
    832            trace!("Miss: SMIL");
    833            return None;
    834        }
    835 
    836        if target.matches_user_and_content_rules()
    837            != candidate.element.matches_user_and_content_rules()
    838        {
    839            trace!("Miss: User and Author Rules");
    840            return None;
    841        }
    842 
    843        // It's possible that there are no styles for either id.
    844        if checks::may_match_different_id_rules(shared, target.element, candidate.element) {
    845            trace!("Miss: ID Attr");
    846            return None;
    847        }
    848 
    849        if !checks::have_same_style_attribute(target, candidate, shared_context) {
    850            trace!("Miss: Style Attr");
    851            return None;
    852        }
    853 
    854        if !checks::have_same_class(target, candidate) {
    855            trace!("Miss: Class");
    856            return None;
    857        }
    858 
    859        if !checks::have_same_presentational_hints(target, candidate) {
    860            trace!("Miss: Pres Hints");
    861            return None;
    862        }
    863 
    864        if !checks::have_same_parts(target, candidate) {
    865            trace!("Miss: Shadow parts");
    866            return None;
    867        }
    868 
    869        if !checks::revalidate(target, candidate, shared, bloom, selector_caches) {
    870            trace!("Miss: Revalidation");
    871            return None;
    872        }
    873 
    874        // While the scoped style rules may be different (e.g. `@scope { .foo + .foo { /* .. */} }`),
    875        // we rely on revalidation to handle that.
    876        if candidate.considered_nontrivial_scoped_style
    877            && !checks::revalidate_scope(target, candidate, shared, selector_caches)
    878        {
    879            trace!("Miss: Active Scopes");
    880            return None;
    881        }
    882 
    883        debug!(
    884            "Sharing allowed between {:?} and {:?}",
    885            target.element, candidate.element
    886        );
    887        Some(candidate.element.borrow_data().unwrap().share_styles())
    888    }
    889 
    890    /// Attempts to find an element in the cache with the given primary rule
    891    /// node and parent.
    892    ///
    893    /// FIXME(emilio): re-measure this optimization, and remove if it's not very
    894    /// useful... It's probably not worth the complexity / obscure bugs.
    895    pub fn lookup_by_rules(
    896        &mut self,
    897        shared_context: &SharedStyleContext,
    898        inherited: &ComputedValues,
    899        rules: &StrongRuleNode,
    900        visited_rules: Option<&StrongRuleNode>,
    901        target: E,
    902    ) -> Option<PrimaryStyle> {
    903        if shared_context.options.disable_style_sharing_cache {
    904            return None;
    905        }
    906 
    907        self.cache_mut().entries.lookup(|candidate| {
    908            debug_assert_ne!(candidate.element, target);
    909            if !candidate.parent_style_identity().eq(inherited) {
    910                return None;
    911            }
    912            let data = candidate.element.borrow_data().unwrap();
    913            let style = data.styles.primary();
    914            if style.rules.as_ref() != Some(&rules) {
    915                return None;
    916            }
    917            if style.visited_rules() != visited_rules {
    918                return None;
    919            }
    920            // NOTE(emilio): We only need to check name / namespace because we
    921            // do name-dependent style adjustments, like the display: contents
    922            // to display: none adjustment.
    923            if target.namespace() != candidate.element.namespace()
    924                || target.local_name() != candidate.element.local_name()
    925            {
    926                return None;
    927            }
    928            // When using container units, inherited style + rules matched aren't enough to
    929            // determine whether the style is the same. We could actually do a full container
    930            // lookup but for now we just check that our actual traversal parent matches.
    931            if data
    932                .styles
    933                .primary()
    934                .flags
    935                .intersects(ComputedValueFlags::USES_CONTAINER_UNITS)
    936                && candidate.element.traversal_parent() != target.traversal_parent()
    937            {
    938                return None;
    939            }
    940            // Rule nodes and styles are computed independent of the element's actual visitedness,
    941            // but at the end of the cascade (in `adjust_for_visited`) we do store the
    942            // RELEVANT_LINK_VISITED flag, so we can't share by rule node between visited and
    943            // unvisited styles. We don't check for visitedness and just refuse to share for links
    944            // entirely, so that visitedness doesn't affect timing.
    945            if target.is_link() || candidate.element.is_link() {
    946                return None;
    947            }
    948 
    949            Some(data.share_primary_style())
    950        })
    951    }
    952 }