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;
6
7use bitflags::Flags;
8use layout_api::LayoutDamage;
9use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
10use script::layout_dom::ServoThreadSafeLayoutNode;
11use style::context::{SharedStyleContext, StyleContext};
12use style::data::ElementData;
13use style::dom::{NodeInfo, TElement, TNode};
14use style::selector_parser::RestyleDamage;
15use style::traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at};
16
17use crate::context::LayoutContext;
18use crate::dom::{DOMLayoutData, NodeExt};
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 element_data = &node
112        .style_data()
113        .expect("Should not run `compute_damage` before styling.")
114        .element_data;
115    let (element_damage, is_display_none) = {
116        let mut element_data = element_data.borrow_mut();
117        (
118            std::mem::take(&mut element_data.damage),
119            element_data.styles.is_display_none(),
120        )
121    };
122
123    let element_and_parent_damage = element_damage | damage_from_parent;
124    if is_display_none {
125        node.unset_all_boxes();
126        return element_and_parent_damage;
127    }
128
129    // Children only receive layout mode damage from their parents, except when an ancestor
130    // needs to be completely rebuilt. In that case, descendants are rebuilt down to the
131    // first independent formatting context, which should isolate that tree from further
132    // box damage.
133    let mut damage_for_children = element_and_parent_damage;
134    damage_for_children.truncate();
135    let rebuild_children = element_damage.contains(LayoutDamage::rebuild_box_tree()) ||
136        (damage_from_parent.contains(LayoutDamage::rebuild_box_tree()) &&
137            !node.isolates_box_tree_rebuild_damage());
138    if rebuild_children {
139        damage_for_children.insert(LayoutDamage::rebuild_box_tree());
140    }
141
142    let mut damage_from_children = RestyleDamage::empty();
143    for child in node.children() {
144        if child.is_element() {
145            damage_from_children |=
146                compute_damage_and_repair_style_inner(context, child, damage_for_children);
147        }
148    }
149
150    // Only propagate up layout phases from children. Other types of damage can be
151    // propagated from children but via the `LayoutBoxBase::add_damage` return value.
152    let mut layout_damage_for_parent =
153        element_and_parent_damage | (damage_from_children & RestyleDamage::RELAYOUT);
154
155    if damage_from_children.contains(LayoutDamage::recollect_box_tree_children()) ||
156        element_and_parent_damage.contains(LayoutDamage::recollect_box_tree_children())
157    {
158        // In this case, this node, an ancestor, or a descendant needs to be completely
159        // rebuilt. That means that this box is no longer valid and also needs to be rebuilt
160        // (perhaps some of its children do not though). In this case, unset all existing
161        // boxes for the node and ensure that the appropriate rebuild-type damage propagates
162        // up the tree.
163        node.unset_all_boxes();
164        layout_damage_for_parent
165            .insert(LayoutDamage::recollect_box_tree_children() | RestyleDamage::RELAYOUT);
166    } else {
167        // In this case, this node's boxes are preserved! It's possible that we still need
168        // to run fragment tree layout in this subtree due to an ancestor, this node, or a
169        // descendant changing style. In that case, we ask the `LayoutBoxBase` to clear
170        // any cached information that cannot be used.
171        if (element_and_parent_damage | damage_from_children).contains(RestyleDamage::RELAYOUT) {
172            let extra_layout_damage_for_parent = Cell::new(LayoutDamage::empty());
173            node.with_layout_box_base_including_pseudos(|base| {
174                extra_layout_damage_for_parent.set(
175                    extra_layout_damage_for_parent.get() |
176                        base.add_damage(element_damage.into(), damage_from_children.into()),
177                );
178            });
179            layout_damage_for_parent.insert(extra_layout_damage_for_parent.get().into());
180        }
181
182        // The box is preserved. Whether or not we run fragment tree layout, we need to
183        // update any preserved layout data structures' style references, if *this*
184        // element's style has changed.
185        if !element_damage.is_empty() {
186            node.repair_style(context);
187        }
188    }
189
190    layout_damage_for_parent
191}