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::atomic::Ordering;
7
8use bitflags::Flags;
9use layout_api::LayoutDamage;
10use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
11use script::layout_dom::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};
17use style::values::computed::Display;
18
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_repair_style(
101    context: &SharedStyleContext,
102    node: ServoThreadSafeLayoutNode<'_>,
103    damage_from_environment: RestyleDamage,
104) -> RestyleDamage {
105    compute_damage_and_repair_style_inner(context, node, damage_from_environment)
106}
107
108pub(crate) fn compute_damage_and_repair_style_inner(
109    context: &SharedStyleContext,
110    node: ServoThreadSafeLayoutNode<'_>,
111    damage_from_parent: RestyleDamage,
112) -> RestyleDamage {
113    let mut element_damage;
114    let original_element_damage;
115    let element_data = &node
116        .style_data()
117        .expect("Should not run `compute_damage` before styling.")
118        .element_data;
119
120    {
121        let mut element_data = element_data.borrow_mut();
122        original_element_damage = element_data.damage;
123        element_damage = original_element_damage | damage_from_parent;
124
125        if let Some(ref style) = element_data.styles.primary {
126            if style.get_box().display == Display::None {
127                element_data.damage = element_damage;
128                return element_damage;
129            }
130        }
131    }
132
133    // If we are reconstructing this node, then all of the children should be reconstructed as well.
134    // Otherwise, do not propagate down its box damage.
135    let mut damage_for_children = element_damage;
136    if !element_damage.contains(LayoutDamage::rebuild_box_tree()) {
137        damage_for_children.truncate();
138    }
139
140    let mut damage_from_children = RestyleDamage::empty();
141    for child in node.children() {
142        if child.is_element() {
143            damage_from_children |=
144                compute_damage_and_repair_style_inner(context, child, damage_for_children);
145        }
146    }
147
148    // If one of our children needed to be reconstructed, we need to recollect children
149    // during box tree construction.
150    if damage_from_children.contains(LayoutDamage::recollect_box_tree_children()) {
151        element_damage.insert(LayoutDamage::recollect_box_tree_children());
152    }
153
154    // If this node's box will not be preserved, we need to relayout its box tree.
155    let element_layout_damage = LayoutDamage::from(element_damage);
156    if element_layout_damage.has_box_damage() {
157        element_damage.insert(RestyleDamage::RELAYOUT);
158    }
159
160    // Only propagate up layout phases from children, as other types of damage are
161    // incorporated into `element_damage` above.
162    let mut damage_for_parent = element_damage | (damage_from_children & RestyleDamage::RELAYOUT);
163
164    // If we are going to potentially reuse this box tree node, then clear any cached
165    // fragment layout.
166    //
167    // TODO: If this node has `recollect_box_tree_children` damage, this is unnecessary
168    // unless it's entirely above the dirty root.
169    if element_damage != RestyleDamage::reconstruct() &&
170        damage_for_parent.contains(RestyleDamage::RELAYOUT)
171    {
172        let outer_inline_content_sizes_depend_on_content = Cell::new(false);
173        node.with_layout_box_base_including_pseudos(|base| {
174            base.clear_fragments();
175            if original_element_damage.contains(RestyleDamage::RELAYOUT) {
176                // If the node itself has damage, we must clear both the cached layout results
177                // and also the cached intrinsic inline sizes.
178                *base.cached_layout_result.borrow_mut() = None;
179                *base.cached_inline_content_size.borrow_mut() = None;
180            } else if damage_from_children.contains(RestyleDamage::RELAYOUT) {
181                // If the damage is propagated from children, then we still need to clear the cached
182                // layout results, but sometimes we can keep the cached intrinsic inline sizes.
183                *base.cached_layout_result.borrow_mut() = None;
184                if !damage_from_children.contains(LayoutDamage::recompute_inline_content_sizes()) {
185                    // This happens when there is a node which is a descendant of the current one and
186                    // an ancestor of the damaged one, whose inline size doesn't depend on its contents.
187                    return;
188                }
189                *base.cached_inline_content_size.borrow_mut() = None;
190            }
191
192            // When a block container has a mix of inline-level and block-level contents,
193            // the inline-level ones are wrapped inside an anonymous block associated with
194            // the block container. The anonymous block has an `auto` size, so its intrinsic
195            // contribution depends on content, but it can't affect the intrinsic size of
196            // ancestors if the block container is sized extrinsically.
197            if !base.base_fragment_info.is_anonymous() {
198                // TODO: Use `Cell::update()` once it becomes stable.
199                outer_inline_content_sizes_depend_on_content.set(
200                    outer_inline_content_sizes_depend_on_content.get() ||
201                        base.outer_inline_content_sizes_depend_on_content
202                            .load(Ordering::Relaxed),
203                );
204            }
205        });
206
207        // If the intrinsic contributions of this node depend on content, we will need to clear
208        // the cached intrinsic sizes of the parent. But if the contributions are purely extrinsic,
209        // then the intrinsic sizes of the ancestors won't be affected, and we can keep the cache.
210        if outer_inline_content_sizes_depend_on_content.get() {
211            damage_for_parent.insert(LayoutDamage::recompute_inline_content_sizes())
212        }
213    }
214
215    // If the box will be preserved, update the box's style and also in any fragments
216    // that haven't been cleared. Meanwhile, clear the damage to avoid affecting the
217    // next reflow.
218    if !element_layout_damage.has_box_damage() {
219        if !original_element_damage.is_empty() {
220            node.repair_style(context);
221        }
222
223        element_damage = RestyleDamage::empty();
224    }
225
226    if element_damage != original_element_damage {
227        element_data.borrow_mut().damage = element_damage;
228    }
229
230    damage_for_parent
231}