Skip to main content

style/
traversal.rs

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