LibWeb: Parse the transform-origin z-value

This commit is contained in:
Tim Ledbetter 2025-06-14 00:52:42 +01:00 committed by Alexander Kalenik
commit 68d3ddb1a7
Notes: github-actions[bot] 2025-06-15 14:02:53 +00:00
10 changed files with 88 additions and 77 deletions

View file

@ -656,15 +656,16 @@ TransformOrigin ComputedProperties::transform_origin() const
}; };
auto const& value = property(PropertyID::TransformOrigin); auto const& value = property(PropertyID::TransformOrigin);
if (!value.is_value_list() || value.as_value_list().size() != 2) if (!value.is_value_list() || value.as_value_list().size() != 3)
return {}; return {};
auto const& list = value.as_value_list(); auto const& list = value.as_value_list();
auto x_value = length_percentage_with_keywords_resolved(list.values()[0]); auto x_value = length_percentage_with_keywords_resolved(list.values()[0]);
auto y_value = length_percentage_with_keywords_resolved(list.values()[1]); auto y_value = length_percentage_with_keywords_resolved(list.values()[1]);
if (!x_value.has_value() || !y_value.has_value()) auto z_value = length_percentage_for_style_value(list.values()[2]);
if (!x_value.has_value() || !y_value.has_value() || !z_value.has_value())
return {}; return {};
return { x_value.value(), y_value.value() }; return { x_value.value(), y_value.value(), z_value.value() };
} }
Optional<Color> ComputedProperties::accent_color(Layout::NodeWithStyle const& node) const Optional<Color> ComputedProperties::accent_color(Layout::NodeWithStyle const& node) const

View file

