diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index d51b0685a2a..2ff27cb99aa 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -849,6 +849,13 @@ impl Fragment { mode: StackingContextBuildMode, text_decorations: &Arc>, ) { + if self + .base() + .is_some_and(|base| base.flags.contains(FragmentFlags::IS_COLLAPSED)) + { + return; + } + let containing_block = containing_block_info.get_containing_block_for_fragment(self); let fragment_clone = self.clone(); match self { diff --git a/components/layout/fragment_tree/base_fragment.rs b/components/layout/fragment_tree/base_fragment.rs index 0392647c502..1768e85cae6 100644 --- a/components/layout/fragment_tree/base_fragment.rs +++ b/components/layout/fragment_tree/base_fragment.rs @@ -178,6 +178,9 @@ bitflags! { const IS_ROOT_ELEMENT = 1 << 9; /// If element has propagated the overflow value to viewport. const PROPAGATED_OVERFLOW_TO_VIEWPORT = 1 << 10; + /// Whether or not this is a table cell that is part of a collapsed row or column. + /// In that case it should not be painted. + const IS_COLLAPSED = 1 << 11; } } diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 989c8f79d39..62a8bf220fb 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -6,6 +6,7 @@ use app_units::{Au, MAX_AU, MIN_AU}; use atomic_refcell::AtomicRefCell; use base::id::ScrollTreeNodeId; use base::print_tree::PrintTree; +use euclid::Rect; use malloc_size_of_derive::MallocSizeOf; use servo_arc::Arc as ServoArc; use servo_geometry::f32_rect_to_au_rect; @@ -291,6 +292,15 @@ impl BoxFragment { acc.union(&scrollable_overflow_from_child) }); + // Fragments with `IS_COLLAPSED` (currently only table cells that are part of + // table tracks with `visibility: collapse`) should not contribute to scrollable + // overflow. This behavior matches Chrome, but not Firefox. + // See https://github.com/w3c/csswg-drafts/issues/12689 + if self.base.flags.contains(FragmentFlags::IS_COLLAPSED) { + self.scrollable_overflow = Some(Rect::zero()); + return; + } + self.scrollable_overflow = Some(scrollable_overflow) } diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs index c85857e5d38..2ef5202bc0c 100644 --- a/components/layout/table/layout.rs +++ b/components/layout/table/layout.rs @@ -1817,10 +1817,7 @@ impl<'a> TableLayout<'a> { baselines.last = Some(row_end); } - if self.is_row_collapsed(row_index) { - continue; - } - + let row_is_collapsed = self.is_row_collapsed(row_index); let table_row = self.table.rows[row_index].borrow(); let mut row_fragment_layout = RowFragmentLayout::new( &table_row, @@ -1856,10 +1853,6 @@ impl<'a> TableLayout<'a> { let column_indices = 0..self.table.size.width; row_fragment_layout.fragments.reserve(self.table.size.width); for column_index in column_indices { - if self.is_column_collapsed(column_index) { - continue; - } - self.do_final_cell_layout( row_index, column_index, @@ -1868,6 +1861,7 @@ impl<'a> TableLayout<'a> { &mut row_fragment_layout, row_group_fragment_layout.as_mut(), positioning_context, + self.is_column_collapsed(column_index) || row_is_collapsed, ); } @@ -1997,6 +1991,7 @@ impl<'a> TableLayout<'a> { row_fragment_layout: &mut RowFragmentLayout, row_group_fragment_layout: Option<&mut RowGroupFragmentLayout>, positioning_context_for_table: &mut PositioningContext, + is_collapsed: bool, ) { // The PositioningContext for cells is, in order or preference, the PositioningContext of the row, // the PositioningContext of the row group, or the PositioningContext of the table. @@ -2046,6 +2041,7 @@ impl<'a> TableLayout<'a> { positioning_context, &self.table.style, &row_fragment_layout.containing_block, + is_collapsed, ); // Make a table part rectangle relative to the row fragment for the purposes of @@ -2829,6 +2825,7 @@ impl TableSlotCell { .sizes } + #[allow(clippy::too_many_arguments)] fn create_fragment( &self, mut layout: CellLayout, @@ -2837,6 +2834,7 @@ impl TableSlotCell { positioning_context: &mut PositioningContext, table_style: &ComputedValues, containing_block: &ContainingBlock, + is_collapsed: bool, ) -> BoxFragment { // This must be scoped to this function because it conflicts with euclid's Zero. use style::Zero as StyleZero; @@ -2864,6 +2862,10 @@ impl TableSlotCell { base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT); } + if is_collapsed { + base_fragment_info.flags.insert(FragmentFlags::IS_COLLAPSED); + } + // Create an `AnonymousFragment` to move the cell contents to the cell baseline. let mut vertical_align_fragment_rect = cell_content_rect; vertical_align_fragment_rect.start_corner = LogicalVec2 { diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index f49618fcb20..c13f56d6d51 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -5414,6 +5414,13 @@ {} ] ], + "table-collapsed-row-or-column-crash.html": [ + "5a8bb7e5479e93b00d7d02731aa00a48a45269be", + [ + null, + {} + ] + ], "visibility-collapse-colspan-crash.html": [ "591fbd9a9941648f1456b41aeee1e23ed660a1ed", [ diff --git a/tests/wpt/tests/css/css-tables/table-collapsed-row-or-column-crash.html b/tests/wpt/tests/css/css-tables/table-collapsed-row-or-column-crash.html new file mode 100644 index 00000000000..5a8bb7e5479 --- /dev/null +++ b/tests/wpt/tests/css/css-tables/table-collapsed-row-or-column-crash.html @@ -0,0 +1,22 @@ + + + + +
+
+
+ + + + + + + + +
+ + +