LibWeb: Generically serialize "positional-value-list-shorthand"s

We were previously handling this ad-hoc via logic in
`get_property_internal` but this didn't cover all contexts (for
instance `CSSStyleProperties::serialized`.

Gains us 9 more WPT tests as we now cover properties which weren't
included in the previous ad-hoc approach.
This commit is contained in:
Callum Law 2025-07-10 22:23:07 +12:00 committed by Sam Atkins
commit 927cd969b2
Notes: github-actions[bot] 2025-07-15 13:27:25 +00:00
14 changed files with 234 additions and 115 deletions

View file

@ -425,44 +425,6 @@ StringView CSSStyleProperties::get_property_priority(StringView property_name) c
return maybe_property->important == Important::Yes ? "important"sv : ""sv;
}
static Optional<StyleProperty> style_property_for_sided_shorthand(PropertyID property_id, Optional<StyleProperty> const& top, Optional<StyleProperty> const& right, Optional<StyleProperty> const& bottom, Optional<StyleProperty> const& left)
{
if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value())
return {};
if (top->important != right->important || top->important != bottom->important || top->important != left->important)
return {};
ValueComparingNonnullRefPtr<CSSStyleValue const> const top_value { top->value };
ValueComparingNonnullRefPtr<CSSStyleValue const> const right_value { right->value };
ValueComparingNonnullRefPtr<CSSStyleValue const> const bottom_value { bottom->value };
ValueComparingNonnullRefPtr<CSSStyleValue const> const left_value { left->value };
bool const top_and_bottom_same = top_value == bottom_value;
bool const left_and_right_same = left_value == right_value;
if ((top_value->is_css_wide_keyword() || right_value->is_css_wide_keyword() || bottom_value->is_css_wide_keyword() || left_value->is_css_wide_keyword()) && (!top_and_bottom_same || !left_and_right_same || top_value != left_value))
return {};
RefPtr<CSSStyleValue const> value;
if (top_and_bottom_same && left_and_right_same && top_value == left_value) {
value = top_value;
} else if (top_and_bottom_same && left_and_right_same) {
value = StyleValueList::create(StyleValueVector { top_value, right_value }, StyleValueList::Separator::Space);
} else if (left_and_right_same) {
value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value }, StyleValueList::Separator::Space);
} else {
value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value, left_value }, StyleValueList::Separator::Space);
}
return StyleProperty {
.important = top->important,
.property_id = property_id,
.value = value.release_nonnull(),
};
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
Optional<StyleProperty> CSSStyleProperties::get_property_internal(PropertyID property_id) const
{
@ -488,41 +450,6 @@ Optional<StyleProperty> CSSStyleProperties::get_property_internal(PropertyID pro
{ width->value, style->value, color->value })
};
}
case PropertyID::BorderColor: {
auto top = get_property_internal(PropertyID::BorderTopColor);
auto right = get_property_internal(PropertyID::BorderRightColor);
auto bottom = get_property_internal(PropertyID::BorderBottomColor);
auto left = get_property_internal(PropertyID::BorderLeftColor);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::BorderStyle: {
auto top = get_property_internal(PropertyID::BorderTopStyle);
auto right = get_property_internal(PropertyID::BorderRightStyle);
auto bottom = get_property_internal(PropertyID::BorderBottomStyle);
auto left = get_property_internal(PropertyID::BorderLeftStyle);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::BorderWidth: {
auto top = get_property_internal(PropertyID::BorderTopWidth);
auto right = get_property_internal(PropertyID::BorderRightWidth);
auto bottom = get_property_internal(PropertyID::BorderBottomWidth);
auto left = get_property_internal(PropertyID::BorderLeftWidth);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::Margin: {
auto top = get_property_internal(PropertyID::MarginTop);
auto right = get_property_internal(PropertyID::MarginRight);
auto bottom = get_property_internal(PropertyID::MarginBottom);
auto left = get_property_internal(PropertyID::MarginLeft);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::Padding: {
auto top = get_property_internal(PropertyID::PaddingTop);
auto right = get_property_internal(PropertyID::PaddingRight);
auto bottom = get_property_internal(PropertyID::PaddingBottom);
auto left = get_property_internal(PropertyID::PaddingLeft);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
default:
break;
}

View file

@ -711,6 +711,7 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue const>> Parser::parse_css_value
if (auto parsed_value = parse_opacity_value(property_id, tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
// FIXME: This can be removed once we have generic logic for parsing "positional-value-list-shorthand"s
case PropertyID::Overflow:
if (auto parsed_value = parse_overflow_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();

View file

@ -638,6 +638,8 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
return;
}
// FIXME: We should add logic in parse_css_value to parse "positional-value-list-shorthand"s as
// ShorthandStyleValues to avoid the need for this (and assign_start_and_end_values).
auto assign_edge_values = [&](PropertyID top_property, PropertyID right_property, PropertyID bottom_property, PropertyID left_property, CSSStyleValue const& value) {
if (value.is_value_list()) {
auto values = value.as_value_list().values();

View file

@ -68,6 +68,39 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
return ""_string;
}
auto positional_value_list_shorthand_to_string = [&](Vector<ValueComparingNonnullRefPtr<CSSStyleValue const>> values) -> String {
switch (values.size()) {
case 2: {
auto first_property_serialized = values[0]->to_string(mode);
auto second_property_serialized = values[1]->to_string(mode);
if (first_property_serialized == second_property_serialized)
return first_property_serialized;
return MUST(String::formatted("{} {}", first_property_serialized, second_property_serialized));
}
case 4: {
auto first_property_serialized = values[0]->to_string(mode);
auto second_property_serialized = values[1]->to_string(mode);
auto third_property_serialized = values[2]->to_string(mode);
auto fourth_property_serialized = values[3]->to_string(mode);
if (first_is_equal_to_all_of(first_property_serialized, second_property_serialized, third_property_serialized, fourth_property_serialized))
return first_property_serialized;
if (first_property_serialized == third_property_serialized && second_property_serialized == fourth_property_serialized)
return MUST(String::formatted("{} {}", first_property_serialized, second_property_serialized));
if (second_property_serialized == fourth_property_serialized)
return MUST(String::formatted("{} {} {}", first_property_serialized, second_property_serialized, third_property_serialized));
return MUST(String::formatted("{} {} {} {}", first_property_serialized, second_property_serialized, third_property_serialized, fourth_property_serialized));
}
default:
VERIFY_NOT_REACHED();
}
};
auto default_to_string = [&]() {
auto all_properties_same_value = true;
auto first_property_value = m_properties.values.first();
@ -368,13 +401,6 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
return "normal"_string;
return MUST(String::join(' ', values));
}
case PropertyID::Gap: {
auto row_gap = longhand(PropertyID::RowGap);
auto column_gap = longhand(PropertyID::ColumnGap);
if (row_gap == column_gap)
return row_gap->to_string(mode);
return MUST(String::formatted("{} {}", row_gap->to_string(mode), column_gap->to_string(mode)));
}
case PropertyID::GridArea: {
auto& row_start = longhand(PropertyID::GridRowStart)->as_grid_track_placement();
auto& column_start = longhand(PropertyID::GridColumnStart)->as_grid_track_placement();
@ -442,35 +468,10 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
return start->to_string(mode);
return MUST(String::formatted("{} / {}", start->to_string(mode), end->to_string(mode)));
}
case PropertyID::Overflow: {
auto overflow_x = longhand(PropertyID::OverflowX);
auto overflow_y = longhand(PropertyID::OverflowY);
if (overflow_x == overflow_y)
return overflow_x->to_string(mode);
return MUST(String::formatted("{} {}", overflow_x->to_string(mode), overflow_y->to_string(mode)));
}
case PropertyID::PlaceContent: {
auto align_content = longhand(PropertyID::AlignContent)->to_string(mode);
auto justify_content = longhand(PropertyID::JustifyContent)->to_string(mode);
if (align_content == justify_content)
return align_content;
return MUST(String::formatted("{} {}", align_content, justify_content));
}
case PropertyID::PlaceItems: {
auto align_items = longhand(PropertyID::AlignItems)->to_string(mode);
auto justify_items = longhand(PropertyID::JustifyItems)->to_string(mode);
if (align_items == justify_items)
return align_items;
return MUST(String::formatted("{} {}", align_items, justify_items));
}
case PropertyID::PlaceSelf: {
auto align_self = longhand(PropertyID::AlignSelf)->to_string(mode);
auto justify_self = longhand(PropertyID::JustifySelf)->to_string(mode);
if (align_self == justify_self)
return align_self;
return MUST(String::formatted("{} {}", align_self, justify_self));
}
case PropertyID::PlaceContent:
case PropertyID::PlaceItems:
case PropertyID::PlaceSelf:
return positional_value_list_shorthand_to_string(m_properties.values);
case PropertyID::TextDecoration: {
// The rule here seems to be, only print what's different from the default value,
// but if they're all default, print the line.
@ -521,6 +522,9 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
return default_to_string();
}
default:
if (property_is_positional_value_list_shorthand(m_properties.shorthand_property))
return positional_value_list_shorthand_to_string(m_properties.values);
return default_to_string();
}
}