aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout/fragment_tree
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2025-04-18 16:19:24 +0200
committerMartin Robinson <mrobinson@igalia.com>2025-04-22 16:37:03 +0200
commit57428bc5da0b73f6fc81510a1aa7816e720baf14 (patch)
treeb1587a3e79a2984bea0e3a9f80ebc87830472c51 /components/layout/fragment_tree
parent73703e75ba17ca2b4f7999c62c1585360bda5ed6 (diff)
downloadservo-57428bc5da0b73f6fc81510a1aa7816e720baf14.tar.gz
servo-57428bc5da0b73f6fc81510a1aa7816e720baf14.zip
layout: Implement node geometry queries against `BoxTree`'s `Fragment`
This is a followup to #36629, continuing to implement script-based layout queries using the `Fragment`s attached to the `BoxTree`. In this change, geometry queris (apart from parent offset) are calculated using `Fragment`s hanging of the `BoxTree`. In order to make this work, all `Fragment`s for inlines split by blocks, need to be accessible in the `BoxTree`. This required some changes to the way that box tree items were stored in DOM `BoxSlot`s. Now every inline level item can have more than a single `BoxTree` item. These are carefully collected by the `InlineFormattingContextBuilder` -- currently a bit fragile, but with more documentation. Co-authored-by: Oriol Brufau <obrufau@igalia.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'components/layout/fragment_tree')
-rw-r--r--components/layout/fragment_tree/fragment.rs53
-rw-r--r--components/layout/fragment_tree/fragment_tree.rs83
2 files changed, 55 insertions, 81 deletions
diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs
index c08ddae55e9..ceccd1ec304 100644
--- a/components/layout/fragment_tree/fragment.rs
+++ b/components/layout/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 malloc_size_of_derive::MallocSizeOf;
use range::Range as ServoRange;
@@ -21,7 +22,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, MallocSizeOf)]
@@ -190,6 +191,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/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs
index 589ae69e8e5..3a082c99389 100644
--- a/components/layout/fragment_tree/fragment_tree.rs
+++ b/components/layout/fragment_tree/fragment_tree.rs
@@ -5,18 +5,17 @@
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 malloc_size_of_derive::MallocSizeOf;
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;
#[derive(MallocSizeOf)]
pub struct FragmentTree {
@@ -139,82 +138,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() {