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