traversal.rs (31148B)
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 //! Traversing the DOM tree; the bloom filter. 6 7 use crate::context::{ElementCascadeInputs, SharedStyleContext, StyleContext}; 8 use crate::data::{ElementData, ElementStyles, RestyleKind}; 9 use crate::dom::{NodeInfo, OpaqueNode, TElement, TNode}; 10 use crate::invalidation::element::restyle_hints::RestyleHint; 11 use crate::matching::{ChildRestyleRequirement, MatchMethods}; 12 use crate::selector_parser::PseudoElement; 13 use crate::sharing::StyleSharingTarget; 14 use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; 15 use crate::stylist::RuleInclusion; 16 use crate::traversal_flags::TraversalFlags; 17 use selectors::matching::SelectorCaches; 18 #[cfg(feature = "gecko")] 19 use selectors::parser::PseudoElement as PseudoElementTrait; 20 use smallvec::SmallVec; 21 use std::collections::HashMap; 22 23 /// A cache from element reference to known-valid computed style. 24 pub type UndisplayedStyleCache = 25 HashMap<selectors::OpaqueElement, servo_arc::Arc<crate::properties::ComputedValues>>; 26 27 /// A per-traversal-level chunk of data. This is sent down by the traversal, and 28 /// currently only holds the dom depth for the bloom filter. 29 /// 30 /// NB: Keep this as small as possible, please! 31 #[derive(Clone, Copy, Debug)] 32 pub struct PerLevelTraversalData { 33 /// The current dom depth. 34 /// 35 /// This is kept with cooperation from the traversal code and the bloom 36 /// filter. 37 pub current_dom_depth: usize, 38 } 39 40 /// We use this structure, rather than just returning a boolean from pre_traverse, 41 /// to enfore that callers process root invalidations before starting the traversal. 42 pub struct PreTraverseToken<E: TElement>(Option<E>); 43 impl<E: TElement> PreTraverseToken<E> { 44 /// Whether we should traverse children. 45 pub fn should_traverse(&self) -> bool { 46 self.0.is_some() 47 } 48 49 /// Returns the traversal root for the current traversal. 50 pub(crate) fn traversal_root(self) -> Option<E> { 51 self.0 52 } 53 } 54 55 /// A global variable holding the state of 56 /// `is_servo_nonincremental_layout()`. 57 /// See [#22854](https://github.com/servo/servo/issues/22854). 58 #[cfg(feature = "servo")] 59 pub static IS_SERVO_NONINCREMENTAL_LAYOUT: std::sync::atomic::AtomicBool = 60 std::sync::atomic::AtomicBool::new(false); 61 62 #[cfg(feature = "servo")] 63 #[inline] 64 fn is_servo_nonincremental_layout() -> bool { 65 use std::sync::atomic::Ordering; 66 67 IS_SERVO_NONINCREMENTAL_LAYOUT.load(Ordering::Relaxed) 68 } 69 70 #[cfg(not(feature = "servo"))] 71 #[inline] 72 fn is_servo_nonincremental_layout() -> bool { 73 false 74 } 75 76 /// A DOM Traversal trait, that is used to generically implement styling for 77 /// Gecko and Servo. 78 pub trait DomTraversal<E: TElement>: Sync { 79 /// Process `node` on the way down, before its children have been processed. 80 /// 81 /// The callback is invoked for each child node that should be processed by 82 /// the traversal. 83 fn process_preorder<F>( 84 &self, 85 data: &PerLevelTraversalData, 86 context: &mut StyleContext<E>, 87 node: E::ConcreteNode, 88 note_child: F, 89 ) where 90 F: FnMut(E::ConcreteNode); 91 92 /// Process `node` on the way up, after its children have been processed. 93 /// 94 /// This is only executed if `needs_postorder_traversal` returns true. 95 fn process_postorder(&self, contect: &mut StyleContext<E>, node: E::ConcreteNode); 96 97 /// Boolean that specifies whether a bottom up traversal should be 98 /// performed. 99 /// 100 /// If it's false, then process_postorder has no effect at all. 101 fn needs_postorder_traversal() -> bool { 102 true 103 } 104 105 /// Handles the postorder step of the traversal, if it exists, by bubbling 106 /// up the parent chain. 107 /// 108 /// If we are the last child that finished processing, recursively process 109 /// our parent. Else, stop. Also, stop at the root. 110 /// 111 /// Thus, if we start with all the leaves of a tree, we end up traversing 112 /// the whole tree bottom-up because each parent will be processed exactly 113 /// once (by the last child that finishes processing). 114 /// 115 /// The only communication between siblings is that they both 116 /// fetch-and-subtract the parent's children count. This makes it safe to 117 /// call durign the parallel traversal. 118 fn handle_postorder_traversal( 119 &self, 120 context: &mut StyleContext<E>, 121 root: OpaqueNode, 122 mut node: E::ConcreteNode, 123 children_to_process: isize, 124 ) { 125 // If the postorder step is a no-op, don't bother. 126 if !Self::needs_postorder_traversal() { 127 return; 128 } 129 130 if children_to_process == 0 { 131 // We are a leaf. Walk up the chain. 132 loop { 133 self.process_postorder(context, node); 134 if node.opaque() == root { 135 break; 136 } 137 let parent = node.traversal_parent().unwrap(); 138 let remaining = parent.did_process_child(); 139 if remaining != 0 { 140 // The parent has other unprocessed descendants. We only 141 // perform postorder processing after the last descendant 142 // has been processed. 143 break; 144 } 145 146 node = parent.as_node(); 147 } 148 } else { 149 // Otherwise record the number of children to process when the time 150 // comes. 151 node.as_element() 152 .unwrap() 153 .store_children_to_process(children_to_process); 154 } 155 } 156 157 /// Style invalidations happen when traversing from a parent to its children. 158 /// However, this mechanism can't handle style invalidations on the root. As 159 /// such, we have a pre-traversal step to handle that part and determine whether 160 /// a full traversal is needed. 161 fn pre_traverse(root: E, shared_context: &SharedStyleContext) -> PreTraverseToken<E> { 162 use crate::invalidation::element::state_and_attributes::propagate_dirty_bit_up_to; 163 164 let traversal_flags = shared_context.traversal_flags; 165 166 let mut data = root.mutate_data(); 167 let mut data = data.as_mut().map(|d| &mut **d); 168 169 if let Some(ref mut data) = data { 170 if !traversal_flags.for_animation_only() { 171 // Invalidate our style, and that of our siblings and 172 // descendants as needed. 173 let invalidation_result = data.invalidate_style_if_needed( 174 root, 175 shared_context, 176 None, 177 &mut SelectorCaches::default(), 178 ); 179 180 if invalidation_result.has_invalidated_siblings() { 181 let actual_root = root.as_node().parent_element_or_host().expect( 182 "How in the world can you invalidate \ 183 siblings without a parent?", 184 ); 185 propagate_dirty_bit_up_to(actual_root, root); 186 return PreTraverseToken(Some(actual_root)); 187 } 188 } 189 } 190 191 let should_traverse = 192 Self::element_needs_traversal(root, traversal_flags, data.as_mut().map(|d| &**d)); 193 194 // If we're not going to traverse at all, we may need to clear some state 195 // off the root (which would normally be done at the end of recalc_style_at). 196 if !should_traverse && data.is_some() { 197 clear_state_after_traversing(root, data.unwrap(), traversal_flags); 198 } 199 200 PreTraverseToken(if should_traverse { Some(root) } else { None }) 201 } 202 203 /// Returns true if traversal should visit a text node. The style system 204 /// never processes text nodes, but Servo overrides this to visit them for 205 /// flow construction when necessary. 206 fn text_node_needs_traversal(node: E::ConcreteNode, _parent_data: &ElementData) -> bool { 207 debug_assert!(node.is_text_node()); 208 false 209 } 210 211 /// Returns true if traversal is needed for the given element and subtree. 212 fn element_needs_traversal( 213 el: E, 214 traversal_flags: TraversalFlags, 215 data: Option<&ElementData>, 216 ) -> bool { 217 debug!( 218 "element_needs_traversal({:?}, {:?}, {:?})", 219 el, traversal_flags, data 220 ); 221 222 // Non-incremental layout visits every node. 223 if is_servo_nonincremental_layout() { 224 return true; 225 } 226 227 // Unwrap the data. 228 let data = match data { 229 Some(d) if d.has_styles() => d, 230 _ => return true, 231 }; 232 233 if traversal_flags.for_animation_only() { 234 // In case of animation-only traversal we need to traverse the element if the element 235 // has animation only dirty descendants bit, or animation-only restyle hint. 236 return el.has_animation_only_dirty_descendants() 237 || data.hint.has_animation_hint_or_recascade(); 238 } 239 240 // If the dirty descendants bit is set, we need to traverse no matter 241 // what. Skip examining the ElementData. 242 if el.has_dirty_descendants() { 243 return true; 244 } 245 246 // If we have a restyle hint or need to recascade, we need to visit the 247 // element. 248 // 249 // Note that this is different than checking has_current_styles_for_traversal(), 250 // since that can return true even if we have a restyle hint indicating 251 // that the element's descendants (but not necessarily the element) need 252 // restyling. 253 if !data.hint.is_empty() { 254 return true; 255 } 256 257 // Servo uses the post-order traversal for flow construction, so we need 258 // to traverse any element with damage so that we can perform fixup / 259 // reconstruction on our way back up the tree. 260 if cfg!(feature = "servo") && !data.damage.is_empty() { 261 return true; 262 } 263 264 trace!("{:?} doesn't need traversal", el); 265 false 266 } 267 268 /// Return the shared style context common to all worker threads. 269 fn shared_context(&self) -> &SharedStyleContext<'_>; 270 } 271 272 /// Manually resolve style by sequentially walking up the parent chain to the 273 /// first styled Element, ignoring pending restyles. The resolved style is made 274 /// available via a callback, and can be dropped by the time this function 275 /// returns in the display:none subtree case. 276 pub fn resolve_style<E>( 277 context: &mut StyleContext<E>, 278 element: E, 279 rule_inclusion: RuleInclusion, 280 pseudo: Option<&PseudoElement>, 281 mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>, 282 ) -> ElementStyles 283 where 284 E: TElement, 285 { 286 debug_assert!( 287 rule_inclusion == RuleInclusion::DefaultOnly 288 || pseudo.map_or(false, |p| p.is_before_or_after()) 289 || element.borrow_data().map_or(true, |d| !d.has_styles()), 290 "Why are we here?" 291 ); 292 debug_assert!( 293 rule_inclusion == RuleInclusion::All || undisplayed_style_cache.is_none(), 294 "can't use the cache for default styles only" 295 ); 296 297 let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new(); 298 299 // Clear the bloom filter, just in case the caller is reusing TLS. 300 context.thread_local.bloom_filter.clear(); 301 302 let mut style = None; 303 let mut ancestor = element.traversal_parent(); 304 while let Some(current) = ancestor { 305 if rule_inclusion == RuleInclusion::All { 306 if let Some(data) = current.borrow_data() { 307 if let Some(ancestor_style) = data.styles.get_primary() { 308 style = Some(ancestor_style.clone()); 309 break; 310 } 311 } 312 } 313 if let Some(ref mut cache) = undisplayed_style_cache { 314 if let Some(s) = cache.get(¤t.opaque()) { 315 style = Some(s.clone()); 316 break; 317 } 318 } 319 ancestors_requiring_style_resolution.push(current); 320 ancestor = current.traversal_parent(); 321 } 322 323 if let Some(ancestor) = ancestor { 324 context.thread_local.bloom_filter.rebuild(ancestor); 325 context.thread_local.bloom_filter.push(ancestor); 326 } 327 328 let mut layout_parent_style = style.clone(); 329 while let Some(style) = layout_parent_style.take() { 330 if !style.is_display_contents() { 331 layout_parent_style = Some(style); 332 break; 333 } 334 335 ancestor = ancestor.unwrap().traversal_parent(); 336 layout_parent_style = 337 ancestor.and_then(|a| a.borrow_data().map(|data| data.styles.primary().clone())); 338 } 339 340 for ancestor in ancestors_requiring_style_resolution.iter().rev() { 341 context.thread_local.bloom_filter.assert_complete(*ancestor); 342 343 // Actually `PseudoElementResolution` doesn't really matter here. 344 // (but it does matter below!). 345 let primary_style = StyleResolverForElement::new( 346 *ancestor, 347 context, 348 rule_inclusion, 349 PseudoElementResolution::IfApplicable, 350 ) 351 .resolve_primary_style( 352 style.as_deref(), 353 layout_parent_style.as_deref(), 354 selectors::matching::IncludeStartingStyle::No, 355 ); 356 357 let is_display_contents = primary_style.style().is_display_contents(); 358 359 style = Some(primary_style.style.0); 360 if !is_display_contents { 361 layout_parent_style = style.clone(); 362 } 363 364 if let Some(ref mut cache) = undisplayed_style_cache { 365 cache.insert(ancestor.opaque(), style.clone().unwrap()); 366 } 367 context.thread_local.bloom_filter.push(*ancestor); 368 } 369 370 context.thread_local.bloom_filter.assert_complete(element); 371 let styles: ElementStyles = StyleResolverForElement::new( 372 element, 373 context, 374 rule_inclusion, 375 PseudoElementResolution::Force, 376 ) 377 .resolve_style(style.as_deref(), layout_parent_style.as_deref()) 378 .into(); 379 380 if let Some(ref mut cache) = undisplayed_style_cache { 381 cache.insert(element.opaque(), styles.primary().clone()); 382 } 383 384 styles 385 } 386 387 /// Calculates the style for a single node. 388 #[inline] 389 #[allow(unsafe_code)] 390 pub fn recalc_style_at<E, D, F>( 391 _traversal: &D, 392 traversal_data: &PerLevelTraversalData, 393 context: &mut StyleContext<E>, 394 element: E, 395 data: &mut ElementData, 396 note_child: F, 397 ) where 398 E: TElement, 399 D: DomTraversal<E>, 400 F: FnMut(E::ConcreteNode), 401 { 402 use std::cmp; 403 404 let flags = context.shared.traversal_flags; 405 let is_initial_style = !data.has_styles(); 406 407 context.thread_local.statistics.elements_traversed += 1; 408 debug_assert!( 409 flags.intersects(TraversalFlags::AnimationOnly) 410 || is_initial_style 411 || !element.has_snapshot() 412 || element.handled_snapshot(), 413 "Should've handled snapshots here already" 414 ); 415 416 let restyle_kind = data.restyle_kind(&context.shared); 417 debug!( 418 "recalc_style_at: {:?} (restyle_kind={:?}, dirty_descendants={:?}, data={:?})", 419 element, 420 restyle_kind, 421 element.has_dirty_descendants(), 422 data 423 ); 424 425 let mut child_restyle_requirement = ChildRestyleRequirement::CanSkipCascade; 426 427 // Compute style for this element if necessary. 428 if let Some(restyle_kind) = restyle_kind { 429 child_restyle_requirement = 430 compute_style(traversal_data, context, element, data, restyle_kind); 431 432 if !element.matches_user_and_content_rules() { 433 // We must always cascade native anonymous subtrees, since they 434 // may have pseudo-elements underneath that would inherit from the 435 // closest non-NAC ancestor instead of us. 436 child_restyle_requirement = cmp::max( 437 child_restyle_requirement, 438 ChildRestyleRequirement::MustCascadeChildren, 439 ); 440 } 441 442 // If we're restyling this element to display:none, throw away all style 443 // data in the subtree, notify the caller to early-return. 444 if data.styles.is_display_none() { 445 debug!( 446 "{:?} style is display:none - clearing data from descendants.", 447 element 448 ); 449 unsafe { 450 clear_descendant_data(element); 451 } 452 } 453 454 // Inform any paint worklets of changed style, to speculatively 455 // evaluate the worklet code. In the case that the size hasn't changed, 456 // this will result in increased concurrency between script and layout. 457 notify_paint_worklet(context, data); 458 } else { 459 debug_assert!(data.has_styles()); 460 data.set_traversed_without_styling(); 461 } 462 463 // Now that matching and cascading is done, clear the bits corresponding to 464 // those operations and compute the propagated restyle hint (unless we're 465 // not processing invalidations, in which case don't need to propagate it 466 // and must avoid clearing it). 467 debug_assert!( 468 flags.for_animation_only() || !data.hint.has_animation_hint(), 469 "animation restyle hint should be handled during \ 470 animation-only restyles" 471 ); 472 let mut propagated_hint = data.hint.propagate(&flags); 473 trace!( 474 "propagated_hint={:?}, restyle_requirement={:?}, \ 475 is_display_none={:?}, implementing_pseudo={:?}", 476 propagated_hint, 477 child_restyle_requirement, 478 data.styles.is_display_none(), 479 element.implemented_pseudo_element() 480 ); 481 482 // Integrate the child cascade requirement into the propagated hint. 483 match child_restyle_requirement { 484 ChildRestyleRequirement::CanSkipCascade => {}, 485 ChildRestyleRequirement::MustCascadeDescendants => { 486 propagated_hint |= RestyleHint::RECASCADE_SELF | RestyleHint::RECASCADE_DESCENDANTS; 487 }, 488 ChildRestyleRequirement::MustCascadeChildrenIfInheritResetStyle => { 489 propagated_hint |= RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE; 490 }, 491 ChildRestyleRequirement::MustCascadeChildren => { 492 propagated_hint |= RestyleHint::RECASCADE_SELF; 493 }, 494 ChildRestyleRequirement::MustMatchDescendants => { 495 propagated_hint |= RestyleHint::restyle_subtree(); 496 }, 497 } 498 499 let has_dirty_descendants_for_this_restyle = if flags.for_animation_only() { 500 element.has_animation_only_dirty_descendants() 501 } else { 502 element.has_dirty_descendants() 503 }; 504 505 // Before examining each child individually, try to prove that our children 506 // don't need style processing. They need processing if any of the following 507 // conditions hold: 508 // 509 // * We have the dirty descendants bit. 510 // * We're propagating a restyle hint. 511 // * This is a servo non-incremental traversal. 512 // 513 // We only do this if we're not a display: none root, since in that case 514 // it's useless to style children. 515 let mut traverse_children = has_dirty_descendants_for_this_restyle 516 || !propagated_hint.is_empty() 517 || is_servo_nonincremental_layout(); 518 519 traverse_children = traverse_children && !data.styles.is_display_none(); 520 521 // Examine our children, and enqueue the appropriate ones for traversal. 522 if traverse_children { 523 note_children::<E, D, F>( 524 context, 525 element, 526 data, 527 propagated_hint, 528 is_initial_style, 529 note_child, 530 ); 531 } 532 533 // FIXME(bholley): Make these assertions pass for servo. 534 if cfg!(feature = "gecko") && cfg!(debug_assertions) && data.styles.is_display_none() { 535 debug_assert!(!element.has_dirty_descendants()); 536 debug_assert!(!element.has_animation_only_dirty_descendants()); 537 } 538 539 clear_state_after_traversing(element, data, flags); 540 } 541 542 fn clear_state_after_traversing<E>(element: E, data: &mut ElementData, flags: TraversalFlags) 543 where 544 E: TElement, 545 { 546 if flags.intersects(TraversalFlags::FinalAnimationTraversal) { 547 debug_assert!(flags.for_animation_only()); 548 data.clear_restyle_flags_and_damage(); 549 unsafe { 550 element.unset_animation_only_dirty_descendants(); 551 } 552 } 553 } 554 555 fn compute_style<E>( 556 traversal_data: &PerLevelTraversalData, 557 context: &mut StyleContext<E>, 558 element: E, 559 data: &mut ElementData, 560 kind: RestyleKind, 561 ) -> ChildRestyleRequirement 562 where 563 E: TElement, 564 { 565 use crate::data::RestyleKind::*; 566 567 context.thread_local.statistics.elements_styled += 1; 568 debug!("compute_style: {:?} (kind={:?})", element, kind); 569 570 if data.has_styles() { 571 data.set_restyled(); 572 } 573 574 let mut important_rules_changed = false; 575 let new_styles = match kind { 576 MatchAndCascade => { 577 debug_assert!( 578 !context.shared.traversal_flags.for_animation_only() || !data.has_styles(), 579 "MatchAndCascade shouldn't normally be processed during animation-only traversal" 580 ); 581 // Ensure the bloom filter is up to date. 582 context 583 .thread_local 584 .bloom_filter 585 .insert_parents_recovering(element, traversal_data.current_dom_depth); 586 587 context.thread_local.bloom_filter.assert_complete(element); 588 debug_assert_eq!( 589 context.thread_local.bloom_filter.matching_depth(), 590 traversal_data.current_dom_depth 591 ); 592 593 // This is only relevant for animations as of right now. 594 important_rules_changed = true; 595 596 let mut target = StyleSharingTarget::new(element); 597 598 // Now that our bloom filter is set up, try the style sharing 599 // cache. 600 match target.share_style_if_possible(context) { 601 Some(shared_styles) => { 602 context.thread_local.statistics.styles_shared += 1; 603 shared_styles 604 }, 605 None => { 606 context.thread_local.statistics.elements_matched += 1; 607 // Perform the matching and cascading. 608 let new_styles = { 609 let mut resolver = StyleResolverForElement::new( 610 element, 611 context, 612 RuleInclusion::All, 613 PseudoElementResolution::IfApplicable, 614 ); 615 616 resolver.resolve_style_with_default_parents() 617 }; 618 619 context.thread_local.sharing_cache.insert_if_possible( 620 &element, 621 &new_styles.primary, 622 Some(&mut target), 623 traversal_data.current_dom_depth, 624 &context.shared, 625 ); 626 627 new_styles 628 }, 629 } 630 }, 631 CascadeWithReplacements(flags) => { 632 // Skipping full matching, load cascade inputs from previous values. 633 let mut cascade_inputs = ElementCascadeInputs::new_from_element_data(data); 634 important_rules_changed = element.replace_rules(flags, context, &mut cascade_inputs); 635 636 let mut resolver = StyleResolverForElement::new( 637 element, 638 context, 639 RuleInclusion::All, 640 PseudoElementResolution::IfApplicable, 641 ); 642 643 resolver 644 .cascade_styles_with_default_parents(cascade_inputs, data.may_have_starting_style()) 645 }, 646 CascadeOnly => { 647 // Skipping full matching, load cascade inputs from previous values. 648 let cascade_inputs = ElementCascadeInputs::new_from_element_data(data); 649 650 let new_styles = { 651 let mut resolver = StyleResolverForElement::new( 652 element, 653 context, 654 RuleInclusion::All, 655 PseudoElementResolution::IfApplicable, 656 ); 657 658 resolver.cascade_styles_with_default_parents( 659 cascade_inputs, 660 data.may_have_starting_style(), 661 ) 662 }; 663 664 // Insert into the cache, but only if this style isn't reused from a 665 // sibling or cousin. Otherwise, recascading a bunch of identical 666 // elements would unnecessarily flood the cache with identical entries. 667 // 668 // This is analogous to the obvious "don't insert an element that just 669 // got a hit in the style sharing cache" behavior in the MatchAndCascade 670 // handling above. 671 // 672 // Note that, for the MatchAndCascade path, we still insert elements that 673 // shared styles via the rule node, because we know that there's something 674 // different about them that caused them to miss the sharing cache before 675 // selector matching. If we didn't, we would still end up with the same 676 // number of eventual styles, but would potentially miss out on various 677 // opportunities for skipping selector matching, which could hurt 678 // performance. 679 if !new_styles.primary.reused_via_rule_node { 680 context.thread_local.sharing_cache.insert_if_possible( 681 &element, 682 &new_styles.primary, 683 None, 684 traversal_data.current_dom_depth, 685 &context.shared, 686 ); 687 } 688 689 new_styles 690 }, 691 }; 692 693 element.finish_restyle(context, data, new_styles, important_rules_changed) 694 } 695 696 #[cfg(feature = "servo")] 697 fn notify_paint_worklet<E>(context: &StyleContext<E>, data: &ElementData) 698 where 699 E: TElement, 700 { 701 use crate::values::generics::image::Image; 702 use style_traits::ToCss; 703 704 // We speculatively evaluate any paint worklets during styling. 705 // This allows us to run paint worklets in parallel with style and layout. 706 // Note that this is wasted effort if the size of the node has 707 // changed, but in may cases it won't have. 708 if let Some(ref values) = data.styles.primary { 709 for image in &values.get_background().background_image.0 { 710 let (name, arguments) = match *image { 711 Image::PaintWorklet(ref worklet) => (&worklet.name, &worklet.arguments), 712 _ => continue, 713 }; 714 let painter = match context.shared.registered_speculative_painters.get(name) { 715 Some(painter) => painter, 716 None => continue, 717 }; 718 let properties = painter 719 .properties() 720 .iter() 721 .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id))) 722 .map(|(name, id)| (name.clone(), values.computed_value_to_string(id))) 723 .collect(); 724 let arguments = arguments 725 .iter() 726 .map(|argument| argument.to_css_string()) 727 .collect(); 728 debug!("Notifying paint worklet {}.", painter.name()); 729 painter.speculatively_draw_a_paint_image(properties, arguments); 730 } 731 } 732 } 733 734 #[cfg(not(feature = "servo"))] 735 fn notify_paint_worklet<E>(_context: &StyleContext<E>, _data: &ElementData) 736 where 737 E: TElement, 738 { 739 // The CSS paint API is Servo-only at the moment 740 } 741 742 fn note_children<E, D, F>( 743 context: &mut StyleContext<E>, 744 element: E, 745 data: &ElementData, 746 propagated_hint: RestyleHint, 747 is_initial_style: bool, 748 mut note_child: F, 749 ) where 750 E: TElement, 751 D: DomTraversal<E>, 752 F: FnMut(E::ConcreteNode), 753 { 754 trace!("note_children: {:?}", element); 755 let flags = context.shared.traversal_flags; 756 757 // Loop over all the traversal children. 758 for child_node in element.traversal_children() { 759 let child = match child_node.as_element() { 760 Some(el) => el, 761 None => { 762 if is_servo_nonincremental_layout() 763 || D::text_node_needs_traversal(child_node, data) 764 { 765 note_child(child_node); 766 } 767 continue; 768 }, 769 }; 770 771 let mut child_data = child.mutate_data(); 772 let mut child_data = child_data.as_mut().map(|d| &mut **d); 773 trace!( 774 " > {:?} -> {:?} + {:?}, pseudo: {:?}", 775 child, 776 child_data.as_ref().map(|d| d.hint), 777 propagated_hint, 778 child.implemented_pseudo_element() 779 ); 780 781 if let Some(ref mut child_data) = child_data { 782 child_data.hint.insert(propagated_hint); 783 784 // Handle element snapshots and invalidation of descendants and siblings 785 // as needed. 786 // 787 // NB: This will be a no-op if there's no snapshot. 788 child_data.invalidate_style_if_needed( 789 child, 790 &context.shared, 791 Some(&context.thread_local.stack_limit_checker), 792 &mut context.thread_local.selector_caches, 793 ); 794 } 795 796 if D::element_needs_traversal(child, flags, child_data.map(|d| &*d)) { 797 note_child(child_node); 798 799 // Set the dirty descendants bit on the parent as needed, so that we 800 // can find elements during the post-traversal. 801 // 802 // Note that these bits may be cleared again at the bottom of 803 // recalc_style_at if requested by the caller. 804 if !is_initial_style { 805 if flags.for_animation_only() { 806 unsafe { 807 element.set_animation_only_dirty_descendants(); 808 } 809 } else { 810 unsafe { 811 element.set_dirty_descendants(); 812 } 813 } 814 } 815 } 816 } 817 } 818 819 /// Clear style data for all the subtree under `root` (but not for root itself). 820 /// 821 /// We use a list to avoid unbounded recursion, which we need to avoid in the 822 /// parallel traversal because the rayon stacks are small. 823 pub unsafe fn clear_descendant_data<E>(root: E) 824 where 825 E: TElement, 826 { 827 let mut parents = SmallVec::<[E; 32]>::new(); 828 parents.push(root); 829 while let Some(p) = parents.pop() { 830 for kid in p.traversal_children() { 831 if let Some(kid) = kid.as_element() { 832 // We maintain an invariant that, if an element has data, all its 833 // ancestors have data as well. 834 // 835 // By consequence, any element without data has no descendants with 836 // data. 837 if kid.has_data() { 838 kid.clear_data(); 839 parents.push(kid); 840 } 841 } 842 } 843 } 844 845 // Make sure not to clear NODE_NEEDS_FRAME on the root. 846 root.clear_descendant_bits(); 847 }