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