Skip to content
Permalink
Browse files
Fixed position elements should layout relative to transformed container
https://bugs.webkit.org/show_bug.cgi?id=118635

Reviewed by Alan Bujtas.

Layout code already handled `transform` and `will-change: transform` triggering containing block for
`position: fixed`. However, compositing code would still treat the elements as fixed, registering
them with the scrolling tree, so that scrolling would reposition them.

Fix this by having `RenderLayerCompositor::isViewportConstrainedFixedOrStickyLayer()` consult the
`behavesAsFixed()` flag on RenderLayer (rather than the position style).

However, we also need to make `behavesAsFixed()` correct under a `will-change` ancestor, so have
`RenderLayer::recursiveUpdateLayerPositions()` consult `canContainFixedPositionObjects()` and
maintain a new `m_hasFixedContainingBlockAncestor` flag on RenderLayer (since the
`m_hasTransformedAncestor`) one is used for other purposes.

* LayoutTests/scrollingcoordinator/scrolling-tree/fixed-position-within-transformed-expected.txt: Added.
* LayoutTests/platform/ios-wk2/scrollingcoordinator/scrolling-tree/fixed-position-within-transformed-expected.txt: Added.
* LayoutTests/scrollingcoordinator/scrolling-tree/fixed-position-within-transformed.html: Added.
* Source/WebCore/rendering/RenderLayer.cpp:
(WebCore::RenderLayer::RenderLayer):
(WebCore::RenderLayer::flagsForUpdateLayerPositions):
(WebCore::RenderLayer::recursiveUpdateLayerPositions):
* Source/WebCore/rendering/RenderLayer.h:
(WebCore::RenderLayer::hasFixedContainingBlockAncestor const):
* Source/WebCore/rendering/RenderLayerCompositor.cpp:
(WebCore::RenderLayerCompositor::isViewportConstrainedFixedOrStickyLayer const):

Canonical link: https://commits.webkit.org/253809@main
  • Loading branch information
smfr committed Aug 26, 2022
1 parent 88d1b92 commit 546a1c950d420fbdca2a84d3cfce3efb3e60549e
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 11 deletions.
@@ -0,0 +1,15 @@

(Frame scrolling node
(scrollable area size 800 600)
(contents size 800 3016)
(scrollable area parameters
(horizontal scroll elasticity 1)
(vertical scroll elasticity 1)
(horizontal scrollbar mode 0)
(vertical scrollbar mode 0))
(layout viewport at (0,0) size 800x600)
(min layout viewport origin (0,0))
(max layout viewport origin (0,2416))
(behavior for fixed 1)
)

@@ -0,0 +1,16 @@

(Frame scrolling node
(scrollable area size 785 600)
(contents size 785 3016)
(scrollable area parameters
(horizontal scroll elasticity 2)
(vertical scroll elasticity 2)
(horizontal scrollbar mode 0)
(vertical scrollbar mode 0)
(allows vertical scrolling 1))
(layout viewport at (0,0) size 785x600)
(min layout viewport origin (0,0))
(max layout viewport origin (0,2416))
(behavior for fixed 1)
)

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 3000px;
}

.container {
position: relative;
top: 500px;
width: 200px;
height: 200px;
border: 1px solid black;
}

.not-fixed {
position: fixed;
width: 200px;
height: 200px;
background-color: green;
}

.compositing-trigger {
position: absolute;
left: 20px;
top: 20px;
width: 50px;
height: 50px;
background-color: gray;
will-change: transform;
}
</style>
</head>
<body>
<div class="container" style="transform: translate(0, 0)">
<div class="compositing-trigger"></div>
<div class="not-fixed"></div>
</div>

<div class="container" style="will-change: transform">
<div class="compositing-trigger"></div>
<div class="not-fixed"></div>
</div>

<pre id="tree">Scrolling tree goes here</pre>
<script>
if (window.testRunner)
testRunner.dumpAsText();

if (window.internals)
document.getElementById('tree').innerText = internals.scrollingStateTreeAsText();
</script>
</body>
</html>
@@ -324,6 +324,7 @@ RenderLayer::RenderLayer(RenderLayerModelObject& renderer)
, m_hasCompositingDescendant(false)
, m_hasCompositedNonContainedDescendants(false)
, m_hasCompositedScrollingAncestor(false)
, m_hasFixedContainingBlockAncestor(false)
, m_hasTransformedAncestor(false)
, m_has3DTransformedAncestor(false)
, m_insideSVGForeignObject(false)
@@ -901,13 +902,16 @@ OptionSet<RenderLayer::UpdateLayerPositionsFlag> RenderLayer::flagsForUpdateLaye
OptionSet<UpdateLayerPositionsFlag> flags = { CheckForRepaint };

