layout/flow/
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 app_units::Au;
6use euclid::Rect;
7use euclid::default::Size2D as UntypedSize2D;
8use layout_api::{AxesOverflow, LayoutElement, LayoutNode};
9use malloc_size_of_derive::MallocSizeOf;
10use paint_api::display_list::AxesScrollSensitivity;
11use script::layout_dom::ServoLayoutNode;
12use style::values::computed::Overflow;
13use style_traits::CSSPixel;
14
15use crate::cell::ArcRefCell;
16use crate::context::LayoutContext;
17use crate::dom::{LayoutBox, NodeExt};
18use crate::dom_traversal::{Contents, NodeAndStyleInfo};
19use crate::flow::float::FloatBox;
20use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
21use crate::formatting_contexts::IndependentFormattingContext;
22use crate::fragment_tree::{FragmentFlags, FragmentTree};
23use crate::geom::{LogicalVec2, PhysicalSize};
24use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
25use crate::style_ext::Display;
26use crate::{DefiniteContainingBlock, PropagatedBoxTreeData};
27
28#[derive(MallocSizeOf)]
29pub struct BoxTree {
30    /// Contains typically exactly one block-level box, which was generated by the root element.
31    /// There may be zero if that element has `display: none`.
32    root: BlockFormattingContext,
33
34    /// Whether or not the viewport should be sensitive to scrolling input events in two axes
35    pub(crate) viewport_overflow: AxesOverflow,
36}
37
38impl BoxTree {
39    #[servo_tracing::instrument(name = "Box Tree Construction", skip_all)]
40    pub(crate) fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self {
41        let boxes = construct_for_root_element(context, root_element);
42
43        // Zero box for `:root { display: none }`, one for the root element otherwise.
44        assert!(boxes.len() <= 1);
45
46        let viewport_overflow = Self::viewport_overflow(root_element, boxes.first());
47        let contents = BlockContainer::BlockLevelBoxes(boxes);
48        let contains_floats = contents.contains_floats();
49        Self {
50            root: BlockFormattingContext {
51                contents,
52                contains_floats,
53            },
54            // From https://www.w3.org/TR/css-overflow-3/#overflow-propagation:
55            // > If visible is applied to the viewport, it must be interpreted as auto.
56            // > If clip is applied to the viewport, it must be interpreted as hidden.
57            viewport_overflow: viewport_overflow.to_scrollable(),
58        }
59    }
60
61    fn viewport_overflow(
62        root_element: ServoLayoutNode<'_>,
63        root_box: Option<&ArcRefCell<BlockLevelBox>>,
64    ) -> AxesOverflow {
65        // From https://www.w3.org/TR/css-overflow-3/#overflow-propagation:
66        // > UAs must apply the overflow-* values set on the root element to the viewport when the
67        // > root element’s display value is not none. However, when the root element is an [HTML]
68        // > html element (including XML syntax for HTML) whose overflow value is visible (in both
69        // > axes), and that element has as a child a body element whose display value is also not
70        // > none, user agents must instead apply the overflow-* values of the first such child
71        // > element to the viewport. The element from which the value is propagated must then have a
72        // > used overflow value of visible.
73
74        // If there is no root box, the root element has `display: none`, so don't propagate.
75        // The spec isn't very clear about what value to use, but the initial value seems fine.
76        // See https://github.com/w3c/csswg-drafts/issues/12649
77        let Some(root_box) = root_box else {
78            return AxesOverflow::default();
79        };
80
81        let propagate_from_body = || {
82            // Unlike what the spec implies, we stop iterating when we find the first <body>,
83            // even if it's not suitable because it lacks a box. This matches other browsers.
84            // See https://github.com/w3c/csswg-drafts/issues/12644
85            let body = root_element.flat_tree_children().find(|child| {
86                child
87                    .as_element()
88                    .is_some_and(|element| element.is_body_element_of_html_element_root())
89            })?;
90
91            // We only propagate from the <body> if it generates a box. The spec only checks for
92            // `display: none`, but other browsers don't propagate for `display: contents` either.
93            // See https://github.com/w3c/csswg-drafts/issues/12643
94            let body_layout_data = body.inner_layout_data()?;
95            let mut body_box = body_layout_data.self_box.borrow_mut();
96            body_box.as_mut()?.with_base_mut(|base| {
97                base.base_fragment_info
98                    .flags
99                    .insert(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT);
100                AxesOverflow::from(&*base.style)
101            })
102        };
103
104        root_box.borrow_mut().with_base_mut(|base| {
105            let root_overflow = AxesOverflow::from(&*base.style);
106            if root_overflow.x == Overflow::Visible && root_overflow.y == Overflow::Visible {
107                if let Some(body_overflow) = propagate_from_body() {
108                    return body_overflow;
109                }
110            }
111            base.base_fragment_info
112                .flags
113                .insert(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT);
114            root_overflow
115        })
116    }
117}
118
119fn construct_for_root_element(
120    context: &LayoutContext,
121    root_element: ServoLayoutNode<'_>,
122) -> Vec<ArcRefCell<BlockLevelBox>> {
123    let info = NodeAndStyleInfo::new(root_element, root_element.style(&context.style_context));
124    let box_style = info.style.get_box();
125
126    let display_inside = match Display::from(box_style.display) {
127        Display::None => return Vec::new(),
128        Display::Contents => {
129            // Unreachable because the style crate adjusts the computed values:
130            // https://drafts.csswg.org/css-display-3/#transformations
131            // “'display' of 'contents' computes to 'block' on the root element”
132            unreachable!()
133        },
134        // The root element is blockified, ignore DisplayOutside
135        Display::GeneratingBox(display_generating_box) => display_generating_box.display_inside(),
136    };
137
138    let contents = Contents::for_element(root_element, context);
139
140    let propagated_data = PropagatedBoxTreeData::default();
141    let root_box = if box_style.position.is_absolutely_positioned() {
142        BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new(
143            AbsolutelyPositionedBox::construct(context, &info, display_inside, contents),
144        ))
145    } else if box_style.float.is_floating() {
146        BlockLevelBox::OutOfFlowFloatBox(FloatBox::construct(
147            context,
148            &info,
149            display_inside,
150            contents,
151            propagated_data,
152        ))
153    } else {
154        BlockLevelBox::Independent(IndependentFormattingContext::construct(
155            context,
156            &info,
157            display_inside,
158            contents,
159            propagated_data,
160        ))
161    };
162
163    let root_box = ArcRefCell::new(root_box);
164    root_element
165        .box_slot()
166        .set(LayoutBox::BlockLevel(root_box.clone()));
167    vec![root_box]
168}
169
170impl BoxTree {
171    #[servo_tracing::instrument(name = "Fragment Tree Construction", skip_all)]
172    pub(crate) fn layout(
173        &self,
174        layout_context: &LayoutContext,
175        viewport: UntypedSize2D<Au>,
176    ) -> FragmentTree {
177        let style = layout_context
178            .style_context
179            .stylist
180            .device()
181            .default_computed_values();
182
183        // FIXME: use the document’s mode:
184        // https://drafts.csswg.org/css-writing-modes/#principal-flow
185        let physical_containing_block: Rect<Au, CSSPixel> =
186            PhysicalSize::from_untyped(viewport).into();
187        let initial_containing_block = DefiniteContainingBlock {
188            size: LogicalVec2 {
189                inline: physical_containing_block.size.width,
190                block: physical_containing_block.size.height,
191            },
192            style,
193        };
194
195        let mut positioning_context = PositioningContext::default();
196        let independent_layout = self.root.layout(
197            layout_context,
198            &mut positioning_context,
199            &(&initial_containing_block).into(),
200        );
201
202        let mut root_fragments = independent_layout.fragments;
203
204        // Zero box for `:root { display: none }`, one for the root element otherwise.
205        assert!(root_fragments.len() <= 1);
206
207        // There may be more fragments at the top-level
208        // (for positioned boxes whose containing is the initial containing block)
209        // but only if there was one fragment for the root element.
210        positioning_context.layout_initial_containing_block_children(
211            layout_context,
212            &initial_containing_block,
213            &mut root_fragments,
214        );
215
216        let viewport_scroll_sensitivity = AxesScrollSensitivity {
217            x: self.viewport_overflow.x.into(),
218            y: self.viewport_overflow.y.into(),
219        };
220
221        FragmentTree::new(
222            root_fragments,
223            physical_containing_block,
224            viewport_scroll_sensitivity,
225        )
226    }
227}