LibWeb: Parse and propagate white-space-trim CSS property

This commit is contained in:
Callum Law 2025-05-18 02:21:42 +12:00 committed by Jelle Raaijmakers
commit 9480b1fc5c
Notes: github-actions[bot] 2025-05-29 10:06:01 +00:00
14 changed files with 151 additions and 7 deletions

View file

@ -836,6 +836,38 @@ WhiteSpaceCollapse ComputedProperties::white_space_collapse() const
return keyword_to_white_space_collapse(value.to_keyword()).release_value();
}
WhiteSpaceTrimData ComputedProperties::white_space_trim() const
{
auto const& value = property(PropertyID::WhiteSpaceTrim);
if (value.is_keyword() && value.to_keyword() == Keyword::None)
return WhiteSpaceTrimData {};
if (value.is_value_list()) {
auto white_space_trim_data = WhiteSpaceTrimData {};
for (auto const& value : value.as_value_list().values()) {
switch (value->as_keyword().keyword()) {
case Keyword::DiscardBefore:
white_space_trim_data.discard_before = true;
break;
case Keyword::DiscardAfter:
white_space_trim_data.discard_after = true;
break;
case Keyword::DiscardInner:
white_space_trim_data.discard_inner = true;
break;
default:
VERIFY_NOT_REACHED();
}
}
return white_space_trim_data;
}
VERIFY_NOT_REACHED();
}
Optional<LengthOrCalculated> ComputedProperties::letter_spacing() const
{
auto const& value = property(PropertyID::LetterSpacing);

View file

@ -102,6 +102,7 @@ public:
Variant<LengthOrCalculated, NumberOrCalculated> tab_size() const;
WhiteSpace white_space() const;
WhiteSpaceCollapse white_space_collapse() const;
WhiteSpaceTrimData white_space_trim() const;
WordBreak word_break() const;
Optional<LengthOrCalculated> word_spacing() const;
Optional<LengthOrCalculated> letter_spacing() const;

View file

@ -334,6 +334,12 @@ struct TouchActionData {
}
};
struct WhiteSpaceTrimData {
bool discard_before : 1 { false };
bool discard_after : 1 { false };
bool discard_inner : 1 { false };
};
struct TransformOrigin {
CSS::LengthPercentage x { Percentage(50) };
CSS::LengthPercentage y { Percentage(50) };
@ -427,6 +433,7 @@ public:
CSS::Positioning position() const { return m_noninherited.position; }
CSS::WhiteSpace white_space() const { return m_inherited.white_space; }
CSS::WhiteSpaceCollapse white_space_collapse() const { return m_inherited.white_space_collapse; }
WhiteSpaceTrimData white_space_trim() const { return m_noninherited.white_space_trim; }
CSS::LengthOrCalculated word_spacing() const { return m_inherited.word_spacing; }
LengthOrCalculated letter_spacing() const { return m_inherited.letter_spacing; }
CSS::FlexDirection flex_direction() const { return m_noninherited.flex_direction; }
@ -742,6 +749,7 @@ protected:
CSS::Isolation isolation { InitialValues::isolation() };
CSS::Containment contain { InitialValues::contain() };
CSS::MixBlendMode mix_blend_mode { InitialValues::mix_blend_mode() };
WhiteSpaceTrimData white_space_trim;
Optional<FlyString> view_transition_name;
TouchActionData touch_action;
@ -827,6 +835,7 @@ public:
void set_position(CSS::Positioning position) { m_noninherited.position = position; }
void set_white_space(CSS::WhiteSpace value) { m_inherited.white_space = value; }
void set_white_space_collapse(CSS::WhiteSpaceCollapse value) { m_inherited.white_space_collapse = value; }
void set_white_space_trim(WhiteSpaceTrimData value) { m_noninherited.white_space_trim = value; }
void set_word_spacing(CSS::LengthOrCalculated value) { m_inherited.word_spacing = move(value); }
void set_word_break(CSS::WordBreak value) { m_inherited.word_break = value; }
void set_letter_spacing(CSS::LengthOrCalculated value) { m_inherited.letter_spacing = value; }

View file

@ -159,6 +159,9 @@
"difference",
"disc",
"discard",
"discard-before",
"discard-after",
"discard-inner",
"disclosure-closed",
"disclosure-open",
"discretionary-ligatures",

View file

@ -445,6 +445,7 @@ private:
RefPtr<CSSStyleValue const> parse_grid_area_shorthand_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue const> parse_grid_shorthand_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue const> parse_touch_action_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue const> parse_white_space_trim_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue const> parse_list_of_time_values(PropertyID, TokenStream<ComponentValue>&);

View file

@ -708,6 +708,10 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue const>> Parser::parse_css_value
if (auto parsed_value = parse_contain_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::WhiteSpaceTrim:
if (auto parsed_value = parse_white_space_trim_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
default:
break;
}
@ -4761,4 +4765,58 @@ RefPtr<CSSStyleValue const> Parser::parse_contain_value(TokenStream<ComponentVal
return StyleValueList::create(move(containments), StyleValueList::Separator::Space);
}
// https://www.w3.org/TR/css-text-4/#white-space-trim
RefPtr<CSSStyleValue const> Parser::parse_white_space_trim_value(TokenStream<ComponentValue>& tokens)
{
// none | discard-before || discard-after || discard-inner
if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
return none;
auto transaction = tokens.begin_transaction();
RefPtr<CSSStyleValue const> discard_before;
RefPtr<CSSStyleValue const> discard_after;
RefPtr<CSSStyleValue const> discard_inner;
while (auto parsed_value = parse_css_value_for_property(PropertyID::WhiteSpaceTrim, tokens)) {
switch (parsed_value->as_keyword().keyword()) {
case Keyword::DiscardBefore:
if (discard_before)
return {};
discard_before = parsed_value;
break;
case Keyword::DiscardAfter:
if (discard_after)
return {};
discard_after = parsed_value;
break;
case Keyword::DiscardInner:
if (discard_inner)
return {};
discard_inner = parsed_value;
break;
default:
return {};
}
if (!tokens.has_next_token())
break;
}
StyleValueVector parsed_values;
// NOTE: The values are appended here rather than in the loop above to canonicalize their order.
if (discard_before)
parsed_values.append(discard_before.release_nonnull());
if (discard_after)
parsed_values.append(discard_after.release_nonnull());
if (discard_inner)
parsed_values.append(discard_inner.release_nonnull());
transaction.commit();
return StyleValueList::create(move(parsed_values), StyleValueList::Separator::Space);
}
}

View file

@ -3142,6 +3142,17 @@
"white-space-collapse"
]
},
"white-space-trim": {
"animation-type": "discrete",
"inherited": false,
"initial": "none",
"valid-identifiers": [
"none",
"discard-before",
"discard-after",
"discard-inner"
]
},
"width": {
"animation-type": "by-computed-value",
"inherited": false,

View file

@ -437,7 +437,7 @@ bool pseudo_element_supports_property(PseudoElement pseudo_element, PropertyID p
// FIXME: text-wrap-style
append_property("white-space"sv);
append_property("white-space-collapse"sv);
// FIXME: white-space-trim
append_property("white-space-trim"sv);
append_property("word-break"sv);
// FIXME: word-space-transform
append_property("word-spacing"sv);

View file

@ -233,10 +233,11 @@ All properties associated with getComputedStyle(document.body):
"230": "user-select",
"231": "vertical-align",
"232": "view-transition-name",
"233": "width",
"234": "x",
"235": "y",
"236": "z-index"
"233": "white-space-trim",
"234": "width",
"235": "x",
"236": "y",
"237": "z-index"
}
All properties associated with document.body.style by default:
{}

View file

@ -650,6 +650,8 @@ All supported properties and their default values exposed from CSSStylePropertie
'white-space': 'normal'
'whiteSpaceCollapse': 'collapse'
'white-space-collapse': 'collapse'
'whiteSpaceTrim': 'none'
'white-space-trim': 'none'
'width': '284px'
'wordBreak': 'normal'
'word-break': 'normal'

View file

@ -231,6 +231,7 @@ unicode-bidi: normal
user-select: auto
vertical-align: baseline
view-transition-name: none
white-space-trim: none
width: 784px
x: 0px
y: 0px

View file

@ -0,0 +1,3 @@
none
discard-inner
discard-after discard-inner

View file

@ -1,8 +1,8 @@
Harness status: OK
Found 199 tests
Found 200 tests
189 Pass
190 Pass
10 Fail
Pass accent-color
Pass border-collapse
@ -199,6 +199,7 @@ Pass unicode-bidi
Pass user-select
Pass vertical-align
Pass view-transition-name
Pass white-space-trim
Fail width
Pass x
Pass y

View file

@ -0,0 +1,21 @@
<!DOCTYPE html>
<style>
#single-value {
white-space-trim: discard-inner;
}
#multiple-values {
white-space-trim: discard-inner discard-after;
}
</style>
<div id="default"></div>
<div id="single-value"></div>
<div id="multiple-values"></div>
<script src="../include.js"></script>
<script>
test(() => {
println(getComputedStyle(document.getElementById("default")).whiteSpaceTrim);
println(getComputedStyle(document.getElementById("single-value")).whiteSpaceTrim);
println(getComputedStyle(document.getElementById("multiple-values")).whiteSpaceTrim);
});
</script>