- Revision
- 294566
- Author
- alanc...@apple.com
- Date
- 2022-05-20 13:06:26 -0700 (Fri, 20 May 2022)
Log Message
Cherry-pick r294530. rdar://problem/92984518
REGRESSION (r293126): Gmail formatting menu/panel in compose view becomes blank/empty while scrolling
https://bugs.webkit.org/show_bug.cgi?id=240625
<rdar://92984518>
Reviewed by Alan Bujtas.
Gmail uses the css `clip` property with negative offsets, which is surprising, and revealed a bug in
the compositing code for clipping.
When a stacking-context layer has overflow:hidden or clip:, we assume that the clip encloses all the
descendants, so make a GraphicsLayer with masksToBounds as a parent of the child layers. When the
layer has border-radius with uneven corners, we implement that with a shape layer which acts as a
mask on that clipping GraphicsLayer.
The content in question had CSS clip with negative offsets and border-radius, so the shape layer
mask would clip out any content outside the border shape.
So if the clip rect extends beyond the border, we need to follow a different code path, which pushes
the clipping layers onto descendants; this code path is used for non-stacking context clipping, and
for mix-blend-mode which needs to avoid the creation of the intermediate clipping layer.
So generalize the `isolatesCompositedBlending()` code path to also be used in the scenario of CSS
clip which extends outside the border box.
Tests: compositing/clipping/css-clip-and-border-radius.html
compositing/clipping/css-clip-non-stacking.html
* Source/WebCore/rendering/RenderLayerCompositor.cpp:
(WebCore::canUseDescendantClippingLayer):
(WebCore::RenderLayerCompositor::clippedByAncestor const):
(WebCore::RenderLayerCompositor::computeAncestorClippingStack const):
(WebCore::RenderLayerCompositor::clipsCompositingDescendants):
* LayoutTests/compositing/clipping/css-clip-and-border-radius-expected.html: Added.
* LayoutTests/compositing/clipping/css-clip-and-border-radius.html: Added.
* LayoutTests/compositing/clipping/css-clip-non-stacking-expected.html: Added.
* LayoutTests/compositing/clipping/css-clip-non-stacking.html: Added.
Canonical link: https://commits.webkit.org/250785@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@294530 268f45cc-cd09-0410-ab3c-d52691b4dbfc
Modified Paths
Added Paths
Diff
Added: branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-and-border-radius-expected.html (0 => 294566)
--- branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-and-border-radius-expected.html (rev 0)
+++ branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-and-border-radius-expected.html 2022-05-20 20:06:26 UTC (rev 294566)
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ .container {
+ position: absolute;
+ z-index: 1;
+ left: 100px;
+ height: 50px;
+ width: 500px;
+ border-radius: 0 25px 25px 0;
+ background-color: silver;
+ box-sizing: border-box;
+ transform: translateZ(0);
+ }
+
+ .child {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ height: 50px;
+ width: 220px;
+ border: 4px solid green;
+ padding: 10px;
+ background-color: orange;
+ transform: translateZ(0);
+ }
+ </style>
+</head>
+<body>
+ <div class="container" style="top: 50px;">
+ <div class="child">
+ this element should not be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 200px; border-radius: 0px">
+
+ <div class="child">
+ this element should not be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 350px; overflow: hidden;">
+ <div class="child">
+ this element should be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 500px; overflow: hidden; border-radius: 0px">
+
+ <div class="child">
+ this element should be clipped
+ </div>
+ </div>
+</body>
+</html>
Added: branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-and-border-radius.html (0 => 294566)
--- branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-and-border-radius.html (rev 0)
+++ branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-and-border-radius.html 2022-05-20 20:06:26 UTC (rev 294566)
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ .container {
+ position: absolute;
+ z-index: 1;
+ left: 100px;
+ height: 50px;
+ width: 500px;
+ border-radius: 0 25px 25px 0;
+ background-color: silver;
+ box-sizing: border-box;
+ transform: translateZ(0);
+ }
+
+ .child {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ height: 50px;
+ width: 220px;
+ border: 4px solid green;
+ padding: 10px;
+ background-color: orange;
+ transform: translateZ(0);
+ }
+ </style>
+</head>
+<body>
+ <div class="container" style="top: 50px; clip: rect(0px, 500px, 150px, 0px);">
+ <div class="child">
+ this element should not be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 200px; clip: rect(0px, 500px, 150px, 0px); border-radius: 0px">
+ <div class="child">
+ this element should not be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 350px; clip: rect(0px, 500px, 50px, 0px);">
+ <div class="child">
+ this element should be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 500px; clip: rect(0px, 500px, 50px, 0px); border-radius: 0px">
+ <div class="child">
+ this element should be clipped
+ </div>
+ </div>
+</body>
+</html>
Added: branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-non-stacking-expected.html (0 => 294566)
--- branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-non-stacking-expected.html (rev 0)
+++ branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-non-stacking-expected.html 2022-05-20 20:06:26 UTC (rev 294566)
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ .container {
+ position: absolute;
+ left: 100px;
+ height: 50px;
+ width: 500px;
+ border-radius: 0 25px 25px 0;
+ background-color: silver;
+ box-sizing: border-box;
+ }
+
+ .child {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ height: 50px;
+ width: 220px;
+ border: 4px solid green;
+ padding: 10px;
+ background-color: orange;
+ transform: translateZ(0);
+ }
+
+ .compositing-trigger {
+ position: absolute;
+ top: 40px;
+ left: 110px;
+ width: 20px;
+ height: 520px;
+ background-color: gray;
+ transform: translateZ(0);
+ }
+ </style>
+</head>
+<body>
+ <div class="compositing-trigger"></div>
+
+ <div class="container" style="top: 50px;">
+ <div class="child">
+ this element should not be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 200px; border-radius: 0px">
+ <div class="child">
+ this element should not be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 350px; overflow: hidden">
+ <div class="child">
+ this element should be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 500px; overflow: hidden; border-radius: 0px">
+ <div class="child">
+ this element should be clipped
+ </div>
+ </div>
+</body>
+</html>
Added: branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-non-stacking.html (0 => 294566)
--- branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-non-stacking.html (rev 0)
+++ branches/safari-7614.1.14.0-branch/LayoutTests/compositing/clipping/css-clip-non-stacking.html 2022-05-20 20:06:26 UTC (rev 294566)
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ .container {
+ position: absolute;
+ left: 100px;
+ height: 50px;
+ width: 500px;
+ border-radius: 0 25px 25px 0;
+ background-color: silver;
+ box-sizing: border-box;
+ }
+
+ .child {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ height: 50px;
+ width: 220px;
+ border: 4px solid green;
+ padding: 10px;
+ background-color: orange;
+ transform: translateZ(0);
+ }
+
+ .compositing-trigger {
+ position: absolute;
+ top: 40px;
+ left: 110px;
+ width: 20px;
+ height: 520px;
+ background-color: gray;
+ transform: translateZ(0);
+ }
+ </style>
+</head>
+<body>
+ <div class="compositing-trigger"></div>
+
+ <div class="container" style="top: 50px; clip: rect(0px, 500px, 150px, 0px);">
+ <div class="child">
+ this element should not be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 200px; clip: rect(0px, 500px, 150px, 0px); border-radius: 0px">
+ <div class="child">
+ this element should not be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 350px; clip: rect(0px, 500px, 50px, 0px);">
+ <div class="child">
+ this element should be clipped
+ </div>
+ </div>
+
+ <div class="container" style="top: 500px; clip: rect(0px, 500px, 50px, 0px); border-radius: 0px">
+ <div class="child">
+ this element should be clipped
+ </div>
+ </div>
+</body>
+</html>
Modified: branches/safari-7614.1.14.0-branch/Source/WebCore/rendering/RenderLayerCompositor.cpp (294565 => 294566)
--- branches/safari-7614.1.14.0-branch/Source/WebCore/rendering/RenderLayerCompositor.cpp 2022-05-20 19:34:21 UTC (rev 294565)
+++ branches/safari-7614.1.14.0-branch/Source/WebCore/rendering/RenderLayerCompositor.cpp 2022-05-20 20:06:26 UTC (rev 294566)
@@ -2839,6 +2839,25 @@
}
#endif
+
+static bool canUseDescendantClippingLayer(const RenderLayer& layer)
+{
+ if (layer.isolatesCompositedBlending())
+ return false;
+
+ // We can only use the "descendant clipping layer" strategy when the clip rect is entirely within
+ // the border box, because of interactions with border-radius clipping and compositing.
+ if (auto* renderer = layer.renderBox(); renderer && renderer->hasClip()) {
+ auto borderBoxRect = renderer->borderBoxRect();
+ auto clipRect = renderer->clipRect({ }, nullptr);
+
+ bool clipRectInsideBorderRect = intersection(borderBoxRect, clipRect) == clipRect;
+ return clipRectInsideBorderRect;
+ }
+
+ return true;
+}
+
// Return true if the given layer has some ancestor in the RenderLayer hierarchy that clips,
// up to the enclosing compositing ancestor. This is required because compositing layers are parented
// according to the z-order hierarchy, yet clipping goes down the renderer hierarchy.
@@ -2857,7 +2876,7 @@
// in this case it is not allowed to clipsCompositingDescendants() and each of its children
// will be clippedByAncestor()s, including the compositingAncestor.
auto* computeClipRoot = compositingAncestor;
- if (!compositingAncestor->isolatesCompositedBlending()) {
+ if (canUseDescendantClippingLayer(*compositingAncestor)) {
computeClipRoot = nullptr;
auto* parent = &layer;
while (parent) {
@@ -2873,7 +2892,8 @@
return false;
}
- return !layer.backgroundClipRect(RenderLayer::ClipRectsContext(computeClipRoot, TemporaryClipRects)).isInfinite(); // FIXME: Incorrect for CSS regions.
+ auto backgroundClipRect = layer.backgroundClipRect(RenderLayer::ClipRectsContext(computeClipRoot, TemporaryClipRects));
+ return !backgroundClipRect.isInfinite(); // FIXME: Incorrect for CSS regions.
}
bool RenderLayerCompositor::updateAncestorClippingStack(const RenderLayer& layer, const RenderLayer* compositingAncestor) const
@@ -2913,8 +2933,8 @@
if (&ancestorLayer == compositingAncestor) {
if (haveNonScrollableClippingIntermediateLayer)
- pushNonScrollableClip(*currentClippedLayer, ancestorLayer, ancestorLayer.isolatesCompositedBlending() ? RespectOverflowClip : IgnoreOverflowClip);
- else if (ancestorLayer.isolatesCompositedBlending() && newStack.isEmpty())
+ pushNonScrollableClip(*currentClippedLayer, ancestorLayer, !canUseDescendantClippingLayer(ancestorLayer) ? RespectOverflowClip : IgnoreOverflowClip);
+ else if (!canUseDescendantClippingLayer(ancestorLayer) && newStack.isEmpty())
pushNonScrollableClip(*currentClippedLayer, ancestorLayer, RespectOverflowClip);
return AncestorTraversal::Stop;
@@ -2988,7 +3008,13 @@
// into the hierarchy between this layer and its children in the z-order hierarchy.
bool RenderLayerCompositor::clipsCompositingDescendants(const RenderLayer& layer)
{
- return layer.hasCompositingDescendant() && layer.renderer().hasClipOrNonVisibleOverflow() && !layer.isolatesCompositedBlending() && !layer.hasCompositedNonContainedDescendants();
+ if (!(layer.hasCompositingDescendant() && layer.renderer().hasClipOrNonVisibleOverflow()))
+ return false;
+
+ if (layer.hasCompositedNonContainedDescendants())
+ return false;
+
+ return canUseDescendantClippingLayer(layer);
}
bool RenderLayerCompositor::requiresCompositingForAnimation(RenderLayerModelObject& renderer) const