layout: Store table parts in DOM layout data (#36447)

When laying out tables, store the boxes of non-anonymous table parts in
their respective DOM objects. This is going to be important for
incremental layout, but also for mapping from the DOM to the box tree
(and eventually the fragment tree).

For now, anonymous table parts are still lost to time and space, but
in a followup change we hope to store them somewhere.

Testing: This has no visible change to web rendering, so is covered by
existing
WPT.

Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>

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-12 16:13:23 +02:00 committed by GitHub
parent a4a308e434
commit 56e7c21fe7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 229 additions and 143 deletions

View file

@ -8,7 +8,7 @@ use std::ops::Deref;
use atomic_refcell::AtomicRefCell;
use servo_arc::Arc;
pub(crate) struct ArcRefCell<T> {
pub struct ArcRefCell<T> {
value: Arc<AtomicRefCell<T>>,
}

View file

@ -26,6 +26,7 @@ use crate::flow::BlockLevelBox;
use crate::flow::inline::InlineItem;
use crate::geom::PhysicalSize;
use crate::replaced::{CanvasInfo, CanvasSource};
use crate::table::TableLevelBox;
use crate::taffy::TaffyItemBox;
/// The data that is stored in each DOM node that is used by layout.
@ -43,6 +44,7 @@ pub(super) enum LayoutBox {
BlockLevel(ArcRefCell<BlockLevelBox>),
InlineLevel(ArcRefCell<InlineItem>),
FlexLevel(ArcRefCell<FlexLevelBox>),
TableLevelBox(TableLevelBox),
TaffyItemBox(ArcRefCell<TaffyItemBox>),
}
@ -62,6 +64,7 @@ impl LayoutBox {
LayoutBox::TaffyItemBox(taffy_item_box) => {
taffy_item_box.borrow_mut().invalidate_cached_fragment()
},
LayoutBox::TableLevelBox(table_box) => table_box.invalidate_cached_fragment(),
}
}
}

View file

@ -212,6 +212,7 @@ impl BoxTree {
},
_ => return None,
},
LayoutBox::TableLevelBox(..) => return None,
LayoutBox::TaffyItemBox(taffy_level_box) => match &taffy_level_box
.borrow()
.taffy_level_box

View file

@ -29,6 +29,7 @@ pub mod table;
pub mod traversal;
use app_units::Au;
pub use cell::ArcRefCell;
pub use flow::BoxTree;
pub use fragment_tree::FragmentTree;
use style::logical_geometry::WritingMode;

View file

