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(); 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 Optional<LengthOrCalculated> ComputedProperties::letter_spacing() const
{ {
auto const& value = property(PropertyID::LetterSpacing); auto const& value = property(PropertyID::LetterSpacing);

View file

@ -102,6 +102,7 @@ public:
Variant<LengthOrCalculated, NumberOrCalculated> tab_size() const; Variant<LengthOrCalculated, NumberOrCalculated> tab_size() const;
WhiteSpace white_space() const; WhiteSpace white_space() const;
WhiteSpaceCollapse white_space_collapse() const; WhiteSpaceCollapse white_space_collapse() const;
WhiteSpaceTrimData white_space_trim() const;
WordBreak word_break() const; WordBreak word_break() const;
Optional<LengthOrCalculated> word_spacing() const; Optional<LengthOrCalculated> word_spacing() const;
Optional<LengthOrCalculated> letter_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 { struct TransformOrigin {
CSS::LengthPercentage x { Percentage(50) }; CSS::LengthPercentage x { Percentage(50) };
CSS::LengthPercentage y { Percentage(50) }; CSS::LengthPercentage y { Percentage(50) };
@ -427,6 +433,7 @@ public:
CSS::Positioning position() const { return m_noninherited.position; } CSS::Positioning position() const { return m_noninherited.position; }
CSS::WhiteSpace white_space() const { return m_inherited.white_space; } CSS::WhiteSpace white_space() const { return m_inherited.white_space; }
CSS::WhiteSpaceCollapse white_space_collapse() const { return m_inherited.white_space_collapse; } 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; } CSS::LengthOrCalculated word_spacing() const { return m_inherited.word_spacing; }
LengthOrCalculated letter_spacing() const { return m_inherited.letter_spacing; } LengthOrCalculated letter_spacing() const { return m_inherited.letter_spacing; }
CSS::FlexDirection flex_direction() const { return m_noninherited.flex_direction; } CSS::FlexDirection flex_direction() const { return m_noninherited.flex_direction; }
@ -742,6 +749,7 @@ protected:
CSS::Isolation isolation { InitialValues::isolation() }; CSS::Isolation isolation { InitialValues::isolation() };
CSS::Containment contain { InitialValues::contain() }; CSS::Containment contain { InitialValues::contain() };
CSS::MixBlendMode mix_blend_mode { InitialValues::mix_blend_mode() }; CSS::MixBlendMode mix_blend_mode { InitialValues::mix_blend_mode() };
WhiteSpaceTrimData white_space_trim;
Optional<FlyString> view_transition_name; Optional<FlyString> view_transition_name;
TouchActionData touch_action; TouchActionData touch_action;
@ -827,6 +835,7 @@ public:
void set_position(CSS::Positioning position) { m_noninherited.position = position; } 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(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_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_spacing(CSS::LengthOrCalculated value) { m_inherited.word_spacing = move(value); }
void set_word_break(CSS::WordBreak value) { m_inherited.word_break = 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; } void set_letter_spacing(CSS::LengthOrCalculated value) { m_inherited.letter_spacing = value; }

View file

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

View file

@ -445,6 +445,7 @@ private:
RefPtr<CSSStyleValue const> parse_grid_area_shorthand_value(TokenStream<ComponentValue>&); RefPtr<CSSStyleValue const> parse_grid_area_shorthand_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue const> parse_grid_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_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>&); 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()) if (auto parsed_value = parse_contain_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull(); return parsed_value.release_nonnull();
return ParseError::SyntaxError; 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: default:
break; break;
} }
@ -4761,4 +4765,58 @@ RefPtr<CSSStyleValue const> Parser::parse_contain_value(TokenStream<ComponentVal
return StyleValueList::create(move(containments), StyleValueList::Separator::Space); 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-collapse"
] ]
}, },
"white-space-trim": {
"animation-type": "discrete",
"inherited": false,
"initial": "none",
"valid-identifiers": [
"none",
"discard-before",
"discard-after",
"discard-inner"
]
},
"width": { "width": {
"animation-type": "by-computed-value", "animation-type": "by-computed-value",
"inherited": false, "inherited": false,

View file

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

View file

@ -233,10 +233,11 @@ All properties associated with getComputedStyle(document.body):
"230": "user-select", "230": "user-select",
"231": "vertical-align", "231": "vertical-align",
"232": "view-transition-name", "232": "view-transition-name",
"233": "width", "233": "white-space-trim",
"234": "x", "234": "width",
"235": "y", "235": "x",
"236": "z-index" "236": "y",
"237": "z-index"
} }
All properties associated with document.body.style by default: 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' 'white-space': 'normal'
'whiteSpaceCollapse': 'collapse' 'whiteSpaceCollapse': 'collapse'
'white-space-collapse': 'collapse' 'white-space-collapse': 'collapse'
'whiteSpaceTrim': 'none'
'white-space-trim': 'none'
'width': '284px' 'width': '284px'
'wordBreak': 'normal' 'wordBreak': 'normal'
'word-break': 'normal' 'word-break': 'normal'

View file

@ -231,6 +231,7 @@ unicode-bidi: normal
user-select: auto user-select: auto
vertical-align: baseline vertical-align: baseline
view-transition-name: none view-transition-name: none
white-space-trim: none
width: 784px width: 784px
x: 0px x: 0px
y: 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 Harness status: OK
Found 199 tests Found 200 tests
189 Pass 190 Pass
10 Fail 10 Fail
Pass accent-color Pass accent-color
Pass border-collapse Pass border-collapse
@ -199,6 +199,7 @@ Pass unicode-bidi
Pass user-select Pass user-select
Pass vertical-align Pass vertical-align
Pass view-transition-name Pass view-transition-name
Pass white-space-trim
Fail width Fail width
Pass x Pass x
Pass y 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>