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 bitflags::Flags;
6use layout_api::LayoutDamage;
7use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
8use script::layout_dom::ServoThreadSafeLayoutNode;
9use style::context::{SharedStyleContext, StyleContext};
10use style::data::ElementData;
11use style::dom::{NodeInfo, TElement, TNode};
12use style::selector_parser::RestyleDamage;
13use style::traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at};
14use style::values::computed::Display;
15
16use crate::context::LayoutContext;
17use crate::dom::{DOMLayoutData, NodeExt};
18use crate::layout_box_base::LayoutBoxBase;
19
20pub struct RecalcStyle<'a> {
21    context: &'a LayoutContext<'a>,
22}
23
24impl<'a> RecalcStyle<'a> {
25    pub(crate) fn new(context: &'a LayoutContext<'a>) -> Self {
26        RecalcStyle { context }
27    }
28
29    pub(crate) fn context(&self) -> &LayoutContext<'a> {
30        self.context
31    }
32}
33
34#[expect(unsafe_code)]
35impl<'dom, E> DomTraversal<E> for RecalcStyle<'_>
36where
37    E: TElement,
38    E::ConcreteNode: 'dom + LayoutNode<'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        if node.is_text_node() {
50            return;
51        }
52
53        let had_style_data = node.style_data().is_some();
54        unsafe {
55            node.initialize_style_and_layout_data::<DOMLayoutData>();
56        }
57
58        let element = node.as_element().unwrap();
59        let mut element_data = element.mutate_data().unwrap();
60
61        if !had_style_data {
62            element_data.damage = RestyleDamage::reconstruct();
63        }
64
65        recalc_style_at(
66            self,
67            traversal_data,
68            context,
69            element,
70            &mut element_data,
71            note_child,
72        );
73
74        unsafe {
75            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_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#[servo_tracing::instrument(skip_all)]
98pub(crate) fn compute_damage_and_repair_style(
99    context: &SharedStyleContext,
100    node: ServoThreadSafeLayoutNode<'_>,
101    damage_from_environment: RestyleDamage,
102) -> RestyleDamage {
103    compute_damage_and_repair_style_inner(context, node, damage_from_environment)
104}
105
106pub(crate) fn compute_damage_and_repair_style_inner(
107    context: &SharedStyleContext,
108    node: ServoThreadSafeLayoutNode<'_>,
109    damage_from_parent: RestyleDamage,
110) -> RestyleDamage {
111    let mut element_damage;
112    let original_element_damage;
113    let element_data = &node
114        .style_data()
115        .expect("Should not run `compute_damage` before styling.")
116        .element_data;
117
118    {
119        let mut element_data = element_data.borrow_mut();
120        original_element_damage = element_data.damage;
121        element_damage = original_element_damage | damage_from_parent;
122
123        if let Some(ref style) = element_data.styles.primary {
124            if style.get_box().display == Display::None {
125                element_data.damage = element_damage;
126                return element_damage;
127            }
128        }
129    }
130
131    // If we are reconstructing this node, then all of the children should be reconstructed as well.
132    // Otherwise, do not propagate down its box damage.
133    let mut damage_for_children = element_damage;
134    if !element_damage.contains(LayoutDamage::rebuild_box_tree()) {
135        damage_for_children.truncate();
136    }
137
138    let mut damage_from_children = RestyleDamage::empty();
139    for child in node.children() {
140        if child.is_element() {
141            damage_from_children |=
142                compute_damage_and_repair_style_inner(context, child, damage_for_children);
143        }
144    }
145
146    // If one of our children needed to be reconstructed, we need to recollect children
147    // during box tree construction.
148    if damage_from_children.contains(LayoutDamage::recollect_box_tree_children()) {
149        element_damage.insert(LayoutDamage::recollect_box_tree_children());
150    }
151
152    // If this node's box will not be preserved, we need to relayout its box tree.
153    let element_layout_damage = LayoutDamage::from(element_damage);
154    if element_layout_damage.has_box_damage() {
155        element_damage.insert(RestyleDamage::RELAYOUT);
156    }
157
158    // Only propagate up layout phases from children, as other types of damage are
159    // incorporated into `element_damage` above.
160    let damage_for_parent = element_damage | (damage_from_children & RestyleDamage::RELAYOUT);
161
162    // If we are going to potentially reuse this box tree node, then clear any cached
163    // fragment layout.
164    //
165    // TODO: If this node has `recollect_box_tree_children` damage, this is unnecessary
166    // unless it's entirely above the dirty root.
167    if element_damage != RestyleDamage::reconstruct() &&
168        damage_for_parent.contains(RestyleDamage::RELAYOUT)
169    {
170        node.with_each_layout_box_base_including_pseudos(
171            if (original_element_damage | damage_from_children).contains(RestyleDamage::RELAYOUT) {
172                LayoutBoxBase::clear_fragments_and_layout_cache
173            } else {
174                LayoutBoxBase::clear_fragments
175            },
176        );
177    }
178
179    // If the box will be preserved, update the box's style and also in any fragments
180    // that haven't been cleared. Meanwhile, clear the damage to avoid affecting the
181    // next reflow.
182    if !element_layout_damage.has_box_damage() {
183        if !original_element_damage.is_empty() {
184            node.repair_style(context);
185        }
186
187        element_damage = RestyleDamage::empty();
188    }
189
190    if element_damage != original_element_damage {
191        element_data.borrow_mut().damage = element_damage;
192    }
193
194    damage_for_parent
195}