mirror of
https://github.com/servo/servo.git
synced 2025-08-02 12:10:29 +01:00
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:
parent
cc91395397
commit
b63a1818c4
13 changed files with 197 additions and 170 deletions
|
@ -61,7 +61,7 @@ impl InnerDOMLayoutData {
|
||||||
pub(super) enum LayoutBox {
|
pub(super) enum LayoutBox {
|
||||||
DisplayContents,
|
DisplayContents,
|
||||||
BlockLevel(ArcRefCell<BlockLevelBox>),
|
BlockLevel(ArcRefCell<BlockLevelBox>),
|
||||||
InlineLevel(ArcRefCell<InlineItem>),
|
InlineLevel(Vec<ArcRefCell<InlineItem>>),
|
||||||
FlexLevel(ArcRefCell<FlexLevelBox>),
|
FlexLevel(ArcRefCell<FlexLevelBox>),
|
||||||
TableLevelBox(TableLevelBox),
|
TableLevelBox(TableLevelBox),
|
||||||
TaffyItemBox(ArcRefCell<TaffyItemBox>),
|
TaffyItemBox(ArcRefCell<TaffyItemBox>),
|
||||||
|
@ -74,8 +74,10 @@ impl LayoutBox {
|
||||||
LayoutBox::BlockLevel(block_level_box) => {
|
LayoutBox::BlockLevel(block_level_box) => {
|
||||||
block_level_box.borrow().invalidate_cached_fragment()
|
block_level_box.borrow().invalidate_cached_fragment()
|
||||||
},
|
},
|
||||||
LayoutBox::InlineLevel(inline_item) => {
|
LayoutBox::InlineLevel(inline_items) => {
|
||||||
inline_item.borrow().invalidate_cached_fragment()
|
for inline_item in inline_items.iter() {
|
||||||
|
inline_item.borrow().invalidate_cached_fragment()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
LayoutBox::FlexLevel(flex_level_box) => {
|
LayoutBox::FlexLevel(flex_level_box) => {
|
||||||
flex_level_box.borrow().invalidate_cached_fragment()
|
flex_level_box.borrow().invalidate_cached_fragment()
|
||||||
|
@ -91,7 +93,10 @@ impl LayoutBox {
|
||||||
match self {
|
match self {
|
||||||
LayoutBox::DisplayContents => vec![],
|
LayoutBox::DisplayContents => vec![],
|
||||||
LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().fragments(),
|
LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().fragments(),
|
||||||
LayoutBox::InlineLevel(inline_item) => inline_item.borrow().fragments(),
|
LayoutBox::InlineLevel(inline_items) => inline_items
|
||||||
|
.iter()
|
||||||
|
.flat_map(|inline_item| inline_item.borrow().fragments())
|
||||||
|
.collect(),
|
||||||
LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().fragments(),
|
LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().fragments(),
|
||||||
LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().fragments(),
|
LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().fragments(),
|
||||||
LayoutBox::TableLevelBox(table_box) => table_box.fragments(),
|
LayoutBox::TableLevelBox(table_box) => table_box.fragments(),
|
||||||
|
|
|
@ -458,15 +458,14 @@ where
|
||||||
self.propagated_data.without_text_decorations(),
|
self.propagated_data.without_text_decorations(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
box_slot.set(LayoutBox::InlineLevel(atomic));
|
box_slot.set(LayoutBox::InlineLevel(vec![atomic]));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Otherwise, this is just a normal inline box. Whatever happened before, all we need to do
|
// Otherwise, this is just a normal inline box. Whatever happened before, all we need to do
|
||||||
// before recurring is to remember this ongoing inline level box.
|
// before recurring is to remember this ongoing inline level box.
|
||||||
let inline_item = self
|
self.inline_formatting_context_builder
|
||||||
.inline_formatting_context_builder
|
.start_inline_box(InlineBox::new(info), None);
|
||||||
.start_inline_box(InlineBox::new(info));
|
|
||||||
|
|
||||||
if is_list_item {
|
if is_list_item {
|
||||||
if let Some((marker_info, marker_contents)) =
|
if let Some((marker_info, marker_contents)) =
|
||||||
|
@ -486,8 +485,14 @@ where
|
||||||
|
|
||||||
self.finish_anonymous_table_if_needed();
|
self.finish_anonymous_table_if_needed();
|
||||||
|
|
||||||
self.inline_formatting_context_builder.end_inline_box();
|
// As we are ending this inline box, during the course of the `traverse()` above, the ongoing
|
||||||
box_slot.set(LayoutBox::InlineLevel(inline_item));
|
// inline formatting context may have been split around block-level elements. In that case,
|
||||||
|
// more than a single inline box tree item may have been produced for this inline-level box.
|
||||||
|
// `InlineFormattingContextBuilder::end_inline_box()` is returning all of those box tree
|
||||||
|
// items.
|
||||||
|
box_slot.set(LayoutBox::InlineLevel(
|
||||||
|
self.inline_formatting_context_builder.end_inline_box(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_block_level_element(
|
fn handle_block_level_element(
|
||||||
|
@ -574,7 +579,7 @@ where
|
||||||
display_inside,
|
display_inside,
|
||||||
contents,
|
contents,
|
||||||
));
|
));
|
||||||
box_slot.set(LayoutBox::InlineLevel(inline_level_box));
|
box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,7 +612,7 @@ where
|
||||||
contents,
|
contents,
|
||||||
self.propagated_data,
|
self.propagated_data,
|
||||||
));
|
));
|
||||||
box_slot.set(LayoutBox::InlineLevel(inline_level_box));
|
box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::borrow::Cow;
|
||||||
use std::char::{ToLowercase, ToUppercase};
|
use std::char::{ToLowercase, ToUppercase};
|
||||||
|
|
||||||
use icu_segmenter::WordSegmenter;
|
use icu_segmenter::WordSegmenter;
|
||||||
|
use itertools::izip;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
|
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
|
||||||
use style::values::specified::text::TextTransformCase;
|
use style::values::specified::text::TextTransformCase;
|
||||||
|
@ -67,6 +68,16 @@ pub(crate) struct InlineFormattingContextBuilder {
|
||||||
/// When an inline box ends, it's removed from this stack.
|
/// When an inline box ends, it's removed from this stack.
|
||||||
inline_box_stack: Vec<InlineBoxIdentifier>,
|
inline_box_stack: Vec<InlineBoxIdentifier>,
|
||||||
|
|
||||||
|
/// Normally, an inline box produces a single box tree [`InlineItem`]. When a block
|
||||||
|
/// element causes an inline box [to be split], it can produce multiple
|
||||||
|
/// [`InlineItem`]s, all inserted into different [`InlineFormattingContext`]s.
|
||||||
|
/// [`Self::block_in_inline_splits`] is responsible for tracking all of these split
|
||||||
|
/// inline box results, so that they can be inserted into the [`crate::dom::BoxSlot`]
|
||||||
|
/// for the DOM element once it has been processed for BoxTree construction.
|
||||||
|
///
|
||||||
|
/// [to be split]: https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level
|
||||||
|
block_in_inline_splits: Vec<Vec<ArcRefCell<InlineItem>>>,
|
||||||
|
|
||||||
/// Whether or not the inline formatting context under construction has any
|
/// Whether or not the inline formatting context under construction has any
|
||||||
/// uncollapsible text content.
|
/// uncollapsible text content.
|
||||||
pub has_uncollapsible_text_content: bool,
|
pub has_uncollapsible_text_content: bool,
|
||||||
|
@ -162,29 +173,42 @@ impl InlineFormattingContextBuilder {
|
||||||
inline_level_box
|
inline_level_box
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) -> ArcRefCell<InlineItem> {
|
pub(crate) fn start_inline_box(
|
||||||
|
&mut self,
|
||||||
|
inline_box: InlineBox,
|
||||||
|
block_in_inline_splits: Option<Vec<ArcRefCell<InlineItem>>>,
|
||||||
|
) {
|
||||||
self.push_control_character_string(inline_box.base.style.bidi_control_chars().0);
|
self.push_control_character_string(inline_box.base.style.bidi_control_chars().0);
|
||||||
|
|
||||||
let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box);
|
let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box);
|
||||||
let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box));
|
let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box));
|
||||||
self.inline_items.push(inline_level_box.clone());
|
self.inline_items.push(inline_level_box.clone());
|
||||||
self.inline_box_stack.push(identifier);
|
self.inline_box_stack.push(identifier);
|
||||||
inline_level_box
|
|
||||||
|
let mut block_in_inline_splits = block_in_inline_splits.unwrap_or_default();
|
||||||
|
block_in_inline_splits.push(inline_level_box);
|
||||||
|
self.block_in_inline_splits.push(block_in_inline_splits);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_inline_box(&mut self) -> ArcRefCell<InlineBox> {
|
/// End the ongoing inline box in this [`InlineFormattingContextBuilder`], returning
|
||||||
let identifier = self.end_inline_box_internal();
|
/// shared references to all of the box tree items that were created for it. More than
|
||||||
|
/// a single box tree items may be produced for a single inline box when that inline
|
||||||
|
/// box is split around a block-level element.
|
||||||
|
pub(crate) fn end_inline_box(&mut self) -> Vec<ArcRefCell<InlineItem>> {
|
||||||
|
let (identifier, block_in_inline_splits) = self.end_inline_box_internal();
|
||||||
let inline_level_box = self.inline_boxes.get(&identifier);
|
let inline_level_box = self.inline_boxes.get(&identifier);
|
||||||
inline_level_box.borrow_mut().is_last_fragment = true;
|
{
|
||||||
|
let mut inline_level_box = inline_level_box.borrow_mut();
|
||||||
|
inline_level_box.is_last_split = true;
|
||||||
|
self.push_control_character_string(inline_level_box.base.style.bidi_control_chars().1);
|
||||||
|
}
|
||||||
|
|
||||||
self.push_control_character_string(
|
block_in_inline_splits.unwrap_or_default()
|
||||||
inline_level_box.borrow().base.style.bidi_control_chars().1,
|
|
||||||
);
|
|
||||||
|
|
||||||
inline_level_box
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier {
|
fn end_inline_box_internal(
|
||||||
|
&mut self,
|
||||||
|
) -> (InlineBoxIdentifier, Option<Vec<ArcRefCell<InlineItem>>>) {
|
||||||
let identifier = self
|
let identifier = self
|
||||||
.inline_box_stack
|
.inline_box_stack
|
||||||
.pop()
|
.pop()
|
||||||
|
@ -193,7 +217,12 @@ impl InlineFormattingContextBuilder {
|
||||||
.push(ArcRefCell::new(InlineItem::EndInlineBox));
|
.push(ArcRefCell::new(InlineItem::EndInlineBox));
|
||||||
|
|
||||||
self.inline_boxes.end_inline_box(identifier);
|
self.inline_boxes.end_inline_box(identifier);
|
||||||
identifier
|
|
||||||
|
// This might be `None` if this builder has already drained its block-in-inline-splits
|
||||||
|
// into the new builder on the other side of a new block-in-inline split.
|
||||||
|
let block_in_inline_splits = self.block_in_inline_splits.pop();
|
||||||
|
|
||||||
|
(identifier, block_in_inline_splits)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>(
|
pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>(
|
||||||
|
@ -295,12 +324,21 @@ impl InlineFormattingContextBuilder {
|
||||||
// marked as not being the first fragment. No inline content is carried over to this new
|
// marked as not being the first fragment. No inline content is carried over to this new
|
||||||
// builder.
|
// builder.
|
||||||
let mut new_builder = InlineFormattingContextBuilder::new();
|
let mut new_builder = InlineFormattingContextBuilder::new();
|
||||||
for identifier in self.inline_box_stack.iter() {
|
let block_in_inline_splits = std::mem::take(&mut self.block_in_inline_splits);
|
||||||
|
for (identifier, historical_inline_boxes) in
|
||||||
|
izip!(self.inline_box_stack.iter(), block_in_inline_splits)
|
||||||
|
{
|
||||||
|
// Start a new inline box for every ongoing inline box in this
|
||||||
|
// InlineFormattingContext once we are done processing this block element,
|
||||||
|
// being sure to give the block-in-inline-split to the new
|
||||||
|
// InlineFormattingContext. These will finally be inserted into the DOM's
|
||||||
|
// BoxSlot once the inline box has been fully processed.
|
||||||
new_builder.start_inline_box(
|
new_builder.start_inline_box(
|
||||||
self.inline_boxes
|
self.inline_boxes
|
||||||
.get(identifier)
|
.get(identifier)
|
||||||
.borrow()
|
.borrow()
|
||||||
.split_around_block(),
|
.split_around_block(),
|
||||||
|
Some(historical_inline_boxes),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mut inline_builder_from_before_split = std::mem::replace(self, new_builder);
|
let mut inline_builder_from_before_split = std::mem::replace(self, new_builder);
|
||||||
|
|
|
@ -23,8 +23,12 @@ pub(crate) struct InlineBox {
|
||||||
pub base: LayoutBoxBase,
|
pub base: LayoutBoxBase,
|
||||||
/// The identifier of this inline box in the containing [`super::InlineFormattingContext`].
|
/// The identifier of this inline box in the containing [`super::InlineFormattingContext`].
|
||||||
pub(super) identifier: InlineBoxIdentifier,
|
pub(super) identifier: InlineBoxIdentifier,
|
||||||
pub is_first_fragment: bool,
|
/// Whether or not this is the first instance of an [`InlineBox`] before a possible
|
||||||
pub is_last_fragment: bool,
|
/// block-in-inline split. When no split occurs, this is always true.
|
||||||
|
pub is_first_split: bool,
|
||||||
|
/// Whether or not this is the last instance of an [`InlineBox`] before a possible
|
||||||
|
/// block-in-inline split. When no split occurs, this is always true.
|
||||||
|
pub is_last_split: bool,
|
||||||
/// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store.
|
/// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store.
|
||||||
/// This is initialized during IFC shaping.
|
/// This is initialized during IFC shaping.
|
||||||
pub default_font_index: Option<usize>,
|
pub default_font_index: Option<usize>,
|
||||||
|
@ -36,8 +40,8 @@ impl InlineBox {
|
||||||
base: LayoutBoxBase::new(info.into(), info.style.clone()),
|
base: LayoutBoxBase::new(info.into(), info.style.clone()),
|
||||||
// This will be assigned later, when the box is actually added to the IFC.
|
// This will be assigned later, when the box is actually added to the IFC.
|
||||||
identifier: InlineBoxIdentifier::default(),
|
identifier: InlineBoxIdentifier::default(),
|
||||||
is_first_fragment: true,
|
is_first_split: true,
|
||||||
is_last_fragment: false,
|
is_last_split: false,
|
||||||
default_font_index: None,
|
default_font_index: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,8 +49,8 @@ impl InlineBox {
|
||||||
pub(crate) fn split_around_block(&self) -> Self {
|
pub(crate) fn split_around_block(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()),
|
base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()),
|
||||||
is_first_fragment: false,
|
is_first_split: false,
|
||||||
is_last_fragment: false,
|
is_last_split: false,
|
||||||
..*self
|
..*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -744,7 +744,7 @@ impl InlineFormattingContextLayout<'_> {
|
||||||
self.containing_block,
|
self.containing_block,
|
||||||
self.layout_context,
|
self.layout_context,
|
||||||
self.current_inline_container_state(),
|
self.current_inline_container_state(),
|
||||||
inline_box.is_last_fragment,
|
inline_box.is_last_split,
|
||||||
inline_box
|
inline_box
|
||||||
.default_font_index
|
.default_font_index
|
||||||
.map(|index| &self.ifc.font_metrics[index].metrics),
|
.map(|index| &self.ifc.font_metrics[index].metrics),
|
||||||
|
@ -773,7 +773,7 @@ impl InlineFormattingContextLayout<'_> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if inline_box.is_first_fragment {
|
if inline_box.is_first_split {
|
||||||
self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start +
|
self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start +
|
||||||
inline_box_state.pbm.border.inline_start +
|
inline_box_state.pbm.border.inline_start +
|
||||||
inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
|
inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
|
||||||
|
@ -2349,10 +2349,10 @@ impl<'layout_data> ContentSizesComputation<'layout_data> {
|
||||||
.auto_is(Au::zero);
|
.auto_is(Au::zero);
|
||||||
|
|
||||||
let pbm = margin + padding + border;
|
let pbm = margin + padding + border;
|
||||||
if inline_box.is_first_fragment {
|
if inline_box.is_first_split {
|
||||||
self.add_inline_size(pbm.inline_start);
|
self.add_inline_size(pbm.inline_start);
|
||||||
}
|
}
|
||||||
if inline_box.is_last_fragment {
|
if inline_box.is_last_split {
|
||||||
self.ending_inline_pbm_stack.push(pbm.inline_end);
|
self.ending_inline_pbm_stack.push(pbm.inline_end);
|
||||||
} else {
|
} else {
|
||||||
self.ending_inline_pbm_stack.push(Au::zero());
|
self.ending_inline_pbm_stack.push(Au::zero());
|
||||||
|
|
|
@ -195,16 +195,17 @@ impl BoxTree {
|
||||||
},
|
},
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() {
|
LayoutBox::InlineLevel(inline_level_items) => {
|
||||||
InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index)
|
let inline_level_box = inline_level_items.first()?;
|
||||||
if box_style.position.is_absolutely_positioned() =>
|
let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) =
|
||||||
{
|
&*inline_level_box.borrow()
|
||||||
UpdatePoint::AbsolutelyPositionedInlineLevelBox(
|
else {
|
||||||
inline_level_box.clone(),
|
return None;
|
||||||
*text_offset_index,
|
};
|
||||||
)
|
UpdatePoint::AbsolutelyPositionedInlineLevelBox(
|
||||||
},
|
inline_level_box.clone(),
|
||||||
_ => return None,
|
*text_offset_index,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
|
LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
|
||||||
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
|
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::sync::Arc;
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use base::id::PipelineId;
|
use base::id::PipelineId;
|
||||||
use base::print_tree::PrintTree;
|
use base::print_tree::PrintTree;
|
||||||
|
use euclid::{Point2D, Rect, Size2D, UnknownUnit};
|
||||||
use fonts::{ByteIndex, FontMetrics, GlyphStore};
|
use fonts::{ByteIndex, FontMetrics, GlyphStore};
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use range::Range as ServoRange;
|
use range::Range as ServoRange;
|
||||||
|
@ -21,7 +22,7 @@ use super::{
|
||||||
Tag,
|
Tag,
|
||||||
};
|
};
|
||||||
use crate::cell::ArcRefCell;
|
use crate::cell::ArcRefCell;
|
||||||
use crate::geom::{LogicalSides, PhysicalRect};
|
use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
|
||||||
use crate::style_ext::ComputedValuesExt;
|
use crate::style_ext::ComputedValuesExt;
|
||||||
|
|
||||||
#[derive(Clone, MallocSizeOf)]
|
#[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>(
|
pub(crate) fn find<T>(
|
||||||
&self,
|
&self,
|
||||||
manager: &ContainingBlockManager<PhysicalRect<Au>>,
|
manager: &ContainingBlockManager<PhysicalRect<Au>>,
|
||||||
|
|
|
@ -5,18 +5,17 @@
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use base::print_tree::PrintTree;
|
use base::print_tree::PrintTree;
|
||||||
use compositing_traits::display_list::AxesScrollSensitivity;
|
use compositing_traits::display_list::AxesScrollSensitivity;
|
||||||
use euclid::default::{Point2D, Rect, Size2D};
|
use euclid::default::Size2D;
|
||||||
use fxhash::FxHashSet;
|
use fxhash::FxHashSet;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use style::animation::AnimationSetKey;
|
use style::animation::AnimationSetKey;
|
||||||
use style::dom::OpaqueNode;
|
|
||||||
use webrender_api::units;
|
use webrender_api::units;
|
||||||
|
|
||||||
use super::{ContainingBlockManager, Fragment, Tag};
|
use super::{ContainingBlockManager, Fragment};
|
||||||
use crate::context::LayoutContext;
|
use crate::context::LayoutContext;
|
||||||
use crate::display_list::StackingContext;
|
use crate::display_list::StackingContext;
|
||||||
use crate::flow::CanvasBackground;
|
use crate::flow::CanvasBackground;
|
||||||
use crate::geom::{PhysicalPoint, PhysicalRect};
|
use crate::geom::PhysicalRect;
|
||||||
|
|
||||||
#[derive(MallocSizeOf)]
|
#[derive(MallocSizeOf)]
|
||||||
pub struct FragmentTree {
|
pub struct FragmentTree {
|
||||||
|
@ -139,82 +138,6 @@ impl FragmentTree {
|
||||||
.find_map(|child| child.find(&info, 0, &mut process_func))
|
.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> {
|
pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect<Au> {
|
||||||
let mut scroll_area = self.initial_containing_block;
|
let mut scroll_area = self.initial_containing_block;
|
||||||
for fragment in self.root_fragments.iter() {
|
for fragment in self.root_fragments.iter() {
|
||||||
|
|
|
@ -81,8 +81,8 @@ use webrender_api::{ExternalScrollId, HitTestFlags};
|
||||||
use crate::context::LayoutContext;
|
use crate::context::LayoutContext;
|
||||||
use crate::display_list::{DisplayList, WebRenderImageInfo};
|
use crate::display_list::{DisplayList, WebRenderImageInfo};
|
||||||
use crate::query::{
|
use crate::query::{
|
||||||
get_the_text_steps, process_content_box_request, process_content_boxes_request,
|
get_the_text_steps, process_client_rect_request, process_content_box_request,
|
||||||
process_node_geometry_request, process_node_scroll_area_request, process_offset_parent_query,
|
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,
|
process_resolved_font_style_query, process_resolved_style_request, process_text_index_request,
|
||||||
};
|
};
|
||||||
use crate::traversal::RecalcStyle;
|
use crate::traversal::RecalcStyle;
|
||||||
|
@ -235,24 +235,27 @@ impl Layout for LayoutThread {
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
||||||
)]
|
)]
|
||||||
fn query_content_box(&self, node: OpaqueNode) -> Option<UntypedRect<Au>> {
|
fn query_content_box(&self, node: TrustedNodeAddress) -> Option<UntypedRect<Au>> {
|
||||||
process_content_box_request(node, self.fragment_tree.borrow().clone())
|
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||||
|
process_content_box_request(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
||||||
)]
|
)]
|
||||||
fn query_content_boxes(&self, node: OpaqueNode) -> Vec<UntypedRect<Au>> {
|
fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<UntypedRect<Au>> {
|
||||||
process_content_boxes_request(node, self.fragment_tree.borrow().clone())
|
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||||
|
process_content_boxes_request(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
||||||
)]
|
)]
|
||||||
fn query_client_rect(&self, node: OpaqueNode) -> UntypedRect<i32> {
|
fn query_client_rect(&self, node: TrustedNodeAddress) -> UntypedRect<i32> {
|
||||||
process_node_geometry_request(node, self.fragment_tree.borrow().clone())
|
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||||
|
process_client_rect_request(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
|
|
@ -46,42 +46,36 @@ use crate::fragment_tree::{
|
||||||
use crate::geom::{PhysicalRect, PhysicalVec};
|
use crate::geom::{PhysicalRect, PhysicalVec};
|
||||||
use crate::taffy::SpecificTaffyGridInfo;
|
use crate::taffy::SpecificTaffyGridInfo;
|
||||||
|
|
||||||
pub fn process_content_box_request(
|
pub fn process_content_box_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Option<Rect<Au>> {
|
||||||
requested_node: OpaqueNode,
|
let rects: Vec<_> = node
|
||||||
fragment_tree: Option<Arc<FragmentTree>>,
|
.fragments_for_pseudo(None)
|
||||||
) -> Option<Rect<Au>> {
|
.iter()
|
||||||
let rects = fragment_tree?.get_content_boxes_for_node(requested_node);
|
.filter_map(Fragment::cumulative_content_box_rect)
|
||||||
|
.collect();
|
||||||
if rects.is_empty() {
|
if rects.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(
|
Some(rects.iter().fold(Rect::zero(), |unioned_rect, rect| {
|
||||||
rects
|
rect.to_untyped().union(&unioned_rect)
|
||||||
.iter()
|
}))
|
||||||
.fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_content_boxes_request(
|
pub fn process_content_boxes_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Vec<Rect<Au>> {
|
||||||
requested_node: OpaqueNode,
|
node.fragments_for_pseudo(None)
|
||||||
fragment_tree: Option<Arc<FragmentTree>>,
|
.iter()
|
||||||
) -> Vec<Rect<Au>> {
|
.filter_map(Fragment::cumulative_content_box_rect)
|
||||||
fragment_tree
|
.map(|rect| rect.to_untyped())
|
||||||
.map(|tree| tree.get_content_boxes_for_node(requested_node))
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
|
/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
|
||||||
pub fn process_node_scroll_area_request<'dom>(
|
pub fn process_node_scroll_area_request<'dom>(
|
||||||
requested_node: Option<impl LayoutNode<'dom> + 'dom>,
|
requested_node: Option<impl LayoutNode<'dom> + 'dom>,
|
||||||
|
|
|
@ -2261,7 +2261,9 @@ impl Window {
|
||||||
|
|
||||||
// Query content box without considering any reflow
|
// Query content box without considering any reflow
|
||||||
pub(crate) fn content_box_query_unchecked(&self, node: &Node) -> Option<UntypedRect<Au>> {
|
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>> {
|
pub(crate) fn content_box_query(&self, node: &Node, can_gc: CanGc) -> Option<UntypedRect<Au>> {
|
||||||
|
@ -2275,14 +2277,18 @@ impl Window {
|
||||||
if !self.layout_reflow(QueryMsg::ContentBoxes, can_gc) {
|
if !self.layout_reflow(QueryMsg::ContentBoxes, can_gc) {
|
||||||
return vec![];
|
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> {
|
pub(crate) fn client_rect_query(&self, node: &Node, can_gc: CanGc) -> UntypedRect<i32> {
|
||||||
if !self.layout_reflow(QueryMsg::ClientRectQuery, can_gc) {
|
if !self.layout_reflow(QueryMsg::ClientRectQuery, can_gc) {
|
||||||
return Rect::zero();
|
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
|
/// Find the scroll area of the given node, if it is not None. If the node
|
||||||
|
|
|
@ -243,9 +243,9 @@ pub trait Layout {
|
||||||
/// Set the scroll states of this layout after a compositor scroll.
|
/// Set the scroll states of this layout after a compositor scroll.
|
||||||
fn set_scroll_offsets(&mut self, scroll_states: &[ScrollState]);
|
fn set_scroll_offsets(&mut self, scroll_states: &[ScrollState]);
|
||||||
|
|
||||||
fn query_content_box(&self, node: OpaqueNode) -> Option<Rect<Au>>;
|
fn query_content_box(&self, node: TrustedNodeAddress) -> Option<Rect<Au>>;
|
||||||
fn query_content_boxes(&self, node: OpaqueNode) -> Vec<Rect<Au>>;
|
fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<Rect<Au>>;
|
||||||
fn query_client_rect(&self, node: OpaqueNode) -> Rect<i32>;
|
fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;
|
||||||
fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
|
fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
|
||||||
fn query_nodes_from_point(
|
fn query_nodes_from_point(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -29,8 +29,5 @@
|
||||||
[Rendered soft-hyphen should have a width.]
|
[Rendered soft-hyphen should have a width.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Collapsed soft-hyphen in a span should be 0 width.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Rendered soft-hyphen in a span should have a width.]
|
[Rendered soft-hyphen in a span should have a width.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue