mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
197 lines
7.3 KiB
Rust
197 lines
7.3 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 euclid::default::{Point2D, Rect, Size2D};
|
||
use fxhash::FxHashSet;
|
||
use gfx_traits::print_tree::PrintTree;
|
||
use serde::Serialize;
|
||
use style::animation::AnimationSetKey;
|
||
use style::dom::OpaqueNode;
|
||
use style::values::computed::Length;
|
||
use webrender_api::units;
|
||
|
||
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<Length>,
|
||
|
||
/// The containing block used in the layout of this fragment tree.
|
||
pub(crate) initial_containing_block: PhysicalRect<Length>,
|
||
|
||
/// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>
|
||
#[serde(skip)]
|
||
pub(crate) canvas_background: CanvasBackground,
|
||
}
|
||
|
||
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.px(),
|
||
self.scrollable_overflow.size.height.px(),
|
||
))
|
||
}
|
||
|
||
pub(crate) fn find<T>(
|
||
&self,
|
||
mut process_func: impl FnMut(&Fragment, usize, &PhysicalRect<Length>) -> 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::<()>
|
||
});
|
||
}
|
||
|
||
pub fn get_content_box_for_node(&self, requested_node: OpaqueNode) -> Rect<Au> {
|
||
let mut bounding_box = PhysicalRect::zero();
|
||
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()
|
||
.to_physical(fragment.style.writing_mode, containing_block),
|
||
Fragment::Text(fragment) => fragment
|
||
.rect
|
||
.to_physical(fragment.parent_style.writing_mode, containing_block),
|
||
Fragment::AbsoluteOrFixedPositioned(_) |
|
||
Fragment::Image(_) |
|
||
Fragment::IFrame(_) |
|
||
Fragment::Anonymous(_) => return None,
|
||
};
|
||
|
||
bounding_box = fragment_relative_rect
|
||
.translate(containing_block.origin.to_vector())
|
||
.union(&bounding_box);
|
||
None::<()>
|
||
});
|
||
|
||
Rect::new(
|
||
Point2D::new(
|
||
Au::from_f32_px(bounding_box.origin.x.px()),
|
||
Au::from_f32_px(bounding_box.origin.y.px()),
|
||
),
|
||
Size2D::new(
|
||
Au::from_f32_px(bounding_box.size.width.px()),
|
||
Au::from_f32_px(bounding_box.size.height.px()),
|
||
),
|
||
)
|
||
}
|
||
|
||
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 (style, padding_rect) = match fragment {
|
||
Fragment::Box(fragment) => (&fragment.style, fragment.padding_rect()),
|
||
_ => return None,
|
||
};
|
||
|
||
// 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 display = &style.get_box().display;
|
||
if display.inside() == style::values::specified::box_::DisplayInside::Flow &&
|
||
display.outside() == style::values::specified::box_::DisplayOutside::Inline
|
||
{
|
||
return Some(Rect::zero());
|
||
}
|
||
|
||
let padding_rect = padding_rect.to_physical(style.writing_mode, containing_block);
|
||
let border = style.get_border();
|
||
Some(Rect::new(
|
||
Point2D::new(
|
||
border.border_left_width.to_px(),
|
||
border.border_top_width.to_px(),
|
||
),
|
||
Size2D::new(
|
||
padding_rect.size.width.px() as i32,
|
||
padding_rect.size.height.px() as i32,
|
||
),
|
||
))
|
||
})
|
||
.unwrap_or_else(Rect::zero)
|
||
}
|
||
|
||
pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect<Length> {
|
||
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<Length> {
|
||
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::<Length>::zero)
|
||
}
|
||
}
|