@ -6,6 +6,7 @@ use std::borrow::Cow;
use std::convert::{TryFrom, TryInto};
use std::iter::repeat;
use atomic_refcell::AtomicRef;
use log::warn;
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
use servo_arc::Arc;
@ -15,13 +16,13 @@ use style::selector_parser::PseudoElement;
use style::str::char_is_whitespace;
use super::{
Table, TableCaption, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset,
TableTrack, TableTrackGroup, TableTrackGroupType,
Table, TableCaption, TableLevelBox, TableSlot, TableSlotCell, TableSlotCoordinates,
TableSlotOffset, TableTrack, TableTrackGroup, TableTrackGroupType,
};
use crate::PropagatedBoxTreeData;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, NodeExt};
use crate::dom::{BoxSlot, LayoutBox, NodeExt};
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler};
use crate::flow::{BlockContainerBuilder, BlockFormattingContext};
use crate::formatting_contexts::{
@ -33,9 +34,9 @@ use crate::layout_box_base::LayoutBoxBase;
use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal};
/// A reference to a slot and its coordinates in the table
#[derive(Clone, Copy, Debug)]
#[derive(Debug)]
pub(super) struct ResolvedSlotAndLocation<'a> {
pub cell: &'a TableSlotCell,
pub cell: AtomicRef<'a, TableSlotCell>,
pub coords: TableSlotCoordinates,
}
@ -161,7 +162,10 @@ impl Table {
) -> Vec<ResolvedSlotAndLocation<'_>> {
let slot = self.get_slot(coords);
match slot {
Some(TableSlot::Cell(cell)) => vec![ResolvedSlotAndLocation { cell, coords }],
Some(TableSlot::Cell(cell)) => vec![ResolvedSlotAndLocation {
cell: cell.borrow(),
coords,
}],
Some(TableSlot::Spanned(offsets)) => offsets
.iter()
.flat_map(|offset| self.resolve_slot_at(coords - *offset))
@ -241,6 +245,7 @@ impl TableBuilder {
// generally less than or equal to three row groups, but if we notice a lot
// of web content with more, we can consider a binary search here.
for row_group in self.table.row_groups.iter() {
let row_group = row_group.borrow();
if row_group.track_range.start > n {
return row_group.track_range.start - 1;
}
@ -286,6 +291,7 @@ impl TableBuilder {
let mut thead_index = None;
let mut tfoot_index = None;
for (row_group_index, row_group) in self.table.row_groups.iter().enumerate() {
let row_group = row_group.borrow();
if thead_index.is_none() && row_group.group_type == TableTrackGroupType::HeaderGroup {
thead_index = Some(row_group_index);
}
@ -315,25 +321,35 @@ impl TableBuilder {
// Now update all track group ranges.
let mut current_row_group_index = None;
for (row_index, row) in self.table.rows.iter().enumerate() {
let row = row.borrow();
if current_row_group_index == row.group_index {
continue;
}
// Finish any row group that is currently being processed.
if let Some(current_group_index) = current_row_group_index {
self.table.row_groups[current_group_index].track_range.end = row_index;
self.table.row_groups[current_group_index]
.borrow_mut()
.track_range
.end = row_index;
}
// Start processing this new row group and update its starting index.
current_row_group_index = row.group_index;
if let Some(current_group_index) = current_row_group_index {
self.table.row_groups[current_group_index].track_range.start = row_index;
self.table.row_groups[current_group_index]
.borrow_mut()
.track_range
.start = row_index;
}
}
// Finish the last row group.
if let Some(current_group_index) = current_row_group_index {
self.table.row_groups[current_group_index].track_range.end = self.table.rows.len();
self.table.row_groups[current_group_index]
.borrow_mut()
.track_range
.end = self.table.rows.len();
}
}
@ -344,6 +360,7 @@ impl TableBuilder {
self.table.row_groups.insert(0, removed_row_group);
for row in self.table.rows.iter_mut() {
let mut row = row.borrow_mut();
match row.group_index.as_mut() {
Some(group_index) if *group_index < index_to_move => *group_index += 1,
Some(group_index) if *group_index == index_to_move => *group_index = 0,
@ -352,7 +369,7 @@ impl TableBuilder {
}
}
let row_range = self.table.row_groups[0].track_range.clone();
let row_range = self.table.row_groups[0].borrow().track_range.clone();
if row_range.start > 0 {
// Move the slots associated with the moved group.
let removed_slots: Vec<Vec<TableSlot>> = self
@ -363,7 +380,7 @@ impl TableBuilder {
self.table.slots.splice(0..0, removed_slots);
// Move the rows associated with the moved group.
let removed_rows: Vec<TableTrack> = self
let removed_rows: Vec<_> = self
.table
.rows
.splice(row_range, std::iter::empty())
@ -385,6 +402,7 @@ impl TableBuilder {
self.table.row_groups.push(removed_row_group);
for row in self.table.rows.iter_mut() {
let mut row = row.borrow_mut();
match row.group_index.as_mut() {
Some(group_index) if *group_index > index_to_move => *group_index -= 1,
Some(group_index) if *group_index == index_to_move => {
@ -396,6 +414,7 @@ impl TableBuilder {
}
let row_range = self.table.row_groups[last_row_group_index]
.borrow()
.track_range
.clone();
if row_range.end < self.table.rows.len() {
@ -408,7 +427,7 @@ impl TableBuilder {
self.table.slots.extend(removed_slots);
// Move the rows associated with the moved group.
let removed_rows: Vec<TableTrack> = self
let removed_rows: Vec<_> = self
.table
.rows
.splice(row_range, std::iter::empty())
@ -427,6 +446,7 @@ impl TableBuilder {
let last_row_index_in_group = self.last_row_index_in_row_group_at_row_n(row_index);
for cell in self.table.slots[row_index].iter_mut() {
if let TableSlot::Cell(cell) = cell {
let mut cell = cell.borrow_mut();
if cell.rowspan == 1 {
continue;
}
@ -547,14 +567,17 @@ impl TableBuilder {
/// <https://html.spec.whatwg.org/multipage/#algorithm-for-processing-rows>
/// Push a single cell onto the slot map, handling any colspans it may have, and
/// setting up the outgoing rowspans.
pub fn add_cell(&mut self, cell: TableSlotCell) {
pub fn add_cell(&mut self, cell: ArcRefCell<TableSlotCell>) {
// Make sure the incoming_rowspans table is large enough
// because we will be writing to it.
let current_coords = self
.current_coords()
.expect("Should have rows before calling `add_cell`");
let colspan = cell.colspan;
let rowspan = cell.rowspan;
let (colspan, rowspan) = {
let cell = cell.borrow();
(cell.colspan, cell.rowspan)
};
if self.incoming_rowspans.len() < current_coords.x + colspan {
self.incoming_rowspans
@ -704,21 +727,21 @@ where
row_builder.finish();
let style = anonymous_info.style.clone();
self.push_table_row(TableTrack {
self.push_table_row(ArcRefCell::new(TableTrack {
base_fragment_info: (&anonymous_info).into(),
style,
group_index: self.current_row_group_index,
is_anonymous: true,
});
}));
}
fn push_table_row(&mut self, table_track: TableTrack) {
fn push_table_row(&mut self, table_track: ArcRefCell<TableTrack>) {
self.builder.table.rows.push(table_track);
let last_row = self.builder.table.rows.len();
if let Some(index) = self.current_row_group_index {
let row_group = &mut self.builder.table.row_groups[index];
row_group.track_range.end = last_row;
row_group.borrow_mut().track_range.end = last_row;
}
}
}
@ -749,12 +772,13 @@ where
self.builder.incoming_rowspans.clear();
let next_row_index = self.builder.table.rows.len();
self.builder.table.row_groups.push(TableTrackGroup {
let row_group = ArcRefCell::new(TableTrackGroup {
base_fragment_info: info.into(),
style: info.style.clone(),
group_type: internal.into(),
track_range: next_row_index..next_row_index,
});
self.builder.table.row_groups.push(row_group.clone());
let previous_propagated_data = self.current_propagated_data;
self.current_propagated_data = self.current_propagated_data.union(&info.style);
@ -773,8 +797,9 @@ where
self.current_propagated_data = previous_propagated_data;
self.builder.incoming_rowspans.clear();
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup(
row_group,
)));
},
DisplayLayoutInternal::TableRow => {
self.finish_anonymous_row_if_needed();
@ -790,26 +815,23 @@ where
);
row_builder.finish();
self.push_table_row(TableTrack {
let row = ArcRefCell::new(TableTrack {
base_fragment_info: info.into(),
style: info.style.clone(),
group_index: self.current_row_group_index,
is_anonymous: false,
});
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
self.push_table_row(row.clone());
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(row)));
},
DisplayLayoutInternal::TableColumn => {
add_column(
let column = add_column(
&mut self.builder.table.columns,
info,
None, /* group_index */
false, /* is_anonymous */
);
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(column)));
},
DisplayLayoutInternal::TableColumnGroup => {
let column_group_index = self.builder.table.column_groups.len();
@ -839,14 +861,16 @@ where
.extend(column_group_builder.columns);
}
self.builder.table.column_groups.push(TableTrackGroup {
let column_group = ArcRefCell::new(TableTrackGroup {
base_fragment_info: info.into(),
style: info.style.clone(),
group_type: internal.into(),
track_range: first_column..self.builder.table.columns.len(),
});
::std::mem::forget(box_slot);
self.builder.table.column_groups.push(column_group.clone());
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup(
column_group,
)));
},
DisplayLayoutInternal::TableCaption => {
let contents = match contents.try_into() {
@ -864,17 +888,14 @@ where
},
};
let caption = TableCaption {
context: ArcRefCell::new(IndependentFormattingContext {
let caption = ArcRefCell::new(TableCaption {
context: IndependentFormattingContext {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
contents: IndependentFormattingContextContents::NonReplaced(contents),
}),
};
self.builder.table.captions.push(caption);
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
},
});
self.builder.table.captions.push(caption.clone());
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Caption(caption)));
},
DisplayLayoutInternal::TableCell => {
self.current_anonymous_row_content
@ -968,12 +989,14 @@ where
}
let block_container = builder.finish();
self.table_traversal.builder.add_cell(TableSlotCell {
base: LayoutBoxBase::new(BaseFragmentInfo::anonymous(), anonymous_info.style),
contents: BlockFormattingContext::from_block_container(block_container),
colspan: 1,
rowspan: 1,
});
self.table_traversal
.builder
.add_cell(ArcRefCell::new(TableSlotCell {
base: LayoutBoxBase::new(BaseFragmentInfo::anonymous(), anonymous_info.style),
contents: BlockFormattingContext::from_block_container(block_container),
colspan: 1,
rowspan: 1,
}));
}
}
@ -1032,15 +1055,15 @@ where
};
self.finish_current_anonymous_cell_if_needed();
self.table_traversal.builder.add_cell(TableSlotCell {
let cell = ArcRefCell::new(TableSlotCell {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
contents,
colspan,
rowspan,
});
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
self.table_traversal.builder.add_cell(cell.clone());
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Cell(cell)));
},
_ => {
//// TODO: Properly handle other table-like elements in the middle of a row.
@ -1068,7 +1091,7 @@ where
struct TableColumnGroupBuilder {
column_group_index: usize,
columns: Vec<TableTrack>,
columns: Vec<ArcRefCell<TableTrack>>,
}
impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableColumnGroupBuilder
@ -1083,21 +1106,22 @@ where
_contents: Contents,
box_slot: BoxSlot<'dom>,
) {
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot);
if !matches!(
display,
DisplayGeneratingBox::LayoutInternal(DisplayLayoutInternal::TableColumn)
) {
// The BoxSlot destructor will check to ensure that it isn't empty but in this case, the
// DOM node doesn't produce any box, so explicitly skip the destructor here.
::std::mem::forget(box_slot);
return;
}
add_column(
let column = add_column(
&mut self.columns,
info,
Some(self.column_group_index),
false, /* is_anonymous */
);
box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(column)));
}
}
@ -1113,14 +1137,12 @@ impl From<DisplayLayoutInternal> for TableTrackGroupType {
}
}
fn add_column<'dom, Node>(
collection: &mut Vec<TableTrack>,
fn add_column<'dom, Node: NodeExt<'dom>>(
collection: &mut Vec<ArcRefCell<TableTrack>>,
column_info: &NodeAndStyleInfo<Node>,
group_index: Option<usize>,
is_anonymous: bool,
) where
Node: NodeExt<'dom>,
{
) -> ArcRefCell<TableTrack> {
let span = if column_info.pseudo_element_type.is_none() {
column_info
.node
@ -1132,13 +1154,12 @@ fn add_column<'dom, Node>(
1
};
collection.extend(
repeat(TableTrack {
base_fragment_info: column_info.into(),
style: column_info.style.clone(),
group_index,
is_anonymous,
})
.take(span),
);
let column = ArcRefCell::new(TableTrack {
base_fragment_info: column_info.into(),
style: column_info.style.clone(),
group_index,
is_anonymous,
});
collection.extend(repeat(column.clone()).take(span));
column
}

View file

@ -7,6 +7,7 @@ use std::mem;
use std::ops::Range;
use app_units::Au;
use atomic_refcell::AtomicRef;
use log::warn;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use servo_arc::Arc;
@ -280,7 +281,8 @@ impl<'a> TableLayout<'a> {
let cell = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) => cell,
_ => continue,
};
}
.borrow();
let layout_style = cell.layout_style();
let padding = layout_style
@ -393,12 +395,13 @@ impl<'a> TableLayout<'a> {
for column_index in 0..self.table.size.width {
if let Some(column) = self.table.columns.get(column_index) {
let column = column.borrow();
if is_length(&column.style.box_size(writing_mode).inline) {
self.columns[column_index].constrained = true;
continue;
}
if let Some(column_group_index) = column.group_index {
let column_group = &self.table.column_groups[column_group_index];
let column_group = self.table.column_groups[column_group_index].borrow();
if is_length(&column_group.style.box_size(writing_mode).inline) {
self.columns[column_index].constrained = true;
continue;
@ -409,12 +412,13 @@ impl<'a> TableLayout<'a> {
for row_index in 0..self.table.size.height {
if let Some(row) = self.table.rows.get(row_index) {
let row = row.borrow();
if is_length(&row.style.box_size(writing_mode).block) {
self.rows[row_index].constrained = true;
continue;
}
if let Some(row_group_index) = row.group_index {
let row_group = &self.table.row_groups[row_group_index];
let row_group = self.table.row_groups[row_group_index].borrow();
if is_length(&row_group.style.box_size(writing_mode).block) {
self.rows[row_index].constrained = true;
continue;
@ -434,7 +438,7 @@ impl<'a> TableLayout<'a> {
};
let rowspan_greater_than_1 = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) => cell.rowspan > 1,
TableSlot::Cell(ref cell) => cell.borrow().rowspan > 1,
_ => false,
};
@ -501,7 +505,8 @@ impl<'a> TableLayout<'a> {
let cell = match self.table.get_slot(coords) {
Some(TableSlot::Cell(cell)) => cell,
_ => continue,
};
}
.borrow();
if cell.colspan != 1 {
colspan_cell_constraints.push(ColspanToDistribute {
@ -708,8 +713,9 @@ impl<'a> TableLayout<'a> {
.captions
.iter()
.map(|caption| {
let context = caption.context.borrow();
context
caption
.borrow()
.context
.outer_inline_content_sizes(
layout_context,
&containing_block,
@ -1075,9 +1081,11 @@ impl<'a> TableLayout<'a> {
let collect_for_nearest_positioned_ancestor = parent_positioning_context
.collects_for_nearest_positioned_ancestor() ||
self.table.rows.get(row_index).is_some_and(|row| {
let row = row.borrow();
let row_group_collects_for_nearest_positioned_ancestor =
row.group_index.is_some_and(|group_index| {
self.table.row_groups[group_index]
.borrow()
.style
.establishes_containing_block_for_absolute_descendants(
FragmentFlags::empty(),
@ -1098,6 +1106,7 @@ impl<'a> TableLayout<'a> {
return None;
};
let cell = cell.borrow();
let area = LogicalSides {
inline_start: column_index,
inline_end: column_index + cell.colspan,
@ -1192,6 +1201,7 @@ impl<'a> TableLayout<'a> {
},
};
let cell = cell.borrow();
let outer_block_size = layout.outer_block_size();
if cell.rowspan == 1 {
max_row_height.max_assign(outer_block_size);
@ -1251,7 +1261,7 @@ impl<'a> TableLayout<'a> {
let cell_measure = &self.cell_measures[row_index][column_index].block;
let cell = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) if cell.rowspan > 1 => cell,
TableSlot::Cell(ref cell) if cell.borrow().rowspan > 1 => cell,
TableSlot::Cell(_) => {
// If this is an originating cell, that isn't spanning, then we make sure the row is
// at least big enough to hold the cell.
@ -1263,7 +1273,7 @@ impl<'a> TableLayout<'a> {
cells_to_distribute.push(RowspanToDistribute {
coordinates: TableSlotCoordinates::new(column_index, row_index),
cell,
cell: cell.borrow(),
measure: cell_measure,
});
}
@ -1491,8 +1501,7 @@ impl<'a> TableLayout<'a> {
layout_context: &LayoutContext,
parent_positioning_context: &mut PositioningContext,
) -> BoxFragment {
let context = caption.context.borrow();
let mut positioning_context = PositioningContext::new_for_style(context.style());
let mut positioning_context = PositioningContext::new_for_style(caption.context.style());
let containing_block = &ContainingBlock {
size: ContainingBlockSize {
inline: self.table_width + self.pbm.padding_border_sums.inline,
@ -1506,7 +1515,7 @@ impl<'a> TableLayout<'a> {
// stretch block size. https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing
let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false);
let mut box_fragment = context.layout_in_flow_block_level(
let mut box_fragment = caption.context.layout_in_flow_block_level(
layout_context,
positioning_context
.as_mut()
@ -1596,13 +1605,14 @@ impl<'a> TableLayout<'a> {
table_layout
.fragments
.extend(self.table.captions.iter().filter_map(|caption| {
if caption.context.borrow().style().clone_caption_side() != CaptionSide::Top {
let caption = caption.borrow();
if caption.context.style().clone_caption_side() != CaptionSide::Top {
return None;
}
let original_positioning_context_length = positioning_context.len();
let mut caption_fragment =
self.layout_caption(caption, layout_context, positioning_context);
self.layout_caption(&caption, layout_context, positioning_context);
// The caption is not placed yet. Construct a rectangle for it in the adjusted containing block
// for the table children and only then convert the result to physical geometry.
@ -1698,13 +1708,14 @@ impl<'a> TableLayout<'a> {
table_layout
.fragments
.extend(self.table.captions.iter().filter_map(|caption| {
if caption.context.borrow().style().clone_caption_side() != CaptionSide::Bottom {
let caption = caption.borrow();
if caption.context.style().clone_caption_side() != CaptionSide::Bottom {
return None;
}
let original_positioning_context_length = positioning_context.len();
let mut caption_fragment =
self.layout_caption(caption, layout_context, positioning_context);
self.layout_caption(&caption, layout_context, positioning_context);
// The caption is not placed yet. Construct a rectangle for it in the adjusted containing block
// for the table children and only then convert the result to physical geometry.
@ -1820,9 +1831,9 @@ impl<'a> TableLayout<'a> {
continue;
}
let table_row = &self.table.rows[row_index];
let table_row = self.table.rows[row_index].borrow();
let mut row_fragment_layout = RowFragmentLayout::new(
table_row,
&table_row,
row_index,
&table_and_track_dimensions,
&self.table.style,
@ -1845,7 +1856,7 @@ impl<'a> TableLayout<'a> {
// Then, create a new RowGroupFragmentLayout for the current and potentially subsequent rows.
if let Some(new_group_index) = table_row.group_index {
row_group_fragment_layout = Some(RowGroupFragmentLayout::new(
&self.table.row_groups[new_group_index],
&self.table.row_groups[new_group_index].borrow(),
new_group_index,
&table_and_track_dimensions,
));
@ -1960,25 +1971,28 @@ impl<'a> TableLayout<'a> {
let Some(row) = &self.table.rows.get(row_index) else {
return false;
};
let row = row.borrow();
if row.style.get_inherited_box().visibility == Visibility::Collapse {
return true;
}
let row_group = match row.group_index {
Some(group_index) => &self.table.row_groups[group_index],
Some(group_index) => self.table.row_groups[group_index].borrow(),
None => return false,
};
row_group.style.get_inherited_box().visibility == Visibility::Collapse
}
fn is_column_collapsed(&self, column_index: usize) -> bool {
let Some(col) = &self.table.columns.get(column_index) else {
let Some(column) = &self.table.columns.get(column_index) else {
return false;
};
if col.style.get_inherited_box().visibility == Visibility::Collapse {
let column = column.borrow();
if column.style.get_inherited_box().visibility == Visibility::Collapse {
return true;
}
let col_group = match col.group_index {
Some(group_index) => &self.table.column_groups[group_index],
let col_group = match column.group_index {
Some(group_index) => self.table.column_groups[group_index].borrow(),
None => return false,
};
col_group.style.get_inherited_box().visibility == Visibility::Collapse
@ -2017,7 +2031,8 @@ impl<'a> TableLayout<'a> {
warn!("Did not find a non-spanned cell at index with layout.");
return;
},
};
}
.borrow();
// If this cell has baseline alignment, it can adjust the table's overall baseline.
let row_block_offset = row_fragment_layout.rect.start_corner.block;
@ -2067,16 +2082,18 @@ impl<'a> TableLayout<'a> {
let column = self.table.columns.get(column_index);
let column_group = column
.and_then(|column| column.group_index)
.and_then(|column| column.borrow().group_index)
.and_then(|index| self.table.column_groups.get(index));
if let Some(column_group) = column_group {
let rect = make_relative_to_row_start(dimensions.get_column_group_rect(column_group));
let column_group = column_group.borrow();
let rect = make_relative_to_row_start(dimensions.get_column_group_rect(&column_group));
fragment.add_extra_background(ExtraBackground {
style: column_group.style.clone(),
rect,
})
}
if let Some(column) = column {
let column = column.borrow();
if !column.is_anonymous {
let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index));
fragment.add_extra_background(ExtraBackground {
@ -2087,16 +2104,18 @@ impl<'a> TableLayout<'a> {
}
let row = self.table.rows.get(row_index);
let row_group = row
.and_then(|row| row.group_index)
.and_then(|row| row.borrow().group_index)
.and_then(|index| self.table.row_groups.get(index));
if let Some(row_group) = row_group {
let rect = make_relative_to_row_start(dimensions.get_row_group_rect(row_group));
let rect =
make_relative_to_row_start(dimensions.get_row_group_rect(&row_group.borrow()));
fragment.add_extra_background(ExtraBackground {
style: row_group.style.clone(),
style: row_group.borrow().style.clone(),
rect,
})
}
if let Some(row) = row {
let row = row.borrow();
let rect = make_relative_to_row_start(row_fragment_layout.rect);
fragment.add_extra_background(ExtraBackground {
style: row.style.clone(),
@ -2114,11 +2133,12 @@ impl<'a> TableLayout<'a> {
fragments: &mut Vec<Fragment>,
) {
for column_group in self.table.column_groups.iter() {
let column_group = column_group.borrow();
if !column_group.is_empty() {
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
column_group.base_fragment_info,
dimensions
.get_column_group_rect(column_group)
.get_column_group_rect(&column_group)
.as_physical(None),
column_group.style.clone(),
)));
@ -2126,6 +2146,7 @@ impl<'a> TableLayout<'a> {
}
for (column_index, column) in self.table.columns.iter().enumerate() {
let column = column.borrow();
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
column.base_fragment_info,
dimensions.get_column_rect(column_index).as_physical(None),
@ -2190,7 +2211,8 @@ impl<'a> TableLayout<'a> {
let cell = match self.table.slots[row_index][column_index] {
TableSlot::Cell(ref cell) => cell,
_ => continue,
};
}
.borrow();
let block_range = row_index..row_index + cell.rowspan;
let inline_range = column_index..column_index + cell.colspan;
hide_inner_borders(&mut collapsed_borders, &block_range, &inline_range);
@ -2203,6 +2225,7 @@ impl<'a> TableLayout<'a> {
}
}
for (row_index, row) in self.table.rows.iter().enumerate() {
let row = row.borrow();
apply_border(
&mut collapsed_borders,
&row.layout_style(),
@ -2211,6 +2234,7 @@ impl<'a> TableLayout<'a> {
);
}
for row_group in &self.table.row_groups {
let row_group = row_group.borrow();
apply_border(
&mut collapsed_borders,
&row_group.layout_style(),
@ -2219,6 +2243,7 @@ impl<'a> TableLayout<'a> {
);
}
for (column_index, column) in self.table.columns.iter().enumerate() {
let column = column.borrow();
apply_border(
&mut collapsed_borders,
&column.layout_style(),
@ -2227,6 +2252,7 @@ impl<'a> TableLayout<'a> {
);
}
for column_group in &self.table.column_groups {
let column_group = column_group.borrow();
apply_border(
&mut collapsed_borders,
&column_group.layout_style(),
@ -2599,7 +2625,8 @@ impl Table {
let column = match self.columns.get(column_index) {
Some(column) => column,
None => return CellOrTrackMeasure::zero(),
};
}
.borrow();
let CellOrColumnOuterSizes {
preferred: preferred_size,
@ -2644,6 +2671,7 @@ impl Table {
// In the block axis, the min-content and max-content sizes are the same
// (except for new layout boxes like grid and flex containers). Note that
// other browsers don't seem to use the min and max sizing properties here.
let row = row.borrow();
let size = row.style.box_size(writing_mode);
let max_size = row.style.max_box_size(writing_mode);
let percentage_contribution = get_size_percentage_contribution(&size, &max_size);
@ -2979,7 +3007,7 @@ impl CellOrColumnOuterSizes {
struct RowspanToDistribute<'a> {
coordinates: TableSlotCoordinates,
cell: &'a TableSlotCell,
cell: AtomicRef<'a, TableSlotCell>,
measure: &'a CellOrTrackMeasure,
}

View file

@ -71,6 +71,7 @@ mod layout;
use std::ops::Range;
use app_units::Au;
use atomic_refcell::AtomicRef;
pub(crate) use construct::AnonymousTableContent;
pub use construct::TableBuilder;
use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
@ -107,20 +108,20 @@ pub struct Table {
grid_base_fragment_info: BaseFragmentInfo,
/// The captions for this table.
pub captions: Vec<TableCaption>,
pub captions: Vec<ArcRefCell<TableCaption>>,
/// The column groups for this table.
pub column_groups: Vec<TableTrackGroup>,
pub column_groups: Vec<ArcRefCell<TableTrackGroup>>,
/// The columns of this table defined by `<colgroup> | display: table-column-group`
/// and `<col> | display: table-column` elements as well as `display: table-column`.
pub columns: Vec<TableTrack>,
pub columns: Vec<ArcRefCell<TableTrack>>,
/// The rows groups for this table defined by `<tbody>`, `<thead>`, and `<tfoot>`.
pub row_groups: Vec<TableTrackGroup>,
pub row_groups: Vec<ArcRefCell<TableTrackGroup>>,
/// The rows of this table defined by `<tr>` or `display: table-row` elements.
pub rows: Vec<TableTrack>,
pub rows: Vec<ArcRefCell<TableTrack>>,
/// The content of the slots of this table.
pub slots: Vec<Vec<TableSlot>>,
@ -175,11 +176,14 @@ impl Table {
}
}
fn resolve_first_cell(&self, coords: TableSlotCoordinates) -> Option<&TableSlotCell> {
fn resolve_first_cell(
&self,
coords: TableSlotCoordinates,
) -> Option<AtomicRef<'_, TableSlotCell>> {
let resolved_coords = self.resolve_first_cell_coords(coords)?;
let slot = self.get_slot(resolved_coords);
match slot {
Some(TableSlot::Cell(cell)) => Some(cell),
Some(TableSlot::Cell(cell)) => Some(cell.borrow()),
_ => unreachable!(
"Spanned slot should not point to an empty cell or another spanned slot."
),
@ -234,7 +238,7 @@ impl TableSlotCell {
/// In case of table model errors, it may be multiple references
pub enum TableSlot {
/// A table cell, with a colspan and a rowspan.
Cell(TableSlotCell),
Cell(ArcRefCell<TableSlotCell>),
/// This slot is spanned by one or more multiple cells earlier in the table, which are
/// found at the given negative coordinate offsets. The vector is in the order of most
@ -317,7 +321,7 @@ impl TableTrackGroup {
#[derive(Debug)]
pub struct TableCaption {
/// The contents of this cell, with its own layout.
context: ArcRefCell<IndependentFormattingContext>,
context: IndependentFormattingContext,
}
/// A calculated collapsed border.
@ -340,3 +344,30 @@ pub(crate) struct TableLayoutStyle<'a> {
table: &'a Table,
layout: Option<&'a TableLayout<'a>>,
}
/// Table parts that are stored in the DOM. This is used in order to map from
/// the DOM to the box tree and will eventually be important for incremental
/// layout.
pub(crate) enum TableLevelBox {
Caption(ArcRefCell<TableCaption>),
Cell(ArcRefCell<TableSlotCell>),
#[allow(dead_code)]
TrackGroup(ArcRefCell<TableTrackGroup>),
#[allow(dead_code)]
Track(ArcRefCell<TableTrack>),
}
impl TableLevelBox {
pub(crate) fn invalidate_cached_fragment(&self) {
match self {
TableLevelBox::Caption(caption) => {
caption.borrow().context.base.invalidate_cached_fragment();
},
TableLevelBox::Cell(cell) => {
cell.borrow().base.invalidate_cached_fragment();
},
TableLevelBox::TrackGroup(..) => {},
TableLevelBox::Track(..) => {},
}
}
}

View file

@ -6,6 +6,7 @@
mod tables {
use euclid::Vector2D;
use layout_2020::ArcRefCell;
use layout_2020::table::{Table, TableBuilder, TableSlot, TableSlotCell, TableSlotOffset};
fn row_lengths(table: &Table) -> Vec<usize> {
@ -14,7 +15,7 @@ mod tables {
fn slot_is_cell_with_id(slot: &TableSlot, id: usize) -> bool {
match slot {
TableSlot::Cell(cell) if cell.node_id() == id => true,
TableSlot::Cell(cell) if cell.borrow().node_id() == id => true,
_ => false,
}
}
@ -51,13 +52,13 @@ mod tables {
let mut table_builder = TableBuilder::new_for_tests();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(1, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(2, 1, 1));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(1, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(2, 1, 1)));
table_builder.end_row();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(3, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(4, 1, 1));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(3, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(4, 1, 1)));
table_builder.end_row();
let table = table_builder.finish();
@ -74,13 +75,13 @@ mod tables {
let mut table_builder = TableBuilder::new_for_tests();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(1, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(2, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(3, 1, 2));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(1, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(2, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(3, 1, 2)));
table_builder.end_row();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(4, 1, 1));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(4, 1, 1)));
table_builder.end_row();
let table = table_builder.finish();
@ -103,21 +104,21 @@ mod tables {
let mut table_builder = TableBuilder::new_for_tests();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(1, 3, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(2, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(3, 1, 1));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(1, 3, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(2, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(3, 1, 1)));
table_builder.end_row();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(4, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(5, 3, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(6, 1, 1));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(4, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(5, 3, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(6, 1, 1)));
table_builder.end_row();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(7, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(8, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(9, 3, 1));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(7, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(8, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(9, 3, 1)));
table_builder.end_row();
let table = table_builder.finish();
@ -165,13 +166,13 @@ mod tables {
let mut table_builder = TableBuilder::new_for_tests();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(1, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(2, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(3, 1, 2));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(1, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(2, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(3, 1, 2)));
table_builder.end_row();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(4, 3, 1));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(4, 3, 1)));
table_builder.end_row();
let table = table_builder.finish();
@ -197,9 +198,9 @@ mod tables {
let mut table_builder = TableBuilder::new_for_tests();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(1, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(2, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(3, 1, 0));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(1, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(2, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(3, 1, 0)));
table_builder.end_row();
table_builder.start_row();
@ -235,12 +236,12 @@ mod tables {
let mut table_builder = TableBuilder::new_for_tests();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(1, 1, 1));
table_builder.add_cell(TableSlotCell::mock_for_testing(2, 1, 30));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(1, 1, 1)));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(2, 1, 30)));
table_builder.end_row();
table_builder.start_row();
table_builder.add_cell(TableSlotCell::mock_for_testing(3, 2, 1));
table_builder.add_cell(ArcRefCell::new(TableSlotCell::mock_for_testing(3, 2, 1)));
table_builder.end_row();
assert_eq!(table_builder.incoming_rowspans, vec![0, 28]);