/* 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 script_traits::compositor::ScrollSensitivity; 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::{physical_rect_to_au_rect, 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>, /// The scrollable overflow rectangle for the entire tree /// pub(crate) scrollable_overflow: PhysicalRect, /// The containing block used in the layout of this fragment tree. pub(crate) initial_containing_block: PhysicalRect, /// #[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.px(), self.scrollable_overflow.size.height.px(), )) } pub(crate) fn find( &self, mut process_func: impl FnMut(&Fragment, usize, &PhysicalRect) -> Option, ) -> Option { 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) { 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> { 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() .to_physical(fragment.style.writing_mode, containing_block), Fragment::Positioning(fragment) => fragment .rect .to_physical(fragment.writing_mode, containing_block), Fragment::Text(fragment) => fragment .rect .to_physical(fragment.parent_style.writing_mode, containing_block), Fragment::AbsoluteOrFixedPositioned(_) | Fragment::Image(_) | Fragment::IFrame(_) => return None, }; content_boxes.push(physical_rect_to_au_rect( fragment_relative_rect.translate(containing_block.origin.to_vector()), )); None::<()> }); content_boxes } pub fn get_border_dimensions_for_node(&self, requested_node: OpaqueNode) -> Rect { 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) => { // 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.style.get_box().display.is_inline_flow() { return Some(Rect::zero()); } let border = fragment.style.get_border(); let padding_rect = fragment .padding_rect() .to_physical(fragment.style.writing_mode, containing_block); Rect::new( Point2D::new( border.border_left_width.into(), border.border_top_width.into(), ), Size2D::new(padding_rect.size.width, padding_rect.size.height), ) }, Fragment::Positioning(fragment) => fragment .rect .to_physical(fragment.writing_mode, containing_block) .cast_unit(), _ => return None, }; let rect = Rect::new( Point2D::new(rect.origin.x.px(), rect.origin.y.px()), Size2D::new(rect.size.width.px(), rect.size.height.px()), ); Some(rect.round().to_i32().to_untyped()) }) .unwrap_or_else(Rect::zero) } pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect { 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 { 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::::zero) } }