Skip to content

Commit

Permalink
LibWeb: Produce resolved transform value according to spec algorithm
Browse files Browse the repository at this point in the history
We now produce a `matrix3d()` value when appropriate.

Some sites (such as gsap.com) request the resolved style for `transform`
when there's no viewport paintable, but the element itself does already
have a stacking context. This fixes crashes in that case, because we now
do not access the stacking context at all.

We also do not wrap the result as a StyleValueList any more. The
returned StyleValue is only serialized and exposed to JS, so making it a
StyleValueList has no effect.
  • Loading branch information
AtkinsSJ authored and awesomekling committed Oct 15, 2023
1 parent c65d696 commit c9c99b3
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 29 deletions.
17 changes: 17 additions & 0 deletions Tests/LibWeb/Text/expected/css/getComputedStyle-transform.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
none => none
matrix(1, 2, 3, 4, 5, 6) => matrix(1, 2, 3, 4, 5, 6)
matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) => matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
translate(1%, 2px) => matrix(1, 0, 0, 1, 7.84375, 2)
translate3d(1%, 2px, 3em) => matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7.84375, 2, 48, 1)
translateX(1px) => matrix(1, 0, 0, 1, 1, 0)
translateY(1%) => matrix(1, 0, 0, 1, 0, 0)
scale(1, 2) => matrix(1, 0, 0, 2, 0, 0)
scaleX(2) => matrix(2, 0, 0, 1, 0, 0)
scaleY(2.5) => matrix(1, 0, 0, 2.5, 0, 0)
rotate(1deg) => matrix(0.999847, 0.017452, -0.017452, 0.999847, 0, 0)
rotateX(1rad) => matrix3d(1, 0, 0, 0, 0, 0.540302, 0.841470, 0, 0, -0.841470, 0.540302, 0, 0, 0, 0, 1)
rotateY(1grad) => matrix3d(0.999876, 0, -0.015707, 0, 0, 1, 0, 0, 0.015707, 0, 0.999876, 0, 0, 0, 0, 1)
rotateZ(1turn) => matrix(1, 0, -0, 1, 0, 0)
skew(1deg, 1rad) => matrix(1, 1.557407, 0.017455, 1, 0, 0)
skewX(1deg) => matrix(1, 0, 0.017455, 1, 0, 0)
skewY(1rad) => matrix(1, 1.557407, 0, 1, 0, 0)
35 changes: 35 additions & 0 deletions Tests/LibWeb/Text/input/css/getComputedStyle-transform.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script src="../include.js"></script>
<script>
test(() => {
const e = document.createElement("div");
document.body.appendChild(e);
function checkTransform(transform) {
e.style.transform = transform;
const computedStyle = getComputedStyle(e);
const serialized = computedStyle.transform;
println(transform + " => " + serialized);
}
for (transform of [
"none",
"matrix(1, 2, 3, 4, 5, 6)",
"matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)",
"translate(1%, 2px)",
"translate3d(1%, 2px, 3em)",
"translateX(1px)",
"translateY(1%)",
"scale(1, 2)",
"scaleX(2)",
"scaleY(2.5)",
"rotate(1deg)",
"rotateX(1rad)",
"rotateY(1grad)",
"rotateZ(1turn)",
"skew(1deg, 1rad)",
"skewX(1deg)",
"skewY(1rad)",
]) {
checkTransform(transform);
}
e.remove();
});
</script>
102 changes: 73 additions & 29 deletions Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,41 +340,85 @@ RefPtr<StyleValue const> ResolvedCSSStyleDeclaration::style_value_for_property(L
// -> A resolved value special case property defined in another specification
// As defined in the relevant specification.
case PropertyID::Transform: {
// NOTE: The computed value for `transform` serializes as a single `matrix(...)` value, instead of
// the original list of transform functions. So, we produce a StyleValue for that.
// https://www.w3.org/TR/css-transforms-1/#serialization-of-the-computed-value
// FIXME: Computing values should happen in the StyleComputer!
auto transformations = layout_node.computed_values().transformations();
if (transformations.is_empty())
return IdentifierStyleValue::create(ValueID::None);

// The transform matrix is held by the StackingContext, so we need to make sure we have one first.
auto const* viewport = layout_node.document().paintable();
VERIFY(viewport);
const_cast<Painting::ViewportPaintable&>(*viewport).build_stacking_context_tree_if_needed();
// https://drafts.csswg.org/css-transforms-2/#serialization-of-the-computed-value
// The transform property is a resolved value special case property. [CSSOM]
// When the computed value is a <transform-list>, the resolved value is one <matrix()> function or one <matrix3d()> function computed by the following algorithm:
// 1. Let transform be a 4x4 matrix initialized to the identity matrix.
// The elements m11, m22, m33 and m44 of transform must be set to 1; all other elements of transform must be set to 0.
auto transform = FloatMatrix4x4::identity();

// 2. Post-multiply all <transform-function>s in <transform-list> to transform.
VERIFY(layout_node.paintable());
auto const& paintable_box = verify_cast<Painting::PaintableBox const>(layout_node.paintable());
VERIFY(paintable_box->stacking_context());

// FIXME: This needs to serialize to matrix3d if the transformation matrix is a 3D matrix.
// https://w3c.github.io/csswg-drafts/css-transforms-2/#serialization-of-the-computed-value
auto affine_matrix = paintable_box->stacking_context()->affine_transform_matrix();

StyleValueVector parameters;
parameters.ensure_capacity(6);
parameters.unchecked_append(NumberStyleValue::create(affine_matrix.a()));
parameters.unchecked_append(NumberStyleValue::create(affine_matrix.b()));
parameters.unchecked_append(NumberStyleValue::create(affine_matrix.c()));
parameters.unchecked_append(NumberStyleValue::create(affine_matrix.d()));
parameters.unchecked_append(NumberStyleValue::create(affine_matrix.e()));
parameters.unchecked_append(NumberStyleValue::create(affine_matrix.f()));

NonnullRefPtr<StyleValue> matrix_function = TransformationStyleValue::create(TransformFunction::Matrix, move(parameters));
// Elsewhere we always store the transform property's value as a StyleValueList of TransformationStyleValues,
// so this is just for consistency.
StyleValueVector matrix_functions { matrix_function };
return StyleValueList::create(move(matrix_functions), StyleValueList::Separator::Space);
auto const& paintable_box = verify_cast<Painting::PaintableBox const>(*layout_node.paintable());
for (auto transformation : transformations) {
transform = transform * transformation.to_matrix(paintable_box);
}

// https://drafts.csswg.org/css-transforms-1/#2d-matrix
auto is_2d_matrix = [](Gfx::FloatMatrix4x4 const& matrix) -> bool {
// A 3x2 transformation matrix,
// or a 4x4 matrix where the items m31, m32, m13, m23, m43, m14, m24, m34 are equal to 0
// and m33, m44 are equal to 1.
// NOTE: We only care about 4x4 matrices here.
// NOTE: Our elements are 0-indexed not 1-indexed, and in the opposite order.
if (matrix.elements()[0][2] != 0 // m31
|| matrix.elements()[1][2] != 0 // m32
|| matrix.elements()[2][0] != 0 // m13
|| matrix.elements()[2][1] != 0 // m23
|| matrix.elements()[2][3] != 0 // m43
|| matrix.elements()[3][0] != 0 // m14
|| matrix.elements()[3][1] != 0 // m24
|| matrix.elements()[3][2] != 0) // m34
return false;

if (matrix.elements()[2][2] != 1 // m33
|| matrix.elements()[3][3] != 1) // m44
return false;

return true;
};

// 3. Chose between <matrix()> or <matrix3d()> serialization:
// -> If transform is a 2D matrix
// Serialize transform to a <matrix()> function.
if (is_2d_matrix(transform)) {
StyleValueVector parameters {
NumberStyleValue::create(transform.elements()[0][0]),
NumberStyleValue::create(transform.elements()[1][0]),
NumberStyleValue::create(transform.elements()[0][1]),
NumberStyleValue::create(transform.elements()[1][1]),
NumberStyleValue::create(transform.elements()[0][3]),
NumberStyleValue::create(transform.elements()[1][3]),
};
return TransformationStyleValue::create(TransformFunction::Matrix, move(parameters));
}
// -> Otherwise
// Serialize transform to a <matrix3d()> function.
else {
StyleValueVector parameters {
NumberStyleValue::create(transform.elements()[0][0]),
NumberStyleValue::create(transform.elements()[1][0]),
NumberStyleValue::create(transform.elements()[2][0]),
NumberStyleValue::create(transform.elements()[3][0]),
NumberStyleValue::create(transform.elements()[0][1]),
NumberStyleValue::create(transform.elements()[1][1]),
NumberStyleValue::create(transform.elements()[2][1]),
NumberStyleValue::create(transform.elements()[3][1]),
NumberStyleValue::create(transform.elements()[0][2]),
NumberStyleValue::create(transform.elements()[1][2]),
NumberStyleValue::create(transform.elements()[2][2]),
NumberStyleValue::create(transform.elements()[3][2]),
NumberStyleValue::create(transform.elements()[0][3]),
NumberStyleValue::create(transform.elements()[1][3]),
NumberStyleValue::create(transform.elements()[2][3]),
NumberStyleValue::create(transform.elements()[3][3]),
};
return TransformationStyleValue::create(TransformFunction::Matrix3d, move(parameters));
}
}

// -> Any other property
Expand Down

0 comments on commit c9c99b3

Please sign in to comment.