LibWeb/CSS: Implement the empty-cells property

This property sets whether table borders and backgrounds are painted
if a given table cell has no visible content.
This commit is contained in:
Tim Ledbetter 2025-06-18 12:28:18 +01:00 committed by Sam Atkins
parent a0cef5a7da
commit e0af205d69
Notes: github-actions[bot] 2025-06-18 13:56:15 +00:00
22 changed files with 253 additions and 10 deletions

View file

@ -1577,6 +1577,12 @@ BorderCollapse ComputedProperties::border_collapse() const
return keyword_to_border_collapse(value.to_keyword()).release_value(); return keyword_to_border_collapse(value.to_keyword()).release_value();
} }
EmptyCells ComputedProperties::empty_cells() const
{
auto const& value = property(PropertyID::EmptyCells);
return keyword_to_empty_cells(value.to_keyword()).release_value();
}
Vector<Vector<String>> ComputedProperties::grid_template_areas() const Vector<Vector<String>> ComputedProperties::grid_template_areas() const
{ {
auto const& value = property(PropertyID::GridTemplateAreas); auto const& value = property(PropertyID::GridTemplateAreas);

View file

@ -159,6 +159,7 @@ public:
GridTrackPlacement grid_row_end() const; GridTrackPlacement grid_row_end() const;
GridTrackPlacement grid_row_start() const; GridTrackPlacement grid_row_start() const;
BorderCollapse border_collapse() const; BorderCollapse border_collapse() const;
CSS::EmptyCells empty_cells() const;
Vector<Vector<String>> grid_template_areas() const; Vector<Vector<String>> grid_template_areas() const;
ObjectFit object_fit() const; ObjectFit object_fit() const;
ObjectPosition object_position() const; ObjectPosition object_position() const;

View file

@ -182,6 +182,7 @@ public:
static CSS::Size column_width() { return CSS::Size::make_auto(); } static CSS::Size column_width() { return CSS::Size::make_auto(); }
static Variant<LengthPercentage, NormalGap> row_gap() { return NormalGap {}; } static Variant<LengthPercentage, NormalGap> row_gap() { return NormalGap {}; }
static CSS::BorderCollapse border_collapse() { return CSS::BorderCollapse::Separate; } static CSS::BorderCollapse border_collapse() { return CSS::BorderCollapse::Separate; }
static CSS::EmptyCells empty_cells() { return CSS::EmptyCells::Show; }
static Vector<Vector<String>> grid_template_areas() { return {}; } static Vector<Vector<String>> grid_template_areas() { return {}; }
static CSS::Time transition_delay() { return CSS::Time::make_seconds(0); } static CSS::Time transition_delay() { return CSS::Time::make_seconds(0); }
static CSS::ObjectFit object_fit() { return CSS::ObjectFit::Fill; } static CSS::ObjectFit object_fit() { return CSS::ObjectFit::Fill; }
@ -490,6 +491,7 @@ public:
CSS::Size const& column_width() const { return m_noninherited.column_width; } CSS::Size const& column_width() const { return m_noninherited.column_width; }
Variant<LengthPercentage, NormalGap> const& row_gap() const { return m_noninherited.row_gap; } Variant<LengthPercentage, NormalGap> const& row_gap() const { return m_noninherited.row_gap; }
CSS::BorderCollapse border_collapse() const { return m_inherited.border_collapse; } CSS::BorderCollapse border_collapse() const { return m_inherited.border_collapse; }
CSS::EmptyCells empty_cells() const { return m_inherited.empty_cells; }
Vector<Vector<String>> const& grid_template_areas() const { return m_noninherited.grid_template_areas; } Vector<Vector<String>> const& grid_template_areas() const { return m_noninherited.grid_template_areas; }
CSS::ObjectFit object_fit() const { return m_noninherited.object_fit; } CSS::ObjectFit object_fit() const { return m_noninherited.object_fit; }
CSS::ObjectPosition object_position() const { return m_noninherited.object_position; } CSS::ObjectPosition object_position() const { return m_noninherited.object_position; }
@ -622,6 +624,7 @@ protected:
Optional<HashMap<FlyString, NumberOrCalculated>> font_variation_settings; Optional<HashMap<FlyString, NumberOrCalculated>> font_variation_settings;
CSSPixels line_height { InitialValues::line_height() }; CSSPixels line_height { InitialValues::line_height() };
CSS::BorderCollapse border_collapse { InitialValues::border_collapse() }; CSS::BorderCollapse border_collapse { InitialValues::border_collapse() };
CSS::EmptyCells empty_cells { InitialValues::empty_cells() };
CSS::Length border_spacing_horizontal { InitialValues::border_spacing() }; CSS::Length border_spacing_horizontal { InitialValues::border_spacing() };
CSS::Length border_spacing_vertical { InitialValues::border_spacing() }; CSS::Length border_spacing_vertical { InitialValues::border_spacing() };
CSS::CaptionSide caption_side { InitialValues::caption_side() }; CSS::CaptionSide caption_side { InitialValues::caption_side() };
@ -930,6 +933,7 @@ public:
void set_column_width(CSS::Size const& column_width) { m_noninherited.column_width = column_width; } void set_column_width(CSS::Size const& column_width) { m_noninherited.column_width = column_width; }
void set_row_gap(Variant<LengthPercentage, NormalGap> const& row_gap) { m_noninherited.row_gap = row_gap; } void set_row_gap(Variant<LengthPercentage, NormalGap> const& row_gap) { m_noninherited.row_gap = row_gap; }
void set_border_collapse(CSS::BorderCollapse const border_collapse) { m_inherited.border_collapse = border_collapse; } void set_border_collapse(CSS::BorderCollapse const border_collapse) { m_inherited.border_collapse = border_collapse; }
void set_empty_cells(CSS::EmptyCells const empty_cells) { m_inherited.empty_cells = empty_cells; }
void set_grid_template_areas(Vector<Vector<String>> const& grid_template_areas) { m_noninherited.grid_template_areas = grid_template_areas; } void set_grid_template_areas(Vector<Vector<String>> const& grid_template_areas) { m_noninherited.grid_template_areas = grid_template_areas; }
void set_grid_auto_flow(CSS::GridAutoFlow grid_auto_flow) { m_noninherited.grid_auto_flow = grid_auto_flow; } void set_grid_auto_flow(CSS::GridAutoFlow grid_auto_flow) { m_noninherited.grid_auto_flow = grid_auto_flow; }
void set_transition_delay(CSS::Time const& transition_delay) { m_noninherited.transition_delay = transition_delay; } void set_transition_delay(CSS::Time const& transition_delay) { m_noninherited.transition_delay = transition_delay; }

View file

@ -224,6 +224,10 @@
"inline", "inline",
"run-in" "run-in"
], ],
"empty-cells": [
"show",
"hide"
],
"fill-rule": [ "fill-rule": [
"nonzero", "nonzero",
"evenodd" "evenodd"

View file

@ -224,6 +224,7 @@
"groove", "groove",
"hard-light", "hard-light",
"help", "help",
"hide",
"hidden", "hidden",
"high", "high",
"high-quality", "high-quality",
@ -449,6 +450,7 @@
"separate", "separate",
"serif", "serif",
"shorter", "shorter",
"show",
"sideways-lr", "sideways-lr",
"sideways-rl", "sideways-rl",
"simplified", "simplified",

View file

@ -1265,6 +1265,14 @@
"display-legacy" "display-legacy"
] ]
}, },
"empty-cells": {
"animation-type": "discrete",
"inherited": true,
"initial": "show",
"valid-types": [
"empty-cells"
]
},
"fill": { "fill": {
"affects-layout": false, "affects-layout": false,
"animation-type": "none", "animation-type": "none",

View file

@ -924,6 +924,8 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
computed_values.set_border_collapse(computed_style.border_collapse()); computed_values.set_border_collapse(computed_style.border_collapse());
computed_values.set_empty_cells(computed_style.empty_cells());
computed_values.set_table_layout(computed_style.table_layout()); computed_values.set_table_layout(computed_style.table_layout());
auto const& aspect_ratio = computed_style.property(CSS::PropertyID::AspectRatio); auto const& aspect_ratio = computed_style.property(CSS::PropertyID::AspectRatio);

View file

@ -437,7 +437,11 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
if (!is_visible()) if (!is_visible())
return; return;
if (phase == PaintPhase::Background) { auto empty_cells_property_applies = [this]() {
return display().is_internal_table() && computed_values().empty_cells() == CSS::EmptyCells::Hide && !has_children();
};
if (phase == PaintPhase::Background && !empty_cells_property_applies()) {
paint_backdrop_filter(context); paint_backdrop_filter(context);
paint_background(context); paint_background(context);
paint_box_shadow(context); paint_box_shadow(context);

View file

@ -378,6 +378,8 @@ void paint_table_borders(PaintContext& context, PaintableBox const& table_painta
auto cell_coordinates_to_device_rect = snap_cells_to_device_coordinates(cell_coordinates_to_box, row_count, column_count, context); auto cell_coordinates_to_device_rect = snap_cells_to_device_coordinates(cell_coordinates_to_box, row_count, column_count, context);
for (auto const& cell_box : cell_boxes) { for (auto const& cell_box : cell_boxes) {
if (table_paintable.computed_values().border_collapse() == CSS::BorderCollapse::Separate) { if (table_paintable.computed_values().border_collapse() == CSS::BorderCollapse::Separate) {
if (cell_box.computed_values().empty_cells() == CSS::EmptyCells::Hide && !cell_box.has_children())
continue;
paint_separate_cell_borders(cell_box, cell_coordinates_to_device_rect, context); paint_separate_cell_borders(cell_box, cell_coordinates_to_device_rect, context);
continue; continue;
} }

View file

@ -0,0 +1,9 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>No red visible, blank page, reference</title>
</head>
<body>
<p>Test passes if there is no red visible on the page.</p>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>CSS Test: Empty-cells and 'display: table-column' elements</title>
<link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-empty-cells" />
<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#empty-cells" />
<link rel="match" href="../../../../../expected/wpt-import/css/CSS2/reference/no-red-on-blank-page-ref.xht" />
<meta name="assert" content="Empty-cells does not apply to 'display: table-column' elements." />
<style type="text/css">
#table
{
display: table;
}
.tr
{
display: table-row;
}
.td
{
color: white;
display: table-cell;
}
#test
{
background: red;
border: 5px solid red;
display: table-column;
empty-cells: hide;
height: 1em;
width: 1em;
}
</style>
</head>
<body>
<p>Test passes if there is no red visible on the page.</p>
<div id="table">
<div class="tr">
<div id="test"></div>
<div class="td">XXXXX</div>
</div>
<div class="tr">
<div class="td">XXXXX</div>
<div class="td">XXXXX</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>CSS Test: Empty-cells and 'display: table-column-group' elements</title>
<link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-empty-cells" />
<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#empty-cells" />
<link rel="match" href="../../../../../expected/wpt-import/css/CSS2/reference/no-red-on-blank-page-ref.xht" />
<meta name="assert" content="Empty-cells does not apply to 'display: table-column-group' elements." />
<style type="text/css">
#table
{
display: table;
table-layout: fixed;
}
.tr
{
display: table-row;
}
.td
{
color: white;
display: table-cell;
}
#test
{
background: red;
border: 5px solid red;
display: table-column-group;
empty-cells: hide;
height: 1em;
width: 1em;
}
</style>
</head>
<body>
<p>Test passes if there is no red visible on the page.</p>
<div id="table">
<div class="tr">
<div id="test"></div>
<div class="td">XXXXX</div>
</div>
<div class="tr">
<div class="td">XXXXX</div>
<div class="td">XXXXX</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>CSS Test: Empty-cells and 'display: table-cell' elements</title>
<link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-empty-cells" />
<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#empty-cells" />
<link rel="match" href="../../../../../expected/wpt-import/css/CSS2/reference/no-red-on-blank-page-ref.xht" />
<meta name="assert" content="Empty-cells applies to 'display: table-cell' elements." />
<style type="text/css">
#table
{
display: table;
table-layout: fixed;
}
.tr
{
display: table-row;
}
.td
{
color: white;
display: table-cell;
}
#test
{
background: red;
border: 5px solid red;
display: table-cell;
empty-cells: hide;
height: 1em;
width: 1em;
}
</style>
</head>
<body>
<p>Test passes if there is no red visible on the page.</p>
<div id="table">
<div class="tr">
<div id="test"></div>
<div class="td">XXXXX</div>
</div>
<div class="tr">
<div class="td">XXXXX</div>
<div class="td">XXXXX</div>
</div>
</div>
</body>
</html>

View file

@ -11,6 +11,7 @@ All properties associated with getComputedStyle(document.body):
"color-scheme", "color-scheme",
"cursor", "cursor",
"direction", "direction",
"empty-cells",
"fill", "fill",
"fill-opacity", "fill-opacity",
"fill-rule", "fill-rule",

View file

@ -327,6 +327,8 @@ All supported properties and their default values exposed from CSSStylePropertie
'cy': '0px' 'cy': '0px'
'direction': 'ltr' 'direction': 'ltr'
'display': 'block' 'display': 'block'
'emptyCells': 'show'
'empty-cells': 'show'
'fill': 'rgb(0, 0, 0)' 'fill': 'rgb(0, 0, 0)'
'fillOpacity': '1' 'fillOpacity': '1'
'fill-opacity': '1' 'fill-opacity': '1'

View file

@ -9,6 +9,7 @@ color: rgb(0, 0, 0)
color-scheme: normal color-scheme: normal
cursor: auto cursor: auto
direction: ltr direction: ltr
empty-cells: show
fill: rgb(0, 0, 0) fill: rgb(0, 0, 0)
fill-opacity: 1 fill-opacity: 1
fill-rule: nonzero fill-rule: nonzero
@ -86,7 +87,7 @@ background-position-x: 0%
background-position-y: 0% background-position-y: 0%
background-repeat: repeat background-repeat: repeat
background-size: auto auto background-size: auto auto
block-size: 1320px block-size: 1335px
border-block-end-color: rgb(0, 0, 0) border-block-end-color: rgb(0, 0, 0)
border-block-end-style: none border-block-end-style: none
border-block-end-width: medium border-block-end-width: medium
@ -151,7 +152,7 @@ grid-row-start: auto
grid-template-areas: none grid-template-areas: none
grid-template-columns: none grid-template-columns: none
grid-template-rows: none grid-template-rows: none
height: 2295px height: 2310px
inline-size: 784px inline-size: 784px
inset-block-end: auto inset-block-end: auto
inset-block-start: auto inset-block-start: auto

View file

@ -1,8 +1,8 @@
Harness status: OK Harness status: OK
Found 204 tests Found 205 tests
200 Pass 201 Pass
4 Fail 4 Fail
Pass accent-color Pass accent-color
Pass border-collapse Pass border-collapse
@ -14,6 +14,7 @@ Pass color
Pass color-scheme Pass color-scheme
Pass cursor Pass cursor
Pass direction Pass direction
Pass empty-cells
Pass fill Pass fill
Pass fill-opacity Pass fill-opacity
Pass fill-rule Pass fill-rule

View file

@ -0,0 +1,7 @@
Harness status: OK
Found 2 tests
2 Pass
Pass e.style['empty-cells'] = "auto" should not set the property value
Pass e.style['empty-cells'] = "show hide" should not set the property value

View file

@ -0,0 +1,7 @@
Harness status: OK
Found 2 tests
2 Pass
Pass e.style['empty-cells'] = "show" should set the property value
Pass e.style['empty-cells'] = "hide" should set the property value

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 687 tests Found 687 tests
654 Pass 657 Pass
33 Fail 30 Fail
Pass background-attachment: scroll Pass background-attachment: scroll
Pass background-attachment: fixed Pass background-attachment: fixed
Pass background-attachment: inherit Pass background-attachment: inherit
@ -374,9 +374,9 @@ Pass display: table-cell
Pass display: table-caption Pass display: table-caption
Pass display: none Pass display: none
Pass display: inherit Pass display: inherit
Fail empty-cells: show Pass empty-cells: show
Fail empty-cells: hide Pass empty-cells: hide
Fail empty-cells: inherit Pass empty-cells: inherit
Pass float: left Pass float: left
Pass float: right Pass float: right
Pass float: none Pass float: none

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Table Module Test: parsing empty-cells with invalid values</title>
<link rel="help" href="https://drafts.csswg.org/css-tables/#propdef-empty-cells">
<meta name="assert" content="empty-cells supports only the grammar 'show | hide'.">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../css/support/parsing-testcommon.js"></script>
</head>
<body>
<script>
test_invalid_value("empty-cells", "auto");
test_invalid_value("empty-cells", "show hide");
</script>
</body>
</html>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Table Module Test: parsing empty-cells with valid values</title>
<link rel="help" href="https://drafts.csswg.org/css-tables/#propdef-empty-cells">
<meta name="assert" content="empty-cells supports the full grammar 'show | hide'.">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../css/support/parsing-testcommon.js"></script>
</head>
<body>
<script>
test_valid_value("empty-cells", "show");
test_valid_value("empty-cells", "hide");
</script>
</body>
</html>