LibWeb: Reset border-image to initial value when using border

Also includes associated handling for serialization of the `border`
shorthand.
This commit is contained in:
Callum Law 2025-09-15 11:40:27 +12:00 committed by Tim Ledbetter
commit 50239b58aa
Notes: github-actions[bot] 2025-09-15 07:43:28 +00:00
8 changed files with 161 additions and 14 deletions

View file

@ -1570,6 +1570,7 @@ RefPtr<StyleValue const> Parser::parse_single_background_size_value(PropertyID p
return BackgroundSizeStyleValue::create(x_size.release_value(), y_size.release_value());
}
// https://drafts.csswg.org/css-backgrounds-3/#propdef-border
RefPtr<StyleValue const> Parser::parse_border_value(PropertyID property_id, TokenStream<ComponentValue>& tokens)
{
RefPtr<StyleValue const> border_width;
@ -1639,11 +1640,17 @@ RefPtr<StyleValue const> Parser::parse_border_value(PropertyID property_id, Toke
if (!border_color)
border_color = property_initial_value(color_property);
// FIXME: Also reset border-image, in line with the spec: https://www.w3.org/TR/css-backgrounds-3/#border-shorthands
transaction.commit();
if (first_is_one_of(property_id, PropertyID::BorderBlock, PropertyID::BorderInline))
return ShorthandStyleValue::create(property_id,
{ width_property, style_property, color_property },
{ border_width.release_nonnull(), border_style.release_nonnull(), border_color.release_nonnull() });
// The border shorthand also resets border-image to its initial value
return ShorthandStyleValue::create(property_id,
{ width_property, style_property, color_property },
{ border_width.release_nonnull(), border_style.release_nonnull(), border_color.release_nonnull() });
{ width_property, style_property, color_property, PropertyID::BorderImage },
{ border_width.release_nonnull(), border_style.release_nonnull(), border_color.release_nonnull(), property_initial_value(PropertyID::BorderImage) });
}
// https://drafts.csswg.org/css-backgrounds/#border-image

View file

@ -498,7 +498,8 @@
"longhands": [
"border-width",
"border-style",
"border-color"
"border-color",
"border-image"
],
"needs-layout-for-getcomputedstyle": true
},

View file

