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::cell::Cell;
6use std::sync::Arc;
7
8use bitflags::Flags;
9use layout_api::{
10    DangerousStyleElement, DangerousStyleNode, LayoutDamage, LayoutElement, LayoutNode,
11};
12use script::layout_dom::ServoLayoutNode;
13use style::context::{SharedStyleContext, StyleContext};
14use style::data::ElementData;
15use style::dom::{NodeInfo, TElement, TNode};
16use style::selector_parser::RestyleDamage;
17use style::traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at};
18
19use crate::BoxTree;
20use crate::context::LayoutContext;
21use crate::dom::{DOMLayoutData, NodeExt};
22
23pub struct RecalcStyle<'a> {
24    context: &'a LayoutContext<'a>,
25}
26
27impl<'a> RecalcStyle<'a> {
28    pub(crate) fn new(context: &'a LayoutContext<'a>) -> Self {
29        RecalcStyle { context }
30    }
31
32    pub(crate) fn context(&self) -> &LayoutContext<'a> {
33        self.context
34    }
35}
36
37impl<'dom, E> DomTraversal<E> for RecalcStyle<'_>
38where
39    E: DangerousStyleElement<'dom> + TElement,
40    E::ConcreteNode: 'dom + DangerousStyleNode<'dom>,
41{
42    fn process_preorder<F>(
43        &self,
44        traversal_data: &PerLevelTraversalData,
45        context: &mut StyleContext<E>,
46        node: E::ConcreteNode,
47        note_child: F,
48    ) where
49        F: FnMut(E::ConcreteNode),
50    {
51        let Some(dangerous_style_element) = node.as_element() else {
52            return;
53        };
54
55        let layout_element = dangerous_style_element.layout_element();
56        let had_style_data = layout_element.style_data().is_some();
57        layout_element.initialize_style_and_layout_data::<DOMLayoutData>();
58
59        let mut element_data = dangerous_style_element.mutate_data().unwrap();
60        if !had_style_data {
61            element_data.damage = RestyleDamage::reconstruct();
62        }
63
64        recalc_style_at(
65            self,
66            traversal_data,
67            context,
68            dangerous_style_element,
69            &mut element_data,
70            note_child,
71        );
72
73        #[expect(unsafe_code)]
74        unsafe {
75            dangerous_style_element.unset_dirty_descendants();
76        }
77    }
78
79    #[inline]
80    fn needs_postorder_traversal() -> bool {
81        false
82    }
83
84    fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
85        panic!("this should never be called")
86    }
87
88    fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool {
89        node.layout_node().layout_data().is_none() || !parent_data.damage.is_empty()
90    }
91
92    fn shared_context(&self) -> &SharedStyleContext<'_> {
93        &self.context.style_context
94    }
95}
96
97#[expect(unsafe_code)]
98#[servo_tracing::instrument(skip_all)]
99pub(crate) fn compute_damage_and_rebuild_box_tree(
100    box_tree: &mut Option<Arc<BoxTree>>,
101    layout_context: &LayoutContext,
102    dirty_root: ServoLayoutNode<'_>,
103    root_node: ServoLayoutNode<'_>,
104    damage_from_environment: RestyleDamage,
105) -> RestyleDamage {
106    let restyle_damage = compute_damage_and_rebuild_box_tree_inner(
107        layout_context,
108        dirty_root,
109        damage_from_environment,
110    );
111
112    let layout_damage: LayoutDamage = restyle_damage.into();
113    if box_tree.is_none() {
114        *box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node)));
115        return restyle_damage;
116    }
117
118    // There are two cases where we need to do more work:
119    //
120    // 1. Fragment tree layout needs to run again, in which case we should invalidate all
121    //    fragments to the root of the DOM.
122    // 2. Box tree reconstruction needs to run at the dirty root, in which case we need to
123    //    find an appropriate place to run box tree reconstruction and *also* invalidate all
124    //    fragments to the root of the DOM.
125    if !restyle_damage.contains(RestyleDamage::RELAYOUT) {
126        return restyle_damage;
127    }
128
129    // If the damage traversal indicated that the dirty root needs a new box, walk up the
130    // tree to find an appropriate place to run box tree reconstruction.
131    let mut needs_box_tree_rebuild = layout_damage.needs_new_box();
132
133    let mut damage_for_ancestors = LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES;
134
135    let mut maybe_parent_node = unsafe { dirty_root.dangerous_flat_tree_parent() };
136    while let Some(parent_node) = maybe_parent_node {
137        // If we need box tree reconstruction, try it here.
138        if needs_box_tree_rebuild &&
139            parent_node.rebuild_box_tree_from_independent_formatting_context(layout_context)
140        {
141            needs_box_tree_rebuild = false;
142        }
143
144        if needs_box_tree_rebuild {
145            // We have not yet found a place to run box tree reconstruction, so clear this
146            // node's boxes to ensure that they are invalidated for the reconstruction we
147            // will run later.
148            parent_node.unset_all_boxes();
149        } else {
150            // Reconstruction has already run or was not necessary, so we just need to
151            // ensure that fragment tree layout does not reuse any cached fragments.
152            let new_damage_for_ancestors = Cell::new(LayoutDamage::empty());
153            parent_node.with_layout_box_base_including_pseudos(|base| {
154                new_damage_for_ancestors.set(
155                    new_damage_for_ancestors.get() |
156                        base.add_damage(Default::default(), damage_for_ancestors),
157                );
158            });
159            damage_for_ancestors = new_damage_for_ancestors.get();
160        }
161
162        maybe_parent_node = unsafe { parent_node.dangerous_flat_tree_parent() };
163    }
164
165    // We could not find a place in the middle of the tree to run box tree reconstruction,
166    // so just rebuild the whole tree.
167    if needs_box_tree_rebuild {
168        *box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node)));
169    }
170
171    restyle_damage
172}
173
174pub(crate) fn compute_damage_and_rebuild_box_tree_inner(
175    layout_context: &LayoutContext,
176    node: ServoLayoutNode<'_>,
177    damage_from_parent: RestyleDamage,
178) -> RestyleDamage {
179    // Don't do any kind of damage propagation or box tree constuction for non-Element nodes,
180    // such as text and comments.
181    let Some(element) = node.as_element() else {
182        return damage_from_parent;
183    };
184
185    let (element_damage, is_display_none) = {
186        let mut element_data = element.element_data_mut();
187        (
188            std::mem::take(&mut element_data.damage),
189            element_data.styles.is_display_none(),
190        )
191    };
192
193    let mut element_and_parent_damage = element_damage | damage_from_parent;
194    if is_display_none {
195        node.unset_all_boxes();
196        return element_and_parent_damage;
197    }
198
199    // Children only receive layout mode damage from their parents, except when an ancestor
200    // needs to be completely rebuilt. In that case, descendants are rebuilt down to the
201    // first independent formatting context, which should isolate that tree from further
202    // box damage.
203    let mut damage_for_children = element_and_parent_damage;
204    damage_for_children.truncate();
205    let rebuild_children = element_damage.contains(LayoutDamage::box_damage()) ||
206        (damage_from_parent.contains(LayoutDamage::box_damage()) &&
207            !node.isolates_damage_for_damage_propagation());
208    if rebuild_children {
209        damage_for_children.insert(LayoutDamage::box_damage());
210    } else if element_and_parent_damage.contains(RestyleDamage::RELAYOUT) &&
211        !element_damage.contains(RestyleDamage::RELAYOUT) &&
212        node.isolates_damage_for_damage_propagation()
213    {
214        // If not rebuilding the boxes for this node, but fragments need to be rebuilt
215        // only because of an ancestor, fragment layout caches should still be valid when
216        // crossing down into new independent formatting contexts.
217        damage_for_children.remove(RestyleDamage::RELAYOUT);
218        element_and_parent_damage.remove(RestyleDamage::RELAYOUT);
219    }
220
221    let mut damage_from_children = RestyleDamage::empty();
222    for child in node.flat_tree_children() {
223        if child.is_element() {
224            damage_from_children |= compute_damage_and_rebuild_box_tree_inner(
225                layout_context,
226                child,
227                damage_for_children,
228            );
229        }
230    }
231
232    // Only propagate up layout phases from children. Other types of damage can be
233    // propagated from children but via the `LayoutBoxBase::add_damage` return value.
234    let mut layout_damage_for_parent =
235        element_and_parent_damage | (damage_from_children & RestyleDamage::RELAYOUT);
236
237    let element_or_ancestors_need_rebuild =
238        element_and_parent_damage.contains(LayoutDamage::descendant_has_box_damage());
239    let descendant_needs_rebuild =
240        damage_from_children.contains(LayoutDamage::descendant_has_box_damage());
241    if element_or_ancestors_need_rebuild || descendant_needs_rebuild {
242        if damage_from_parent.contains(LayoutDamage::descendant_has_box_damage()) ||
243            !node.rebuild_box_tree_from_independent_formatting_context(layout_context)
244        {
245            // In this case:
246            //  - an ancestor needs to be completely rebuilt, or
247            //  - a descendant needs to be rebuilt, but we are still propagating the rebuild
248            //    damage to an independent formatting context with a compatible box level.
249            //
250            // This means that this box is no longer valid and also needs to be rebuilt
251            // (perhaps some of its descendants do not though). In this case, unset all existing
252            // boxes for the node and ensure that the appropriate rebuild-type damage
253            // propagates up the tree.
254            node.unset_all_boxes();
255            layout_damage_for_parent
256                .insert(LayoutDamage::descendant_has_box_damage() | RestyleDamage::RELAYOUT);
257        } else {
258            // In this case, we have rebuilt the box tree from this point and we do not
259            // have to propagate rebuild box tree damage up the tree any further.
260            layout_damage_for_parent.remove(LayoutDamage::box_damage());
261            layout_damage_for_parent
262                .insert(RestyleDamage::RELAYOUT | LayoutDamage::recompute_inline_content_sizes());
263        }
264    } else {
265        // In this case, this node's boxes are preserved! It's possible that we still need
266        // to run fragment tree layout in this subtree due to an ancestor, this node, or a
267        // descendant changing style. In that case, we ask the `LayoutBoxBase` to clear
268        // any cached information that cannot be used.
269        if (element_and_parent_damage | damage_from_children).contains(RestyleDamage::RELAYOUT) {
270            let extra_layout_damage_for_parent = Cell::new(LayoutDamage::empty());
271            node.with_layout_box_base_including_pseudos(|base| {
272                extra_layout_damage_for_parent.set(
273                    extra_layout_damage_for_parent.get() |
274                        base.add_damage(element_damage.into(), damage_from_children.into()),
275                );
276            });
277            layout_damage_for_parent.insert(extra_layout_damage_for_parent.get().into());
278        }
279
280        // The box is preserved. Whether or not we run fragment tree layout, we need to
281        // update any preserved layout data structures' style references, if *this*
282        // element's style has changed.
283        if !element_damage.is_empty() {
284            node.repair_style(&layout_context.style_context);
285        }
286    }
287
288    layout_damage_for_parent
289}