Skip to content

Commit

Permalink
layout: Allow transforming inline replaced elements (#31833)
Browse files Browse the repository at this point in the history
This requires passing through information about whether or not the
element in question is replaced when checking to see if it's
transformable and transitively all functions that make decisions about
containing blocks. A new FragmentFlag is added to help track this -- it
will be set on both the replaced items BoxFragment container as well as
the Fragment for the replaced item itself.

Fixes #31806.
  • Loading branch information
mrobinson committed Mar 27, 2024
1 parent 15cb9dd commit b8c82c1
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 35 deletions.
12 changes: 6 additions & 6 deletions components/layout_2020/display_list/stacking_context.rs
Expand Up @@ -474,7 +474,7 @@ impl StackingContext {
if effects.filter.0.is_empty() &&
effects.opacity == 1.0 &&
effects.mix_blend_mode == ComputedMixBlendMode::Normal &&
!style.has_transform_or_perspective()
!style.has_transform_or_perspective(FragmentFlags::empty())
{
return false;
}
Expand Down Expand Up @@ -896,7 +896,7 @@ struct ReferenceFrameData {

impl BoxFragment {
fn get_stacking_context_type(&self) -> Option<StackingContextType> {
if self.style.establishes_stacking_context() {
if self.style.establishes_stacking_context(self.base.flags) {
return Some(StackingContextType::RealStackingContext);
}

Expand Down Expand Up @@ -986,7 +986,7 @@ impl BoxFragment {
// properties will be replaced before recursing into children.
assert!(self
.style
.establishes_containing_block_for_all_descendants());
.establishes_containing_block_for_all_descendants(self.base.flags));
let adjusted_containing_block = ContainingBlock::new(
containing_block
.rect
Expand Down Expand Up @@ -1168,15 +1168,15 @@ impl BoxFragment {
// absolute and fixed descendants.
let new_containing_block_info = if self
.style
.establishes_containing_block_for_all_descendants()
.establishes_containing_block_for_all_descendants(self.base.flags)
{
containing_block_info.new_for_absolute_and_fixed_descendants(
&for_non_absolute_descendants,
&for_absolute_descendants,
)
} else if self
.style
.establishes_containing_block_for_absolute_descendants()
.establishes_containing_block_for_absolute_descendants(self.base.flags)
{
containing_block_info.new_for_absolute_descendants(
&for_non_absolute_descendants,
Expand Down Expand Up @@ -1389,7 +1389,7 @@ impl BoxFragment {
&self,
containing_block_rect: &PhysicalRect<Length>,
) -> Option<ReferenceFrameData> {
if !self.style.has_transform_or_perspective() {
if !self.style.has_transform_or_perspective(self.base.flags) {
return None;
}

Expand Down
16 changes: 10 additions & 6 deletions components/layout_2020/formatting_contexts.rs
Expand Up @@ -16,7 +16,7 @@ use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::flexbox::FlexContainer;
use crate::flow::BlockFormattingContext;
use crate::fragment_tree::{BaseFragmentInfo, Fragment};
use crate::fragment_tree::{BaseFragmentInfo, Fragment, FragmentFlags};
use crate::positioned::PositioningContext;
use crate::replaced::ReplacedContent;
use crate::sizing::{self, ContentSizes};
Expand Down Expand Up @@ -131,11 +131,15 @@ impl IndependentFormattingContext {
contents,
})
},
Err(contents) => Self::Replaced(ReplacedFormattingContext {
base_fragment_info: node_and_style_info.into(),
style: Arc::clone(&node_and_style_info.style),
contents,
}),
Err(contents) => {
let mut base_fragment_info: BaseFragmentInfo = node_and_style_info.into();
base_fragment_info.flags.insert(FragmentFlags::IS_REPLACED);
Self::Replaced(ReplacedFormattingContext {
base_fragment_info,
style: Arc::clone(&node_and_style_info.style),
contents,
})
},
}
}

Expand Down
3 changes: 3 additions & 0 deletions components/layout_2020/fragment_tree/base_fragment.rs
Expand Up @@ -88,6 +88,9 @@ bitflags! {
const IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT = 0b00000001;
/// Whether or not the node that created this Fragment is a `<br>` element.
const IS_BR_ELEMENT = 0b00000010;
/// Whether or not this Fragment was created to contain a replaced element or is
/// a replaced element.
const IS_REPLACED = 0b00000100;
}
}

Expand Down
4 changes: 2 additions & 2 deletions components/layout_2020/fragment_tree/fragment.rs
Expand Up @@ -192,12 +192,12 @@ impl Fragment {
.translate(containing_block.origin.to_vector());
let new_manager = if fragment
.style
.establishes_containing_block_for_all_descendants()
.establishes_containing_block_for_all_descendants(fragment.base.flags)
{
manager.new_for_absolute_and_fixed_descendants(&content_rect, &padding_rect)
} else if fragment
.style
.establishes_containing_block_for_absolute_descendants()
.establishes_containing_block_for_absolute_descendants(fragment.base.flags)
{
manager.new_for_absolute_descendants(&content_rect, &padding_rect)
} else {
Expand Down
11 changes: 8 additions & 3 deletions components/layout_2020/positioned.rs
Expand Up @@ -18,7 +18,8 @@ use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::{
AbsoluteBoxOffsets, BoxFragment, CollapsedBlockMargins, Fragment, HoistedSharedFragment,
AbsoluteBoxOffsets, BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags,
HoistedSharedFragment,
};
use crate::geom::{
AuOrAuto, LengthOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalVec2,
Expand Down Expand Up @@ -144,9 +145,13 @@ impl PositioningContext {
}

pub(crate) fn new_for_style(style: &ComputedValues) -> Option<Self> {
if style.establishes_containing_block_for_all_descendants() {
// NB: We never make PositioningContexts for replaced elements, which is why we always
// pass false here.
if style.establishes_containing_block_for_all_descendants(FragmentFlags::empty()) {
Some(Self::new_for_containing_block_for_all_descendants())
} else if style.establishes_containing_block_for_absolute_descendants() {
} else if style
.establishes_containing_block_for_absolute_descendants(FragmentFlags::empty())
{
Some(Self {
for_nearest_positioned_ancestor: Some(Vec::new()),
for_nearest_containing_block_for_all_descendants: Vec::new(),
Expand Down
40 changes: 27 additions & 13 deletions components/layout_2020/style_ext.rs
Expand Up @@ -20,6 +20,7 @@ use style::Zero;
use webrender_api as wr;

use crate::dom_traversal::Contents;
use crate::fragment_tree::FragmentFlags;
use crate::geom::{
AuOrAuto, LengthOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalVec2, PhysicalSides,
PhysicalSize,
Expand Down Expand Up @@ -179,13 +180,19 @@ pub(crate) trait ComputedValuesExt {
&self,
containing_block_writing_mode: WritingMode,
) -> LogicalSides<LengthPercentageOrAuto<'_>>;
fn has_transform_or_perspective(&self) -> bool;
fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool;
fn effective_z_index(&self) -> i32;
fn establishes_block_formatting_context(&self) -> bool;
fn establishes_stacking_context(&self) -> bool;
fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool;
fn establishes_scroll_container(&self) -> bool;
fn establishes_containing_block_for_absolute_descendants(&self) -> bool;
fn establishes_containing_block_for_all_descendants(&self) -> bool;
fn establishes_containing_block_for_absolute_descendants(
&self,
fragment_flags: FragmentFlags,
) -> bool;
fn establishes_containing_block_for_all_descendants(
&self,
fragment_flags: FragmentFlags,
) -> bool;
fn background_is_transparent(&self) -> bool;
fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
}
Expand Down Expand Up @@ -411,7 +418,7 @@ impl ComputedValuesExt for ComputedValues {

/// Returns true if this style has a transform, or perspective property set and
/// it applies to this element.
fn has_transform_or_perspective(&self) -> bool {
fn has_transform_or_perspective(&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
Expand All @@ -420,8 +427,9 @@ impl ComputedValuesExt for ComputedValues {
// elements with the exception of any descendant element of text content
// elements."
// https://drafts.csswg.org/css-transforms/#transformable-element
// FIXME(mrobinson): Properly handle tables and replaced elements here.
if self.get_box().display.is_inline_flow() {
if self.get_box().display.is_inline_flow() &&
!fragment_flags.contains(FragmentFlags::IS_REPLACED)
{
return false;
}

Expand Down Expand Up @@ -464,7 +472,7 @@ impl ComputedValuesExt for ComputedValues {
}

/// Returns true if this fragment establishes a new stacking context and false otherwise.
fn establishes_stacking_context(&self) -> bool {
fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool {
let effects = self.get_effects();
if effects.opacity != 1.0 {
return true;
Expand All @@ -474,7 +482,7 @@ impl ComputedValuesExt for ComputedValues {
return true;
}

if self.has_transform_or_perspective() {
if self.has_transform_or_perspective(fragment_flags) {
return true;
}

Expand Down Expand Up @@ -513,8 +521,11 @@ impl ComputedValuesExt for ComputedValues {
/// descendants) this method will return true, but a true return value does
/// not imply that the style establishes a containing block for all descendants.
/// Use `establishes_containing_block_for_all_descendants()` instead.
fn establishes_containing_block_for_absolute_descendants(&self) -> bool {
if self.establishes_containing_block_for_all_descendants() {
fn establishes_containing_block_for_absolute_descendants(
&self,
fragment_flags: FragmentFlags,
) -> bool {
if self.establishes_containing_block_for_all_descendants(fragment_flags) {
return true;
}

Expand All @@ -525,8 +536,11 @@ impl ComputedValuesExt for ComputedValues {
/// all descendants, including fixed descendants (`position: fixed`).
/// Note that this also implies that it establishes a containing block
/// for absolute descendants (`position: absolute`).
fn establishes_containing_block_for_all_descendants(&self) -> bool {
if self.has_transform_or_perspective() {
fn establishes_containing_block_for_all_descendants(
&self,
fragment_flags: FragmentFlags,
) -> bool {
if self.has_transform_or_perspective(fragment_flags) {
return true;
}

Expand Down
10 changes: 7 additions & 3 deletions components/layout_2020/table/layout.rs
Expand Up @@ -22,7 +22,7 @@ use super::{Table, TableSlot, TableSlotCell, TableTrack, TableTrackGroup};
use crate::context::LayoutContext;
use crate::formatting_contexts::{Baselines, IndependentLayout};
use crate::fragment_tree::{
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, ExtraBackground, Fragment,
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, ExtraBackground, Fragment, FragmentFlags,
PositioningFragment,
};
use crate::geom::{AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalVec2};
Expand Down Expand Up @@ -985,11 +985,15 @@ impl<'a> TableLayout<'a> {
row.group_index.map_or(false, |group_index| {
self.table.row_groups[group_index]
.style
.establishes_containing_block_for_absolute_descendants()
.establishes_containing_block_for_absolute_descendants(
FragmentFlags::empty(),
)
});
row_group_collects_for_nearest_positioned_ancestor ||
row.style
.establishes_containing_block_for_absolute_descendants()
.establishes_containing_block_for_absolute_descendants(
FragmentFlags::empty(),
)
});

let mut cells_laid_out_row = Vec::new();
Expand Down
17 changes: 17 additions & 0 deletions tests/wpt/meta/MANIFEST.json
Expand Up @@ -260846,6 +260846,19 @@
{}
]
],
"transform-iframe-002.html": [
"b9b10ea368324efa93e73476fc8084dadb89f2d0",
[
null,
[
[
"/css/reference/ref-filled-green-100px-square-only.html",
"=="
]
],
{}
]
],
"transform-image-001.html": [
"0565b8dbeeb86b82993847a139c8f38b66c0b163",
[
Expand Down Expand Up @@ -415399,6 +415412,10 @@
"d92c3705b6b9dd270d17c64fce9010a5ab1fe4f3",
[]
],
"transform-iframe-002-contents.html": [
"84f079c90bcb590e81ba39753edf723bcb123858",
[]
],
"transform-lime-square.png": [
"8f939993332e1101b921615723ec6067f3bb90a3",
[]
Expand Down

This file was deleted.

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Test (Transforms): Iframe (contents)</title>
<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
<style>
body {
background: green;
}
</style>
</head>
<body>
</body>
</html>
20 changes: 20 additions & 0 deletions tests/wpt/tests/css/css-transforms/transform-iframe-002.html
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Test (Transforms): Iframe</title>
<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
<link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
<meta name="assert" content="This test ensures that an iframe element can be transformed.">
<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
<style>
iframe {
height: 50px;
width: 50px;
transform: translate(25px, 25px) scale(2, 2);
border: none;
}
</style>
<p>Test passes if there is a filled green square.</p>
<iframe src="support/transform-iframe-002-contents.html"></iframe>
</body>
</html>

0 comments on commit b8c82c1

Please sign in to comment.