@ -239,17 +239,45 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
return MUST(builder.to_string());
}
case PropertyID::Border: {
// `border` only has a reasonable value if border-image is it's initial value (in which case it is omitted)
if (!longhand(PropertyID::BorderImage)->equals(property_initial_value(PropertyID::BorderImage)))
return ""_string;
auto all_longhands_same_value = [](ValueComparingRefPtr<StyleValue const> const& shorthand) -> bool {
auto longhands = shorthand->as_shorthand().values();
return all_of(longhands, [&](auto const& longhand) { return longhand == longhands[0]; });
};
auto const& border_width = longhand(PropertyID::BorderWidth);
auto const& border_style = longhand(PropertyID::BorderStyle);
auto const& border_color = longhand(PropertyID::BorderColor);
// `border` only has a reasonable value if all four sides are the same.
if (!all_longhands_same_value(longhand(PropertyID::BorderWidth)) || !all_longhands_same_value(longhand(PropertyID::BorderStyle)) || !all_longhands_same_value(longhand(PropertyID::BorderColor)))
if (!all_longhands_same_value(border_width) || !all_longhands_same_value(border_style) || !all_longhands_same_value(border_color))
return ""_string;
return default_to_string();
StringBuilder builder;
if (!border_width->equals(property_initial_value(PropertyID::BorderWidth)))
builder.appendff("{}", border_width->to_string(mode));
if (!border_style->equals(property_initial_value(PropertyID::BorderStyle))) {
if (!builder.is_empty())
builder.append(' ');
builder.appendff("{}", border_style->to_string(mode));
}
if (!border_color->equals(property_initial_value(PropertyID::BorderColor))) {
if (!builder.is_empty())
builder.append(' ');
builder.appendff("{}", border_color->to_string(mode));
}
if (builder.is_empty())
return border_width->to_string(mode);
return builder.to_string_without_validation();
}
case PropertyID::BorderImage: {
auto source = longhand(PropertyID::BorderImageSource);

View file

@ -30,7 +30,7 @@ Setting border: '1px solid red'; becomes...
border-style: 'solid'
border-color: 'red'
border: '1px solid red'
e.style.length: 12
e.style.length: 17
> [0] border-top-width
> [1] border-right-width
> [2] border-bottom-width
@ -43,4 +43,9 @@ Setting border: '1px solid red'; becomes...
> [9] border-right-color
> [10] border-bottom-color
> [11] border-left-color
> [12] border-image-source
> [13] border-image-slice
> [14] border-image-width
> [15] border-image-outset
> [16] border-image-repeat

View file

@ -0,0 +1,41 @@
Harness status: OK
Found 36 tests
36 Pass
Pass e.style['border'] = "2px solid color-mix(42deg)" should not set the property value
Pass e.style['border'] = "2px solid color-contrast(42deg)" should not set the property value
Pass e.style['border'] = "5px dotted blue" should set border-bottom-color
Pass e.style['border'] = "5px dotted blue" should set border-bottom-style
Pass e.style['border'] = "5px dotted blue" should set border-bottom-width
Pass e.style['border'] = "5px dotted blue" should set border-image-outset
Pass e.style['border'] = "5px dotted blue" should set border-image-repeat
Pass e.style['border'] = "5px dotted blue" should set border-image-slice
Pass e.style['border'] = "5px dotted blue" should set border-image-source
Pass e.style['border'] = "5px dotted blue" should set border-image-width
Pass e.style['border'] = "5px dotted blue" should set border-left-color
Pass e.style['border'] = "5px dotted blue" should set border-left-style
Pass e.style['border'] = "5px dotted blue" should set border-left-width
Pass e.style['border'] = "5px dotted blue" should set border-right-color
Pass e.style['border'] = "5px dotted blue" should set border-right-style
Pass e.style['border'] = "5px dotted blue" should set border-right-width
Pass e.style['border'] = "5px dotted blue" should set border-top-color
Pass e.style['border'] = "5px dotted blue" should set border-top-style
Pass e.style['border'] = "5px dotted blue" should set border-top-width
Pass e.style['border'] = "5px dotted blue" should not set unrelated longhands
Pass e.style['border-top'] = "thin" should set border-top-color
Pass e.style['border-top'] = "thin" should set border-top-style
Pass e.style['border-top'] = "thin" should set border-top-width
Pass e.style['border-top'] = "thin" should not set unrelated longhands
Pass e.style['border-right'] = "double" should set border-right-color
Pass e.style['border-right'] = "double" should set border-right-style
Pass e.style['border-right'] = "double" should set border-right-width
Pass e.style['border-right'] = "double" should not set unrelated longhands
Pass e.style['border-bottom'] = "green" should set border-bottom-color
Pass e.style['border-bottom'] = "green" should set border-bottom-style
Pass e.style['border-bottom'] = "green" should set border-bottom-width
Pass e.style['border-bottom'] = "green" should not set unrelated longhands
Pass e.style['border-left'] = "1px dotted red" should set border-left-color
Pass e.style['border-left'] = "1px dotted red" should set border-left-style
Pass e.style['border-left'] = "1px dotted red" should set border-left-width
Pass e.style['border-left'] = "1px dotted red" should not set unrelated longhands

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 3 tests
1 Pass
2 Fail
Fail Declaration with border longhands is not serialized to a border shorthand declaration.
Fail Declaration with border longhands and border-image is not serialized to a border shorthand declaration.
3 Pass
Pass Declaration with border longhands is not serialized to a border shorthand declaration.
Pass Declaration with border longhands and border-image is not serialized to a border shorthand declaration.
Pass Border shorthand is serialized correctly if all border-image-* are set to their initial specified values.

View file

@ -2,13 +2,13 @@ Harness status: OK
Found 20 tests
14 Pass
6 Fail
15 Pass
5 Fail
Pass The serialization of border: 1px; border-top: 1px; should be canonical.
Pass The serialization of border: 1px solid red; should be canonical.
Pass The serialization of border: 1px red; should be canonical.
Pass The serialization of border: red; should be canonical.
Fail The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; border-image: none; should be canonical.
Pass The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; border-image: none; should be canonical.
Fail The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; should be canonical.
Fail The serialization of border-top: 1px; border-right: 2px; border-bottom: 3px; border-left: 4px; should be canonical.
Fail The serialization of border: 1px; border-top: 2px; should be canonical.

View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Backgrounds and Borders Module Level 3: border sets longhands</title>
<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-shorthands">
<meta name="assert" content="border supports the full grammar '<line-width> || <line-style> || <color>'.">
<meta name="assert" content="The border shorthand also resets border-image to its initial value.">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../css/support/shorthand-testcommon.js"></script>
<script src="../../../css/support/parsing-testcommon.js"></script>
</head>
<body>
<script>
test_invalid_value('border', '2px solid color-mix(42deg)');
test_invalid_value('border', '2px solid color-contrast(42deg)');
test_shorthand_value('border', '5px dotted blue', {
'border-top-width': '5px',
'border-right-width': '5px',
'border-bottom-width': '5px',
'border-left-width': '5px',
'border-top-style': 'dotted',
'border-right-style': 'dotted',
'border-bottom-style': 'dotted',
'border-left-style': 'dotted',
'border-top-color': 'blue',
'border-right-color': 'blue',
'border-bottom-color': 'blue',
'border-left-color': 'blue',
'border-image-source': 'none',
'border-image-slice': '100%',
'border-image-width': '1',
'border-image-outset': '0',
'border-image-repeat': 'stretch'
});
test_shorthand_value('border-top', 'thin', {
'border-top-width': 'thin',
'border-top-style': 'none',
'border-top-color': 'currentcolor'
});
test_shorthand_value('border-right', 'double', {
'border-right-width': 'medium',
'border-right-style': 'double',
'border-right-color': 'currentcolor'
});
test_shorthand_value('border-bottom', 'green', {
'border-bottom-width': 'medium',
'border-bottom-style': 'none',
'border-bottom-color': 'green'
});
test_shorthand_value('border-left', '1px dotted red', {
'border-left-width': '1px',
'border-left-style': 'dotted',
'border-left-color': 'red'
});
</script>
</body>
</html>