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