Skip to main content

layout/
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
5use std::sync::Arc;
6
7use layout_api::{
8    DangerousStyleElement, DangerousStyleNode, LayoutDamage, LayoutElement, LayoutNode,
9};
10use script::layout_dom::ServoLayoutNode;
11use style::context::{SharedStyleContext, StyleContext};
12use style::dom::{NodeInfo, TElement, TNode};
13use style::selector_parser::RestyleDamage;
14use style::traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at};
15
16use crate::BoxTree;
17use crate::context::LayoutContext;
18use crate::dom::{DOMLayoutData, NodeExt};
19use crate::layout_root::LayoutRoot;
20
21pub struct RecalcStyle<'a> {
22    context: &'a LayoutContext<'a>,
23}
24
25impl<'a> RecalcStyle<'a> {
26    pub(crate) fn new(context: &'a LayoutContext<'a>) -> Self {
27        RecalcStyle { context }
28    }
29
30    pub(crate) fn context(&self) -> &LayoutContext<'a> {
31        self.context
32    }
33}
34
35impl<'dom, E> DomTraversal<E> for RecalcStyle<'_>
36where
37    E: DangerousStyleElement<'dom> + TElement,
38    E::ConcreteNode: 'dom + DangerousStyleNode<'dom>,
39{
40    fn process_preorder<F>(
41        &self,
42        traversal_data: &PerLevelTraversalData,
43        context: &mut StyleContext<E>,
44        node: E::ConcreteNode,
45        note_child: F,
46    ) where
47        F: FnMut(E::ConcreteNode),
48    {
49        let Some(dangerous_style_element) = node.as_element() else {
50            return;
51        };
52
53        let layout_element = dangerous_style_element.layout_element();
54        let had_style_data = layout_element.style_data().is_some();
55        layout_element.initialize_style_and_layout_data::<DOMLayoutData>();
56
57        let mut element_data = dangerous_style_element.mutate_data().unwrap();
58        if !had_style_data {
59            element_data.damage = RestyleDamage::reconstruct();
60        }
61
62        recalc_style_at(
63            self,
64            traversal_data,
65            context,
66            dangerous_style_element,
67            &mut element_data,
68            note_child,
69        );
70    }
71
72    #[inline]
73    fn needs_postorder_traversal() -> bool {
74        false
75    }
76
77    fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
78        panic!("this should never be called")
79    }
80
81    fn shared_context(&self) -> &SharedStyleContext<'_> {
82        &self.context.style_context
83    }
84}
85
86#[servo_tracing::instrument(skip_all)]
87pub(crate) fn compute_damage_and_rebuild_box_tree<'dom>(
88    box_tree: &mut Option<Arc<BoxTree>>,
89    layout_context: &LayoutContext,
90    dirty_root: ServoLayoutNode<'dom>,
91    root_node: ServoLayoutNode<'dom>,
92    damage_from_environment: LayoutDamage,
93    layout_roots: &mut Vec<LayoutRoot<'dom>>,
94) -> LayoutDamage {
95    // First process damage below the dirty root, returning the damage that
96    // should be propagated upward into the clean part of the tree.
97    let layout_damage = compute_damage_and_rebuild_box_tree_below_dirty_root(
98        layout_context,
99        dirty_root,
100        damage_from_environment,
101        layout_roots,
102    );
103
104    // If there was no box tree at all at this point, a full box tree / fragment
105    // tree layout is necessary and there is no point processing any other damage.
106    if box_tree.is_none() {
107        *box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node)));
108        return layout_damage;
109    }
110
111    // Propagate the damage from the dirty part of the tree upward. In this part of
112    // the traversal no elements can add damage, but they might isolate damage being
113    // propagated upward between the dirty root and the root of the DOM.
114    let layout_damage = compute_damage_and_rebuild_box_tree_above_dirty_root(
115        layout_context,
116        dirty_root,
117        layout_damage,
118        layout_roots,
119    );
120
121    // We could not find a place in the middle of the tree to run box tree reconstruction,
122    // so just rebuild the whole tree.
123    if layout_damage.contains(LayoutDamage::DescendantHasBoxDamage) {
124        *box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node)));
125    }
126
127    layout_damage
128}
129
130#[expect(unsafe_code)]
131#[servo_tracing::instrument(skip_all)]
132pub(crate) fn compute_damage_and_rebuild_box_tree_above_dirty_root<'dom>(
133    layout_context: &LayoutContext,
134    dirty_root: ServoLayoutNode<'dom>,
135    layout_damage: LayoutDamage,
136    layout_roots: &mut Vec<LayoutRoot<'dom>>,
137) -> LayoutDamage {
138    // Cases where propagating damage up the tree is necessary:
139    //
140    // 1. Box tree layout of the dirty root is necessary, in which case we
141    //    search for a place to re-run box tree layout and also invalidate
142    //    all fragments and fragment caches to the root.
143    // 2. Fragment tree layout needs to run again, in which case fragments
144    //    and fragment caches need to be invalidated.
145    // 3. Overflow is dirty, in which case overflow needs to be cleared.
146    //
147    // In every other case, just return early.
148    let needs_fragment_tree_rebuild = layout_damage.contains(LayoutDamage::Relayout);
149    let needs_overflow_recalculation = layout_damage.contains(LayoutDamage::RecalculateOverflow);
150    if !needs_fragment_tree_rebuild && !needs_overflow_recalculation {
151        assert!(!layout_damage.contains(LayoutDamage::DescendantCollectedAsLayoutRoot));
152        return layout_damage;
153    }
154
155    let mut damage_for_parent = layout_damage;
156    let mut maybe_parent_node = unsafe { dirty_root.dangerous_flat_tree_parent() };
157    while let Some(parent_node) = maybe_parent_node {
158        let damage_set = ElementDamageSet {
159            node: parent_node,
160            from_parent: LayoutDamage::empty(),
161            on_element: LayoutDamage::empty(),
162            from_children: damage_for_parent,
163            // Ancestors above the dirty root do not have damage, so will never subsume
164            // any existing layout roots, but they may isolate upward flowing fragment
165            // tree damage.
166            incoming_layout_root_count: layout_roots.len(),
167        };
168
169        damage_for_parent = damage_set.apply_damage(layout_context, layout_roots);
170        maybe_parent_node = unsafe { parent_node.dangerous_flat_tree_parent() };
171    }
172
173    damage_for_parent
174}
175
176pub(crate) fn compute_damage_and_rebuild_box_tree_below_dirty_root<'dom>(
177    layout_context: &LayoutContext,
178    node: ServoLayoutNode<'dom>,
179    damage_from_parent: LayoutDamage,
180    layout_roots: &mut Vec<LayoutRoot<'dom>>,
181) -> LayoutDamage {
182    // Don't do any kind of damage propagation or box tree construction for non-Element
183    // nodes, such as text and comments.
184    let Some(element) = node.as_element() else {
185        return damage_from_parent;
186    };
187
188    let (element_damage, is_display_none) = {
189        let mut element_data = element.element_data_mut();
190        (
191            LayoutDamage::from(std::mem::take(&mut element_data.damage)),
192            element_data.styles.is_display_none(),
193        )
194    };
195
196    let has_dirty_descendants;
197    #[expect(unsafe_code)]
198    unsafe {
199        let dangerous_style_element = element.dangerous_style_element();
200        has_dirty_descendants = dangerous_style_element.has_dirty_descendants();
201        dangerous_style_element.unset_dirty_descendants();
202    };
203
204    if is_display_none {
205        node.unset_all_boxes();
206        return element_damage | damage_from_parent;
207    }
208
209    let mut damage_set = ElementDamageSet {
210        node,
211        from_parent: damage_from_parent,
212        on_element: element_damage,
213        from_children: LayoutDamage::empty(),
214        incoming_layout_root_count: layout_roots.len(),
215    };
216
217    // Depending on the incoming damage, it can be isolated, meaning that some damage
218    // doesn't get passed down to children.
219    let damage_for_children = damage_set.isolate_incoming_damage();
220
221    // Propagate damage to children and gather the resulting damage into `from_children`.
222    damage_set.propagate_damage_to_children(
223        layout_context,
224        has_dirty_descendants,
225        damage_for_children,
226        layout_roots,
227    );
228
229    // Apply the calculated damage to this element (perhaps triggering box tree layout),
230    // and propagate resulting damage to ancestors.
231    damage_set.apply_damage(layout_context, layout_roots)
232}
233
234enum BoxDamageAction<'a> {
235    RebuildAncestor,
236    TryRebuild,
237    InvalidateFragmentTreeBelowLayoutRoot,
238    CollectLayoutRoot(LayoutRoot<'a>),
239    InvalidateFragmentTreeAboveLayoutRoot,
240    InvalidateScrollableOverflow,
241    None,
242}
243
244impl BoxDamageAction<'_> {
245    fn rebuilds_box(&self) -> bool {
246        matches!(self, Self::RebuildAncestor | Self::TryRebuild)
247    }
248}
249
250pub(crate) struct ElementDamageSet<'a> {
251    node: ServoLayoutNode<'a>,
252    pub from_parent: LayoutDamage,
253    pub on_element: LayoutDamage,
254    pub from_children: LayoutDamage,
255    pub incoming_layout_root_count: usize,
256}
257
258impl<'a> ElementDamageSet<'a> {
259    /// Given the damage on the element and damage from parents, determine which damage
260    /// should be passed to children, returning that value.
261    fn isolate_incoming_damage(&mut self) -> LayoutDamage {
262        // Children only receive layout mode damage from their parents, except when an ancestor
263        // needs to be completely rebuilt. In that case, descendants are rebuilt down to the
264        // first independent formatting context, which should isolate that tree from further
265        // box damage.
266        let mut damage_for_children = (self.on_element | self.from_parent).only_layout_modes();
267        let rebuild_children = self.on_element.contains(LayoutDamage::BoxDamage) ||
268            (self.from_parent.contains(LayoutDamage::BoxDamage) &&
269                !self.node.isolates_damage_for_damage_propagation());
270
271        if rebuild_children {
272            damage_for_children.insert(LayoutDamage::BoxDamage);
273        } else if self.from_parent.contains(LayoutDamage::Relayout) &&
274            !self.on_element.contains(LayoutDamage::Relayout) &&
275            self.node.isolates_damage_for_damage_propagation()
276        {
277            // If not rebuilding the boxes for this node, but fragments need to be laid out
278            // only because of an ancestor, fragment layout caches should still be valid when
279            // crossing down into new independent formatting contexts.
280            damage_for_children.remove(LayoutDamage::Relayout);
281            self.from_parent.remove(LayoutDamage::Relayout);
282        }
283
284        damage_for_children
285    }
286
287    /// Given the damage the damage to children and whether or not this element had any
288    /// dirty descendants, conditionally propagated damage to children and set the resulting
289    /// damage from children on this [`ElementDamageSet`].
290    fn propagate_damage_to_children(
291        &mut self,
292        layout_context: &LayoutContext<'_>,
293        has_dirty_descendants: bool,
294        damage_for_children: LayoutDamage,
295        layout_roots: &mut Vec<LayoutRoot<'a>>,
296    ) {
297        // Propagate damage into children, but only if:
298        //  1. There is a descendant that was dirty / possibly restyled.
299        //  2. We detected that we need to rebuild child boxes.
300        //  3. An ancestor will be laid out and children need to have their fragment caches cleared.
301        //
302        // In other situations, such as when layout will not run at all or when we are
303        // guaranteed that children are undamaged, we can skip traversing children entirely.
304        if has_dirty_descendants ||
305            damage_for_children.intersects(LayoutDamage::BoxDamage | LayoutDamage::Relayout)
306        {
307            for child in self.node.flat_tree_children() {
308                if child.is_element() {
309                    self.from_children |= compute_damage_and_rebuild_box_tree_below_dirty_root(
310                        layout_context,
311                        child,
312                        damage_for_children,
313                        layout_roots,
314                    );
315                }
316            }
317        }
318    }
319
320    /// Given the damage from this element, the parent, and children, determine what action to
321    /// take for this element's boxes and return the damage that should be propagated to parents.
322    fn apply_damage(
323        self,
324        layout_context: &LayoutContext<'_>,
325        layout_roots: &mut Vec<LayoutRoot<'a>>,
326    ) -> LayoutDamage {
327        let only_layout_mode_damage =
328            (self.from_parent | self.on_element | self.from_children).only_layout_modes();
329
330        let invalidate_for_rebuild = || {
331            self.node.unset_all_boxes();
332            LayoutDamage::DescendantHasBoxDamage | LayoutDamage::Relayout
333        };
334
335        // This removes any dirty layout roots from descendants.
336        let discard_any_descendant_layout_roots = |layout_roots: &mut Vec<LayoutRoot>| {
337            layout_roots.truncate(self.incoming_layout_root_count);
338        };
339
340        let action = self.box_damage_action();
341        let will_rebuild_box = action.rebuilds_box();
342        let damage_for_parent = match action {
343            BoxDamageAction::TryRebuild => {
344                discard_any_descendant_layout_roots(layout_roots);
345
346                if self
347                    .node
348                    .rebuild_box_tree_from_independent_formatting_context(layout_context)
349                {
350                    // In this case, we have rebuilt the box tree from this point and we do not
351                    // have to propagate rebuild box tree damage up the tree any further.
352                    LayoutDamage::Relayout | LayoutDamage::RecomputeInlineContentSizes
353                } else {
354                    // A descendant needs to be rebuilt, but couldn't be rebuilt here,
355                    // because this node was an not a rebuild-compatible independent
356                    // formatting context. In this case do the same thing as if we needed
357                    // to rebuild an ancestor.
358                    invalidate_for_rebuild()
359                }
360            },
361            BoxDamageAction::RebuildAncestor => {
362                // In this case an ancestor needs to be completely rebuilt.
363                //
364                // This means that this box is no longer valid and also needs to be rebuilt
365                // (perhaps some of its descendants do not though). In this case, unset all existing
366                // boxes for the node and ensure that the appropriate rebuild-type damage
367                // propagates up the tree.
368                discard_any_descendant_layout_roots(layout_roots);
369                invalidate_for_rebuild()
370            },
371            BoxDamageAction::InvalidateFragmentTreeBelowLayoutRoot => {
372                // In this case, this node's boxes are preserved! It's possible that we still need
373                // to run fragment tree layout in this subtree due to an ancestor, this node, or a
374                // descendant changing style. In that case, we ask the `LayoutBoxBase` to clear
375                // any cached information that cannot be used.
376                discard_any_descendant_layout_roots(layout_roots);
377
378                let mut damage_for_parent =
379                    (self.on_element | self.from_children) | self.from_parent.only_layout_modes();
380
381                // This node also needed new fragment tree layout, so if any descendant
382                // was collected as a layout root, it's now discarded. This means we
383                // should also clear the damage (though harmless as Relayout takes
384                // precedence) indicating that there was a collected layout root.
385                damage_for_parent.remove(LayoutDamage::DescendantCollectedAsLayoutRoot);
386
387                let mut inline_size_depends_on_content = false;
388                self.node.with_layout_box_base_including_pseudos(|base| {
389                    inline_size_depends_on_content |=
390                        base.invalidate_caches_for_fragment_tree_layout(&self);
391                });
392
393                self.adjust_inline_content_size_damage(
394                    &mut damage_for_parent,
395                    inline_size_depends_on_content,
396                );
397
398                damage_for_parent
399            },
400            BoxDamageAction::CollectLayoutRoot(layout_root) => {
401                // A layout root should only be collected if a parent node does not
402                // produce damage requiring a fragment tree layout. This is essential
403                // to ensure the invariant that layout roots are only collected when
404                // they isolate damage from ancestors. If an ancestor has damage, a
405                // layout root's final position depends on that ancestor's layout
406                // and should never be a collected layout root.
407                debug_assert!(!self.from_parent.contains(LayoutDamage::Relayout));
408
409                // This removes any dirty layout roots from descendants and then adds this
410                // node as a dirty layout root. As this node itself as a dirty layout
411                // root, it subsumes all dirty descendant layout roots.
412                discard_any_descendant_layout_roots(layout_roots);
413                layout_roots.push(layout_root);
414
415                self.node.with_layout_box_base_including_pseudos(|base| {
416                    base.invalidate_caches(&self);
417                    base.mark_fragments_as_descendants_changed();
418                });
419                LayoutDamage::RecalculateOverflow |
420                    LayoutDamage::DescendantCollectedAsLayoutRoot |
421                    LayoutDamage::RecomputeInlineContentSizes
422            },
423            BoxDamageAction::InvalidateFragmentTreeAboveLayoutRoot => {
424                // Damage propagation works exactly the same at the point the layout root is collected
425                // and above it. Layout caches are invalidated and damage is adjusted, maybe limited
426                // inline content size recalculation.
427                let mut damage_for_parent = LayoutDamage::RecalculateOverflow |
428                    LayoutDamage::DescendantCollectedAsLayoutRoot;
429
430                let mut inline_size_depends_on_content = false;
431                self.node.with_layout_box_base_including_pseudos(|base| {
432                    inline_size_depends_on_content |= base.invalidate_caches(&self);
433                    base.mark_fragments_as_descendants_changed();
434                });
435                self.adjust_inline_content_size_damage(
436                    &mut damage_for_parent,
437                    inline_size_depends_on_content,
438                );
439
440                damage_for_parent
441            },
442            BoxDamageAction::InvalidateScrollableOverflow => {
443                // In this case the node's fragments are preserved, but it or one of its descendants
444                // had scrollable overflow damage, which means that scrollable overflow should be
445                // cleared. This causes it to be recalculated the next time it's queried.
446                self.node.with_layout_box_base_including_pseudos(|base| {
447                    base.clear_scrollable_overflow_all_on_fragments();
448                });
449                only_layout_mode_damage
450            },
451            BoxDamageAction::None => only_layout_mode_damage,
452        };
453
454        // If this element's boxes are preserved and its style has changed, whether or not
455        // we run fragment tree layout, we need to update any preserved layout data
456        // structures' style references.
457        if !self.on_element.is_empty() && !will_rebuild_box {
458            self.node.repair_style(&layout_context.style_context);
459        }
460
461        damage_for_parent
462    }
463
464    fn box_damage_action(&self) -> BoxDamageAction<'a> {
465        // When a parent box is going to be reconstructed, that overrides everything else.
466        if self
467            .from_parent
468            .contains(LayoutDamage::DescendantHasBoxDamage)
469        {
470            return BoxDamageAction::RebuildAncestor;
471        }
472
473        // When this element or one of its descendants needs to be reconstructed, try to
474        // rebuild it here. If that fails, an ancestor box will be reconstructed instead.
475        let element_and_children_damage = self.on_element | self.from_children;
476        if element_and_children_damage.contains(LayoutDamage::DescendantHasBoxDamage) {
477            return BoxDamageAction::TryRebuild;
478        }
479
480        if element_and_children_damage.contains(LayoutDamage::Relayout) &&
481            !self.from_parent.contains(LayoutDamage::Relayout) &&
482            let Ok(layout_root) = LayoutRoot::try_from(self.node)
483        {
484            return BoxDamageAction::CollectLayoutRoot(layout_root);
485        }
486
487        // If this element needs a new fragment layout, then invalidate fragment caches
488        // clear the resulting fragments, and clear scrollable overflow.
489        if (self.from_parent | element_and_children_damage).contains(LayoutDamage::Relayout) {
490            return BoxDamageAction::InvalidateFragmentTreeBelowLayoutRoot;
491        }
492
493        // If one of this element's descendants was collected as a layout root, then
494        // invalidate fragment caches and clear scrollable overflow.
495        if self
496            .from_children
497            .contains(LayoutDamage::DescendantCollectedAsLayoutRoot)
498        {
499            return BoxDamageAction::InvalidateFragmentTreeAboveLayoutRoot;
500        }
501
502        // If the scrollable overflow of this element has changed, invalidate the
503        // scrollable overflow.
504        if element_and_children_damage.contains(LayoutDamage::RecalculateOverflow) {
505            return BoxDamageAction::InvalidateScrollableOverflow;
506        }
507
508        BoxDamageAction::None
509    }
510
511    fn adjust_inline_content_size_damage(
512        &self,
513        damage_for_parent: &mut LayoutDamage,
514        inline_size_depends_on_content: bool,
515    ) {
516        let children_need_inline_content_size_recalculation = self
517            .from_children
518            .contains(LayoutDamage::RecomputeInlineContentSizes) &&
519            inline_size_depends_on_content;
520        damage_for_parent.set(
521            LayoutDamage::RecomputeInlineContentSizes,
522            !self.on_element.is_empty() || children_need_inline_content_size_recalculation,
523        );
524    }
525}