LibWeb: Convert white-space CSS property to shorthand

This exposed a few bugs which caused the following tests to behave
incorrectly:
- `tab-size-text-wrap.html`: This previously relied on a bug where we
  incorrectly treated `white-space: pre` as allowing text wrapping. The
  fix here is to implement the text-wrap CSS shorthand property.

- `execCommand-preserveWhitespace.html`: We don't correctly serialize
  shorthand properties. This is covered by an existing FIXME in
  `CSSStyleProperties::serialized()`

- `white-space-shorthand.html`: The last 5 subtests here fail as we
  don't correctly handle shorthand properties in
  `CSSStyleProperties::remove_property()`. This is covered by an
  existing FIXME in said function.
This commit is contained in:
Callum Law 2025-05-22 00:31:24 +12:00 committed by Jelle Raaijmakers
commit 94f5a51820
Notes: github-actions[bot] 2025-05-29 10:05:43 +00:00
28 changed files with 568 additions and 308 deletions

View file

@ -61,6 +61,38 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
if (all_same_keyword && built_in_keyword.has_value())
return m_properties.values.first()->to_string(mode);
auto default_to_string = [&]() {
auto all_properties_same_value = true;
auto first_property_value = m_properties.values.first();
for (auto i = 1u; i < m_properties.values.size(); ++i) {
if (m_properties.values[i] != first_property_value) {
all_properties_same_value = false;
break;
}
}
if (all_properties_same_value)
return first_property_value->to_string(mode);
StringBuilder builder;
auto first = true;
for (size_t i = 0; i < m_properties.values.size(); ++i) {
auto value = m_properties.values[i];
auto value_string = value->to_string(mode);
auto initial_value_string = property_initial_value(m_properties.sub_properties[i])->to_string(mode);
if (value_string == initial_value_string)
continue;
if (first)
first = false;
else
builder.append(' ');
builder.append(value->to_string(mode));
}
if (builder.is_empty())
return m_properties.values.first()->to_string(mode);
return MUST(builder.to_string());
};
// Then special cases
switch (m_properties.shorthand_property) {
case PropertyID::Background: {
@ -402,36 +434,34 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
return builder.to_string_without_validation();
}
case PropertyID::WhiteSpace: {
auto white_space_collapse_property = longhand(PropertyID::WhiteSpaceCollapse);
auto text_wrap_mode_property = longhand(PropertyID::TextWrapMode);
auto white_space_trim_property = longhand(PropertyID::WhiteSpaceTrim);
RefPtr<CSSStyleValue const> value;
if (white_space_trim_property->is_keyword() && white_space_trim_property->as_keyword().keyword() == Keyword::None) {
auto white_space_collapse_keyword = white_space_collapse_property->as_keyword().keyword();
auto text_wrap_mode_keyword = text_wrap_mode_property->as_keyword().keyword();
if (white_space_collapse_keyword == Keyword::Collapse && text_wrap_mode_keyword == Keyword::Wrap)
return "normal"_string;
if (white_space_collapse_keyword == Keyword::Preserve && text_wrap_mode_keyword == Keyword::Nowrap)
return "pre"_string;
if (white_space_collapse_keyword == Keyword::Preserve && text_wrap_mode_keyword == Keyword::Wrap)
return "pre-wrap"_string;
if (white_space_collapse_keyword == Keyword::PreserveBreaks && text_wrap_mode_keyword == Keyword::Wrap)
return "pre-line"_string;
}
return default_to_string();
}
default:
auto all_properties_same_value = true;
auto first_property_value = m_properties.values.first();
for (auto i = 1u; i < m_properties.values.size(); ++i) {
if (m_properties.values[i] != first_property_value) {
all_properties_same_value = false;
break;
}
}
if (all_properties_same_value)
return first_property_value->to_string(mode);
StringBuilder builder;
auto first = true;
for (size_t i = 0; i < m_properties.values.size(); ++i) {
auto value = m_properties.values[i];
auto value_string = value->to_string(mode);
auto initial_value_string = property_initial_value(m_properties.sub_properties[i])->to_string(mode);
if (value_string == initial_value_string)
continue;
if (first)
first = false;
else
builder.append(' ');
builder.append(value->to_string(mode));
}
if (builder.is_empty())
return m_properties.values.first()->to_string(mode);
return MUST(builder.to_string());
return default_to_string();
}
}