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