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