aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorchocolate-pie <106949016+chocolate-pie@users.noreply.github.com>2025-03-14 23:46:20 +0900
committerGitHub <noreply@github.com>2025-03-14 14:46:20 +0000
commit455f4202c60724cd50e31e38818d966adeaa075a (patch)
treefe99f4cd51bcd506d0c88af55cadb20439325de0
parent7f2f51b59d1a7e647627eb4fe71d9acf6987b76a (diff)
downloadservo-455f4202c60724cd50e31e38818d966adeaa075a.tar.gz
servo-455f4202c60724cd50e31e38818d966adeaa075a.zip
layout: Add support for basic transform css properties (#35926)
Signed-off-by: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
-rw-r--r--components/layout_2020/display_list/stacking_context.rs69
-rw-r--r--components/layout_2020/style_ext.rs18
-rw-r--r--tests/wpt/meta/css/css-flexbox/flexbox-safe-overflow-position-006.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/individual-transform/change-translate-property.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-1.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2a.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2b.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2c.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2d.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2e.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/scale-animation-with-var-001.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/transform-hit-testing.html.ini15
12 files changed, 57 insertions, 63 deletions
diff --git a/components/layout_2020/display_list/stacking_context.rs b/components/layout_2020/display_list/stacking_context.rs
index a0d8a9f12e0..01fdedd0f7c 100644
--- a/components/layout_2020/display_list/stacking_context.rs
+++ b/components/layout_2020/display_list/stacking_context.rs
@@ -19,10 +19,11 @@ use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::overflow_x::T as ComputedOverflow;
use style::computed_values::position::T as ComputedPosition;
use style::properties::ComputedValues;
+use style::values::computed::angle::Angle;
use style::values::computed::basic_shape::ClipPath;
use style::values::computed::{ClipRectOrAuto, Length};
use style::values::generics::box_::Perspective;
-use style::values::generics::transform;
+use style::values::generics::transform::{self, GenericRotate, GenericScale, GenericTranslate};
use style::values::specified::box_::DisplayOutside;
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D};
use webrender_api::{self as wr, BorderRadius};
@@ -39,7 +40,7 @@ use crate::fragment_tree::{
PositioningFragment, SpecificLayoutInfo,
};
use crate::geom::{AuOrAuto, PhysicalRect, PhysicalSides};
-use crate::style_ext::ComputedValuesExt;
+use crate::style_ext::{ComputedValuesExt, TransformExt};
#[derive(Clone)]
pub(crate) struct ContainingBlock {
@@ -1635,17 +1636,37 @@ impl BoxFragment {
/// Returns the 4D matrix representing this fragment's transform.
pub fn calculate_transform_matrix(&self, border_rect: &Rect<Au>) -> Option<LayoutTransform> {
let list = &self.style.get_box().transform;
+ let length_rect = au_rect_to_length_rect(border_rect);
+ // https://drafts.csswg.org/css-transforms-2/#individual-transforms
+ let rotate = match self.style.clone_rotate() {
+ GenericRotate::Rotate(angle) => (0., 0., 1., angle),
+ GenericRotate::Rotate3D(x, y, z, angle) => (x, y, z, angle),
+ GenericRotate::None => (0., 0., 1., Angle::zero()),
+ };
+ let scale = match self.style.clone_scale() {
+ GenericScale::Scale(sx, sy, sz) => (sx, sy, sz),
+ GenericScale::None => (1., 1., 1.),
+ };
+ let translation = match self.style.clone_translate() {
+ GenericTranslate::Translate(x, y, z) => LayoutTransform::translation(
+ x.resolve(length_rect.size.width).px(),
+ y.resolve(length_rect.size.height).px(),
+ z.px(),
+ ),
+ GenericTranslate::None => LayoutTransform::identity(),
+ };
- let transform = LayoutTransform::from_untyped(
- &list
- .to_transform_3d_matrix(Some(&au_rect_to_length_rect(border_rect)))
- .ok()?
- .0,
- );
+ let angle = euclid::Angle::radians(rotate.3.radians());
+ let transform_base = list.to_transform_3d_matrix(Some(&length_rect)).ok()?;
+ let transform = LayoutTransform::from_untyped(&transform_base.0)
+ .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.
- assert_ne!(transform.m11, 0.);
- assert_ne!(transform.m22, 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
@@ -1658,18 +1679,7 @@ impl BoxFragment {
.to_f32_px();
let transform_origin_z = transform_origin.depth.px();
- let pre_transform = LayoutTransform::translation(
- transform_origin_x,
- transform_origin_y,
- transform_origin_z,
- );
- let post_transform = LayoutTransform::translation(
- -transform_origin_x,
- -transform_origin_y,
- -transform_origin_z,
- );
-
- Some(post_transform.then(&transform).then(&pre_transform))
+ Some(transform.change_basis(transform_origin_x, transform_origin_y, transform_origin_z))
}
/// Returns the 4D matrix representing this fragment's perspective.
@@ -1688,20 +1698,15 @@ impl BoxFragment {
.px(),
);
- let pre_transform =
- LayoutTransform::translation(perspective_origin.x, perspective_origin.y, 0.0);
- let post_transform =
- LayoutTransform::translation(-perspective_origin.x, -perspective_origin.y, 0.0);
-
let perspective_matrix = LayoutTransform::from_untyped(
&transform::create_perspective_matrix(length.px()),
);
- Some(
- post_transform
- .then(&perspective_matrix)
- .then(&pre_transform),
- )
+ Some(perspective_matrix.change_basis(
+ perspective_origin.x,
+ perspective_origin.y,
+ 0.0,
+ ))
},
Perspective::None => None,
}
diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs
index 2d52303129c..92c7b1f3314 100644
--- a/components/layout_2020/style_ext.rs
+++ b/components/layout_2020/style_ext.rs
@@ -24,9 +24,11 @@ use style::values::computed::image::Image as ComputedImageLayer;
use style::values::computed::{AlignItems, BorderStyle, Color, Inset, LengthPercentage, Margin};
use style::values::generics::box_::Perspective;
use style::values::generics::position::{GenericAspectRatio, PreferredRatio};
+use style::values::generics::transform::{GenericRotate, GenericScale, GenericTranslate};
use style::values::specified::align::AlignFlags;
use style::values::specified::{Overflow, WillChangeBits, box_ as stylo};
use webrender_api as wr;
+use webrender_api::units::LayoutTransform;
use crate::dom_traversal::Contents;
use crate::fragment_tree::FragmentFlags;
@@ -501,6 +503,9 @@ impl ComputedValuesExt for ComputedValues {
fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
self.is_transformable(fragment_flags) &&
(!self.get_box().transform.0.is_empty() ||
+ self.get_box().scale != GenericScale::None ||
+ self.get_box().rotate != GenericRotate::None ||
+ self.get_box().translate != GenericTranslate::None ||
self.get_box().perspective != Perspective::None)
}
@@ -1181,3 +1186,16 @@ impl Clamp for Au {
self.clamp_below_max(max).max(min)
}
}
+
+pub(crate) trait TransformExt {
+ fn change_basis(&self, x: f32, y: f32, z: f32) -> Self;
+}
+
+impl TransformExt for LayoutTransform {
+ /// <https://drafts.csswg.org/css-transforms/#transformation-matrix-computation>
+ fn change_basis(&self, x: f32, y: f32, z: f32) -> Self {
+ let pre_translation = Self::translation(x, y, z);
+ let post_translation = Self::translation(-x, -y, -z);
+ post_translation.then(self).then(&pre_translation)
+ }
+}
diff --git a/tests/wpt/meta/css/css-flexbox/flexbox-safe-overflow-position-006.html.ini b/tests/wpt/meta/css/css-flexbox/flexbox-safe-overflow-position-006.html.ini
new file mode 100644
index 00000000000..9edcdef8700
--- /dev/null
+++ b/tests/wpt/meta/css/css-flexbox/flexbox-safe-overflow-position-006.html.ini
@@ -0,0 +1,2 @@
+[flexbox-safe-overflow-position-006.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/individual-transform/change-translate-property.html.ini b/tests/wpt/meta/css/css-transforms/individual-transform/change-translate-property.html.ini
deleted file mode 100644
index f0c9923d7ec..00000000000
--- a/tests/wpt/meta/css/css-transforms/individual-transform/change-translate-property.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[change-translate-property.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-1.html.ini b/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-1.html.ini
deleted file mode 100644
index f10a8cf7451..00000000000
--- a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-1.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[individual-transform-1.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2a.html.ini b/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2a.html.ini
deleted file mode 100644
index 7d934b36ae2..00000000000
--- a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2a.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[individual-transform-2a.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2b.html.ini b/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2b.html.ini
deleted file mode 100644
index bf8df442cb5..00000000000
--- a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2b.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[individual-transform-2b.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2c.html.ini b/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2c.html.ini
deleted file mode 100644
index fb9e5d4d3a9..00000000000
--- a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2c.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[individual-transform-2c.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2d.html.ini b/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2d.html.ini
deleted file mode 100644
index 414a8fd4963..00000000000
--- a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2d.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[individual-transform-2d.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2e.html.ini b/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2e.html.ini
deleted file mode 100644
index b20094b7cc1..00000000000
--- a/tests/wpt/meta/css/css-transforms/individual-transform/individual-transform-2e.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[individual-transform-2e.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/scale-animation-with-var-001.html.ini b/tests/wpt/meta/css/css-transforms/scale-animation-with-var-001.html.ini
deleted file mode 100644
index fbf0a6afbd1..00000000000
--- a/tests/wpt/meta/css/css-transforms/scale-animation-with-var-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[scale-animation-with-var-001.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/transform-hit-testing.html.ini b/tests/wpt/meta/css/css-transforms/transform-hit-testing.html.ini
deleted file mode 100644
index 7d836afc25c..00000000000
--- a/tests/wpt/meta/css/css-transforms/transform-hit-testing.html.ini
+++ /dev/null
@@ -1,15 +0,0 @@
-[transform-hit-testing.html]
- [hit testing of rectangle with 'translate' and 'rotate']
- expected: FAIL
-
- [hit testing of rectangle with 'transform']
- expected: FAIL
-
- [hit testing of rectangle with 'translate' and 'rotate' and 'scale' and 'transform']
- expected: FAIL
-
- [hit testing of square with 'rotate']
- expected: FAIL
-
- [hit testing of square with 'scale']
- expected: FAIL