From 6f33266331598169d8dcbefc0a40fa65c1eb558e Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Mon, 26 May 2025 21:22:39 +0200 Subject: [PATCH] layout: Fix logic for transforms with non-invertible matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the the current transformation matrix of a box isn't invertible, the box and its content shouldn't be displayed. However, the logic was broken: - It was only checking the `transform` property, but not individual transform properties like `scale`. - It was treating matrices with m₁₁=0 or m₂₂=0 and non-invertible, even when they can still be invertible and have a visible outcome. - When m₁₁=0 or m₂₂=0 weren't caused by `transform`, it was replacing the matrix with the identity. Signed-off-by: Oriol Brufau --- .../layout/display_list/stacking_context.rs | 31 ++++---------- tests/wpt/meta/MANIFEST.json | 26 ++++++++++++ .../individual-transform-3.html | 40 ++++++++++++++++++ .../css-transforms/transform-matrix-009.html | 41 +++++++++++++++++++ 4 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 tests/wpt/tests/css/css-transforms/individual-transform/individual-transform-3.html create mode 100644 tests/wpt/tests/css/css-transforms/transform-matrix-009.html diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index d22a2b6656a..66d8421e5f7 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -834,16 +834,6 @@ impl Fragment { _ => text_decorations, }; - // If this fragment has a transform applied that makes it take up no space - // then we don't need to create any stacking contexts for it. - let has_non_invertible_transform = fragment - .has_non_invertible_transform_or_zero_scale( - &containing_block.rect.to_untyped(), - ); - if has_non_invertible_transform { - return; - } - fragment.build_stacking_context_tree( fragment_clone, stacking_context_tree, @@ -991,6 +981,13 @@ impl BoxFragment { }, }; + // + // > If a transform function causes the current transformation matrix of an object + // > to be non-invertible, the object and its content do not get displayed. + if !reference_frame_data.transform.is_invertible() { + return; + } + let new_spatial_id = stacking_context_tree.push_reference_frame( reference_frame_data.origin.to_webrender(), &containing_block.scroll_node_id, @@ -1622,15 +1619,6 @@ impl BoxFragment { }) } - /// Returns true if the given style contains a transform that is not invertible. - fn has_non_invertible_transform_or_zero_scale(&self, containing_block: &Rect) -> bool { - let list = &self.style.get_box().transform; - match list.to_transform_3d_matrix(Some(&au_rect_to_length_rect(containing_block))) { - Ok(t) => !t.0.is_invertible() || t.0.m11 == 0. || t.0.m22 == 0., - Err(_) => false, - } - } - /// Returns the 4D matrix representing this fragment's transform. pub fn calculate_transform_matrix(&self, border_rect: &Rect) -> Option { let list = &self.style.get_box().transform; @@ -1660,11 +1648,6 @@ impl BoxFragment { .then_rotate(rotate.0, rotate.1, rotate.2, angle) .then_scale(scale.0, scale.1, scale.2) .then(&translation); - // WebRender will end up dividing by the scale value of this transform, so we - // want to ensure we don't feed it a divisor of 0. - if transform.m11 == 0. || transform.m22 == 0. { - return Some(LayoutTransform::identity()); - } let transform_origin = &self.style.get_box().transform_origin; let transform_origin_x = transform_origin diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index 59a4cc1cfd8..231abdebcfd 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -279551,6 +279551,19 @@ {} ] ], + "individual-transform-3.html": [ + "2c81cb12156134ee8deca6abf0043848ac8bc750", + [ + null, + [ + [ + "/css/reference/ref-filled-green-200px-square.html", + "==" + ] + ], + {} + ] + ], "stacking-context-001.html": [ "1a754c5d70b607396ffe2db1d04f2294aeca1041", [ @@ -284605,6 +284618,19 @@ {} ] ], + "transform-matrix-009.html": [ + "81f04db0d103202180aba2625205643c376483af", + [ + null, + [ + [ + "/css/reference/ref-filled-green-200px-square.html", + "==" + ] + ], + {} + ] + ], "transform-origin": { "svg-origin-length-001.html": [ "2f2256361cadd4f69aa08fd775a4305219a41fdd", diff --git a/tests/wpt/tests/css/css-transforms/individual-transform/individual-transform-3.html b/tests/wpt/tests/css/css-transforms/individual-transform/individual-transform-3.html new file mode 100644 index 00000000000..2c81cb12156 --- /dev/null +++ b/tests/wpt/tests/css/css-transforms/individual-transform/individual-transform-3.html @@ -0,0 +1,40 @@ + + +Individual transform: non-invertible matrix + + + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-transforms/transform-matrix-009.html b/tests/wpt/tests/css/css-transforms/transform-matrix-009.html new file mode 100644 index 00000000000..81f04db0d10 --- /dev/null +++ b/tests/wpt/tests/css/css-transforms/transform-matrix-009.html @@ -0,0 +1,41 @@ + + +CSS Test (Transforms): matrix() with zeros in the diagonal + + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+