LibWeb: Implement table missing cells fixup

Fixes #19936.
This commit is contained in:
Andi Gallo 2023-08-05 05:50:07 +00:00 committed by Alexander Kalenik
parent 4c0816b002
commit 769f11f9ae
Notes: sideshowbarker 2024-07-18 00:41:35 +09:00
8 changed files with 199 additions and 24 deletions

View file

@ -0,0 +1,57 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x120.40625 children: not-inline
Box <div> at (8,8) content-size 784x120.40625 [GFC] children: not-inline
TableWrapper <(anonymous)> at (8,8) content-size 100x120.40625 [BFC] children: not-inline
Box <table> at (9,9) content-size 98x118.40625 table-box [TFC] children: not-inline
Box <tbody> at (9,9) content-size 98x118.40625 table-row-group children: not-inline
Box <tr> at (9,9) content-size 98x39.46875 table-row children: not-inline
BlockContainer <td> at (20,20) content-size 30.5625x17.46875 table-cell [BFC] children: inline
line 0 width: 14.265625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [20,20 14.265625x17.46875]
"A"
TextNode <#text>
BlockContainer <td> at (72.5625,20) content-size 23.4375x17.46875 table-cell [BFC] children: inline
line 0 width: 9.34375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [72.5625,20 9.34375x17.46875]
"B"
TextNode <#text>
Box <tr> at (9,48.46875) content-size 98x39.46875 table-row children: not-inline
BlockContainer <td> at (20,59.46875) content-size 30.5625x17.46875 table-cell [BFC] children: inline
line 0 width: 14.265625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [20,59.46875 14.265625x17.46875]
"A"
TextNode <#text>
BlockContainer <(anonymous)> at (62.5625,68.703125) content-size 21.71875x0 table-cell [BFC] children: not-inline
BlockContainer <(anonymous)> at (84.28125,68.203125) content-size 21.71875x0 table-cell [BFC] children: not-inline
Box <tr> at (9,87.9375) content-size 98x39.46875 table-row children: not-inline
BlockContainer <td> at (20,98.9375) content-size 30.5625x17.46875 table-cell [BFC] children: inline
line 0 width: 14.265625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [20,98.9375 14.265625x17.46875]
"A"
TextNode <#text>
BlockContainer <(anonymous)> at (62.5625,107.171875) content-size 21.71875x0 table-cell [BFC] children: not-inline
BlockContainer <(anonymous)> at (84.28125,107.171875) content-size 21.71875x0 table-cell [BFC] children: not-inline
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x120.40625]
PaintableBox (Box<DIV>) [8,8 784x120.40625]
PaintableWithLines (TableWrapper(anonymous)) [8,8 100x120.40625]
PaintableBox (Box<TABLE>) [8,8 100x120.40625]
PaintableBox (Box<TBODY>) [9,9 98x118.40625]
PaintableBox (Box<TR>) [9,9 98x39.46875]
PaintableWithLines (BlockContainer<TD>) [9,9 52.5625x39.46875]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer<TD>) [61.5625,9 45.4375x39.46875]
TextPaintable (TextNode<#text>)
PaintableBox (Box<TR>) [9,48.46875 98x39.46875]
PaintableWithLines (BlockContainer<TD>) [9,48.46875 52.5625x39.46875]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [61.5625,48.46875 22.71875x39.46875]
PaintableWithLines (BlockContainer(anonymous)) [84.28125,48.46875 22.71875x39.46875]
PaintableBox (Box<TR>) [9,87.9375 98x39.46875]
PaintableWithLines (BlockContainer<TD>) [9,87.9375 52.5625x39.46875]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [61.5625,87.9375 22.71875x39.46875]
PaintableWithLines (BlockContainer(anonymous)) [84.28125,87.9375 22.71875x39.46875]

View file

@ -0,0 +1,53 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x120.40625 children: not-inline
Box <div> at (8,8) content-size 784x120.40625 [GFC] children: not-inline
TableWrapper <(anonymous)> at (8,8) content-size 100x120.40625 [BFC] children: not-inline
Box <table> at (9,9) content-size 98x118.40625 table-box [TFC] children: not-inline
Box <tbody> at (9,9) content-size 98x118.40625 table-row-group children: not-inline
Box <tr> at (9,9) content-size 98x39.46875 table-row children: not-inline
BlockContainer <td> at (20,20) content-size 30.5625x17.46875 table-cell [BFC] children: inline
line 0 width: 14.265625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [20,20 14.265625x17.46875]
"A"
TextNode <#text>
BlockContainer <td> at (72.5625,20) content-size 23.4375x17.46875 table-cell [BFC] children: inline
line 0 width: 9.34375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [72.5625,20 9.34375x17.46875]
"B"
TextNode <#text>
Box <tr> at (9,48.46875) content-size 98x39.46875 table-row children: not-inline
BlockContainer <td> at (20,59.46875) content-size 30.5625x17.46875 table-cell [BFC] children: inline
line 0 width: 14.265625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [20,59.46875 14.265625x17.46875]
"A"
TextNode <#text>
BlockContainer <(anonymous)> at (62.5625,68.703125) content-size 43.4375x0 table-cell [BFC] children: not-inline
Box <tr> at (9,87.9375) content-size 98x39.46875 table-row children: not-inline
BlockContainer <td> at (20,98.9375) content-size 30.5625x17.46875 table-cell [BFC] children: inline
line 0 width: 14.265625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 1, rect: [20,98.9375 14.265625x17.46875]
"A"
TextNode <#text>
BlockContainer <(anonymous)> at (62.5625,107.171875) content-size 43.4375x0 table-cell [BFC] children: not-inline
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x120.40625]
PaintableBox (Box<DIV>) [8,8 784x120.40625]
PaintableWithLines (TableWrapper(anonymous)) [8,8 100x120.40625]
PaintableBox (Box<TABLE>) [8,8 100x120.40625]
PaintableBox (Box<TBODY>) [9,9 98x118.40625]
PaintableBox (Box<TR>) [9,9 98x39.46875]
PaintableWithLines (BlockContainer<TD>) [9,9 52.5625x39.46875]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer<TD>) [61.5625,9 45.4375x39.46875]
TextPaintable (TextNode<#text>)
PaintableBox (Box<TR>) [9,48.46875 98x39.46875]
PaintableWithLines (BlockContainer<TD>) [9,48.46875 52.5625x39.46875]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [61.5625,48.46875 45.4375x39.46875]
PaintableBox (Box<TR>) [9,87.9375 98x39.46875]
PaintableWithLines (BlockContainer<TD>) [9,87.9375 52.5625x39.46875]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [61.5625,87.9375 45.4375x39.46875]

View file

@ -0,0 +1,16 @@
<style>
div {
display: grid;
}
table {
border: 1px solid black;
border-collapse: collapse;
width: 100px;
}
td {
border: 1px solid black;
padding: 10px;
}
</style><div><table><tr><td>A</td><td colspan="2">B</td></tr><tr><td>A</td></tr><tr><td>A</td></tr></table></div>

View file

@ -0,0 +1,16 @@
<style>
div {
display: grid;
}
table {
border: 1px solid black;
border-collapse: collapse;
width: 100px;
}
td {
border: 1px solid black;
padding: 10px;
}
</style><div><table><tr><td>A</td><td>B</td></tr><tr><td>A</td></tr><tr><td>A</td></tr></table></div>

View file

@ -12,26 +12,6 @@
#include <LibWeb/Layout/InlineFormattingContext.h>
#include <LibWeb/Layout/TableFormattingContext.h>
struct GridPosition {
size_t x;
size_t y;
};
inline bool operator==(GridPosition const& a, GridPosition const& b)
{
return a.x == b.x && a.y == b.y;
}
namespace AK {
template<>
struct Traits<GridPosition> : public GenericTraits<GridPosition> {
static unsigned hash(GridPosition const& key)
{
return pair_int_hash(key.x, key.y);
}
};
}
namespace Web::Layout {
TableFormattingContext::TableFormattingContext(LayoutState& state, Box const& root, FormattingContext* parent)

View file

@ -38,7 +38,6 @@ public:
private:
CSSPixels run_caption_layout(LayoutMode, CSS::CaptionSide);
CSSPixels compute_capmin();
void calculate_row_column_grid(Box const&);
void compute_constrainedness();
void compute_cell_measures();
void compute_outer_content_sizes();

View file

@ -25,6 +25,7 @@
#include <LibWeb/Layout/ListItemMarkerBox.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Layout/Progress.h>
#include <LibWeb/Layout/TableGrid.h>
#include <LibWeb/Layout/TableWrapper.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Layout/TreeBuilder.h>
@ -451,7 +452,8 @@ void TreeBuilder::fixup_tables(NodeWithStyle& root)
{
remove_irrelevant_boxes(root);
generate_missing_child_wrappers(root);
generate_missing_parents(root);
auto table_root_boxes = generate_missing_parents(root);
missing_cells_fixup(table_root_boxes);
}
void TreeBuilder::remove_irrelevant_boxes(NodeWithStyle& root)
@ -621,7 +623,7 @@ void TreeBuilder::generate_missing_child_wrappers(NodeWithStyle& root)
});
}
void TreeBuilder::generate_missing_parents(NodeWithStyle& root)
Vector<JS::Handle<Box>> TreeBuilder::generate_missing_parents(NodeWithStyle& root)
{
Vector<JS::Handle<Box>> table_roots_to_wrap;
root.for_each_in_inclusive_subtree_of_type<Box>([&](auto& parent) {
@ -671,6 +673,57 @@ void TreeBuilder::generate_missing_parents(NodeWithStyle& root)
else
parent.append_child(*wrapper);
}
return table_roots_to_wrap;
}
template<typename Matcher, typename Callback>
static void for_each_child_box_matching(Box& parent, Matcher matcher, Callback callback)
{
parent.for_each_child_of_type<Box>([&](Box& child_box) {
if (matcher(child_box))
callback(child_box);
});
}
static void fixup_row(Box& row_box, TableGrid const& table_grid, size_t row_index)
{
bool missing_cells_run_has_started = false;
for (size_t column_index = 0; column_index < table_grid.column_count(); ++column_index) {
if (table_grid.occupancy_grid().contains({ column_index, row_index })) {
VERIFY(!missing_cells_run_has_started);
continue;
}
missing_cells_run_has_started = true;
auto row_computed_values = row_box.computed_values().clone_inherited_values();
auto& cell_computed_values = static_cast<CSS::MutableComputedValues&>(row_computed_values);
cell_computed_values.set_display(Web::CSS::Display { CSS::Display::Internal::TableCell });
// Ensure that the cell (with zero content height) will have the same height as the row by setting vertical-align to middle.
cell_computed_values.set_vertical_align(CSS::VerticalAlign::Middle);
auto cell_box = row_box.heap().template allocate_without_realm<BlockContainer>(row_box.document(), nullptr, cell_computed_values);
row_box.append_child(cell_box);
}
}
void TreeBuilder::missing_cells_fixup(Vector<JS::Handle<Box>> const& table_root_boxes)
{
// Implements https://www.w3.org/TR/css-tables-3/#missing-cells-fixup.
for (auto& table_box : table_root_boxes) {
auto table_grid = TableGrid::calculate_row_column_grid(*table_box);
size_t row_index = 0;
for_each_child_box_matching(*table_box, TableGrid::is_table_row_group, [&](auto& row_group_box) {
for_each_child_box_matching(row_group_box, is_table_row, [&](auto& row_box) {
fixup_row(row_box, table_grid, row_index);
++row_index;
return IterationDecision::Continue;
});
});
for_each_child_box_matching(*table_box, is_table_row, [&](auto& row_box) {
fixup_row(row_box, table_grid, row_index);
++row_index;
return IterationDecision::Continue;
});
}
}
}

View file

@ -39,7 +39,8 @@ private:
void fixup_tables(NodeWithStyle& root);
void remove_irrelevant_boxes(NodeWithStyle& root);
void generate_missing_child_wrappers(NodeWithStyle& root);
void generate_missing_parents(NodeWithStyle& root);
Vector<JS::Handle<Box>> generate_missing_parents(NodeWithStyle& root);
void missing_cells_fixup(Vector<JS::Handle<Box>> const&);
enum class AppendOrPrepend {
Append,