Skip to content

Commit 4719a76

Browse files
committed
LibWeb: Resolve percentage and explicit cell widths in fixed tables
In fixed table layouts, cell percentage widths were incorrectly being resolved against the containing block of the table wrapper (the page) instead of the table's computed width. This caused cells with percentage widths to evaluate to incorrect absolute dimensions and throw off the table column distribution logic. This commit properly queries the table's computed width first and uses that as the reference width when resolving percentage widths in TableFormattingContext::compute_cell_measures(), ensuring that the math adheres to the CSS Tables Level 3 Specification. Test baselines have been updated to match the correct output. Additionally, this commit follows explicit cell widths when calculating fixed table layouts.
1 parent c701781 commit 4719a76

7 files changed

Lines changed: 293 additions & 82 deletions

Libraries/LibWeb/Layout/TableFormattingContext.cpp

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ void TableFormattingContext::compute_constrainedness()
130130

131131
for (auto& cell : m_cells) {
132132
auto const& computed_values = cell.box->computed_values();
133-
if (computed_values.width().is_length()) {
133+
// https://drafts.csswg.org/css-tables-3/#computing-column-measures
134+
// For the purpose of measuring a column when laid out in fixed mode, only cells which originate in the first row of the table (after reordering the header and footer) will be considered, if any.
135+
if (computed_values.width().is_length() && (!use_fixed_mode_layout() || cell.row_index == 0)) {
134136
m_columns[cell.column_index].is_constrained = true;
135137
}
136138

@@ -175,10 +177,27 @@ void TableFormattingContext::compute_cell_measures()
175177
// The outer min-content width of a table-cell is max(min-width, min-content width) adjusted by the cell intrinsic offsets.
176178
auto min_width = computed_values.min_width().to_px(cell.box, containing_block_width);
177179
auto cell_intrinsic_width_offsets = padding_left + padding_right + border_left + border_right;
178-
// For fixed mode, according to https://www.w3.org/TR/css-tables-3/#computing-column-measures:
180+
// For fixed mode, according to https://drafts.csswg.org/css-tables-3/#computing-column-measures:
179181
// The min-content and max-content width of cells is considered zero unless they are directly specified as a length-percentage,
180182
// in which case they are resolved based on the table width (if it is definite, otherwise use 0).
181183
auto width_is_specified_length_or_percentage = computed_values.width().is_length() || computed_values.width().is_percentage();
184+
if (use_fixed_mode_layout()) {
185+
if (computed_values.width().is_percentage()) {
186+
CSSPixels reference_width = 0;
187+
auto table_width = table_box().computed_values().width();
188+
if (table_width.is_length() || table_width.is_percentage())
189+
reference_width = table_width.to_px(table_box(), containing_block_width);
190+
min_content_width = max_content_width = computed_values.width().to_px(cell.box, reference_width);
191+
} else if (computed_values.width().is_length()) {
192+
min_content_width = max_content_width = computed_values.width().to_px(cell.box, containing_block_width);
193+
} else {
194+
min_content_width = max_content_width = 0;
195+
}
196+
197+
if (computed_values.box_sizing() == CSS::BoxSizing::BorderBox)
198+
min_content_width = max_content_width = max(CSSPixels(0), min_content_width - cell_intrinsic_width_offsets);
199+
}
200+
182201
if (!use_fixed_mode_layout() || width_is_specified_length_or_percentage) {
183202
cell.outer_min_width = max(min_width, min_content_width) + cell_intrinsic_width_offsets;
184203
}
@@ -277,8 +296,11 @@ void TableFormattingContext::initialize_table_measures<TableFormattingContext::C
277296
// Implement the following parts of the specification, accounting for fixed layout mode:
278297
// https://www.w3.org/TR/css-tables-3/#min-content-width-of-a-column-based-on-cells-of-span-up-to-1
279298
// https://www.w3.org/TR/css-tables-3/#max-content-width-of-a-column-based-on-cells-of-span-up-to-1
299+
bool const fixed = use_fixed_mode_layout();
280300
for (auto& cell : m_cells) {
281-
if (cell.column_span == 1 && (cell.row_index == 0 || !use_fixed_mode_layout())) {
301+
// https://drafts.csswg.org/css-tables-3/#computing-column-measures
302+
// For the purpose of measuring a column when laid out in fixed mode, only cells which originate in the first row of the table (after reordering the header and footer) will be considered, if any.
303+
if (cell.column_span == 1 && (cell.row_index == 0 || !fixed)) {
282304
m_columns[cell.column_index].min_size = max(m_columns[cell.column_index].min_size, cell.outer_min_width);
283305
m_columns[cell.column_index].max_size = max(m_columns[cell.column_index].max_size, cell.outer_max_width);
284306
}
@@ -301,9 +323,16 @@ void TableFormattingContext::compute_intrinsic_percentage(size_t max_cell_span)
301323
intrinsic_percentage_contribution_by_index[rc_index] = rows_or_columns[rc_index].intrinsic_percentage;
302324
}
303325

326+
[[maybe_unused]] bool const fixed_layout = use_fixed_mode_layout();
304327
for (size_t current_span = 2; current_span <= max_cell_span; current_span++) {
305328
// https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column-based-on-cells-of-span-up-to-n-n--1
306329
for (auto& cell : m_cells) {
330+
if constexpr (IsSame<RowOrColumn, Column>) {
331+
// https://drafts.csswg.org/css-tables-3/#computing-column-measures
332+
// For the purpose of measuring a column when laid out in fixed mode, only cells which originate in the first row of the table (after reordering the header and footer) will be considered, if any.
333+
if (fixed_layout && cell.row_index != 0)
334+
continue;
335+
}
307336
auto cell_span_value = cell_span<RowOrColumn>(cell);
308337
if (cell_span_value != current_span) {
309338
continue;
@@ -372,8 +401,15 @@ void TableFormattingContext::compute_table_measures()
372401

373402
auto& rows_or_columns = table_rows_or_columns<RowOrColumn>();
374403

404+
[[maybe_unused]] bool const fixed_layout = use_fixed_mode_layout();
375405
size_t max_cell_span = 1;
376406
for (auto& cell : m_cells) {
407+
if constexpr (IsSame<RowOrColumn, Column>) {
408+
// https://drafts.csswg.org/css-tables-3/#computing-column-measures
409+
// For the purpose of measuring a column when laid out in fixed mode, only cells which originate in the first row of the table (after reordering the header and footer) will be considered, if any.
410+
if (fixed_layout && cell.row_index != 0)
411+
continue;
412+
}
377413
max_cell_span = max(max_cell_span, cell_span<RowOrColumn>(cell));
378414
}
379415

@@ -389,6 +425,12 @@ void TableFormattingContext::compute_table_measures()
389425
Vector<Vector<CSSPixels>> cell_max_contributions_by_rc_index;
390426
cell_max_contributions_by_rc_index.resize(rows_or_columns.size());
391427
for (auto& cell : m_cells) {
428+
if constexpr (IsSame<RowOrColumn, Column>) {
429+
// https://drafts.csswg.org/css-tables-3/#computing-column-measures
430+
// For the purpose of measuring a column when laid out in fixed mode, only cells which originate in the first row of the table (after reordering the header and footer) will be considered, if any.
431+
if (fixed_layout && cell.row_index != 0)
432+
continue;
433+
}
392434
auto cell_span_value = cell_span<RowOrColumn>(cell);
393435
if (cell_span_value == current_span) {
394436
// Define the baseline max-content size as the sum of the max-content sizes based on cells of span up to N-1 of all columns that the cell spans.
@@ -598,17 +640,21 @@ void TableFormattingContext::compute_table_width()
598640
// https://www.w3.org/TR/CSS22/tables.html#auto-table-layout
599641
// A percentage value for a column width is relative to the table width. If the table has 'width: auto',
600642
// a percentage represents a constraint on the column's width, which a UA should try to satisfy.
601-
for (auto& cell : m_cells) {
602-
auto const& cell_width = cell.box->computed_values().width();
603-
if (cell_width.is_percentage()) {
604-
CSSPixels adjusted_used_width = undistributable_space;
605-
if (cell_width.percentage().value() != 0)
606-
adjusted_used_width += CSSPixels::nearest_value_for(ceil(100 / cell_width.percentage().value() * cell.outer_max_width));
607-
608-
if (width_of_table_containing_block.is_definite())
609-
used_width = min(max(used_width, adjusted_used_width), width_of_table_containing_block.to_px_or_zero());
610-
else
643+
if (!use_fixed_mode_layout()) {
644+
for (auto& cell : m_cells) {
645+
auto const& cell_width = cell.box->computed_values().width();
646+
if (cell_width.is_percentage()) {
647+
auto const pct = cell_width.percentage().value();
648+
if (pct == 0)
649+
continue;
650+
651+
CSSPixels adjusted_used_width = undistributable_space
652+
+ CSSPixels::nearest_value_for(ceil(100 / pct * cell.outer_max_width));
653+
611654
used_width = max(used_width, adjusted_used_width);
655+
if (width_of_table_containing_block.is_definite())
656+
used_width = min(used_width, width_of_table_containing_block.to_px_or_zero());
657+
}
612658
}
613659
}
614660
} else if (computed_values.width().is_max_content()) {
@@ -1955,7 +2001,14 @@ void TableFormattingContext::initialize_intrinsic_percentages_from_cells()
19552001
{
19562002
auto& rows_or_columns = table_rows_or_columns<RowOrColumn>();
19572003

2004+
[[maybe_unused]] bool const fixed_layout = use_fixed_mode_layout();
19582005
for (auto& cell : m_cells) {
2006+
if constexpr (IsSame<RowOrColumn, Column>) {
2007+
// https://drafts.csswg.org/css-tables-3/#computing-column-measures
2008+
// For the purpose of measuring a column when laid out in fixed mode, only cells which originate in the first row of the table (after reordering the header and footer) will be considered, if any.
2009+
if (fixed_layout && cell.row_index != 0)
2010+
continue;
2011+
}
19592012
auto cell_span_value = cell_span<RowOrColumn>(cell);
19602013
auto cell_start_rc_index = cell_index<RowOrColumn>(cell);
19612014
auto cell_end_rc_index = cell_start_rc_index + cell_span_value;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children: not-inline
2+
BlockContainer <html> at [0,0] [0+0+0 800 0+0+0] [0+0+0 26 0+0+0] [BFC] children: not-inline
3+
BlockContainer <body> at [8,8] [8+0+0 784 0+0+8] [8+0+0 10 0+0+8] children: not-inline
4+
TableWrapper <(anonymous)> at [8,8] [0+0+0 50 0+0+734] [0+0+0 10 0+0+0] [BFC] children: not-inline
5+
Box <table> at [8,8] table-box [0+0+0 50 0+0+50] [0+0+0 10 0+0+0] [TFC] children: not-inline
6+
BlockContainer <(anonymous)> (not painted) children: inline
7+
TextNode <#text> (not painted)
8+
Box <tbody> at [8,8] table-row-group [0+0+0 50 0+0+0] [0+0+0 10 0+0+0] children: not-inline
9+
Box <tr> at [8,8] table-row [0+0+0 50 0+0+0] [0+0+0 10 0+0+0] children: not-inline
10+
BlockContainer <(anonymous)> (not painted) children: inline
11+
TextNode <#text> (not painted)
12+
BlockContainer <td> at [8,8] table-cell [0+0+0 50 0+0+0] [0+0+0 10 0+0+0] [BFC] children: not-inline
13+
BlockContainer <(anonymous)> at [8,8] [0+0+0 50 0+0+0] [0+0+0 0 0+0+0] children: inline
14+
TextNode <#text> (not painted)
15+
BlockContainer <div> at [8,8] [0+0+0 10 0+0+40] [0+0+0 10 0+0+0] children: not-inline
16+
BlockContainer <(anonymous)> at [8,18] [0+0+0 50 0+0+0] [0+0+0 0 0+0+0] children: inline
17+
TextNode <#text> (not painted)
18+
BlockContainer <(anonymous)> (not painted) children: inline
19+
TextNode <#text> (not painted)
20+
BlockContainer <(anonymous)> (not painted) children: inline
21+
TextNode <#text> (not painted)
22+
BlockContainer <(anonymous)> at [8,18] [0+0+0 784 0+0+0] [0+0+0 0 0+0+0] children: inline
23+
TextNode <#text> (not painted)
24+
25+
ViewportPaintable (Viewport<#document>) [0,0 800x600]
26+
PaintableWithLines (BlockContainer<HTML>) [0,0 800x26]
27+
PaintableWithLines (BlockContainer<BODY>) [8,8 784x10]
28+
PaintableWithLines (TableWrapper(anonymous)) [8,8 50x10]
29+
PaintableBox (Box<TABLE>) [8,8 50x10]
30+
PaintableBox (Box<TBODY>) [8,8 50x10]
31+
PaintableBox (Box<TR>) [8,8 50x10]
32+
PaintableWithLines (BlockContainer<TD>) [8,8 50x10]
33+
PaintableWithLines (BlockContainer(anonymous)) [8,8 50x0]
34+
PaintableWithLines (BlockContainer<DIV>) [8,8 10x10]
35+
PaintableWithLines (BlockContainer(anonymous)) [8,18 50x0]
36+
PaintableWithLines (BlockContainer(anonymous)) [8,18 784x0]
37+
38+
SC for Viewport<#document> [0,0 800x600] [children: 1] (z-index: auto)
39+
SC for BlockContainer<HTML> [0,0 800x26] [children: 0] (z-index: auto)

0 commit comments

Comments
 (0)