Skip to main content

layout/fragment_tree/
fragment_tree.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::Arc;
7
8use app_units::Au;
9use malloc_size_of_derive::MallocSizeOf;
10use paint_api::display_list::AxesScrollSensitivity;
11use servo_base::print_tree::PrintTree;
12use style::computed_values::position::T as Position;
13
14use super::{BoxFragment, ContainingBlockManager, Fragment};
15use crate::geom::PhysicalRect;
16
17#[derive(MallocSizeOf)]
18pub struct FragmentTree {
19    /// Fragments at the top-level of the tree.
20    ///
21    /// If the root element has `display: none`, there are zero fragments.
22    /// Otherwise, there is at least one:
23    ///
24    /// * The first fragment is generated by the root element.
25    /// * There may be additional fragments generated by positioned boxes
26    ///   that have the initial containing block.
27    pub(crate) root_fragments: Vec<Fragment>,
28
29    /// The scrollable overflow rectangle for the entire tree
30    /// <https://drafts.csswg.org/css-overflow/#scrollable>
31    scrollable_overflow: Cell<Option<PhysicalRect<Au>>>,
32
33    /// The containing block used in the layout of this fragment tree.
34    pub(crate) initial_containing_block: PhysicalRect<Au>,
35
36    /// Whether or not the viewport is sensitive to scroll input events.
37    pub viewport_scroll_sensitivity: AxesScrollSensitivity,
38}
39
40impl FragmentTree {
41    pub(crate) fn new(
42        root_fragments: Vec<Fragment>,
43        initial_containing_block: PhysicalRect<Au>,
44        viewport_scroll_sensitivity: AxesScrollSensitivity,
45    ) -> Self {
46        Self {
47            root_fragments,
48            scrollable_overflow: Cell::default(),
49            initial_containing_block,
50            viewport_scroll_sensitivity,
51        }
52    }
53
54    pub fn print(&self) {
55        let mut print_tree = PrintTree::new("Fragment Tree".to_string());
56        for fragment in &self.root_fragments {
57            fragment.print(&mut print_tree);
58        }
59    }
60
61    pub(crate) fn scrollable_overflow(&self) -> PhysicalRect<Au> {
62        if let Some(scrollable_overflow) = self.scrollable_overflow.get() {
63            return scrollable_overflow;
64        }
65        let scrollable_overflow = self.calculate_scrollable_overflow();
66        self.scrollable_overflow.set(Some(scrollable_overflow));
67        scrollable_overflow
68    }
69
70    pub(crate) fn clear_scrollable_overflow(&self) {
71        self.scrollable_overflow.set(None);
72    }
73
74    /// Calculate the scrollable overflow / scrolling area for this [`FragmentTree`] according
75    /// to <https://drafts.csswg.org/cssom-view/#scrolling-area>.
76    fn calculate_scrollable_overflow(&self) -> PhysicalRect<Au> {
77        let Some(first_root_fragment) = self.root_fragments.first() else {
78            return self.initial_containing_block;
79        };
80
81        let scrollable_overflow =
82            self.root_fragments
83                .iter()
84                .fold(self.initial_containing_block, |overflow, fragment| {
85                    // Scrollable overflow should be accumulated in the block that
86                    // establishes the containing block for the element. Thus, fixed
87                    // positioned fragments whose containing block is the initial
88                    // containing block should not be included in overflow calculation.
89                    // See <https://www.w3.org/TR/css-overflow-3/#scrollable>.
90                    if fragment
91                        .retrieve_box_fragment()
92                        .is_some_and(|box_fragment| {
93                            box_fragment.style().get_box().position == Position::Fixed
94                        })
95                    {
96                        return overflow;
97                    }
98
99                    overflow.union(&fragment.scrollable_overflow_for_parent())
100                });
101
102        // Assuming that the first fragment is the root element, ensure that
103        // scrollable overflow that is unreachable is not included in the final
104        // rectangle. See
105        // <https://drafts.csswg.org/css-overflow/#scrolling-direction>.
106        let first_root_fragment = match first_root_fragment {
107            Fragment::Box(fragment) | Fragment::Float(fragment) => fragment,
108            _ => return scrollable_overflow,
109        };
110        if !first_root_fragment.is_root_element() {
111            return scrollable_overflow;
112        }
113        first_root_fragment.clip_wholly_unreachable_scrollable_overflow(
114            scrollable_overflow,
115            self.initial_containing_block,
116        )
117    }
118
119    pub(crate) fn find<T>(
120        &self,
121        mut process_func: impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
122    ) -> Option<T> {
123        let info = ContainingBlockManager {
124            for_non_absolute_descendants: &self.initial_containing_block,
125            for_absolute_descendants: None,
126            for_absolute_and_fixed_descendants: &self.initial_containing_block,
127        };
128        self.root_fragments
129            .iter()
130            .find_map(|child| child.find(&info, 0, &mut process_func))
131    }
132
133    /// Find the `<body>` element's [`Fragment`], if it exists in this [`FragmentTree`].
134    pub(crate) fn body_fragment(&self) -> Option<Arc<BoxFragment>> {
135        fn find_body(children: &[Fragment]) -> Option<Arc<BoxFragment>> {
136            children.iter().find_map(|fragment| {
137                match fragment {
138                    Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
139                        if box_fragment.is_body_element_of_html_element_root() {
140                            return Some(box_fragment.clone());
141                        }
142
143                        // The fragment for the `<body>` element is typically a child of the root (though,
144                        // not if it's absolutely positioned), so we need to recurse into the children of
145                        // the root to find it.
146                        //
147                        // Additionally, recurse into any anonymous fragments, as the `<body>` fragment may
148                        // have created anonymous parents (for instance by creating an inline formatting context).
149                        if box_fragment.is_root_element() || box_fragment.base.is_anonymous() {
150                            find_body(&box_fragment.children)
151                        } else {
152                            None
153                        }
154                    },
155                    Fragment::Positioning(positioning_fragment)
156                        if positioning_fragment.base.is_anonymous() =>
157                    {
158                        // If the `<body>` element is a `display: inline` then it might be nested inside of a
159                        // `PositioningFragment` for the purposes of putting it on the first line of the implied
160                        // inline formatting context.
161                        find_body(&positioning_fragment.children)
162                    },
163                    _ => None,
164                }
165            })
166        }
167
168        find_body(&self.root_fragments)
169    }
170}