layout: Make transform-style: preserve-3d establish a containing block for all descendants (#35808)

* layout: Fix behavior of `transform-style: preserve-3d`

This makes `transform-style: preserve-3d` establish a containing block
for all descendants, as specified here:
<https://drafts.csswg.org/css-transforms-2/#transform-style-property>

Signed-off-by: Daniel Hast <hast.daniel@protonmail.com>

* layout: Check for transformable elements

Adds a new `is_transformable` helper method and use this in several other
methods, including the methods for whether the fragment establishes a
new stacking context or a containing block for all descendants.

Signed-off-by: Daniel Hast <hast.daniel@protonmail.com>

* Use generic green square reference for reftest.

Signed-off-by: Daniel Hast <hast.daniel@protonmail.com>

* layout: Fix stacking context & containing block checks.

Only the computed value of `transform-style` should be used to determine
whether the element establishes a stacking context and/or a containing
block, not the used value.

Signed-off-by: Daniel Hast <hast.daniel@protonmail.com>

* Update clip-no-stacking-context test expectation to pass.

Signed-off-by: Daniel Hast <hast.daniel@protonmail.com>

---------

Signed-off-by: Daniel Hast <hast.daniel@protonmail.com>
This commit is contained in:
Daniel Hast 2025-03-07 12:39:59 -05:00 committed by GitHub
parent 991635eefb
commit 34b000c86e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 14 deletions

View file

@ -283,6 +283,7 @@ pub(crate) trait ComputedValuesExt {
&self,
containing_block_writing_mode: WritingMode,
) -> LogicalSides<LengthPercentageOrAuto<'_>>;
fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool;
fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool;
fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32;
fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow;
@ -463,9 +464,8 @@ impl ComputedValuesExt for ComputedValues {
LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode)
}
/// Returns true if this style has a transform, or perspective property set and
/// it applies to this element.
fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
/// Returns true if this is a transformable element.
fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
// "A transformable element is an element in one of these categories:
// * all elements whose layout is governed by the CSS box model except for
// non-replaced inline boxes, table-column boxes, and table-column-group
@ -473,14 +473,18 @@ impl ComputedValuesExt for ComputedValues {
// * all SVG paint server elements, the clipPath element and SVG renderable
// elements with the exception of any descendant element of text content
// elements."
// https://drafts.csswg.org/css-transforms/#transformable-element
if self.get_box().display.is_inline_flow() &&
!fragment_flags.contains(FragmentFlags::IS_REPLACED)
{
return false;
}
// <https://drafts.csswg.org/css-transforms/#transformable-element>
// TODO: check for all cases listed in the above spec.
!self.get_box().display.is_inline_flow() ||
fragment_flags.contains(FragmentFlags::IS_REPLACED)
}
!self.get_box().transform.0.is_empty() || self.get_box().perspective != Perspective::None
/// Returns true if this style has a transform, or perspective property set and
/// it applies to this element.
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().perspective != Perspective::None)
}
/// Get the effective z-index of this fragment. Z-indices only apply to positioned elements
@ -614,8 +618,9 @@ impl ComputedValuesExt for ComputedValues {
return true;
}
if self.get_box().transform_style == ComputedTransformStyle::Preserve3d ||
self.overrides_transform_style()
// See <https://drafts.csswg.org/css-transforms-2/#transform-style-property>.
if self.is_transformable(fragment_flags) &&
self.get_box().transform_style == ComputedTransformStyle::Preserve3d
{
return true;
}
@ -688,6 +693,13 @@ impl ComputedValuesExt for ComputedValues {
return true;
}
// See <https://drafts.csswg.org/css-transforms-2/#transform-style-property>.
if self.is_transformable(fragment_flags) &&
self.get_box().transform_style == ComputedTransformStyle::Preserve3d
{
return true;
}
// TODO: We need to handle CSS Contain here.
false
}

View file

@ -276759,6 +276759,19 @@
{}
]
],
"preserve3d-containing-block.html": [
"fc74e3874c67a2965b549036dd6699d593eeb3ef",
[
null,
[
[
"/css/reference/ref-filled-green-100px-square.xht",
"=="
]
],
{}
]
],
"preserve3d-nested-perspective.html": [
"368784c74f51c774cf8ea1b6f55127e72fd5854e",
[

View file

@ -1,2 +0,0 @@
[clip-no-stacking-context.html]
expected: FAIL

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS test: "transform-style: preserve-3d" should establish a containing block for all descendants</title>
<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#transform-style-property">
<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="The box should be green (not red) because the div establishes a containing block for all descendants.">
<style>
body { transform: scale(1); height: 0 }
div { background: red; width: 100px; height: 100px; }
div::before { content: ""; position: fixed; width: 100%; height: 100%; background: green; }
</style>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div style="transform-style: preserve-3d"></div>