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.
23 /// NB: Keep this as small as possible, please!
24 #[derive(Clone, Debug)]
25 pub struct PerLevelTraversalData {
26 /// The current dom depth.
28 /// This is kept with cooperation from the traversal code and the bloom
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 {
42 /// Returns the traversal root for the current traversal.
43 pub(crate) fn traversal_root(self) -> Option<E> {
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")]
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"))]
65 fn is_servo_nonincremental_layout() -> bool {
69 /// A DOM Traversal trait, that is used to generically implement styling for
71 pub trait DomTraversal<E: TElement>: Sync {
72 /// Process `node` on the way down, before its children have been processed.
74 /// The callback is invoked for each child node that should be processed by
76 fn process_preorder<F>(
78 data: &PerLevelTraversalData,
79 context: &mut StyleContext<E>,
80 node: E::ConcreteNode,
83 F: FnMut(E::ConcreteNode);
85 /// Process `node` on the way up, after its children have been processed.
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
93 /// If it's false, then process_postorder has no effect at all.
94 fn needs_postorder_traversal() -> bool {
98 /// Handles the postorder step of the traversal, if it exists, by bubbling
99 /// up the parent chain.
101 /// If we are the last child that finished processing, recursively process
102 /// our parent. Else, stop. Also, stop at the root.
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).
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(
113 context: &mut StyleContext<E>,
115 mut node: E::ConcreteNode,
116 children_to_process: isize,
118 // If the postorder step is a no-op, don't bother.
119 if !Self::needs_postorder_traversal() {
123 if children_to_process == 0 {
124 // We are a leaf. Walk up the chain.
126 self.process_postorder(context, node);
127 if node.opaque() == root {
130 let parent = node.traversal_parent().unwrap();
131 let remaining = parent.did_process_child();
133 // The parent has other unprocessed descendants. We only
134 // perform postorder processing after the last descendant
135 // has been processed.
139 node = parent.as_node();
142 // Otherwise record the number of children to process when the time
146 .store_children_to_process(children_to_process);
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(
168 &mut NthIndexCache::default(),
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?",
176 unsafe { actual_root.set_dirty_descendants() }
177 return PreTraverseToken(Some(actual_root));
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);
191 PreTraverseToken(if should_traverse { Some(root) } else { None })
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());
202 /// Returns true if traversal is needed for the given element and subtree.
203 fn element_needs_traversal(
205 traversal_flags: TraversalFlags,
206 data: Option<&ElementData>,
209 "element_needs_traversal({:?}, {:?}, {:?})",
210 el, traversal_flags, data
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() ||
222 .has_animation_hint_or_recascade());
225 // Non-incremental layout visits every node.
226 if is_servo_nonincremental_layout() {
231 let data = match data {
232 Some(d) if d.has_styles() => d,
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() {
242 // If we have a restyle hint or need to recascade, we need to visit the
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
249 if !data.hint.is_empty() {
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() {
260 trace!("{:?} doesn't need traversal", el);
264 /// Returns true if we want to cull this subtree from the travesal.
265 fn should_cull_subtree(
267 context: &mut StyleContext<E>,
269 parent_data: &ElementData,
272 parent.has_current_styles_for_traversal(parent_data, context.shared.traversal_flags)
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);
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>,
295 rule_inclusion: RuleInclusion,
296 pseudo: Option<&PseudoElement>,
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()),
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());
323 ancestors_requiring_style_resolution.push(current);
324 ancestor = current.traversal_parent();
327 if let Some(ancestor) = ancestor {
328 context.thread_local.bloom_filter.rebuild(ancestor);
329 context.thread_local.bloom_filter.push(ancestor);
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);
339 ancestor = ancestor.unwrap().traversal_parent();
340 layout_parent_style = ancestor.map(|a| a.borrow_data().unwrap().styles.primary().clone());
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(
352 PseudoElementResolution::IfApplicable,
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();
363 context.thread_local.bloom_filter.push(*ancestor);
366 context.thread_local.bloom_filter.assert_complete(element);
367 StyleResolverForElement::new(
371 PseudoElementResolution::Force,
373 .resolve_style(style.as_deref(), layout_parent_style.as_deref())
377 /// Calculates the style for a single node.
379 #[allow(unsafe_code)]
380 pub fn recalc_style_at<E, D, F>(
382 traversal_data: &PerLevelTraversalData,
383 context: &mut StyleContext<E>,
385 data: &mut ElementData,
390 F: FnMut(E::ConcreteNode),
394 let flags = context.shared.traversal_flags;
395 let is_initial_style = !data.has_styles();
397 context.thread_local.statistics.elements_traversed += 1;
399 flags.intersects(TraversalFlags::AnimationOnly) ||
400 !element.has_snapshot() ||
401 element.handled_snapshot(),
402 "Should've handled snapshots here already"
405 let compute_self = !element.has_current_styles_for_traversal(data, flags);
408 "recalc_style_at: {:?} (compute_self={:?}, \
409 dirty_descendants={:?}, data={:?})",
412 element.has_dirty_descendants(),
416 let mut child_cascade_requirement = ChildCascadeRequirement::CanSkipCascade;
418 // Compute style for this element if necessary.
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,
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() {
436 "{:?} style is display:none - clearing data from descendants.",
440 clear_descendant_data(element);
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);
449 debug_assert!(data.has_styles());
450 data.set_traversed_without_styling();
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).
458 flags.for_animation_only() || !data.hint.has_animation_hint(),
459 "animation restyle hint should be handled during \
460 animation-only restyles"
462 let propagated_hint = data.hint.propagate(&flags);
465 "propagated_hint={:?}, cascade_requirement={:?}, \
466 is_display_none={:?}, implementing_pseudo={:?}",
468 child_cascade_requirement,
469 data.styles.is_display_none(),
470 element.implemented_pseudo_element()
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"
478 let has_dirty_descendants_for_this_restyle = if flags.for_animation_only() {
479 element.has_animation_only_dirty_descendants()
481 element.has_dirty_descendants()
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
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.
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();
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>(
511 child_cascade_requirement,
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());
523 clear_state_after_traversing(element, data, flags);
526 fn clear_state_after_traversing<E>(element: E, data: &mut ElementData, flags: TraversalFlags)
530 if flags.intersects(TraversalFlags::FinalAnimationTraversal) {
531 debug_assert!(flags.for_animation_only());
532 data.clear_restyle_flags_and_damage();
534 element.unset_animation_only_dirty_descendants();
540 traversal_data: &PerLevelTraversalData,
541 context: &mut StyleContext<E>,
543 data: &mut ElementData,
544 ) -> ChildCascadeRequirement
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() {
559 let mut important_rules_changed = false;
560 let new_styles = match kind {
563 !context.shared.traversal_flags.for_animation_only(),
564 "MatchAndCascade shouldn't be processed during \
565 animation-only traversal"
567 // Ensure the bloom filter is up to date.
571 .insert_parents_recovering(element, traversal_data.current_dom_depth);
573 context.thread_local.bloom_filter.assert_complete(element);
575 context.thread_local.bloom_filter.matching_depth(),
576 traversal_data.current_dom_depth
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
586 match target.share_style_if_possible(context) {
587 Some(shared_styles) => {
588 context.thread_local.statistics.styles_shared += 1;
592 context.thread_local.statistics.elements_matched += 1;
593 // Perform the matching and cascading.
595 let mut resolver = StyleResolverForElement::new(
599 PseudoElementResolution::IfApplicable,
602 resolver.resolve_style_with_default_parents()
605 context.thread_local.sharing_cache.insert_if_possible(
609 traversal_data.current_dom_depth,
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(
626 PseudoElementResolution::IfApplicable,
629 resolver.cascade_styles_with_default_parents(cascade_inputs)
632 // Skipping full matching, load cascade inputs from previous values.
633 let cascade_inputs = ElementCascadeInputs::new_from_element_data(data);
636 let mut resolver = StyleResolverForElement::new(
640 PseudoElementResolution::IfApplicable,
643 resolver.cascade_styles_with_default_parents(cascade_inputs)
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.
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
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
661 if !new_styles.primary.reused_via_rule_node {
662 context.thread_local.sharing_cache.insert_if_possible(
666 traversal_data.current_dom_depth,
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)
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),
696 let painter = match context.shared.registered_speculative_painters.get(name) {
697 Some(painter) => painter,
700 let properties = painter
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)))
706 let arguments = arguments
708 .map(|argument| argument.to_css_string())
710 debug!("Notifying paint worklet {}.", painter.name());
711 painter.speculatively_draw_a_paint_image(properties, arguments);
716 #[cfg(not(feature = "servo-layout-2013"))]
717 fn notify_paint_worklet<E>(_context: &StyleContext<E>, _data: &ElementData)
721 // The CSS paint API is Servo-only at the moment
724 fn note_children<E, D, F>(
725 context: &mut StyleContext<E>,
728 propagated_hint: RestyleHint,
729 cascade_requirement: ChildCascadeRequirement,
730 is_initial_style: bool,
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() {
745 if is_servo_nonincremental_layout() ||
746 D::text_node_needs_traversal(child_node, data)
748 note_child(child_node);
754 let mut child_data = child.mutate_data();
755 let mut child_data = child_data.as_mut().map(|d| &mut **d);
757 " > {:?} -> {:?} + {:?}, pseudo: {:?}",
759 child_data.as_ref().map(|d| d.hint),
761 child.implemented_pseudo_element()
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;
771 ChildCascadeRequirement::MustCascadeChildrenIfInheritResetStyle => {
772 use crate::computed_value_flags::ComputedValueFlags;
777 .contains(ComputedValueFlags::INHERITS_RESET_STYLE)
779 child_hint |= RestyleHint::RECASCADE_SELF;
782 ChildCascadeRequirement::MustCascadeChildren => {
783 child_hint |= RestyleHint::RECASCADE_SELF;
787 child_data.hint.insert(child_hint);
789 // Handle element snapshots and invalidation of descendants and siblings
792 // NB: This will be a no-op if there's no snapshot.
793 child_data.invalidate_style_if_needed(
796 Some(&context.thread_local.stack_limit_checker),
797 &mut context.thread_local.nth_index_cache,
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.
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() {
812 element.set_animation_only_dirty_descendants();
816 element.set_dirty_descendants();
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)
832 let mut parents = SmallVec::<[E; 32]>::new();
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.
840 // By consequence, any element without data has no descendants with
850 // Make sure not to clear NODE_NEEDS_FRAME on the root.
851 root.clear_descendant_bits();