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