aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2025-04-18 16:19:24 +0200
committerMartin Robinson <mrobinson@igalia.com>2025-04-18 16:19:24 +0200
commit05930f7ed491cdb540f515de882e2f5d78d010a6 (patch)
tree638cd4937d83766f1c9c6f797d0f1dd817cca04f
parent78847cd64a6633ee9ef9646524cf5a1fce1aa041 (diff)
downloadservo-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.rs53
-rw-r--r--components/layout_2020/fragment_tree/fragment_tree.rs83
-rw-r--r--components/layout_2020/query.rs46
-rw-r--r--components/layout_thread_2020/lib.rs19
-rw-r--r--components/script/dom/window.rs12
-rw-r--r--components/shared/script_layout/lib.rs6
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,