/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Traversing the DOM tree; the bloom filter.
usecrate::context::{ElementCascadeInputs, SharedStyleContext, StyleContext}; usecrate::data::{ElementData, ElementStyles, RestyleKind}; usecrate::dom::{NodeInfo, OpaqueNode, TElement, TNode}; usecrate::invalidation::element::restyle_hints::RestyleHint; usecrate::matching::{ChildRestyleRequirement, MatchMethods}; usecrate::selector_parser::PseudoElement; usecrate::sharing::StyleSharingTarget; usecrate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; usecrate::stylist::RuleInclusion; usecrate::traversal_flags::TraversalFlags; use selectors::matching::SelectorCaches; use smallvec::SmallVec; use std::collections::HashMap;
/// A cache from element reference to known-valid computed style. pubtype UndisplayedStyleCache =
HashMap<selectors::OpaqueElement, servo_arc::Arc<crate::properties::ComputedValues>>;
/// A per-traversal-level chunk of data. This is sent down by the traversal, and /// currently only holds the dom depth for the bloom filter. /// /// NB: Keep this as small as possible, please! #[derive(Clone, Copy, Debug)] pubstruct PerLevelTraversalData { /// The current dom depth. /// /// This is kept with cooperation from the traversal code and the bloom /// filter. pub current_dom_depth: usize,
}
/// We use this structure, rather than just returning a boolean from pre_traverse, /// to enfore that callers process root invalidations before starting the traversal. pubstruct PreTraverseToken<E: TElement>(Option<E>); impl<E: TElement> PreTraverseToken<E> { /// Whether we should traverse children. pubfn should_traverse(&self) -> bool { self.0.is_some()
}
/// Returns the traversal root for the current traversal. pub(crate) fn traversal_root(self) -> Option<E> { self.0
}
}
/// A global variable holding the state of /// `is_servo_nonincremental_layout()`. /// See [#22854](https://github.com/servo/servo/issues/22854). #[cfg(feature = "servo")] pubstatic IS_SERVO_NONINCREMENTAL_LAYOUT: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
/// A DOM Traversal trait, that is used to generically implement styling for /// Gecko and Servo. pubtrait DomTraversal<E: TElement>: Sync { /// Process `node` on the way down, before its children have been processed. /// /// The callback is invoked for each child node that should be processed by /// the traversal. fn process_preorder<F>(
&self,
data: &PerLevelTraversalData,
context: &mut StyleContext<E>,
node: E::ConcreteNode,
note_child: F,
) where
F: FnMut(E::ConcreteNode);
/// Process `node` on the way up, after its children have been processed. /// /// This is only executed if `needs_postorder_traversal` returns true. fn process_postorder(&self, contect: &mut StyleContext<E>, node: E::ConcreteNode);
/// Boolean that specifies whether a bottom up traversal should be /// performed. /// /// If it's false, then process_postorder has no effect at all. fn needs_postorder_traversal() -> bool { true
}
/// Handles the postorder step of the traversal, if it exists, by bubbling /// up the parent chain. /// /// If we are the last child that finished processing, recursively process /// our parent. Else, stop. Also, stop at the root. /// /// Thus, if we start with all the leaves of a tree, we end up traversing /// the whole tree bottom-up because each parent will be processed exactly /// once (by the last child that finishes processing). /// /// The only communication between siblings is that they both /// fetch-and-subtract the parent's children count. This makes it safe to /// call durign the parallel traversal. fn handle_postorder_traversal(
&self,
context: &mut StyleContext<E>,
root: OpaqueNode, mut node: E::ConcreteNode,
children_to_process: isize,
) { // If the postorder step is a no-op, don't bother. if !Self::needs_postorder_traversal() { return;
}
if children_to_process == 0 { // We are a leaf. Walk up the chain. loop { self.process_postorder(context, node); if node.opaque() == root { break;
} let parent = node.traversal_parent().unwrap(); let remaining = parent.did_process_child(); if remaining != 0 { // The parent has other unprocessed descendants. We only // perform postorder processing after the last descendant // has been processed. break;
}
node = parent.as_node();
}
} else { // Otherwise record the number of children to process when the time // comes.
node.as_element()
.unwrap()
.store_children_to_process(children_to_process);
}
}
/// Style invalidations happen when traversing from a parent to its children. /// However, this mechanism can't handle style invalidations on the root. As /// such, we have a pre-traversal step to handle that part and determine whether /// a full traversal is needed. fn pre_traverse(root: E, shared_context: &SharedStyleContext) -> PreTraverseToken<E> { usecrate::invalidation::element::state_and_attributes::propagate_dirty_bit_up_to;
let traversal_flags = shared_context.traversal_flags;
letmut data = root.mutate_data(); letmut data = data.as_mut().map(|d| &mut **d);
iflet Some(refmut data) = data { if !traversal_flags.for_animation_only() { // Invalidate our style, and that of our siblings and // descendants as needed. let invalidation_result = data.invalidate_style_if_needed(
root,
shared_context,
None,
&mut SelectorCaches::default(),
);
if invalidation_result.has_invalidated_siblings() { let actual_root = root.as_node().parent_element_or_host().expect( "How in the world can you invalidate \
siblings without a parent?",
);
propagate_dirty_bit_up_to(actual_root, root); return PreTraverseToken(Some(actual_root));
}
}
}
let should_traverse = Self::element_needs_traversal(root, traversal_flags, data.as_mut().map(|d| &**d));
// If we're not going to traverse at all, we may need to clear some state // off the root (which would normally be done at the end of recalc_style_at). if !should_traverse && data.is_some() {
clear_state_after_traversing(root, data.unwrap(), traversal_flags);
}
/// Returns true if traversal should visit a text node. The style system /// never processes text nodes, but Servo overrides this to visit them for /// flow construction when necessary. fn text_node_needs_traversal(node: E::ConcreteNode, _parent_data: &ElementData) -> bool {
debug_assert!(node.is_text_node()); false
}
/// Returns true if traversal is needed for the given element and subtree. fn element_needs_traversal(
el: E,
traversal_flags: TraversalFlags,
data: Option<&ElementData>,
) -> bool {
debug!( "element_needs_traversal({:?}, {:?}, {:?})",
el, traversal_flags, data
);
// In case of animation-only traversal we need to traverse the element if the element has // animation only dirty descendants bit, animation-only restyle hint. if traversal_flags.for_animation_only() { return data.map_or(false, |d| d.has_styles()) &&
(el.has_animation_only_dirty_descendants() ||
data.as_ref()
.unwrap()
.hint
.has_animation_hint_or_recascade());
}
// Non-incremental layout visits every node. if is_servo_nonincremental_layout() { returntrue;
}
// Unwrap the data. let data = match data {
Some(d) if d.has_styles() => d,
_ => returntrue,
};
// If the dirty descendants bit is set, we need to traverse no matter // what. Skip examining the ElementData. if el.has_dirty_descendants() { returntrue;
}
// If we have a restyle hint or need to recascade, we need to visit the // element. // // Note that this is different than checking has_current_styles_for_traversal(), // since that can return true even if we have a restyle hint indicating // that the element's descendants (but not necessarily the element) need // restyling. if !data.hint.is_empty() { returntrue;
}
// Servo uses the post-order traversal for flow construction, so we need // to traverse any element with damage so that we can perform fixup / // reconstruction on our way back up the tree. if cfg!(feature = "servo") && !data.damage.is_empty() { returntrue;
}
trace!("{:?} doesn't need traversal", el); false
}
/// Return the shared style context common to all worker threads. fn shared_context(&self) -> &SharedStyleContext;
}
/// Manually resolve style by sequentially walking up the parent chain to the /// first styled Element, ignoring pending restyles. The resolved style is made /// available via a callback, and can be dropped by the time this function /// returns in the display:none subtree case. pubfn resolve_style<E>(
context: &mut StyleContext<E>,
element: E,
rule_inclusion: RuleInclusion,
pseudo: Option<&PseudoElement>, mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>,
) -> ElementStyles where
E: TElement,
{
debug_assert!(
rule_inclusion == RuleInclusion::DefaultOnly ||
pseudo.map_or(false, |p| p.is_before_or_after()) ||
element.borrow_data().map_or(true, |d| !d.has_styles()), "Why are we here?"
);
debug_assert!(
rule_inclusion == RuleInclusion::All || undisplayed_style_cache.is_none(), "can't use the cache for default styles only"
);
// Compute style for this element if necessary. iflet Some(restyle_kind) = restyle_kind {
child_restyle_requirement =
compute_style(traversal_data, context, element, data, restyle_kind);
if !element.matches_user_and_content_rules() { // We must always cascade native anonymous subtrees, since they // may have pseudo-elements underneath that would inherit from the // closest non-NAC ancestor instead of us.
child_restyle_requirement = cmp::max(
child_restyle_requirement,
ChildRestyleRequirement::MustCascadeChildren,
);
}
// If we're restyling this element to display:none, throw away all style // data in the subtree, notify the caller to early-return. if data.styles.is_display_none() {
debug!( "{:?} style is display:none - clearing data from descendants.",
element
); unsafe {
clear_descendant_data(element);
}
}
// Inform any paint worklets of changed style, to speculatively // evaluate the worklet code. In the case that the size hasn't changed, // this will result in increased concurrency between script and layout.
notify_paint_worklet(context, data);
} else {
debug_assert!(data.has_styles());
data.set_traversed_without_styling();
}
// Now that matching and cascading is done, clear the bits corresponding to // those operations and compute the propagated restyle hint (unless we're // not processing invalidations, in which case don't need to propagate it // and must avoid clearing it).
debug_assert!(
flags.for_animation_only() || !data.hint.has_animation_hint(), "animation restyle hint should be handled during \
animation-only restyles"
); letmut propagated_hint = data.hint.propagate(&flags);
trace!( "propagated_hint={:?}, restyle_requirement={:?}, \
is_display_none={:?}, implementing_pseudo={:?}",
propagated_hint,
child_restyle_requirement,
data.styles.is_display_none(),
element.implemented_pseudo_element()
);
let has_dirty_descendants_for_this_restyle = if flags.for_animation_only() {
element.has_animation_only_dirty_descendants()
} else {
element.has_dirty_descendants()
};
// Before examining each child individually, try to prove that our children // don't need style processing. They need processing if any of the following // conditions hold: // // * We have the dirty descendants bit. // * We're propagating a restyle hint. // * This is a servo non-incremental traversal. // // We only do this if we're not a display: none root, since in that case // it's useless to style children. letmut traverse_children = has_dirty_descendants_for_this_restyle ||
!propagated_hint.is_empty() ||
is_servo_nonincremental_layout();
// Examine our children, and enqueue the appropriate ones for traversal. if traverse_children {
note_children::<E, D, F>(
context,
element,
data,
propagated_hint,
is_initial_style,
note_child,
);
}
// FIXME(bholley): Make these assertions pass for servo. if cfg!(feature = "gecko") && cfg!(debug_assertions) && data.styles.is_display_none() {
debug_assert!(!element.has_dirty_descendants());
debug_assert!(!element.has_animation_only_dirty_descendants());
}
letmut important_rules_changed = false; let new_styles = match kind {
MatchAndCascade => {
debug_assert!(
!context.shared.traversal_flags.for_animation_only(), "MatchAndCascade shouldn't be processed during \
animation-only traversal"
); // Ensure the bloom filter is up to date.
context
.thread_local
.bloom_filter
.insert_parents_recovering(element, traversal_data.current_dom_depth);
// Insert into the cache, but only if this style isn't reused from a // sibling or cousin. Otherwise, recascading a bunch of identical // elements would unnecessarily flood the cache with identical entries. // // This is analogous to the obvious "don't insert an element that just // got a hit in the style sharing cache" behavior in the MatchAndCascade // handling above. // // Note that, for the MatchAndCascade path, we still insert elements that // shared styles via the rule node, because we know that there's something // different about them that caused them to miss the sharing cache before // selector matching. If we didn't, we would still end up with the same // number of eventual styles, but would potentially miss out on various // opportunities for skipping selector matching, which could hurt // performance. if !new_styles.primary.reused_via_rule_node {
context.thread_local.sharing_cache.insert_if_possible(
&element,
&new_styles.primary,
None,
traversal_data.current_dom_depth,
&context.shared,
);
}
#[cfg(feature = "servo")] fn notify_paint_worklet<E>(context: &StyleContext<E>, data: &ElementData) where
E: TElement,
{ usecrate::values::generics::image::Image; use style_traits::ToCss;
// We speculatively evaluate any paint worklets during styling. // This allows us to run paint worklets in parallel with style and layout. // Note that this is wasted effort if the size of the node has // changed, but in may cases it won't have. iflet Some(ref values) = data.styles.primary { for image in &values.get_background().background_image.0 { let (name, arguments) = match *image {
Image::PaintWorklet(ref worklet) => (&worklet.name, &worklet.arguments),
_ => continue,
}; let painter = match context.shared.registered_speculative_painters.get(name) {
Some(painter) => painter,
None => continue,
}; let properties = painter
.properties()
.iter()
.filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id)))
.map(|(name, id)| (name.clone(), values.computed_value_to_string(id)))
.collect(); let arguments = arguments
.iter()
.map(|argument| argument.to_css_string())
.collect();
debug!("Notifying paint worklet {}.", painter.name());
painter.speculatively_draw_a_paint_image(properties, arguments);
}
}
}
#[cfg(not(feature = "servo"))] fn notify_paint_worklet<E>(_context: &StyleContext<E>, _data: &ElementData) where
E: TElement,
{ // The CSS paint API is Servo-only at the moment
}
// Loop over all the traversal children. for child_node in element.traversal_children() { let child = match child_node.as_element() {
Some(el) => el,
None => { if is_servo_nonincremental_layout() ||
D::text_node_needs_traversal(child_node, data)
{
note_child(child_node);
} continue;
},
};
// Handle element snapshots and invalidation of descendants and siblings // as needed. // // NB: This will be a no-op if there's no snapshot.
child_data.invalidate_style_if_needed(
child,
&context.shared,
Some(&context.thread_local.stack_limit_checker),
&mut context.thread_local.selector_caches,
);
}
if D::element_needs_traversal(child, flags, child_data.map(|d| &*d)) {
note_child(child_node);
// Set the dirty descendants bit on the parent as needed, so that we // can find elements during the post-traversal. // // Note that these bits may be cleared again at the bottom of // recalc_style_at if requested by the caller. if !is_initial_style { if flags.for_animation_only() { unsafe {
element.set_animation_only_dirty_descendants();
}
} else { unsafe {
element.set_dirty_descendants();
}
}
}
}
}
}
/// Clear style data for all the subtree under `root` (but not for root itself). /// /// We use a list to avoid unbounded recursion, which we need to avoid in the /// parallel traversal because the rayon stacks are small. pubunsafefn clear_descendant_data<E>(root: E) where
E: TElement,
{ letmut parents = SmallVec::<[E; 32]>::new();
parents.push(root); whilelet Some(p) = parents.pop() { for kid in p.traversal_children() { iflet Some(kid) = kid.as_element() { // We maintain an invariant that, if an element has data, all its // ancestors have data as well. // // By consequence, any element without data has no descendants with // data. if kid.has_data() {
kid.clear_data();
parents.push(kid);
}
}
}
}
// Make sure not to clear NODE_NEEDS_FRAME on the root.
root.clear_descendant_bits();
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.29 Sekunden
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.