diff options
author | Martin Robinson <mrobinson@igalia.com> | 2025-04-18 16:19:24 +0200 |
---|---|---|
committer | Martin Robinson <mrobinson@igalia.com> | 2025-04-18 16:19:24 +0200 |
commit | 05930f7ed491cdb540f515de882e2f5d78d010a6 (patch) | |
tree | 638cd4937d83766f1c9c6f797d0f1dd817cca04f | |
parent | 78847cd64a6633ee9ef9646524cf5a1fce1aa041 (diff) | |
download | servo-05930f7ed491cdb540f515de882e2f5d78d010a6.tar.gz servo-05930f7ed491cdb540f515de882e2f5d78d010a6.zip |
layout: Implement node geometry queries against box tree's `Fragment`
-rw-r--r-- | components/layout_2020/fragment_tree/fragment.rs | 53 | ||||
-rw-r--r-- | components/layout_2020/fragment_tree/fragment_tree.rs | 83 | ||||
-rw-r--r-- | components/layout_2020/query.rs | 46 | ||||
-rw-r--r-- | components/layout_thread_2020/lib.rs | 19 | ||||
-rw-r--r-- | components/script/dom/window.rs | 12 | ||||
-rw-r--r-- | components/shared/script_layout/lib.rs | 6 |
6 files changed, 98 insertions, 121 deletions
diff --git a/components/layout_2020/fragment_tree/fragment.rs b/components/layout_2020/fragment_tree/fragment.rs index f86b026ff24..1907141997e 100644 --- a/components/layout_2020/fragment_tree/fragment.rs +++ b/components/layout_2020/fragment_tree/fragment.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use app_units::Au; use base::id::PipelineId; use base::print_tree::PrintTree; +use euclid::{Point2D, Rect, Size2D, UnknownUnit}; use fonts::{ByteIndex, FontMetrics, GlyphStore}; use range::Range as ServoRange; use servo_arc::Arc as ServoArc; @@ -20,7 +21,7 @@ use super::{ Tag, }; use crate::cell::ArcRefCell; -use crate::geom::{LogicalSides, PhysicalRect}; +use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect}; use crate::style_ext::ComputedValuesExt; #[derive(Clone)] @@ -181,6 +182,56 @@ impl Fragment { } } + pub(crate) fn cumulative_content_box_rect(&self) -> Option<PhysicalRect<Au>> { + match self { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + let fragment = fragment.borrow(); + Some(fragment.offset_by_containing_block(&fragment.border_rect())) + }, + Fragment::Positioning(_) | + Fragment::Text(_) | + Fragment::AbsoluteOrFixedPositioned(_) | + Fragment::Image(_) | + Fragment::IFrame(_) => None, + } + } + + pub(crate) fn client_rect(&self) -> Rect<i32, UnknownUnit> { + let rect = match self { + 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 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 + } + }, + _ => return Rect::zero(), + } + .to_untyped(); + + 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()), + ); + rect.round().to_i32() + } + pub(crate) fn find<T>( &self, manager: &ContainingBlockManager<PhysicalRect<Au>>, diff --git a/components/layout_2020/fragment_tree/fragment_tree.rs b/components/layout_2020/fragment_tree/fragment_tree.rs index 51e4e66bd60..b85dcaa82a9 100644 --- a/components/layout_2020/fragment_tree/fragment_tree.rs +++ b/components/layout_2020/fragment_tree/fragment_tree.rs @@ -5,17 +5,16 @@ use app_units::Au; use base::print_tree::PrintTree; use compositing_traits::display_list::AxesScrollSensitivity; -use euclid::default::{Point2D, Rect, Size2D}; +use euclid::default::Size2D; use fxhash::FxHashSet; use style::animation::AnimationSetKey; -use style::dom::OpaqueNode; use webrender_api::units; -use super::{ContainingBlockManager, Fragment, Tag}; +use super::{ContainingBlockManager, Fragment}; use crate::context::LayoutContext; use crate::display_list::StackingContext; use crate::flow::CanvasBackground; -use crate::geom::{PhysicalPoint, PhysicalRect}; +use crate::geom::PhysicalRect; pub struct FragmentTree { /// Fragments at the top-level of the tree. @@ -137,82 +136,6 @@ impl FragmentTree { .find_map(|child| child.find(&info, 0, &mut process_func)) } - /// 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() { diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs index 3409f7c1923..3badff83672 100644 --- a/components/layout_2020/query.rs +++ b/components/layout_2020/query.rs @@ -46,40 +46,34 @@ use crate::fragment_tree::{ use crate::geom::{PhysicalRect, PhysicalVec}; use crate::taffy::SpecificTaffyGridInfo; -pub fn process_content_box_request( - requested_node: OpaqueNode, - fragment_tree: Option<Arc<FragmentTree>>, -) -> Option<Rect<Au>> { - let rects = fragment_tree?.get_content_boxes_for_node(requested_node); +pub fn process_content_box_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Option<Rect<Au>> { + let rects: Vec<_> = node + .fragments_for_pseudo(None) + .iter() + .filter_map(Fragment::cumulative_content_box_rect) + .collect(); if rects.is_empty() { return None; } - Some( - rects - .iter() - .fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect)), - ) + Some(rects.iter().fold(Rect::zero(), |unioned_rect, rect| { + rect.to_untyped().union(&unioned_rect) + })) } -pub fn process_content_boxes_request( - requested_node: OpaqueNode, - fragment_tree: Option<Arc<FragmentTree>>, -) -> Vec<Rect<Au>> { - fragment_tree - .map(|tree| tree.get_content_boxes_for_node(requested_node)) - .unwrap_or_default() +pub fn process_content_boxes_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Vec<Rect<Au>> { + node.fragments_for_pseudo(None) + .iter() + .filter_map(Fragment::cumulative_content_box_rect) + .map(|rect| rect.to_untyped()) + .collect() } -pub fn process_node_geometry_request( - requested_node: OpaqueNode, - fragment_tree: Option<Arc<FragmentTree>>, -) -> Rect<i32> { - if let Some(fragment_tree) = fragment_tree { - fragment_tree.get_border_dimensions_for_node(requested_node) - } else { - Rect::zero() - } +pub fn process_client_rect_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Rect<i32> { + node.fragments_for_pseudo(None) + .first() + .map(Fragment::client_rect) + .unwrap_or_default() } /// <https://drafts.csswg.org/cssom-view/#scrolling-area> diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 59d047e70cd..eceb763c438 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -31,8 +31,8 @@ use ipc_channel::ipc::IpcSender; use layout::context::LayoutContext; use layout::display_list::{DisplayList, WebRenderImageInfo}; use layout::query::{ - get_the_text_steps, process_content_box_request, process_content_boxes_request, - process_node_geometry_request, process_node_scroll_area_request, process_offset_parent_query, + get_the_text_steps, process_client_rect_request, process_content_box_request, + process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query, process_resolved_font_style_query, process_resolved_style_request, process_text_index_request, }; use layout::traversal::RecalcStyle; @@ -233,24 +233,27 @@ impl Layout for LayoutThread { feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_content_box(&self, node: OpaqueNode) -> Option<UntypedRect<Au>> { - process_content_box_request(node, self.fragment_tree.borrow().clone()) + fn query_content_box(&self, node: TrustedNodeAddress) -> Option<UntypedRect<Au>> { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_content_box_request(node) } #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_content_boxes(&self, node: OpaqueNode) -> Vec<UntypedRect<Au>> { - process_content_boxes_request(node, self.fragment_tree.borrow().clone()) + fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<UntypedRect<Au>> { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_content_boxes_request(node) } #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_client_rect(&self, node: OpaqueNode) -> UntypedRect<i32> { - process_node_geometry_request(node, self.fragment_tree.borrow().clone()) + fn query_client_rect(&self, node: TrustedNodeAddress) -> UntypedRect<i32> { + let node = unsafe { ServoLayoutNode::new(&node) }; + process_client_rect_request(node) } #[cfg_attr( diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 6ba22591641..a4c7d34d48d 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -2251,7 +2251,9 @@ impl Window { // Query content box without considering any reflow pub(crate) fn content_box_query_unchecked(&self, node: &Node) -> Option<UntypedRect<Au>> { - self.layout.borrow().query_content_box(node.to_opaque()) + self.layout + .borrow() + .query_content_box(node.to_trusted_node_address()) } pub(crate) fn content_box_query(&self, node: &Node, can_gc: CanGc) -> Option<UntypedRect<Au>> { @@ -2265,14 +2267,18 @@ impl Window { if !self.layout_reflow(QueryMsg::ContentBoxes, can_gc) { return vec![]; } - self.layout.borrow().query_content_boxes(node.to_opaque()) + self.layout + .borrow() + .query_content_boxes(node.to_trusted_node_address()) } pub(crate) fn client_rect_query(&self, node: &Node, can_gc: CanGc) -> UntypedRect<i32> { if !self.layout_reflow(QueryMsg::ClientRectQuery, can_gc) { return Rect::zero(); } - self.layout.borrow().query_client_rect(node.to_opaque()) + self.layout + .borrow() + .query_client_rect(node.to_trusted_node_address()) } /// Find the scroll area of the given node, if it is not None. If the node diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs index 69e577e139d..6efbb2ae3eb 100644 --- a/components/shared/script_layout/lib.rs +++ b/components/shared/script_layout/lib.rs @@ -240,9 +240,9 @@ pub trait Layout { /// Set the scroll states of this layout after a compositor scroll. fn set_scroll_offsets(&mut self, scroll_states: &[ScrollState]); - fn query_content_box(&self, node: OpaqueNode) -> Option<Rect<Au>>; - fn query_content_boxes(&self, node: OpaqueNode) -> Vec<Rect<Au>>; - fn query_client_rect(&self, node: OpaqueNode) -> Rect<i32>; + fn query_content_box(&self, node: TrustedNodeAddress) -> Option<Rect<Au>>; + fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<Rect<Au>>; + fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>; fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String; fn query_nodes_from_point( &self, |