From 16d2e030eb030d18b64b35d70d0e82db24ca3b59 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 23 Sep 2025 09:22:10 +0200 Subject: [PATCH] layout: Clamp sticky positioning offset bounds by zero (#39443) Sticky positioning tries to keep an element visible within the nearest scrollport. However, the element can't be offset to go beyond its containing block. We implement this as offset bounds. The problem was that, if the element would already be overflowing its containing block before applying the sticky positioning, then we were forcing it to move inside the containing block. That was wrong, and is solved by flooring or ceiling the offset bounds by zero. Testing: Adding new tests Signed-off-by: Oriol Brufau Co-authored-by: Martin Robinson --- .../layout/display_list/stacking_context.rs | 8 +-- tests/wpt/meta/MANIFEST.json | 52 +++++++++++++++++++ .../sticky/position-sticky-bottom-004.html | 33 ++++++++++++ .../sticky/position-sticky-bottom-005.html | 33 ++++++++++++ .../sticky/position-sticky-bottom-006.html | 31 +++++++++++ .../sticky/position-sticky-bottom-007.html | 36 +++++++++++++ 6 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-004.html create mode 100644 tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-005.html create mode 100644 tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-006.html create mode 100644 tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-007.html diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index b4c2a8a53c2..955b0a33e9b 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -1558,12 +1558,12 @@ impl BoxFragment { // This is the minimum negative offset and then the maximum positive offset. We just // specify every edge, but if the corresponding margin is None, that offset has no effect. let vertical_offset_bounds = wr::StickyOffsetBounds::new( - containing_block_rect.min.y - frame_rect.min.y, - containing_block_rect.max.y - frame_rect.max.y, + (containing_block_rect.min.y - frame_rect.min.y).min(0.), + (containing_block_rect.max.y - frame_rect.max.y).max(0.), ); let horizontal_offset_bounds = wr::StickyOffsetBounds::new( - containing_block_rect.min.x - frame_rect.min.x, - containing_block_rect.max.x - frame_rect.max.x, + (containing_block_rect.min.x - frame_rect.min.x).min(0.), + (containing_block_rect.max.x - frame_rect.max.x).max(0.), ); let margins = SideOffsets2D::new( diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index 896b9ab643e..13de4a77261 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -238242,6 +238242,58 @@ {} ] ], + "position-sticky-bottom-004.html": [ + "7439c2795360da76b1448ab098942737cbcc8680", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "position-sticky-bottom-005.html": [ + "3d226132a854b0d63c59298e19a1d1f8dbf03536", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "position-sticky-bottom-006.html": [ + "675ade1fcbae7a7c5e5749ecfb3d2313f22ae9d1", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], + "position-sticky-bottom-007.html": [ + "f2648f1e003ac16d29c49be5f61f27a2097fe1b9", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], "position-sticky-change-top.html": [ "6a327ccd567818c2267d808a890796724384726e", [ diff --git a/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-004.html b/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-004.html new file mode 100644 index 00000000000..7439c279536 --- /dev/null +++ b/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-004.html @@ -0,0 +1,33 @@ + +CSS Position Test: sticky element with bottom offset + + + + + + + +

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

+ +
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-005.html b/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-005.html new file mode 100644 index 00000000000..3d226132a85 --- /dev/null +++ b/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-005.html @@ -0,0 +1,33 @@ + +CSS Position Test: sticky element with bottom offset + + + + + + + +

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

+ +
+
+
+
+
+
diff --git a/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-006.html b/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-006.html new file mode 100644 index 00000000000..675ade1fcba --- /dev/null +++ b/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-006.html @@ -0,0 +1,31 @@ + +CSS Position Test: sticky element with bottom offset + + + + + + + +

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

+ +
+
+
+
diff --git a/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-007.html b/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-007.html new file mode 100644 index 00000000000..f2648f1e003 --- /dev/null +++ b/tests/wpt/tests/css/css-position/sticky/position-sticky-bottom-007.html @@ -0,0 +1,36 @@ + +CSS Position Test: sticky element with bottom offset + + + + + + + +

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

+ +
+
+
+
+ +