Skip to main content

layout/
layout_root.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 script::layout_dom::ServoLayoutNode;
6
7use crate::context::LayoutContext;
8use crate::dom::{LayoutBox, NodeExt};
9use crate::flexbox::FlexLevelBox;
10use crate::flow::BlockLevelBox;
11use crate::flow::inline::InlineItem;
12use crate::fragment_tree::Fragment;
13
14/// A data structure to track a layout root.
15///
16/// Layout roots are places in the fragment tree where fragment tree layout can start.
17/// These are fragments that isolate fragment damage from their ancestors sufficiently
18/// that their ancestors can be preserved from one fragment tree layout to the next.
19/// **Currently, this only includes absolutely-or-fixed-positioned fragments that do not
20/// have any escaping fixed position fragments.**.
21///
22/// During damage propagation, upward flowing fragment tree `Relayout` damage can be
23/// converted into `DescendantCollectedAsLayoutRoot` damage. When an ancestor of a layout
24/// root has fragment tree layout damage, `DescendantCollectedAsLayoutRoot` damage is
25/// converted back into `Relayout` and propagated up to the next layout root or the root
26/// of the DOM.
27///
28/// This is only possible because the damage propagation traversal ensures that damage
29/// above the absolute (which might shift its static position) is converted back into
30/// `Relayout` damage. This means that the absolute should be placed with the fully
31/// adjusted static positioning rectangle between two layouts.
32///
33/// It's possible that upon laying out a layout root again, a new fixed position element
34/// has started to escape from the layout root. In that case, a full fragment tree layout
35/// becomes necessary to rebuild the fragment properly.
36pub(crate) struct LayoutRoot<'dom> {
37    node: ServoLayoutNode<'dom>,
38}
39
40impl<'dom> TryFrom<ServoLayoutNode<'dom>> for LayoutRoot<'dom> {
41    type Error = ();
42
43    fn try_from(node: ServoLayoutNode<'dom>) -> Result<Self, Self::Error> {
44        if !node.is_absolutely_positioned() {
45            return Err(());
46        }
47
48        // Only accept previously known layout roots without fragmentation as compatible layout roots.
49        let is_layout_root = node
50            .with_layout_box_base(|base| {
51                let fragments = base.fragments();
52                fragments.len() == 1 && matches!(fragments.first(), Some(Fragment::LayoutRoot(..)))
53            })
54            .unwrap_or(false);
55
56        if !is_layout_root {
57            return Err(());
58        }
59
60        Ok(Self { node })
61    }
62}
63
64impl<'dom> LayoutRoot<'dom> {
65    pub(crate) fn try_layout(&self, layout_context: &LayoutContext) -> bool {
66        let Some(inner_layout_data) = self.node.inner_layout_data_mut() else {
67            return false;
68        };
69        let layout_box = inner_layout_data.self_box.clone();
70
71        let layout_box = layout_box.borrow();
72        let Some(layout_box) = &*layout_box else {
73            return false;
74        };
75
76        let positioned_box = match layout_box {
77            LayoutBox::BlockLevel(block_level) => {
78                let mut block_level = block_level.borrow_mut();
79                match &mut *block_level {
80                    BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
81                        positioned_box.clone()
82                    },
83                    _ => return false,
84                }
85            },
86            LayoutBox::InlineLevel(InlineItem::OutOfFlowAbsolutelyPositionedBox(
87                positioned_box,
88                _,
89            )) => positioned_box.clone(),
90            LayoutBox::FlexLevel(flex_level_box) => {
91                let mut flex_level_box = flex_level_box.borrow_mut();
92                match &mut *flex_level_box {
93                    FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
94                        positioned_box.clone()
95                    },
96                    _ => return false,
97                }
98            },
99            _ => return false,
100        };
101
102        let positioned_box = positioned_box.borrow();
103        let formatting_context = &positioned_box.context;
104        let fragments = formatting_context.base.fragments();
105        let Some(Fragment::LayoutRoot(layout_root_fragment)) = fragments.first() else {
106            return false;
107        };
108
109        let layout_inputs = formatting_context.layout_root_layout_inputs.borrow();
110        let Some(layout_inputs) = &*layout_inputs else {
111            return false;
112        };
113
114        // If an `Err` is returned here, that means that this layout root is no longer
115        // a viable layout root and a full fragment tree layout is necessary.
116        layout_inputs
117            .layout(
118                layout_context,
119                formatting_context,
120                &layout_root_fragment.fragment,
121            )
122            .is_ok()
123    }
124
125    pub(crate) fn handle_failed_layout_root_layout(&self) {
126        self.node
127            .clear_fragments_and_dirty_fragment_caches_recursively();
128    }
129
130    pub(crate) fn node(&self) -> ServoLayoutNode<'dom> {
131        self.node
132    }
133}