/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! Traversing the DOM tree; the bloom filter. use atomic_refcell::AtomicRefCell; use context::{ElementCascadeInputs, StyleContext, SharedStyleContext, ThreadLocalStyleContext}; use data::{ElementData, ElementStyles}; use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode}; use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint}; use matching::{ChildCascadeRequirement, MatchMethods}; use sharing::{StyleSharingBehavior, StyleSharingTarget}; #[cfg(feature = "servo")] use servo_config::opts; use smallvec::SmallVec; use std::borrow::BorrowMut; /// A per-traversal-level chunk of data. This is sent down by the traversal, and /// currently only holds the dom depth for the bloom filter. /// /// NB: Keep this as small as possible, please! #[derive(Clone, Debug)] pub struct PerLevelTraversalData { /// The current dom depth. /// /// This is kept with cooperation from the traversal code and the bloom /// filter. pub current_dom_depth: usize, } bitflags! { /// Flags that control the traversal process. pub flags TraversalFlags: u8 { /// Traverse only unstyled children. const UNSTYLED_CHILDREN_ONLY = 0x01, /// Traverse only elements for animation restyles. const ANIMATION_ONLY = 0x02, /// Traverse without generating any change hints. const FOR_RECONSTRUCT = 0x04, /// Traverse triggered by CSS rule changes. /// /// Traverse and update all elements with CSS animations since /// @keyframes rules may have changed const FOR_CSS_RULE_CHANGES = 0x08, /// Only include user agent style sheets when selector matching. const FOR_DEFAULT_STYLES = 0x10, } } impl TraversalFlags { /// Returns true if the traversal is for animation-only restyles. pub fn for_animation_only(&self) -> bool { self.contains(ANIMATION_ONLY) } /// Returns true if the traversal is for unstyled children. pub fn for_unstyled_children_only(&self) -> bool { self.contains(UNSTYLED_CHILDREN_ONLY) } /// Returns true if the traversal is for a frame reconstruction. pub fn for_reconstruct(&self) -> bool { self.contains(FOR_RECONSTRUCT) } /// Returns true if the traversal is triggered by CSS rule changes. pub fn for_css_rule_changes(&self) -> bool { self.contains(FOR_CSS_RULE_CHANGES) } /// Returns true if the traversal is to compute the default computed styles /// for an element. pub fn for_default_styles(&self) -> bool { self.contains(FOR_DEFAULT_STYLES) } } /// This structure exists to enforce that callers invoke pre_traverse, and also /// to pass information from the pre-traversal into the primary traversal. pub struct PreTraverseToken { traverse: bool, unstyled_children_only: bool, } impl PreTraverseToken { /// Whether we should traverse children. pub fn should_traverse(&self) -> bool { self.traverse } /// Whether we should traverse only unstyled children. pub fn traverse_unstyled_children_only(&self) -> bool { self.unstyled_children_only } } /// Enum to prevent duplicate logging. pub enum LogBehavior { /// We should log. MayLog, /// We shouldn't log. DontLog, } use self::LogBehavior::*; impl LogBehavior { fn allow(&self) -> bool { matches!(*self, MayLog) } } /// The kind of traversals we could perform. #[derive(Debug, Copy, Clone)] pub enum TraversalDriver { /// A potentially parallel traversal. Parallel, /// A sequential traversal. Sequential, } impl TraversalDriver { /// Returns whether this represents a parallel traversal or not. #[inline] pub fn is_parallel(&self) -> bool { matches!(*self, TraversalDriver::Parallel) } } #[cfg(feature = "servo")] fn is_servo_nonincremental_layout() -> bool { opts::get().nonincremental_layout } #[cfg(not(feature = "servo"))] fn is_servo_nonincremental_layout() -> bool { false } /// A DOM Traversal trait, that is used to generically implement styling for /// Gecko and Servo. pub trait DomTraversal : Sync { /// The thread-local context, used to store non-thread-safe stuff that needs /// to be used in the traversal, and of which we use one per worker, like /// the bloom filter, for example. type ThreadLocalContext: Send + BorrowMut>; /// Process `node` on the way down, before its children have been processed. fn process_preorder(&self, data: &PerLevelTraversalData, thread_local: &mut Self::ThreadLocalContext, node: E::ConcreteNode); /// Process `node` on the way up, after its children have been processed. /// /// This is only executed if `needs_postorder_traversal` returns true. fn process_postorder(&self, thread_local: &mut Self::ThreadLocalContext, node: E::ConcreteNode); /// Boolean that specifies whether a bottom up traversal should be /// performed. /// /// If it's false, then process_postorder has no effect at all. fn needs_postorder_traversal() -> bool { true } /// Handles the postorder step of the traversal, if it exists, by bubbling /// up the parent chain. /// /// If we are the last child that finished processing, recursively process /// our parent. Else, stop. Also, stop at the root. /// /// Thus, if we start with all the leaves of a tree, we end up traversing /// the whole tree bottom-up because each parent will be processed exactly /// once (by the last child that finishes processing). /// /// The only communication between siblings is that they both /// fetch-and-subtract the parent's children count. This makes it safe to /// call durign the parallel traversal. fn handle_postorder_traversal( &self, thread_local: &mut Self::ThreadLocalContext, root: OpaqueNode, mut node: E::ConcreteNode, children_to_process: isize ) { // If the postorder step is a no-op, don't bother. if !Self::needs_postorder_traversal() { return; } if children_to_process == 0 { // We are a leaf. Walk up the chain. loop { self.process_postorder(thread_local, node); if node.opaque() == root { break; } let parent = node.traversal_parent().unwrap(); let remaining = parent.did_process_child(); if remaining != 0 { // The parent has other unprocessed descendants. We only // perform postorder processing after the last descendant // has been processed. break } node = parent.as_node(); } } else { // Otherwise record the number of children to process when the time // comes. node.as_element().unwrap() .store_children_to_process(children_to_process); } } /// Must be invoked before traversing the root element to determine whether /// a traversal is needed. Returns a token that allows the caller to prove /// that the call happened. /// /// The traversal_flags is used in Gecko. /// /// If traversal_flag::UNSTYLED_CHILDREN_ONLY is specified, style newly- /// appended children without restyling the parent. /// /// If traversal_flag::ANIMATION_ONLY is specified, style only elements for /// animations. fn pre_traverse( root: E, shared_context: &SharedStyleContext, traversal_flags: TraversalFlags ) -> PreTraverseToken { debug_assert!(!(traversal_flags.for_reconstruct() && traversal_flags.for_unstyled_children_only()), "must not specify FOR_RECONSTRUCT in combination with \ UNSTYLED_CHILDREN_ONLY"); if traversal_flags.for_unstyled_children_only() { if root.borrow_data().map_or(true, |d| d.has_styles() && d.styles.is_display_none()) { return PreTraverseToken { traverse: false, unstyled_children_only: false, }; } return PreTraverseToken { traverse: true, unstyled_children_only: true, }; } // Look at whether there has been any attribute or state change, and // invalidate our style, and the one of our siblings and descendants as // needed. if let Some(mut data) = root.mutate_data() { data.invalidate_style_if_needed(root, shared_context); } PreTraverseToken { traverse: Self::node_needs_traversal(root.as_node(), traversal_flags), unstyled_children_only: false, } } /// Returns true if traversal should visit a text node. The style system /// never processes text nodes, but Servo overrides this to visit them for /// flow construction when necessary. fn text_node_needs_traversal(node: E::ConcreteNode) -> bool { debug_assert!(node.is_text_node()); false } /// Returns true if traversal is needed for the given node and subtree. fn node_needs_traversal( node: E::ConcreteNode, traversal_flags: TraversalFlags ) -> bool { // Non-incremental layout visits every node. if is_servo_nonincremental_layout() { return true; } if traversal_flags.for_reconstruct() { return true; } let el = match node.as_element() { None => return Self::text_node_needs_traversal(node), Some(el) => el, }; // If the element is native-anonymous and an ancestor frame will be // reconstructed, the child and all its descendants will be destroyed. // In that case, we wouldn't need to traverse the subtree... // // Except if there could be transitions of pseudo-elements, in which // case we still need to process them, unfortunately. // // We need to conservatively continue the traversal to style the // pseudo-element in order to properly process potentially-new // transitions that we won't see otherwise. // // But it may be that we no longer match, so detect that case and act // appropriately here. if el.is_native_anonymous() { if let Some(parent) = el.traversal_parent() { let parent_data = parent.borrow_data().unwrap(); let going_to_reframe = parent_data.restyle.reconstructed_self_or_ancestor(); let mut is_before_or_after_pseudo = false; if let Some(pseudo) = el.implemented_pseudo_element() { if pseudo.is_before_or_after() { is_before_or_after_pseudo = true; let still_match = parent_data.styles.pseudos.get(&pseudo).is_some(); if !still_match { debug_assert!(going_to_reframe, "We're removing a pseudo, so we \ should reframe!"); return false; } } } if going_to_reframe && !is_before_or_after_pseudo { debug!("Element {:?} is in doomed NAC subtree, \ culling traversal", el); return false; } } } // In case of animation-only traversal we need to traverse the element // if the element has animation only dirty descendants bit, // animation-only restyle hint or recascade. if traversal_flags.for_animation_only() { // Skip elements that have no style data since animation-only // restyle is not necessary for the elements. let data = match el.borrow_data() { Some(d) => d, None => return false, }; if !data.has_styles() { return false; } if el.has_animation_only_dirty_descendants() { return true; } return data.restyle.hint.has_animation_hint() || data.restyle.hint.has_recascade_self(); } // If the dirty descendants bit is set, we need to traverse no matter // what. Skip examining the ElementData. if el.has_dirty_descendants() { return true; } // Check the element data. If it doesn't exist, we need to visit the // element. let data = match el.borrow_data() { Some(d) => d, None => return true, }; // If we don't have any style data, we need to visit the element. if !data.has_styles() { return true; } // If we have a restyle hint or need to recascade, we need to visit the // element. // // Note that this is different than checking has_current_styles(), // since that can return true even if we have a restyle hint indicating // that the element's descendants (but not necessarily the element) need // restyling. if !data.restyle.hint.is_empty() { return true; } // Servo uses the post-order traversal for flow construction, so we need // to traverse any element with damage so that we can perform fixup / // reconstruction on our way back up the tree. // // We also need to traverse nodes with explicit damage and no other // restyle data, so that this damage can be cleared. if (cfg!(feature = "servo") || traversal_flags.for_reconstruct()) && !data.restyle.damage.is_empty() { return true; } trace!("{:?} doesn't need traversal", el); false } /// Returns true if traversal of this element's children is allowed. We use /// this to cull traversal of various subtrees. /// /// This may be called multiple times when processing an element, so we pass /// a parameter to keep the logs tidy. fn should_traverse_children( &self, thread_local: &mut ThreadLocalStyleContext, parent: E, parent_data: &ElementData, log: LogBehavior ) -> bool { // See the comment on `cascade_node` for why we allow this on Gecko. debug_assert!(cfg!(feature = "gecko") || parent.has_current_styles(parent_data)); // If the parent computed display:none, we don't style the subtree. if parent_data.styles.is_display_none() { if log.allow() { debug!("Parent {:?} is display:none, culling traversal", parent); } return false; } // Gecko-only XBL handling. // // If we're computing initial styles and the parent has a Gecko XBL // binding, that binding may inject anonymous children and remap the // explicit children to an insertion point (or hide them entirely). It // may also specify a scoped stylesheet, which changes the rules that // apply within the subtree. These two effects can invalidate the result // of property inheritance and selector matching (respectively) within // the subtree. // // To avoid wasting work, we defer initial styling of XBL subtrees until // frame construction, which does an explicit traversal of the unstyled // children after shuffling the subtree. That explicit traversal may in // turn find other bound elements, which get handled in the same way. // // We explicitly avoid handling restyles here (explicitly removing or // changing bindings), since that adds complexity and is rarer. If it // happens, we may just end up doing wasted work, since Gecko // recursively drops Servo ElementData when the XBL insertion parent of // an Element is changed. if cfg!(feature = "gecko") && thread_local.is_initial_style() && parent_data.styles.primary().has_moz_binding() { if log.allow() { debug!("Parent {:?} has XBL binding, deferring traversal", parent); } return false; } return true; } /// Helper for the traversal implementations to select the children that /// should be enqueued for processing. fn traverse_children( &self, thread_local: &mut Self::ThreadLocalContext, parent: E, mut f: F ) where F: FnMut(&mut Self::ThreadLocalContext, E::ConcreteNode) { // Check if we're allowed to traverse past this element. let should_traverse = self.should_traverse_children( thread_local.borrow_mut(), parent, &parent.borrow_data().unwrap(), MayLog ); thread_local.borrow_mut().end_element(parent); if !should_traverse { return; } for kid in parent.as_node().traversal_children() { if Self::node_needs_traversal(kid, self.shared_context().traversal_flags) { // If we are in a restyle for reconstruction, there is no need to // perform a post-traversal, so we don't need to set the dirty // descendants bit on the parent. if !self.shared_context().traversal_flags.for_reconstruct() { let el = kid.as_element(); if el.as_ref().and_then(|el| el.borrow_data()) .map_or(false, |d| d.has_styles()) { if self.shared_context().traversal_flags.for_animation_only() { unsafe { parent.set_animation_only_dirty_descendants(); } } else { unsafe { parent.set_dirty_descendants(); } } } } f(thread_local, kid); } } } /// Ensures the existence of the ElementData, and returns it. This can't /// live on TNode because of the trait-based separation between Servo's /// script and layout crates. /// /// This is only safe to call in top-down traversal before processing the /// children of |element|. unsafe fn ensure_element_data(element: &E) -> &AtomicRefCell; /// Clears the ElementData attached to this element, if any. /// /// This is only safe to call in top-down traversal before processing the /// children of |element|. unsafe fn clear_element_data(element: &E); /// Return the shared style context common to all worker threads. fn shared_context(&self) -> &SharedStyleContext; /// Creates a thread-local context. fn create_thread_local_context(&self) -> Self::ThreadLocalContext; /// Whether we're performing a parallel traversal. /// /// NB: We do this check on runtime. We could guarantee correctness in this /// regard via the type system via a `TraversalDriver` trait for this trait, /// that could be one of two concrete types. It's not clear whether the /// potential code size impact of that is worth it. fn is_parallel(&self) -> bool; } /// Helper for the function below. fn resolve_style_internal( context: &mut StyleContext, element: E, ensure_data: &F ) -> Option where E: TElement, F: Fn(E), { ensure_data(element); let mut data = element.mutate_data().unwrap(); let mut display_none_root = None; // If the Element isn't styled, we need to compute its style. if !data.has_styles() { // Compute the parent style if necessary. let parent = element.traversal_parent(); if let Some(p) = parent { display_none_root = resolve_style_internal(context, p, ensure_data); } // Maintain the bloom filter. If it doesn't exist, we need to build it // from scratch. Otherwise we just need to push the parent. if context.thread_local.bloom_filter.is_empty() { context.thread_local.bloom_filter.rebuild(element); } else { context.thread_local.bloom_filter.push(parent.unwrap()); context.thread_local.bloom_filter.assert_complete(element); } // Compute our style. context.thread_local.begin_element(element, &data); element.match_and_cascade(context, &mut data, StyleSharingBehavior::Disallow); context.thread_local.end_element(element); if !context.shared.traversal_flags.for_default_styles() { // Conservatively mark us as having dirty descendants, since there // might be other unstyled siblings we miss when walking straight up // the parent chain. // // No need to do this if we're computing default styles, since // resolve_default_style will want the tree to be left as it is. unsafe { element.note_descendants::() }; } } // If we're display:none and none of our ancestors are, we're the root of a // display:none subtree. if display_none_root.is_none() && data.styles.is_display_none() { display_none_root = Some(element); } return display_none_root } /// Manually resolve style by sequentially walking up the parent chain to the /// first styled Element, ignoring pending restyles. The resolved style is made /// available via a callback, and can be dropped by the time this function /// returns in the display:none subtree case. pub fn resolve_style(context: &mut StyleContext, element: E, ensure_data: &F, clear_data: &G, callback: H) where E: TElement, F: Fn(E), G: Fn(E), H: FnOnce(&ElementStyles) { // Clear the bloom filter, just in case the caller is reusing TLS. context.thread_local.bloom_filter.clear(); // Resolve styles up the tree. let display_none_root = resolve_style_internal(context, element, ensure_data); // Make them available for the scope of the callback. The callee may use the // argument, or perform any other processing that requires the styles to // exist on the Element. callback(&element.borrow_data().unwrap().styles); // Clear any styles in display:none subtrees or subtrees not in the // document, to leave the tree in a valid state. For display:none subtrees, // we leave the styles on the display:none root, but for subtrees not in the // document, we clear styles all the way up to the root of the disconnected // subtree. let in_doc = element.as_node().is_in_doc(); if !in_doc || display_none_root.is_some() { let mut curr = element; loop { unsafe { curr.unset_dirty_descendants(); curr.unset_animation_only_dirty_descendants(); } if in_doc && curr == display_none_root.unwrap() { break; } clear_data(curr); curr = match curr.traversal_parent() { Some(parent) => parent, None => break, }; } } } /// Manually resolve default styles for the given Element, which are the styles /// only taking into account user agent and user cascade levels. The resolved /// style is made available via a callback, and will be dropped by the time this /// function returns. pub fn resolve_default_style( context: &mut StyleContext, element: E, ensure_data: &F, set_data: &G, callback: H ) where E: TElement, F: Fn(E), G: Fn(E, Option) -> Option, H: FnOnce(&ElementStyles), { // Save and clear out element data from the element and its ancestors. let mut old_data: SmallVec<[(E, Option); 8]> = SmallVec::new(); { let mut e = element; loop { old_data.push((e, set_data(e, None))); match e.traversal_parent() { Some(parent) => e = parent, None => break, } } } // Resolve styles up the tree. resolve_style_internal(context, element, ensure_data); // Make them available for the scope of the callback. The callee may use the // argument, or perform any other processing that requires the styles to // exist on the Element. callback(&element.borrow_data().unwrap().styles); // Swap the old element data back into the element and its ancestors. for entry in old_data { set_data(entry.0, entry.1); } } /// Calculates the style for a single node. #[inline] #[allow(unsafe_code)] pub fn recalc_style_at( traversal: &D, traversal_data: &PerLevelTraversalData, context: &mut StyleContext, element: E, data: &mut ElementData ) where E: TElement, D: DomTraversal, { context.thread_local.begin_element(element, data); context.thread_local.statistics.elements_traversed += 1; debug_assert!(!element.has_snapshot() || element.handled_snapshot(), "Should've handled snapshots here already"); let compute_self = !element.has_current_styles(data); let mut hint = RestyleHint::empty(); debug!("recalc_style_at: {:?} (compute_self={:?}, \ dirty_descendants={:?}, data={:?})", element, compute_self, element.has_dirty_descendants(), data); // Compute style for this element if necessary. if compute_self { match compute_style(traversal, traversal_data, context, element, data) { ChildCascadeRequirement::MustCascadeChildren => { hint |= RECASCADE_SELF; } ChildCascadeRequirement::MustCascadeDescendants => { hint |= RECASCADE_SELF | RECASCADE_DESCENDANTS; } ChildCascadeRequirement::CanSkipCascade => {} }; // We must always cascade native anonymous subtrees, since they inherit // styles from their first non-NAC ancestor. if element.is_native_anonymous() { hint |= RECASCADE_SELF; } // If we're restyling this element to display:none, throw away all style // data in the subtree, notify the caller to early-return. if data.styles.is_display_none() { debug!("{:?} style is display:none - clearing data from descendants.", element); clear_descendant_data(element, &|e| unsafe { D::clear_element_data(&e) }); } } // Now that matching and cascading is done, clear the bits corresponding to // those operations and compute the propagated restyle hint. let mut propagated_hint = { debug_assert!(context.shared.traversal_flags.for_animation_only() || !data.restyle.hint.has_animation_hint(), "animation restyle hint should be handled during \ animation-only restyles"); data.restyle.hint.propagate(&context.shared.traversal_flags) }; // FIXME(bholley): Need to handle explicitly-inherited reset properties // somewhere. propagated_hint.insert(hint); trace!("propagated_hint={:?} \ is_display_none={:?}, implementing_pseudo={:?}", propagated_hint, data.styles.is_display_none(), element.implemented_pseudo_element()); debug_assert!(element.has_current_styles(data) || context.shared.traversal_flags.for_animation_only(), "Should have computed style or haven't yet valid computed \ style in case of animation-only restyle"); let has_dirty_descendants_for_this_restyle = if context.shared.traversal_flags.for_animation_only() { element.has_animation_only_dirty_descendants() } else { element.has_dirty_descendants() }; // Preprocess children, propagating restyle hints and handling sibling // relationships. let should_traverse_children = traversal.should_traverse_children( &mut context.thread_local, element, &data, DontLog ); if should_traverse_children && (has_dirty_descendants_for_this_restyle || !propagated_hint.is_empty()) { let reconstructed_ancestor = data.restyle.reconstructed_self_or_ancestor(); preprocess_children::( context, element, propagated_hint, reconstructed_ancestor, ) } // If we are in a restyle for reconstruction, drop the existing restyle // data here, since we won't need to perform a post-traversal to pick up // any change hints. if context.shared.traversal_flags.for_reconstruct() { data.clear_restyle_state(); } if context.shared.traversal_flags.for_animation_only() { unsafe { element.unset_animation_only_dirty_descendants(); } } // There are two cases when we want to clear the dity descendants bit here // after styling this element. // // The first case is when this element is the root of a display:none // subtree, even if the style didn't change (since, if the style did change, // we'd have already cleared it above). // // This keeps the tree in a valid state without requiring the DOM to check // display:none on the parent when inserting new children (which can be // moderately expensive). Instead, DOM implementations can unconditionally // set the dirty descendants bit on any styled parent, and let the traversal // sort it out. // // The second case is when we are in a restyle for reconstruction, where we // won't need to perform a post-traversal to pick up any change hints. if data.styles.is_display_none() || context.shared.traversal_flags.for_reconstruct() { unsafe { element.unset_dirty_descendants(); } } } fn compute_style( _traversal: &D, traversal_data: &PerLevelTraversalData, context: &mut StyleContext, element: E, data: &mut ElementData ) -> ChildCascadeRequirement where E: TElement, D: DomTraversal, { use data::RestyleKind::*; use sharing::StyleSharingResult::*; context.thread_local.statistics.elements_styled += 1; let kind = data.restyle_kind(context.shared); debug!("compute_style: {:?} (kind={:?})", element, kind); if data.has_styles() { data.restyle.set_restyled(); } match kind { MatchAndCascade => { debug_assert!(!context.shared.traversal_flags.for_animation_only(), "MatchAndCascade shouldn't be processed during \ animation-only traversal"); // Ensure the bloom filter is up to date. context.thread_local.bloom_filter .insert_parents_recovering(element, traversal_data.current_dom_depth); context.thread_local.bloom_filter.assert_complete(element); // Now that our bloom filter is set up, try the style sharing // cache. If we get a match we can skip the rest of the work. let target = StyleSharingTarget::new(element); let sharing_result = target.share_style_if_possible(context, data); if let StyleWasShared(index, had_damage) = sharing_result { context.thread_local.statistics.styles_shared += 1; context.thread_local.style_sharing_candidate_cache.touch(index); return had_damage; } context.thread_local.statistics.elements_matched += 1; // Perform the matching and cascading. element.match_and_cascade( context, data, StyleSharingBehavior::Allow ) } CascadeWithReplacements(flags) => { // Skipping full matching, load cascade inputs from previous values. *context.cascade_inputs_mut() = ElementCascadeInputs::new_from_element_data(data); let important_rules_changed = element.replace_rules(flags, context); element.cascade_primary_and_pseudos( context, data, important_rules_changed ) } CascadeOnly => { // Skipping full matching, load cascade inputs from previous values. *context.cascade_inputs_mut() = ElementCascadeInputs::new_from_element_data(data); element.cascade_primary_and_pseudos( context, data, /* important_rules_changed = */ false ) } } } fn preprocess_children( context: &mut StyleContext, element: E, propagated_hint: RestyleHint, reconstructed_ancestor: bool, ) where E: TElement, D: DomTraversal, { trace!("preprocess_children: {:?}", element); // Loop over all the traversal children. for child in element.as_node().traversal_children() { // FIXME(bholley): Add TElement::element_children instead of this. let child = match child.as_element() { Some(el) => el, None => continue, }; // If the child is unstyled, we don't need to set up any restyling. if child.borrow_data().map_or(true, |d| !d.has_styles()) { continue; } let mut child_data = unsafe { D::ensure_element_data(&child).borrow_mut() }; trace!(" > {:?} -> {:?} + {:?}, pseudo: {:?}", child, child_data.restyle.hint, propagated_hint, child.implemented_pseudo_element()); // Propagate the parent restyle hint, that may make us restyle the whole // subtree. if reconstructed_ancestor { child_data.restyle.set_reconstructed_ancestor(); } child_data.restyle.hint.insert(propagated_hint); // Handle element snapshots and invalidation of descendants and siblings // as needed. // // NB: This will be a no-op if there's no snapshot. child_data.invalidate_style_if_needed(child, &context.shared); } } /// Clear style data for all the subtree under `el`. pub fn clear_descendant_data( el: E, clear_data: &F ) where E: TElement, F: Fn(E), { for kid in el.as_node().traversal_children() { if let Some(kid) = kid.as_element() { // We maintain an invariant that, if an element has data, all its // ancestors have data as well. // // By consequence, any element without data has no descendants with // data. if kid.get_data().is_some() { clear_data(kid); clear_descendant_data(kid, clear_data); } } } unsafe { el.unset_dirty_descendants(); el.unset_animation_only_dirty_descendants(); } }