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