mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
191 lines
7.6 KiB
Rust
191 lines
7.6 KiB
Rust
/* 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(),
|
||
_ => 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)
|
||
}
|
||
}
|