layout: Paint collapsed table borders on their own (#35075)

We were previously splitting collapsed borders into two halves, and then
paint each one as part of the corresponding cell. This looked wrong when
the border style wasn't solid, or when a cell spanned multiple tracks
and the border wasn't the same for all of them.

Now the borders of a table wrapper, table grid or table cell aren't
painted in collapsed borders mode. Instead, the resulting collapsed
borders are painted on their own.

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-01-21 05:10:27 -08:00 committed by GitHub
parent e43baed585
commit d00d76c1e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 208 additions and 124 deletions

View file

@ -47,7 +47,9 @@ use crate::fragment_tree::{
BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag,
TextFragment,
};
use crate::geom::{LengthPercentageOrAuto, PhysicalPoint, PhysicalRect};
use crate::geom::{
LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
};
use crate::replaced::NaturalSizes;
use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
@ -650,9 +652,16 @@ impl<'a> BuilderForBoxFragment<'a> {
return;
}
if section == StackingContextSection::Outline {
self.build_outline(builder);
return;
match section {
StackingContextSection::CollapsedTableBorders => {
self.build_collapsed_table_borders(builder);
return;
},
StackingContextSection::Outline => {
self.build_outline(builder);
return;
},
_ => {},
}
self.build_hit_test(builder, self.border_rect);
@ -893,7 +902,90 @@ impl<'a> BuilderForBoxFragment<'a> {
}
}
fn build_collapsed_table_borders(&mut self, builder: &mut DisplayListBuilder) {
let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
&self.fragment.detailed_layout_info
else {
return;
};
let mut common =
builder.common_properties(units::LayoutRect::default(), &self.fragment.style);
let radius = wr::BorderRadius::default();
let mut column_sum = Au::zero();
for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
let mut row_sum = Au::zero();
for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
let left_border = &table_info.collapsed_borders.x[x].list[y];
let right_border = &table_info.collapsed_borders.x[x + 1].list[y];
let top_border = &table_info.collapsed_borders.y[y].list[x];
let bottom_border = &table_info.collapsed_borders.y[y + 1].list[x];
let details = wr::BorderDetails::Normal(wr::NormalBorder {
left: self.build_border_side(left_border.style_color.clone()),
right: self.build_border_side(right_border.style_color.clone()),
top: self.build_border_side(top_border.style_color.clone()),
bottom: self.build_border_side(bottom_border.style_color.clone()),
radius,
do_aa: true,
});
let mut border_widths = PhysicalSides::new(
top_border.width,
right_border.width,
bottom_border.width,
left_border.width,
);
let mut origin = PhysicalPoint::new(column_sum, row_sum);
let mut size = PhysicalSize::new(*column_size, *row_size);
if x == 0 {
origin.x -= table_info.wrapper_border.left;
size.width += table_info.wrapper_border.left;
} else {
border_widths.left = Au::zero();
origin.x += left_border.width / 2;
size.width -= left_border.width / 2;
}
if y == 0 {
origin.y -= table_info.wrapper_border.top;
size.height += table_info.wrapper_border.top;
} else {
border_widths.top = Au::zero();
origin.y += top_border.width / 2;
size.height -= top_border.width / 2;
}
if x + 1 == table_info.track_sizes.x.len() {
size.width += table_info.wrapper_border.right;
} else {
size.width += border_widths.right / 2;
}
if y + 1 == table_info.track_sizes.y.len() {
size.height += table_info.wrapper_border.bottom;
} else {
size.height += border_widths.bottom / 2;
}
let border_rect = PhysicalRect::new(origin, size)
.translate(self.fragment.content_rect.origin.to_vector())
.translate(self.containing_block.origin.to_vector())
.to_webrender();
common.clip_rect = border_rect;
builder.wr().push_border(
&common,
border_rect,
border_widths.to_webrender(),
details,
);
row_sum += *row_size;
}
column_sum += *column_size;
}
}
fn build_border(&mut self, builder: &mut DisplayListBuilder) {
if self.fragment.has_collapsed_borders() {
// Avoid painting borders for tables and table parts in collapsed-borders mode,
// since the resulting collapsed borders are painted on their own in a special way.
return;
}
let border = self.fragment.style.get_border();
let border_widths = self.fragment.border.to_webrender();
@ -907,12 +999,7 @@ impl<'a> BuilderForBoxFragment<'a> {
return;
}
let style_color = match &self.fragment.detailed_layout_info {
Some(SpecificLayoutInfo::TableGridOrTableCell(table_info)) => {
table_info.border_style_color.clone()
},
_ => BorderStyleColor::from_border(border),
};
let style_color = BorderStyleColor::from_border(border);
let details = wr::BorderDetails::Normal(wr::NormalBorder {
top: self.build_border_side(style_color.top),
right: self.build_border_side(style_color.right),

View file

@ -34,7 +34,8 @@ use super::DisplayList;
use crate::display_list::conversions::{FilterToWebRender, ToWebRender};
use crate::display_list::{BuilderForBoxFragment, DisplayListBuilder};
use crate::fragment_tree::{
BoxFragment, ContainingBlockManager, Fragment, FragmentFlags, FragmentTree, PositioningFragment,
BoxFragment, ContainingBlockManager, Fragment, FragmentFlags, FragmentTree,
PositioningFragment, SpecificLayoutInfo,
};
use crate::geom::{AuOrAuto, PhysicalRect, PhysicalSides};
use crate::style_ext::ComputedValuesExt;
@ -87,6 +88,7 @@ pub(crate) type ContainingBlockInfo<'a> = ContainingBlockManager<'a, ContainingB
pub(crate) enum StackingContextSection {
OwnBackgroundsAndBorders,
DescendantBackgroundsAndBorders,
CollapsedTableBorders,
Foreground,
Outline,
}
@ -726,6 +728,16 @@ impl StackingContext {
child.build_display_list(builder, &self.atomic_inline_stacking_containers);
}
// Additional step 4.5: Collapsed table borders
// This step isn't in the spec, but other browsers seem to paint them at this point.
while contents.peek().is_some_and(|(_, child)| {
child.section() == StackingContextSection::CollapsedTableBorders
}) {
let (i, child) = contents.next().unwrap();
self.debug_push_print_item(DebugPrintField::Contents, i);
child.build_display_list(builder, &self.atomic_inline_stacking_containers);
}
// Step 5: Float stacking containers
for (i, child) in self.float_stacking_containers.iter().enumerate() {
self.debug_push_print_item(DebugPrintField::FloatStackingContainers, i);
@ -1181,30 +1193,34 @@ impl BoxFragment {
.for_absolute_and_fixed_descendants
.scroll_node_id
};
stacking_context
.contents
.push(StackingContextContent::Fragment {
scroll_node_id: new_scroll_node_id,
reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments,
clip_chain_id: new_clip_chain_id,
section: self.get_stacking_context_section(),
containing_block: containing_block.rect,
fragment: fragment.clone(),
is_hit_test_for_scrollable_overflow: false,
});
if !self.style.get_outline().outline_width.is_zero() {
let mut add_fragment = |section| {
stacking_context
.contents
.push(StackingContextContent::Fragment {
scroll_node_id: new_scroll_node_id,
reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments,
clip_chain_id: new_clip_chain_id,
section: StackingContextSection::Outline,
section,
containing_block: containing_block.rect,
fragment: fragment.clone(),
is_hit_test_for_scrollable_overflow: false,
});
};
add_fragment(self.get_stacking_context_section());
if let Fragment::Box(box_fragment) = &fragment {
if matches!(
box_fragment.borrow().detailed_layout_info,
Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(_))
) {
add_fragment(StackingContextSection::CollapsedTableBorders);
}
}
if !self.style.get_outline().outline_width.is_zero() {
add_fragment(StackingContextSection::Outline);
}
// We want to build the scroll frame after the background and border, because