if (auto* parent = startingLayer.parent()) {
if (parent->hasFixedContainingBlockAncestor() || parent->renderer().canContainFixedPositionObjects())
flags.add(SeenFixedContainingBlockLayer);

if (parent->hasTransformedAncestor() || parent->transform())
flags.add(SeenTransformedLayer);

if (parent->has3DTransformedAncestor() || (parent->transform() && !parent->transform()->isAffine()))
flags.add(Seen3DTransformedLayer);

if (parent->behavesAsFixed() || (parent->renderer().isFixedPositioned() && !parent->hasTransformedAncestor()))
if (parent->behavesAsFixed() || (parent->renderer().isFixedPositioned() && !parent->hasFixedContainingBlockAncestor()))
flags.add(SeenFixedLayer);

if (parent->hasCompositedScrollingAncestor() || parent->hasCompositedScrollableOverflow())
@@ -1019,6 +1023,7 @@ void RenderLayer::recursiveUpdateLayerPositions(RenderGeometryMap* geometryMap,
clearRepaintRects();

m_repaintStatus = NeedsNormalRepaint;
m_hasFixedContainingBlockAncestor = flags.contains(SeenFixedContainingBlockLayer);
m_hasTransformedAncestor = flags.contains(SeenTransformedLayer);
m_has3DTransformedAncestor = flags.contains(Seen3DTransformedLayer);
m_behavesAsFixed = flags.contains(SeenFixedLayer);
@@ -1033,14 +1038,18 @@ void RenderLayer::recursiveUpdateLayerPositions(RenderGeometryMap* geometryMap,
flags.add(UpdatePagination);
}

if (transform()) {
flags.add(SeenTransformedLayer);
if (!transform()->isAffine())
flags.add(Seen3DTransformedLayer);
if (!isRenderViewLayer() && renderer().canContainFixedPositionObjects()) {
flags.add(SeenFixedContainingBlockLayer);

if (transform()) {
flags.add(SeenTransformedLayer);
if (!transform()->isAffine())
flags.add(Seen3DTransformedLayer);
}
}

// Fixed inside transform behaves like absolute (per spec).
if (renderer().isFixedPositioned() && !m_hasTransformedAncestor) {
if (renderer().isFixedPositioned() && !m_hasFixedContainingBlockAncestor) {
m_behavesAsFixed = true;
flags.add(SeenFixedLayer);
}
@@ -760,6 +760,8 @@ class RenderLayer : public CanMakeWeakPtr<RenderLayer> {
bool has3DTransform() const { return m_transform && !m_transform->isAffine(); }
bool hasTransformedAncestor() const { return m_hasTransformedAncestor; }

bool hasFixedContainingBlockAncestor() const { return m_hasFixedContainingBlockAncestor; }

bool hasFilter() const { return renderer().hasFilter(); }
bool hasFilterOutsets() const { return !filterOutsets().isZero(); }
IntOutsets filterOutsets() const;
@@ -971,9 +973,10 @@ class RenderLayer : public CanMakeWeakPtr<RenderLayer> {
ContainingClippingLayerChangedSize = 1 << 2,
UpdatePagination = 1 << 3,
SeenFixedLayer = 1 << 4,
SeenTransformedLayer = 1 << 5,
Seen3DTransformedLayer = 1 << 6,
SeenCompositedScrollingLayer = 1 << 7,
SeenFixedContainingBlockLayer = 1 << 5,
SeenTransformedLayer = 1 << 6,
Seen3DTransformedLayer = 1 << 7,
SeenCompositedScrollingLayer = 1 << 8,
};
static OptionSet<UpdateLayerPositionsFlag> flagsForUpdateLayerPositions(RenderLayer& startingLayer);

@@ -1235,6 +1238,7 @@ class RenderLayer : public CanMakeWeakPtr<RenderLayer> {
bool m_hasCompositedNonContainedDescendants : 1;
bool m_hasCompositedScrollingAncestor : 1; // In the layer-order tree.

bool m_hasFixedContainingBlockAncestor : 1;
bool m_hasTransformedAncestor : 1;
bool m_has3DTransformedAncestor : 1;

@@ -3436,10 +3436,9 @@ bool RenderLayerCompositor::isViewportConstrainedFixedOrStickyLayer(const Render
if (layer.renderer().isStickilyPositioned())
return isAsyncScrollableStickyLayer(layer);

if (!layer.renderer().isFixedPositioned())
if (!layer.behavesAsFixed())
return false;

// FIXME: Handle fixed inside of a transform, which should not behave as fixed.
for (auto* ancestor = layer.parent(); ancestor; ancestor = ancestor->parent()) {
if (ancestor->hasCompositedScrollableOverflow())
return true;

0 comments on commit 546a1c9

Please sign in to comment.