1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use app_units::Au;
use base::print_tree::PrintTree;
use euclid::default::{Point2D, Rect, Size2D};
use fxhash::FxHashSet;
use serde::Serialize;
use style::animation::AnimationSetKey;
use style::dom::OpaqueNode;
use webrender_api::units;
use webrender_traits::display_list::ScrollSensitivity;

use super::{ContainingBlockManager, Fragment, Tag};
use crate::cell::ArcRefCell;
use crate::display_list::StackingContext;
use crate::flow::CanvasBackground;
use crate::geom::PhysicalRect;

#[derive(Serialize)]
pub struct FragmentTree {
    /// Fragments at the top-level of the tree.
    ///
    /// If the root element has `display: none`, there are zero fragments.
    /// Otherwise, there is at least one:
    ///
    /// * The first fragment is generated by the root element.
    /// * There may be additional fragments generated by positioned boxes
    ///   that have the initial containing block.
    pub(crate) root_fragments: Vec<ArcRefCell<Fragment>>,

    /// The scrollable overflow rectangle for the entire tree
    /// <https://drafts.csswg.org/css-overflow/#scrollable>
    pub(crate) scrollable_overflow: PhysicalRect<Au>,

    /// The containing block used in the layout of this fragment tree.
    pub(crate) initial_containing_block: PhysicalRect<Au>,

    /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>
    #[serde(skip)]
    pub(crate) canvas_background: CanvasBackground,

    /// Whether or not the root element is sensitive to scroll input events.
    pub root_scroll_sensitivity: ScrollSensitivity,
}

impl FragmentTree {
    pub(crate) fn build_display_list(
        &self,
        builder: &mut crate::display_list::DisplayListBuilder,
        root_stacking_context: &StackingContext,
    ) {
        // Paint the canvas’ background (if any) before/under everything else
        root_stacking_context.build_canvas_background_display_list(
            builder,
            self,
            &self.initial_containing_block,
        );
        root_stacking_context.build_display_list(builder);
    }

    pub fn print(&self) {
        let mut print_tree = PrintTree::new("Fragment Tree".to_string());
        for fragment in &self.root_fragments {
            fragment.borrow().print(&mut print_tree);
        }
    }

    pub fn scrollable_overflow(&self) -> units::LayoutSize {
        units::LayoutSize::from_untyped(Size2D::new(
            self.scrollable_overflow.size.width.to_f32_px(),
            self.scrollable_overflow.size.height.to_f32_px(),
        ))
    }

    pub(crate) fn find<T>(
        &self,
        mut process_func: impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
    ) -> Option<T> {
        let info = ContainingBlockManager {
            for_non_absolute_descendants: &self.initial_containing_block,
            for_absolute_descendants: None,
            for_absolute_and_fixed_descendants: &self.initial_containing_block,
        };
        self.root_fragments
            .iter()
            .find_map(|child| child.borrow().find(&info, 0, &mut process_func))
    }

    pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet<AnimationSetKey>) {
        self.find(|fragment, _, _| {
            let tag = fragment.tag()?;
            set.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
            None::<()>
        });
    }

    /// Get the vector of rectangles that surrounds the fragments of the node with the given address.
    /// This function answers the `getClientRects()` query and the union of the rectangles answers
    /// the `getBoundingClientRect()` query.
    ///
    /// TODO: This function is supposed to handle scroll offsets, but that isn't happening at all.
    pub fn get_content_boxes_for_node(&self, requested_node: OpaqueNode) -> Vec<Rect<Au>> {
        let mut content_boxes = Vec::new();
        let tag_to_find = Tag::new(requested_node);
        self.find(|fragment, _, containing_block| {
            if fragment.tag() != Some(tag_to_find) {
                return None::<()>;
            }

            let fragment_relative_rect = match fragment {
                Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.border_rect(),
                Fragment::Positioning(fragment) => fragment.rect,
                Fragment::Text(fragment) => fragment.rect,
                Fragment::AbsoluteOrFixedPositioned(_) |
                Fragment::Image(_) |
                Fragment::IFrame(_) => return None,
            };

            let rect = fragment_relative_rect.translate(containing_block.origin.to_vector());

            content_boxes.push(rect.to_untyped());
            None::<()>
        });
        content_boxes
    }

    pub fn get_border_dimensions_for_node(&self, requested_node: OpaqueNode) -> Rect<i32> {
        let tag_to_find = Tag::new(requested_node);
        self.find(|fragment, _, _containing_block| {
            if fragment.tag() != Some(tag_to_find) {
                return None;
            }

            let rect = match fragment {
                Fragment::Box(fragment) | Fragment::Float(fragment) => {
                    // https://drafts.csswg.org/cssom-view/#dom-element-clienttop
                    // " If the element has no associated CSS layout box or if the
                    //   CSS layout box is inline, return zero." For this check we
                    // also explicitly ignore the list item portion of the display
                    // style.
                    if fragment.is_inline_box() {
                        return Some(Rect::zero());
                    }

                    let border = fragment.style.get_border();
                    let padding_rect = fragment.padding_rect();
                    Rect::new(
                        Point2D::new(border.border_left_width, border.border_top_width),
                        Size2D::new(padding_rect.size.width, padding_rect.size.height),
                    )
                },
                Fragment::Positioning(fragment) => fragment.rect.cast_unit(),
                _ => return None,
            };

            let rect = Rect::new(
                Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
                Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
            );
            Some(rect.round().to_i32().to_untyped())
        })
        .unwrap_or_else(Rect::zero)
    }

    pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect<Au> {
        let mut scroll_area = self.initial_containing_block;
        for fragment in self.root_fragments.iter() {
            scroll_area = fragment
                .borrow()
                .scrolling_area(&self.initial_containing_block)
                .union(&scroll_area);
        }
        scroll_area
    }

    pub fn get_scrolling_area_for_node(&self, requested_node: OpaqueNode) -> PhysicalRect<Au> {
        let tag_to_find = Tag::new(requested_node);
        let scroll_area = self.find(|fragment, _, containing_block| {
            if fragment.tag() == Some(tag_to_find) {
                Some(fragment.scrolling_area(containing_block))
            } else {
                None
            }
        });
        scroll_area.unwrap_or_else(PhysicalRect::<Au>::zero)
    }
}