Skip to main content

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