layout: Implement node geometry queries against BoxTree's Fragment (#36663)

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.

Testing: There are tests for these changes.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-04-25 15:38:05 +02:00 committed by GitHub
parent cc91395397
commit b63a1818c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 197 additions and 170 deletions

View file

@ -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() {