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;
6
7use app_units::Au;
8use base::print_tree::PrintTree;
9use compositing_traits::display_list::AxesScrollSensitivity;
10use malloc_size_of_derive::MallocSizeOf;
11use rustc_hash::FxHashSet;
12use style::animation::AnimationSetKey;
13use style::computed_values::position::T as Position;
14
15use super::{BoxFragment, ContainingBlockManager, Fragment};
16use crate::ArcRefCell;
17use crate::context::LayoutContext;
18use crate::geom::PhysicalRect;
19
20#[derive(MallocSizeOf)]
21pub struct FragmentTree {
22    /// Fragments at the top-level of the tree.
23    ///
24    /// If the root element has `display: none`, there are zero fragments.
25    /// Otherwise, there is at least one:
26    ///
27    /// * The first fragment is generated by the root element.
28    /// * There may be additional fragments generated by positioned boxes
29    ///   that have the initial containing block.
30    pub(crate) root_fragments: Vec<Fragment>,
31
32    /// The scrollable overflow rectangle for the entire tree
33    /// <https://drafts.csswg.org/css-overflow/#scrollable>
34    scrollable_overflow: Cell<Option<PhysicalRect<Au>>>,
35
36    /// The containing block used in the layout of this fragment tree.
37    pub(crate) initial_containing_block: PhysicalRect<Au>,
38
39    /// Whether or not the viewport is sensitive to scroll input events.
40    pub viewport_scroll_sensitivity: AxesScrollSensitivity,
41}
42
43impl FragmentTree {
44    pub(crate) fn new(
45        layout_context: &LayoutContext,
46        root_fragments: Vec<Fragment>,
47        initial_containing_block: PhysicalRect<Au>,
48        viewport_scroll_sensitivity: AxesScrollSensitivity,
49    ) -> Self {
50        let fragment_tree = Self {
51            root_fragments,
52            scrollable_overflow: Cell::default(),
53            initial_containing_block,
54            viewport_scroll_sensitivity,
55        };
56
57        // As part of building the fragment tree, we want to stop animating elements and
58        // pseudo-elements that used to be animating or had animating images attached to
59        // them. Create a set of all elements that used to be animating.
60        let mut animations = layout_context.style_context.animations.sets.write();
61        let mut invalid_animating_nodes: FxHashSet<_> = animations.keys().cloned().collect();
62
63        let mut animating_images = layout_context.image_resolver.animating_images.write();
64        let mut invalid_image_animating_nodes: FxHashSet<_> = animating_images
65            .node_to_state_map
66            .keys()
67            .cloned()
68            .map(|node| AnimationSetKey::new(node, None))
69            .collect();
70
71        fragment_tree.find(|fragment, _level, containing_block| {
72            if let Some(tag) = fragment.tag() {
73                // TODO: Support animations on nested pseudo-elements.
74                invalid_animating_nodes.remove(&AnimationSetKey::new(
75                    tag.node,
76                    tag.pseudo_element_chain.primary,
77                ));
78                invalid_image_animating_nodes.remove(&AnimationSetKey::new(
79                    tag.node,
80                    tag.pseudo_element_chain.primary,
81                ));
82            }
83
84            fragment.set_containing_block(containing_block);
85            None::<()>
86        });
87
88        // Cancel animations for any elements and pseudo-elements that are no longer found
89        // in the fragment tree.
90        for node in &invalid_animating_nodes {
91            if let Some(state) = animations.get_mut(node) {
92                state.cancel_all_animations();
93            }
94        }
95        for node in &invalid_image_animating_nodes {
96            animating_images.remove(node.node);
97        }
98
99        fragment_tree
100    }
101
102    pub fn print(&self) {
103        let mut print_tree = PrintTree::new("Fragment Tree".to_string());
104        for fragment in &self.root_fragments {
105            fragment.print(&mut print_tree);
106        }
107    }
108
109    pub(crate) fn scrollable_overflow(&self) -> PhysicalRect<Au> {
110        self.scrollable_overflow
111            .get()
112            .expect("Should only call `scrollable_overflow()` after calculating overflow")
113    }
114
115    /// Calculate the scrollable overflow / scrolling area for this [`FragmentTree`] according
116    /// to <https://drafts.csswg.org/cssom-view/#scrolling-area>.
117    pub(crate) fn calculate_scrollable_overflow(&self) {
118        let scrollable_overflow = || {
119            let Some(first_root_fragment) = self.root_fragments.first() else {
120                return self.initial_containing_block;
121            };
122
123            let scrollable_overflow = self.root_fragments.iter().fold(
124                self.initial_containing_block,
125                |overflow, fragment| {
126                    // Need to calculate the overflow for each fragments within the tree
127                    // because it is required in the next stages of reflow.
128                    let overflow_from_fragment =
129                        fragment.calculate_scrollable_overflow_for_parent();
130
131                    // Scrollable overflow should be accumulated in the block that
132                    // establishes the containing block for the element. Thus, fixed
133                    // positioned fragments whose containing block is the initial
134                    // containing block should not be included in overflow calculation.
135                    // See <https://www.w3.org/TR/css-overflow-3/#scrollable>.
136                    if fragment
137                        .retrieve_box_fragment()
138                        .is_some_and(|box_fragment| {
139                            box_fragment.borrow().style.get_box().position == Position::Fixed
140                        })
141                    {
142                        return overflow;
143                    }
144
145                    overflow.union(&overflow_from_fragment)
146                },
147            );
148
149            // Assuming that the first fragment is the root element, ensure that
150            // scrollable overflow that is unreachable is not included in the final
151            // rectangle. See
152            // <https://drafts.csswg.org/css-overflow/#scrolling-direction>.
153            let first_root_fragment = match first_root_fragment {
154                Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.borrow(),
155                _ => return scrollable_overflow,
156            };
157            if !first_root_fragment.is_root_element() {
158                return scrollable_overflow;
159            }
160            first_root_fragment.clip_wholly_unreachable_scrollable_overflow(
161                scrollable_overflow,
162                self.initial_containing_block,
163            )
164        };
165
166        self.scrollable_overflow.set(Some(scrollable_overflow()))
167    }
168
169    pub(crate) fn find<T>(
170        &self,
171        mut process_func: impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
172    ) -> Option<T> {
173        let info = ContainingBlockManager {
174            for_non_absolute_descendants: &self.initial_containing_block,
175            for_absolute_descendants: None,
176            for_absolute_and_fixed_descendants: &self.initial_containing_block,
177        };
178        self.root_fragments
179            .iter()
180            .find_map(|child| child.find(&info, 0, &mut process_func))
181    }
182
183    /// Find the `<body>` element's [`Fragment`], if it exists in this [`FragmentTree`].
184    pub(crate) fn body_fragment(&self) -> Option<ArcRefCell<BoxFragment>> {
185        fn find_body(children: &[Fragment]) -> Option<ArcRefCell<BoxFragment>> {
186            children.iter().find_map(|fragment| {
187                match fragment {
188                    Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
189                        let borrowed_box_fragment = box_fragment.borrow();
190                        if borrowed_box_fragment.is_body_element_of_html_element_root() {
191                            return Some(box_fragment.clone());
192                        }
193
194                        // The fragment for the `<body>` element is typically a child of the root (though,
195                        // not if it's absolutely positioned), so we need to recurse into the children of
196                        // the root to find it.
197                        //
198                        // Additionally, recurse into any anonymous fragments, as the `<body>` fragment may
199                        // have created anonymous parents (for instance by creating an inline formatting context).
200                        if borrowed_box_fragment.is_root_element() ||
201                            borrowed_box_fragment.base.is_anonymous()
202                        {
203                            find_body(&borrowed_box_fragment.children)
204                        } else {
205                            None
206                        }
207                    },
208                    Fragment::Positioning(positioning_context)
209                        if positioning_context.borrow().base.is_anonymous() =>
210                    {
211                        // If the `<body>` element is a `display: inline` then it might be nested inside of a
212                        // `PositioningFragment` for the purposes of putting it on the first line of the implied
213                        // inline formatting context.
214                        find_body(&positioning_context.borrow().children)
215                    },
216                    _ => None,
217                }
218            })
219        }
220
221        find_body(&self.root_fragments)
222    }
223}