@ -354,6 +354,7 @@ struct WhiteSpaceTrimData {
struct TransformOrigin { struct TransformOrigin {
CSS::LengthPercentage x { Percentage(50) }; CSS::LengthPercentage x { Percentage(50) };
CSS::LengthPercentage y { Percentage(50) }; CSS::LengthPercentage y { Percentage(50) };
CSS::LengthPercentage z { Percentage(0) };
}; };
struct ShadowData { struct ShadowData {

View file

@ -3806,7 +3806,6 @@ RefPtr<CSSStyleValue const> Parser::parse_transform_value(TokenStream<ComponentV
} }
// https://www.w3.org/TR/css-transforms-1/#propdef-transform-origin // https://www.w3.org/TR/css-transforms-1/#propdef-transform-origin
// FIXME: This only supports a 2D position
RefPtr<CSSStyleValue const> Parser::parse_transform_origin_value(TokenStream<ComponentValue>& tokens) RefPtr<CSSStyleValue const> Parser::parse_transform_origin_value(TokenStream<ComponentValue>& tokens)
{ {
enum class Axis { enum class Axis {
@ -3851,13 +3850,14 @@ RefPtr<CSSStyleValue const> Parser::parse_transform_origin_value(TokenStream<Com
auto transaction = tokens.begin_transaction(); auto transaction = tokens.begin_transaction();
auto make_list = [&transaction](NonnullRefPtr<CSSStyleValue const> const& x_value, NonnullRefPtr<CSSStyleValue const> const& y_value) -> NonnullRefPtr<StyleValueList> { auto make_list = [&transaction](NonnullRefPtr<CSSStyleValue const> const& x_value, NonnullRefPtr<CSSStyleValue const> const& y_value, NonnullRefPtr<CSSStyleValue const> const& z_value) -> NonnullRefPtr<StyleValueList> {
transaction.commit(); transaction.commit();
return StyleValueList::create(StyleValueVector { x_value, y_value }, StyleValueList::Separator::Space); return StyleValueList::create(StyleValueVector { x_value, y_value, z_value }, StyleValueList::Separator::Space);
}; };
switch (tokens.remaining_token_count()) { static CSSStyleValue const& zero_value = LengthStyleValue::create(Length::make_px(0));
case 1: {
if (tokens.remaining_token_count() == 1) {
auto single_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens)); auto single_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
if (!single_value.has_value()) if (!single_value.has_value())
return nullptr; return nullptr;
@ -3866,60 +3866,70 @@ RefPtr<CSSStyleValue const> Parser::parse_transform_origin_value(TokenStream<Com
switch (single_value->axis) { switch (single_value->axis) {
case Axis::None: case Axis::None:
case Axis::X: case Axis::X:
return make_list(single_value->offset, CSSKeywordValue::create(Keyword::Center)); return make_list(single_value->offset, CSSKeywordValue::create(Keyword::Center), zero_value);
case Axis::Y: case Axis::Y:
return make_list(CSSKeywordValue::create(Keyword::Center), single_value->offset); return make_list(CSSKeywordValue::create(Keyword::Center), single_value->offset, zero_value);
} }
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
case 2: {
auto first_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens)); if (tokens.remaining_token_count() > 3)
auto second_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens)); return nullptr;
if (!first_value.has_value() || !second_value.has_value())
auto first_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
auto second_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
auto third_value = parse_length_value(tokens);
if ((first_value->offset->is_length() || first_value->offset->is_percentage()) && second_value->axis == Axis::X)
return nullptr;
if ((second_value->offset->is_length() || second_value->offset->is_percentage()) && first_value->axis == Axis::Y)
return nullptr;
if (!first_value.has_value() || !second_value.has_value())
return nullptr;
if (!third_value)
third_value = zero_value;
RefPtr<CSSStyleValue const> x_value;
RefPtr<CSSStyleValue const> y_value;
if (first_value->axis == Axis::X) {
x_value = first_value->offset;
} else if (first_value->axis == Axis::Y) {
y_value = first_value->offset;
}
if (second_value->axis == Axis::X) {
if (x_value)
return nullptr; return nullptr;
x_value = second_value->offset;
RefPtr<CSSStyleValue const> x_value; // Put the other in Y since its axis can't have been X
RefPtr<CSSStyleValue const> y_value; y_value = first_value->offset;
} else if (second_value->axis == Axis::Y) {
if (first_value->axis == Axis::X) { if (y_value)
x_value = first_value->offset; return nullptr;
} else if (first_value->axis == Axis::Y) { y_value = second_value->offset;
y_value = first_value->offset; // Put the other in X since its axis can't have been Y
} x_value = first_value->offset;
} else {
if (second_value->axis == Axis::X) { if (x_value) {
if (x_value) VERIFY(!y_value);
return nullptr;
x_value = second_value->offset;
// Put the other in Y since its axis can't have been X
y_value = first_value->offset;
} else if (second_value->axis == Axis::Y) {
if (y_value)
return nullptr;
y_value = second_value->offset; y_value = second_value->offset;
// Put the other in X since its axis can't have been Y
x_value = first_value->offset;
} else { } else {
if (x_value) { VERIFY(!x_value);
VERIFY(!y_value); x_value = second_value->offset;
y_value = second_value->offset;
} else {
VERIFY(!x_value);
x_value = second_value->offset;
}
} }
// If two or more values are defined and either no value is a keyword, or the only used keyword is center,
// then the first value represents the horizontal position (or offset) and the second represents the vertical position (or offset).
// FIXME: A third value always represents the Z position (or offset) and must be of type <length>.
if (first_value->axis == Axis::None && second_value->axis == Axis::None) {
x_value = first_value->offset;
y_value = second_value->offset;
}
return make_list(x_value.release_nonnull(), y_value.release_nonnull());
} }
// If two or more values are defined and either no value is a keyword, or the only used keyword is center,
// then the first value represents the horizontal position (or offset) and the second represents the vertical position (or offset).
// A third value always represents the Z position (or offset) and must be of type <length>.
if (first_value->axis == Axis::None && second_value->axis == Axis::None) {
x_value = first_value->offset;
y_value = second_value->offset;
} }
return nullptr; return make_list(x_value.release_nonnull(), y_value.release_nonnull(), third_value.release_nonnull());
} }
RefPtr<CSSStyleValue const> Parser::parse_transition_value(TokenStream<ComponentValue>& tokens) RefPtr<CSSStyleValue const> Parser::parse_transition_value(TokenStream<ComponentValue>& tokens)

View file

@ -123,9 +123,9 @@ All supported properties and their default values exposed from CSSStylePropertie
'WebkitTransform': 'none' 'WebkitTransform': 'none'
'webkitTransform': 'none' 'webkitTransform': 'none'
'-webkit-transform': 'none' '-webkit-transform': 'none'
'WebkitTransformOrigin': '50% 50%' 'WebkitTransformOrigin': '50% 50% 0px'
'webkitTransformOrigin': '50% 50%' 'webkitTransformOrigin': '50% 50% 0px'
'-webkit-transform-origin': '50% 50%' '-webkit-transform-origin': '50% 50% 0px'
'WebkitTransition': 'all' 'WebkitTransition': 'all'
'webkitTransition': 'all' 'webkitTransition': 'all'
'-webkit-transition': 'all' '-webkit-transition': 'all'
@ -630,8 +630,8 @@ All supported properties and their default values exposed from CSSStylePropertie
'transform': 'none' 'transform': 'none'
'transformBox': 'view-box' 'transformBox': 'view-box'
'transform-box': 'view-box' 'transform-box': 'view-box'
'transformOrigin': '50% 50%' 'transformOrigin': '50% 50% 0px'
'transform-origin': '50% 50%' 'transform-origin': '50% 50% 0px'
'transition': 'all' 'transition': 'all'
'transitionBehavior': 'normal' 'transitionBehavior': 'normal'
'transition-behavior': 'normal' 'transition-behavior': 'normal'

View file

@ -150,8 +150,8 @@ text-indent: 'calc(2%)' -> '2%'
text-indent: 'calc(2% * var(--n))' -> 'calc(2 * 2%)' text-indent: 'calc(2% * var(--n))' -> 'calc(2 * 2%)'
top: 'calc(2%)' -> '2%' top: 'calc(2%)' -> '2%'
top: 'calc(2% * var(--n))' -> 'calc(2 * 2%)' top: 'calc(2% * var(--n))' -> 'calc(2 * 2%)'
transform-origin: 'calc(2px) calc(2%)' -> '2px 2%' transform-origin: 'calc(2px) calc(2%)' -> '2px 2% 0px'
transform-origin: 'calc(2px * var(--n)) calc(2% * var(--n))' -> '4px calc(2 * 2%)' transform-origin: 'calc(2px * var(--n)) calc(2% * var(--n))' -> '4px calc(2 * 2%) 0px'
transition-delay: 'calc(2s)' -> '2s' transition-delay: 'calc(2s)' -> '2s'
transition-delay: 'calc(2s * var(--n))' -> '4s' transition-delay: 'calc(2s * var(--n))' -> '4s'
transition-duration: 'calc(2s)' -> '2s' transition-duration: 'calc(2s)' -> '2s'

View file

@ -221,7 +221,7 @@ top: auto
touch-action: auto touch-action: auto
transform: none transform: none
transform-box: view-box transform-box: view-box
transform-origin: 50% 50% transform-origin: 50% 50% 0px
transition-behavior: normal transition-behavior: normal
transition-delay: 0s transition-delay: 0s
transition-duration: 0s transition-duration: 0s

View file

@ -1,7 +1,7 @@
center => center center center => center center 0px
10px => 10px center 10px => 10px center 0px
25% => 25% center 25% => 25% center 0px
left 20% => left 20% left 20% => left 20% 0px
20px bottom => 20px bottom 20px bottom => 20px bottom 0px
top right => right top top right => right top 0px
"center" => (invalid) "center" => (invalid)

View file

@ -1,8 +1,8 @@
Harness status: OK Harness status: OK
Found 203 tests Found 204 tests
199 Pass 200 Pass
4 Fail 4 Fail
Pass accent-color Pass accent-color
Pass border-collapse Pass border-collapse
@ -192,6 +192,7 @@ Pass top
Pass touch-action Pass touch-action
Fail transform Fail transform
Pass transform-box Pass transform-box
Pass transform-origin
Pass transition-behavior Pass transition-behavior
Pass transition-delay Pass transition-delay
Pass transition-duration Pass transition-duration

View file

@ -2,13 +2,12 @@ Harness status: OK
Found 10 tests Found 10 tests
8 Pass 10 Pass
2 Fail
Pass e.style['transform-origin'] = "1px 2px 3%" should not set the property value Pass e.style['transform-origin'] = "1px 2px 3%" should not set the property value
Pass e.style['transform-origin'] = "1px 2px left" should not set the property value Pass e.style['transform-origin'] = "1px 2px left" should not set the property value
Pass e.style['transform-origin'] = "1px 2px 3px 4px" should not set the property value Pass e.style['transform-origin'] = "1px 2px 3px 4px" should not set the property value
Fail e.style['transform-origin'] = "1px left" should not set the property value Pass e.style['transform-origin'] = "1px left" should not set the property value
Fail e.style['transform-origin'] = "top 1px" should not set the property value Pass e.style['transform-origin'] = "top 1px" should not set the property value
Pass e.style['transform-origin'] = "right left" should not set the property value Pass e.style['transform-origin'] = "right left" should not set the property value
Pass e.style['transform-origin'] = "top bottom" should not set the property value Pass e.style['transform-origin'] = "top bottom" should not set the property value
Pass e.style['transform-origin'] = "bottom 10% right 20%" should not set the property value Pass e.style['transform-origin'] = "bottom 10% right 20%" should not set the property value

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 16 tests Found 16 tests
12 Pass 16 Pass
4 Fail
Pass e.style['transform-origin'] = "left" should set the property value Pass e.style['transform-origin'] = "left" should set the property value
Pass e.style['transform-origin'] = "center" should set the property value Pass e.style['transform-origin'] = "center" should set the property value
Pass e.style['transform-origin'] = "right" should set the property value Pass e.style['transform-origin'] = "right" should set the property value
@ -15,8 +14,8 @@ Pass e.style['transform-origin'] = "-4%" should set the property value
Pass e.style['transform-origin'] = "left center" should set the property value Pass e.style['transform-origin'] = "left center" should set the property value
Pass e.style['transform-origin'] = "center top" should set the property value Pass e.style['transform-origin'] = "center top" should set the property value
Pass e.style['transform-origin'] = "right -4%" should set the property value Pass e.style['transform-origin'] = "right -4%" should set the property value
Fail e.style['transform-origin'] = "-1px bottom 5px" should set the property value Pass e.style['transform-origin'] = "-1px bottom 5px" should set the property value
Fail e.style['transform-origin'] = "center left 6px" should set the property value Pass e.style['transform-origin'] = "center left 6px" should set the property value
Pass e.style['transform-origin'] = "top center" should set the property value Pass e.style['transform-origin'] = "top center" should set the property value
Fail e.style['transform-origin'] = "bottom right 7px" should set the property value Pass e.style['transform-origin'] = "bottom right 7px" should set the property value
Fail e.style['transform-origin'] = "-1px -2px -3px" should set the property value Pass e.style['transform-origin'] = "-1px -2px -3px" should set the property value