/* 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 style::animation::AnimationSetKey;
use style::dom::OpaqueNode;
use webrender_api::units;
use webrender_traits::display_list::AxesScrollSensitivity;
use super::{ContainingBlockManager, Fragment, Tag};
use crate::display_list::StackingContext;
use crate::flow::CanvasBackground;
use crate::geom::{PhysicalPoint, PhysicalRect};
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<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>
pub(crate) canvas_background: CanvasBackground,
/// Whether or not the viewport is sensitive to scroll input events.
pub viewport_scroll_sensitivity: AxesScrollSensitivity,
}
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.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.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.borrow().border_rect()
},
Fragment::Positioning(fragment) => fragment.borrow().rect,
Fragment::Text(fragment) => fragment.borrow().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.
let fragment = fragment.borrow();
if fragment.is_inline_box() {
return Some(Rect::zero());
}
if fragment.is_table_wrapper() {
// For tables the border actually belongs to the table grid box,
// so we need to include it in the dimension of the table wrapper box.
let mut rect = fragment.border_rect();
rect.origin = PhysicalPoint::zero();
rect
} else {
let mut rect = fragment.padding_rect();
rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
rect
}
},
Fragment::Positioning(fragment) => fragment.borrow().rect.cast_unit(),
Fragment::Text(text_fragment) => text_fragment.borrow().rect,
_ => 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
.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)
}
}