style/sharing/
mod.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
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
67use crate::applicable_declarations::ApplicableDeclarationBlock;
68use crate::bloom::StyleBloom;
69use crate::computed_value_flags::ComputedValueFlags;
70use crate::context::{SharedStyleContext, StyleContext};
71use crate::dom::{SendElement, TElement};
72use crate::properties::ComputedValues;
73use crate::rule_tree::StrongRuleNode;
74use crate::selector_map::RelevantAttributes;
75use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
76use crate::stylist::Stylist;
77use crate::values::AtomIdent;
78use atomic_refcell::{AtomicRefCell, AtomicRefMut};
79use selectors::matching::{NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode};
80use smallbitvec::SmallBitVec;
81use smallvec::SmallVec;
82use std::marker::PhantomData;
83use std::mem;
84use std::ops::Deref;
85use std::ptr::NonNull;
86use uluru::LRUCache;
87
88mod 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.
98pub const SHARING_CACHE_SIZE: usize = 32;
99
100/// Opaque pointer type to compare ComputedValues identities.
101#[derive(Clone, Debug, Eq, PartialEq)]
102pub struct OpaqueComputedValues(NonNull<()>);
103
104unsafe impl Send for OpaqueComputedValues {}
105unsafe impl Sync for OpaqueComputedValues {}
106
107impl 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)]
135pub 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)]
145pub struct ScopeRevalidationResult {
146    /// A bit for each scope activated.
147    pub scopes_matched: SmallBitVec,
148}
149
150impl 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)]
167pub 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
191impl 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)]
318pub struct StyleSharingCandidate<E: TElement> {
319    /// The element.
320    element: E,
321    validation_data: ValidationData,
322    considered_nontrivial_scoped_style: bool,
323}
324
325struct FakeCandidate {
326    _element: usize,
327    _validation_data: ValidationData,
328    _may_contain_scoped_style: bool,
329}
330
331impl<E: TElement> Deref for StyleSharingCandidate<E> {
332    type Target = E;
333
334    fn deref(&self) -> &Self::Target {
335        &self.element
336    }
337}
338
339impl<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
389impl<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.
396pub struct StyleSharingTarget<E: TElement> {
397    element: E,
398    validation_data: ValidationData,
399}
400
401impl<E: TElement> Deref for StyleSharingTarget<E> {
402    type Target = E;
403
404    fn deref(&self) -> &Self::Target {
405        &self.element
406    }
407}
408
409impl<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
508struct SharingCacheBase<Candidate> {
509    entries: LRUCache<Candidate, SHARING_CACHE_SIZE>,
510}
511
512impl<Candidate> Default for SharingCacheBase<Candidate> {
513    fn default() -> Self {
514        Self {
515            entries: LRUCache::default(),
516        }
517    }
518}
519
520impl<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
530impl<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
561type SharingCache<E> = SharingCacheBase<StyleSharingCandidate<E>>;
562type TypelessSharingCache = SharingCacheBase<FakeCandidate>;
563
564thread_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.
575pub 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
586impl<E: TElement> Drop for StyleSharingCache<E> {
587    fn drop(&mut self) {
588        self.clear();
589    }
590}
591
592impl<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        // We can't share style across shadow hosts right now, because they may
658        // match different :host rules.
659        //
660        // TODO(emilio): We could share across the ones that don't have :host
661        // rules or have the same.
662        if element.shadow_root().is_some() {
663            debug!("Failing to insert into the cache: Shadow Host");
664            return;
665        }
666
667        // If the element has running animations, we can't share style.
668        //
669        // This is distinct from the specifies_{animations,transitions} check below,
670        // because:
671        //   * Animations can be triggered directly via the Web Animations API.
672        //   * Our computed style can still be affected by animations after we no
673        //     longer match any animation rules, since removing animations involves
674        //     a sequential task and an additional traversal.
675        if element.has_animations(shared_context) {
676            debug!("Failing to insert to the cache: running animations");
677            return;
678        }
679
680        if element.smil_override().is_some() {
681            debug!("Failing to insert to the cache: SMIL");
682            return;
683        }
684
685        debug!(
686            "Inserting into cache: {:?} with parent {:?}",
687            element, parent
688        );
689
690        if self.dom_depth != dom_depth {
691            debug!(
692                "Clearing cache because depth changed from {:?} to {:?}, element: {:?}",
693                self.dom_depth, dom_depth, element
694            );
695            self.clear();
696            self.dom_depth = dom_depth;
697        }
698        self.cache_mut().insert(
699            *element,
700            validation_data_holder,
701            style
702                .style()
703                .flags
704                .intersects(ComputedValueFlags::CONSIDERED_NONTRIVIAL_SCOPED_STYLE),
705        );
706    }
707
708    /// Clear the style sharing candidate cache.
709    pub fn clear(&mut self) {
710        self.cache_mut().clear();
711    }
712
713    /// Attempts to share a style with another node.
714    fn share_style_if_possible(
715        &mut self,
716        shared_context: &SharedStyleContext,
717        bloom_filter: &StyleBloom<E>,
718        selector_caches: &mut SelectorCaches,
719        target: &mut StyleSharingTarget<E>,
720    ) -> Option<ResolvedElementStyles> {
721        if shared_context.options.disable_style_sharing_cache {
722            debug!(
723                "{:?} Cannot share style: style sharing cache disabled",
724                target.element
725            );
726            return None;
727        }
728
729        if target.inheritance_parent().is_none() {
730            debug!(
731                "{:?} Cannot share style: element has no parent",
732                target.element
733            );
734            return None;
735        }
736
737        if !target.matches_user_and_content_rules() {
738            debug!("{:?} Cannot share style: content rules", target.element);
739            return None;
740        }
741
742        self.cache_mut().entries.lookup(|candidate| {
743            Self::test_candidate(
744                target,
745                candidate,
746                &shared_context,
747                bloom_filter,
748                selector_caches,
749                shared_context,
750            )
751        })
752    }
753
754    fn test_candidate(
755        target: &mut StyleSharingTarget<E>,
756        candidate: &mut StyleSharingCandidate<E>,
757        shared: &SharedStyleContext,
758        bloom: &StyleBloom<E>,
759        selector_caches: &mut SelectorCaches,
760        shared_context: &SharedStyleContext,
761    ) -> Option<ResolvedElementStyles> {
762        debug_assert!(target.matches_user_and_content_rules());
763
764        // Check that we have the same parent, or at least that the parents
765        // share styles and permit sharing across their children. The latter
766        // check allows us to share style between cousins if the parents
767        // shared style.
768        if !checks::parents_allow_sharing(target, candidate) {
769            trace!("Miss: Parent");
770            return None;
771        }
772
773        if target.local_name() != candidate.element.local_name() {
774            trace!("Miss: Local Name");
775            return None;
776        }
777
778        if target.namespace() != candidate.element.namespace() {
779            trace!("Miss: Namespace");
780            return None;
781        }
782
783        // We do not ignore visited state here, because Gecko needs to store
784        // extra bits on visited styles, so these contexts cannot be shared.
785        if target.element.state() != candidate.state() {
786            trace!("Miss: User and Author State");
787            return None;
788        }
789
790        if target.is_link() != candidate.element.is_link() {
791            trace!("Miss: Link");
792            return None;
793        }
794
795        // If two elements belong to different shadow trees, different rules may
796        // apply to them, from the respective trees.
797        if target.element.containing_shadow() != candidate.element.containing_shadow() {
798            trace!("Miss: Different containing shadow roots");
799            return None;
800        }
801
802        // If the elements are not assigned to the same slot they could match
803        // different ::slotted() rules in the slot scope.
804        //
805        // If two elements are assigned to different slots, even within the same
806        // shadow root, they could match different rules, due to the slot being
807        // assigned to yet another slot in another shadow root.
808        if target.element.assigned_slot() != candidate.element.assigned_slot() {
809            // TODO(emilio): We could have a look at whether the shadow roots
810            // actually have slotted rules and such.
811            trace!("Miss: Different assigned slots");
812            return None;
813        }
814
815        if target.implemented_pseudo_element() != candidate.implemented_pseudo_element() {
816            trace!("Miss: Element backed pseudo-element");
817            return None;
818        }
819
820        if target.element.shadow_root().is_some() {
821            trace!("Miss: Shadow host");
822            return None;
823        }
824
825        if target.element.has_animations(shared_context)
826            || candidate.element.has_animations(shared_context)
827        {
828            trace!("Miss: Has Animations");
829            return None;
830        }
831
832        if target.element.smil_override().is_some() {
833            trace!("Miss: SMIL");
834            return None;
835        }
836
837        if target.matches_user_and_content_rules()
838            != candidate.element.matches_user_and_content_rules()
839        {
840            trace!("Miss: User and Author Rules");
841            return None;
842        }
843
844        // It's possible that there are no styles for either id.
845        if checks::may_match_different_id_rules(shared, target.element, candidate.element) {
846            trace!("Miss: ID Attr");
847            return None;
848        }
849
850        if !checks::have_same_style_attribute(target, candidate) {
851            trace!("Miss: Style Attr");
852            return None;
853        }
854
855        if !checks::have_same_class(target, candidate) {
856            trace!("Miss: Class");
857            return None;
858        }
859
860        if !checks::have_same_presentational_hints(target, candidate) {
861            trace!("Miss: Pres Hints");
862            return None;
863        }
864
865        if !checks::have_same_parts(target, candidate) {
866            trace!("Miss: Shadow parts");
867            return None;
868        }
869
870        if !checks::revalidate(target, candidate, shared, bloom, selector_caches) {
871            trace!("Miss: Revalidation");
872            return None;
873        }
874
875        // While the scoped style rules may be different (e.g. `@scope { .foo + .foo { /* .. */} }`),
876        // we rely on revalidation to handle that.
877        if candidate.considered_nontrivial_scoped_style
878            && !checks::revalidate_scope(target, candidate, shared, selector_caches)
879        {
880            trace!("Miss: Active Scopes");
881            return None;
882        }
883
884        debug!(
885            "Sharing allowed between {:?} and {:?}",
886            target.element, candidate.element
887        );
888        Some(candidate.element.borrow_data().unwrap().share_styles())
889    }
890
891    /// Attempts to find an element in the cache with the given primary rule
892    /// node and parent.
893    ///
894    /// FIXME(emilio): re-measure this optimization, and remove if it's not very
895    /// useful... It's probably not worth the complexity / obscure bugs.
896    pub fn lookup_by_rules(
897        &mut self,
898        shared_context: &SharedStyleContext,
899        inherited: &ComputedValues,
900        rules: &StrongRuleNode,
901        visited_rules: Option<&StrongRuleNode>,
902        target: E,
903    ) -> Option<PrimaryStyle> {
904        if shared_context.options.disable_style_sharing_cache {
905            return None;
906        }
907
908        self.cache_mut().entries.lookup(|candidate| {
909            debug_assert_ne!(candidate.element, target);
910            if !candidate.parent_style_identity().eq(inherited) {
911                return None;
912            }
913            let data = candidate.element.borrow_data().unwrap();
914            let style = data.styles.primary();
915            if style.rules.as_ref() != Some(&rules) {
916                return None;
917            }
918            if style.visited_rules() != visited_rules {
919                return None;
920            }
921            // NOTE(emilio): We only need to check name / namespace because we
922            // do name-dependent style adjustments, like the display: contents
923            // to display: none adjustment.
924            if target.namespace() != candidate.element.namespace()
925                || target.local_name() != candidate.element.local_name()
926            {
927                return None;
928            }
929            // When using container units, inherited style + rules matched aren't enough to
930            // determine whether the style is the same. We could actually do a full container
931            // lookup but for now we just check that our actual traversal parent matches.
932            if data
933                .styles
934                .primary()
935                .flags
936                .intersects(ComputedValueFlags::USES_CONTAINER_UNITS)
937                && candidate.element.traversal_parent() != target.traversal_parent()
938            {
939                return None;
940            }
941            // Rule nodes and styles are computed independent of the element's actual visitedness,
942            // but at the end of the cascade (in `adjust_for_visited`) we do store the
943            // RELEVANT_LINK_VISITED flag, so we can't share by rule node between visited and
944            // unvisited styles. We don't check for visitedness and just refuse to share for links
945            // entirely, so that visitedness doesn't affect timing.
946            if target.is_link() || candidate.element.is_link() {
947                return None;
948            }
949
950            Some(data.share_primary_style())
951        })
952    }
953}