Skip to content

Commit 919df4d

Browse files
committed
LibWeb: Resolve percentage cell widths properly in fixed layout 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.
1 parent 8a657a9 commit 919df4d

5 files changed

Lines changed: 222 additions & 83 deletions

File tree

Libraries/LibWeb/Layout/TableFormattingContext.cpp

Lines changed: 61 additions & 14 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://www.w3.org/TR/css-tables-3/#fixed-layout
134+
// In fixed layout, only cells in the first row are considered for column measurement.
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

@@ -180,9 +182,20 @@ void TableFormattingContext::compute_cell_measures()
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();
182184
if (use_fixed_mode_layout()) {
183-
min_content_width = max_content_width = width_is_specified_length_or_percentage ? computed_values.width().to_px(cell.box, containing_block_width) : 0;
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+
184197
if (computed_values.box_sizing() == CSS::BoxSizing::BorderBox)
185-
min_content_width = max_content_width = max(CSSPixels(0), min_content_width - padding_left - padding_right - border_left - border_right);
198+
min_content_width = max_content_width = max(CSSPixels(0), min_content_width - cell_intrinsic_width_offsets);
186199
}
187200

188201
if (!use_fixed_mode_layout() || width_is_specified_length_or_percentage) {
@@ -283,8 +296,11 @@ void TableFormattingContext::initialize_table_measures<TableFormattingContext::C
283296
// Implement the following parts of the specification, accounting for fixed layout mode:
284297
// https://www.w3.org/TR/css-tables-3/#min-content-width-of-a-column-based-on-cells-of-span-up-to-1
285298
// 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();
286300
for (auto& cell : m_cells) {
287-
if (cell.column_span == 1 && (cell.row_index == 0 || !use_fixed_mode_layout())) {
301+
// https://www.w3.org/TR/css-tables-3/#fixed-layout
302+
// In fixed layout, only cells in the first row are considered for column measurement.
303+
if (cell.column_span == 1 && (cell.row_index == 0 || !fixed)) {
288304
m_columns[cell.column_index].min_size = max(m_columns[cell.column_index].min_size, cell.outer_min_width);
289305
m_columns[cell.column_index].max_size = max(m_columns[cell.column_index].max_size, cell.outer_max_width);
290306
}
@@ -307,9 +323,16 @@ void TableFormattingContext::compute_intrinsic_percentage(size_t max_cell_span)
307323
intrinsic_percentage_contribution_by_index[rc_index] = rows_or_columns[rc_index].intrinsic_percentage;
308324
}
309325

326+
[[maybe_unused]] bool const fixed_layout = use_fixed_mode_layout();
310327
for (size_t current_span = 2; current_span <= max_cell_span; current_span++) {
311328
// https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column-based-on-cells-of-span-up-to-n-n--1
312329
for (auto& cell : m_cells) {
330+
if constexpr (IsSame<RowOrColumn, Column>) {
331+
// https://www.w3.org/TR/css-tables-3/#fixed-layout
332+
// In fixed layout, only cells in the first row are considered for column measurement.
333+
if (fixed_layout && cell.row_index != 0)
334+
continue;
335+
}
313336
auto cell_span_value = cell_span<RowOrColumn>(cell);
314337
if (cell_span_value != current_span) {
315338
continue;
@@ -378,8 +401,15 @@ void TableFormattingContext::compute_table_measures()
378401

379402
auto& rows_or_columns = table_rows_or_columns<RowOrColumn>();
380403

404+
[[maybe_unused]] bool const fixed_layout = use_fixed_mode_layout();
381405
size_t max_cell_span = 1;
382406
for (auto& cell : m_cells) {
407+
if constexpr (IsSame<RowOrColumn, Column>) {
408+
// https://www.w3.org/TR/css-tables-3/#fixed-layout
409+
// In fixed layout, only cells in the first row are considered for column measurement.
410+
if (fixed_layout && cell.row_index != 0)
411+
continue;
412+
}
383413
max_cell_span = max(max_cell_span, cell_span<RowOrColumn>(cell));
384414
}
385415

@@ -395,6 +425,12 @@ void TableFormattingContext::compute_table_measures()
395425
Vector<Vector<CSSPixels>> cell_max_contributions_by_rc_index;
396426
cell_max_contributions_by_rc_index.resize(rows_or_columns.size());
397427
for (auto& cell : m_cells) {
428+
if constexpr (IsSame<RowOrColumn, Column>) {
429+
// https://www.w3.org/TR/css-tables-3/#fixed-layout
430+
// In fixed layout, only cells in the first row are considered for column measurement.
431+
if (fixed_layout && cell.row_index != 0)
432+
continue;
433+
}
398434
auto cell_span_value = cell_span<RowOrColumn>(cell);
399435
if (cell_span_value == current_span) {
400436
// 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.
@@ -604,17 +640,21 @@ void TableFormattingContext::compute_table_width()
604640
// https://www.w3.org/TR/CSS22/tables.html#auto-table-layout
605641
// A percentage value for a column width is relative to the table width. If the table has 'width: auto',
606642
// a percentage represents a constraint on the column's width, which a UA should try to satisfy.
607-
for (auto& cell : m_cells) {
608-
auto const& cell_width = cell.box->computed_values().width();
609-
if (cell_width.is_percentage()) {
610-
CSSPixels adjusted_used_width = undistributable_space;
611-
if (cell_width.percentage().value() != 0)
612-
adjusted_used_width += CSSPixels::nearest_value_for(ceil(100 / cell_width.percentage().value() * cell.outer_max_width));
613-
614-
if (width_of_table_containing_block.is_definite())
615-
used_width = min(max(used_width, adjusted_used_width), width_of_table_containing_block.to_px_or_zero());
616-
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+
617654
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+
}
618658
}
619659
}
620660
} else if (computed_values.width().is_max_content()) {
@@ -1961,7 +2001,14 @@ void TableFormattingContext::initialize_intrinsic_percentages_from_cells()
19612001
{
19622002
auto& rows_or_columns = table_rows_or_columns<RowOrColumn>();
19632003

2004+
[[maybe_unused]] bool const fixed_layout = use_fixed_mode_layout();
19642005
for (auto& cell : m_cells) {
2006+
if constexpr (IsSame<RowOrColumn, Column>) {
2007+
// https://www.w3.org/TR/css-tables-3/#fixed-layout
2008+
// In fixed layout, only cells in the first row are considered for column measurement.
2009+
if (fixed_layout && cell.row_index != 0)
2010+
continue;
2011+
}
19652012
auto cell_span_value = cell_span<RowOrColumn>(cell);
19662013
auto cell_start_rc_index = cell_index<RowOrColumn>(cell);
19672014
auto cell_end_rc_index = cell_start_rc_index + cell_span_value;

Tests/LibWeb/Layout/expected/table/fixed-layout-percentage-width-all-columns.txt

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,59 @@ Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children
55
Box <table> at [9,9] table-box [0+1+0 598 0+1+0] [0+1+0 44 0+1+0] [TFC] children: not-inline
66
BlockContainer <(anonymous)> (not painted) children: inline
77
TextNode <#text> (not painted)
8-
Box <tbody> at [9,9] table-row-group [0+0+0 598 0+0+0] [0+0+0 44 0+0+0] children: not-inline
9-
Box <tr> at [9,9] table-row [0+0+0 598 0+0+0] [0+0+0 22 0+0+0] children: not-inline
8+
Box <tbody> at [9,9] table-row-group [0+0+0 597.984375 0+0+0] [0+0+0 44 0+0+0] children: not-inline
9+
Box <tr> at [9,9] table-row [0+0+0 597.984375 0+0+0] [0+0+0 22 0+0+0] children: not-inline
1010
BlockContainer <(anonymous)> (not painted) children: inline
1111
TextNode <#text> (not painted)
12-
BlockContainer <td> at [11,11] table-cell [0+1+1 62.4375 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
12+
BlockContainer <td> at [11,11] table-cell [0+1+1 64.828125 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
1313
frag 0 from TextNode start: 0, length: 4, rect: [11,11 26.078125x18] baseline: 13.796875
1414
"cell"
1515
TextNode <#text> (not painted)
1616
BlockContainer <(anonymous)> (not painted) children: inline
1717
TextNode <#text> (not painted)
18-
BlockContainer <td> at [77.4375,11] table-cell [0+1+1 62.4375 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
19-
frag 0 from TextNode start: 0, length: 4, rect: [77.4375,11 26.078125x18] baseline: 13.796875
18+
BlockContainer <td> at [79.828125,11] table-cell [0+1+1 64.828125 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
19+
frag 0 from TextNode start: 0, length: 4, rect: [79.828125,11 26.078125x18] baseline: 13.796875
2020
"cell"
2121
TextNode <#text> (not painted)
2222
BlockContainer <(anonymous)> (not painted) children: inline
2323
TextNode <#text> (not painted)
24-
BlockContainer <td> at [143.875,11] table-cell [0+1+1 128.890625 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
25-
frag 0 from TextNode start: 0, length: 12, rect: [143.875,11 94.96875x18] baseline: 13.796875
24+
BlockContainer <td> at [148.65625,11] table-cell [0+1+1 129.359375 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
25+
frag 0 from TextNode start: 0, length: 12, rect: [148.65625,11 94.96875x18] baseline: 13.796875
2626
"A table cell"
2727
TextNode <#text> (not painted)
2828
BlockContainer <(anonymous)> (not painted) children: inline
2929
TextNode <#text> (not painted)
30-
BlockContainer <td> at [276.765625,11] table-cell [0+1+1 328.234375 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
31-
frag 0 from TextNode start: 0, length: 12, rect: [276.765625,11 94.96875x18] baseline: 13.796875
30+
BlockContainer <td> at [282.015625,11] table-cell [0+1+1 322.96875 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
31+
frag 0 from TextNode start: 0, length: 12, rect: [282.015625,11 94.96875x18] baseline: 13.796875
3232
"A table cell"
3333
TextNode <#text> (not painted)
3434
BlockContainer <(anonymous)> (not painted) children: inline
3535
TextNode <#text> (not painted)
3636
BlockContainer <(anonymous)> (not painted) children: inline
3737
TextNode <#text> (not painted)
38-
Box <tr> at [9,31] table-row [0+0+0 598 0+0+0] [0+0+0 22 0+0+0] children: not-inline
38+
Box <tr> at [9,31] table-row [0+0+0 597.984375 0+0+0] [0+0+0 22 0+0+0] children: not-inline
3939
BlockContainer <(anonymous)> (not painted) children: inline
4040
TextNode <#text> (not painted)
41-
BlockContainer <td> at [11,33] table-cell [0+1+1 62.4375 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
41+
BlockContainer <td> at [11,33] table-cell [0+1+1 64.828125 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
4242
frag 0 from TextNode start: 0, length: 4, rect: [11,33 26.078125x18] baseline: 13.796875
4343
"cell"
4444
TextNode <#text> (not painted)
4545
BlockContainer <(anonymous)> (not painted) children: inline
4646
TextNode <#text> (not painted)
47-
BlockContainer <td> at [77.4375,33] table-cell [0+1+1 62.4375 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
48-
frag 0 from TextNode start: 0, length: 4, rect: [77.4375,33 26.078125x18] baseline: 13.796875
47+
BlockContainer <td> at [79.828125,33] table-cell [0+1+1 64.828125 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
48+
frag 0 from TextNode start: 0, length: 4, rect: [79.828125,33 26.078125x18] baseline: 13.796875
4949
"cell"
5050
TextNode <#text> (not painted)
5151
BlockContainer <(anonymous)> (not painted) children: inline
5252
TextNode <#text> (not painted)
53-
BlockContainer <td> at [143.875,33] table-cell [0+1+1 128.890625 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
54-
frag 0 from TextNode start: 0, length: 12, rect: [143.875,33 94.96875x18] baseline: 13.796875
53+
BlockContainer <td> at [148.65625,33] table-cell [0+1+1 129.359375 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
54+
frag 0 from TextNode start: 0, length: 12, rect: [148.65625,33 94.96875x18] baseline: 13.796875
5555
"A table cell"
5656
TextNode <#text> (not painted)
5757
BlockContainer <(anonymous)> (not painted) children: inline
5858
TextNode <#text> (not painted)
59-
BlockContainer <td> at [276.765625,33] table-cell [0+1+1 328.234375 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
60-
frag 0 from TextNode start: 0, length: 12, rect: [276.765625,33 94.96875x18] baseline: 13.796875
59+
BlockContainer <td> at [282.015625,33] table-cell [0+1+1 322.96875 1+1+0] [0+1+1 18 1+1+0] [BFC] children: inline
60+
frag 0 from TextNode start: 0, length: 12, rect: [282.015625,33 94.96875x18] baseline: 13.796875
6161
"A table cell"
6262
TextNode <#text> (not painted)
6363
BlockContainer <(anonymous)> (not painted) children: inline
@@ -70,24 +70,24 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
7070
PaintableWithLines (BlockContainer<BODY>) [8,8 784x46]
7171
PaintableWithLines (TableWrapper(anonymous)) [8,8 600x46]
7272
PaintableBox (Box<TABLE>) [8,8 600x46]
73-
PaintableBox (Box<TBODY>) [9,9 598x44]
74-
PaintableBox (Box<TR>) [9,9 598x22]
75-
PaintableWithLines (BlockContainer<TD>) [9,9 66.4375x22]
73+
PaintableBox (Box<TBODY>) [9,9 597.984375x44]
74+
PaintableBox (Box<TR>) [9,9 597.984375x22]
75+
PaintableWithLines (BlockContainer<TD>) [9,9 68.828125x22]
7676
TextPaintable (TextNode<#text>)
77-
PaintableWithLines (BlockContainer<TD>) [75.4375,9 66.4375x22]
77+
PaintableWithLines (BlockContainer<TD>) [77.828125,9 68.828125x22]
7878
TextPaintable (TextNode<#text>)
79-
PaintableWithLines (BlockContainer<TD>) [141.875,9 132.890625x22]
79+
PaintableWithLines (BlockContainer<TD>) [146.65625,9 133.359375x22]
8080
TextPaintable (TextNode<#text>)
81-
PaintableWithLines (BlockContainer<TD>) [274.765625,9 332.234375x22]
81+
PaintableWithLines (BlockContainer<TD>) [280.015625,9 326.96875x22]
8282
TextPaintable (TextNode<#text>)
83-
PaintableBox (Box<TR>) [9,31 598x22]
84-
PaintableWithLines (BlockContainer<TD>) [9,31 66.4375x22]
83+
PaintableBox (Box<TR>) [9,31 597.984375x22]
84+
PaintableWithLines (BlockContainer<TD>) [9,31 68.828125x22]
8585
TextPaintable (TextNode<#text>)
86-
PaintableWithLines (BlockContainer<TD>) [75.4375,31 66.4375x22]
86+
PaintableWithLines (BlockContainer<TD>) [77.828125,31 68.828125x22]
8787
TextPaintable (TextNode<#text>)
88-
PaintableWithLines (BlockContainer<TD>) [141.875,31 132.890625x22]
88+
PaintableWithLines (BlockContainer<TD>) [146.65625,31 133.359375x22]
8989
TextPaintable (TextNode<#text>)
90-
PaintableWithLines (BlockContainer<TD>) [274.765625,31 332.234375x22]
90+
PaintableWithLines (BlockContainer<TD>) [280.015625,31 326.96875x22]
9191
TextPaintable (TextNode<#text>)
9292

9393
SC for Viewport<#document> [0,0 800x600] [children: 1] (z-index: auto)

0 commit comments

Comments
 (0)