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