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