/* 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::{SharedStyleContext, StyleContext, ThreadLocalStyleContext}; use data::{ElementData, ElementStyles, StoredRestyleHint}; use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode}; use matching::{ChildCascadeRequirement, MatchMethods}; use restyle_hints::{CascadeHint, HintComputationContext, RECASCADE_SELF}; use restyle_hints::{RECASCADE_DESCENDANTS, RestyleHint}; use selector_parser::RestyleDamage; 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_flag 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, }; } // Expand the snapshot, if any. This is normally handled by the parent, so // we need a special case for the root. // // Expanding snapshots here may create a LATER_SIBLINGS restyle hint, which // we propagate to the next sibling element. if let Some(mut data) = root.mutate_data() { let later_siblings = data.compute_final_hint(root, shared_context, HintComputationContext::Root); if later_siblings { if let Some(next) = root.next_sibling_element() { if let Some(mut next_data) = next.mutate_data() { let hint = StoredRestyleHint::subtree_and_later_siblings(); next_data.ensure_restyle().hint.insert(hint); } } } } 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.get_restyle().map_or(false, |r| { (r.damage | r.damage_handled()) .contains(RestyleDamage::reconstruct()) }); 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() { if el.has_animation_only_dirty_descendants() { return true; } let data = match el.borrow_data() { Some(d) => d, None => return false, }; return data.get_restyle() .map_or(false, |r| r.hint.has_animation_hint() || r.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; } // Check the restyle data. if let Some(r) = data.get_restyle() { // 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 !r.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.get_restyle().map_or(false, |r| !r.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.values().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()) { 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.get_styles().is_none() { // 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"); debug_assert!(data.get_restyle().map_or(true, |r| { !r.has_sibling_invalidations() }), "Should've computed the final hint and handled later_siblings already"); let compute_self = !element.has_current_styles(data); let mut cascade_hint = CascadeHint::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 => { cascade_hint |= RECASCADE_SELF; } ChildCascadeRequirement::MustCascadeDescendants => { cascade_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() { cascade_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 = match data.get_restyle_mut() { None => StoredRestyleHint::empty(), Some(r) => { debug_assert!(context.shared.traversal_flags.for_animation_only() || !r.hint.has_animation_hint(), "animation restyle hint should be handled during \ animation-only restyles"); r.hint.propagate(&context.shared.traversal_flags) }, }; // FIXME(bholley): Need to handle explicitly-inherited reset properties // somewhere. propagated_hint.insert_cascade_hint(cascade_hint); trace!("propagated_hint={:?}, cascade_hint={:?}, \ is_display_none={:?}, implementing_pseudo={:?}", propagated_hint, cascade_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. if traversal.should_traverse_children(&mut context.thread_local, element, &data, DontLog) && (has_dirty_descendants_for_this_restyle || !propagated_hint.is_empty()) { let damage_handled = data.get_restyle().map_or(RestyleDamage::empty(), |r| { r.damage_handled() | r.damage.handled_for_descendants() }); preprocess_children::(context, traversal_data, element, propagated_hint, damage_handled); } // 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(); } 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(); match kind { MatchAndCascade => { // 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) => { let important_rules_changed = element.replace_rules(flags, context, data); element.cascade_primary_and_pseudos( context, data, important_rules_changed ) } CascadeOnly => { element.cascade_primary_and_pseudos( context, data, /* important_rules_changed = */ false ) } } } fn preprocess_children(context: &mut StyleContext, parent_traversal_data: &PerLevelTraversalData, element: E, mut propagated_hint: StoredRestyleHint, damage_handled: RestyleDamage) 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, }; let mut child_data = unsafe { D::ensure_element_data(&child).borrow_mut() }; // If the child is unstyled, we don't need to set up any restyling. if !child_data.has_styles() { continue; } // Handle element snapshots and sibling restyle hints. // // NB: This will be a no-op if there's no restyle data and no snapshot. let later_siblings = child_data.compute_final_hint(child, &context.shared, HintComputationContext::Child { local_context: &mut context.thread_local, dom_depth: parent_traversal_data.current_dom_depth + 1, }); trace!(" > {:?} -> {:?} + {:?}, pseudo: {:?}, later_siblings: {:?}", child, child_data.get_restyle().map(|r| &r.hint), propagated_hint, child.implemented_pseudo_element(), later_siblings); // If the child doesn't have pre-existing RestyleData and we don't have // any reason to create one, avoid the useless allocation and move on to // the next child. if propagated_hint.is_empty() && damage_handled.is_empty() && !child_data.has_restyle() { continue; } let mut restyle_data = child_data.ensure_restyle(); // Propagate the parent and sibling restyle hint. restyle_data.hint.insert_from(&propagated_hint); if later_siblings { propagated_hint.insert(RestyleHint::subtree().into()); } // Store the damage already handled by ancestors. restyle_data.set_damage_handled(damage_handled); } } /// Clear style data for all the subtree under `el`. pub fn clear_descendant_data(el: E, clear_data: &F) { 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(); } }