Content sometimes missing in nested scrollers with border-radius
https://bugs.webkit.org/show_bug.cgi?id=213580
<rdar://problem/64460373>
Reviewed by Zalan Bujtas.
Source/WebCore:
RenderLayer::clipToRect() has a special case for clips involving border-radius,
involving an ancestor tree walk which applies the rounded clips.
When inside composited overflow, don't include the border-radius from the scroller since
that does its own clipping.
Test: compositing/clipping/nested-overflow-with-border-radius.html
* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::clipToRect):
(WebCore::RenderLayer::calculateClipRects const):
* rendering/RenderLayer.h:
LayoutTests:
* compositing/clipping/nested-overflow-with-border-radius-expected.html: Added.
* compositing/clipping/nested-overflow-with-border-radius.html: Added.
* compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position-expected.html:
* compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position.html: Tweak the colors and radii to make failures on this test more obvious.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263578 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 5de3de7..25e71ca 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,16 @@
+2020-06-26 Simon Fraser <simon.fraser@apple.com>
+
+ Content sometimes missing in nested scrollers with border-radius
+ https://bugs.webkit.org/show_bug.cgi?id=213580
+ <rdar://problem/64460373>
+
+ Reviewed by Zalan Bujtas.
+
+ * compositing/clipping/nested-overflow-with-border-radius-expected.html: Added.
+ * compositing/clipping/nested-overflow-with-border-radius.html: Added.
+ * compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position-expected.html:
+ * compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position.html: Tweak the colors and radii to make failures on this test more obvious.
+
2020-06-26 Jacob Uphoff <jacob_uphoff@apple.com>
[ macOS wk2 ] REGRESSION: webrtc/video-autoplay1.html is a flaky failure
diff --git a/LayoutTests/compositing/clipping/nested-overflow-with-border-radius-expected.html b/LayoutTests/compositing/clipping/nested-overflow-with-border-radius-expected.html
new file mode 100644
index 0000000..a7476b3e
--- /dev/null
+++ b/LayoutTests/compositing/clipping/nested-overflow-with-border-radius-expected.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ internal:AsyncOverflowScrollingEnabled=true ] -->
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ #container {
+ width: 200px;
+ height: 200px;
+ margin: 20px;
+ overflow-y: scroll;
+ border-radius: 10px;
+ border: 1px solid black;
+ }
+
+ .filler {
+ height: 100%;
+ width: 10px;
+ background-color: silver;
+ }
+
+ .last {
+ width: 100%;
+ height: 100px;
+ overflow: scroll;
+ }
+
+ .box {
+ width: 80%;
+ height: 80px;
+ background-color: green;
+ }
+
+ .obscurer {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background-color: gray;
+ }
+ </style>
+ <script>
+ if (window.testRunner)
+ testRunner.waitUntilDone();
+
+ window.addEventListener('load', () => {
+ setTimeout(() => {
+ container.scrollTo(0, 1000);
+ if (window.testRunner)
+ testRunner.notifyDone();
+ }, 0);
+ }, false);
+ </script>
+</head>
+<body>
+ <div id=container>
+ <div class="filler"></div>
+ <div class=last>
+ <div class="box"></div>
+ </div>
+ <div class="obscurer" style="left: 25px; top: 118px;"></div>
+ <div class="obscurer" style="left: 160px; top: 118px;"></div>
+ <div class="obscurer" style="left: 25px; top: 196px;"></div>
+ <div class="obscurer" style="left: 160px; top: 196px;"></div>
+ </div>
+ <p>You should see a green box in the scroller.</p>
+</body>
+</html>
diff --git a/LayoutTests/compositing/clipping/nested-overflow-with-border-radius.html b/LayoutTests/compositing/clipping/nested-overflow-with-border-radius.html
new file mode 100644
index 0000000..0830020
--- /dev/null
+++ b/LayoutTests/compositing/clipping/nested-overflow-with-border-radius.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ internal:AsyncOverflowScrollingEnabled=true ] -->
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ #container {
+ width: 200px;
+ height: 200px;
+ margin: 20px;
+ overflow-y: scroll;
+ border-radius: 10px;
+ border: 1px solid black;
+ }
+
+ .filler {
+ height: 100%;
+ width: 10px;
+ background-color: silver;
+ }
+
+ .last {
+ width: 100%;
+ height: 100px;
+ overflow: scroll;
+ border-radius: 2px;
+ }
+
+ .box {
+ width: 80%;
+ height: 80px;
+ background-color: green;
+ }
+
+ .obscurer {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background-color: gray;
+ }
+ </style>
+ <script>
+ if (window.testRunner)
+ testRunner.waitUntilDone();
+
+ window.addEventListener('load', () => {
+ setTimeout(() => {
+ container.scrollTo(0, 1000);
+ if (window.testRunner)
+ testRunner.notifyDone();
+ }, 0);
+ }, false);
+ </script>
+</head>
+<body>
+ <div id=container>
+ <div class="filler"></div>
+ <div class=last>
+ <div class="box"></div>
+ </div>
+ <div class="obscurer" style="left: 25px; top: 118px;"></div>
+ <div class="obscurer" style="left: 160px; top: 118px;"></div>
+ <div class="obscurer" style="left: 25px; top: 196px;"></div>
+ <div class="obscurer" style="left: 160px; top: 196px;"></div>
+ </div>
+ <p>You should see a green box in the scroller.</p>
+</body>
+</html>
diff --git a/LayoutTests/compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position-expected.html b/LayoutTests/compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position-expected.html
index 2c11c56..ebe0c02 100644
--- a/LayoutTests/compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position-expected.html
+++ b/LayoutTests/compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position-expected.html
@@ -10,7 +10,7 @@
}
.inner {
- -webkit-border-radius: 1px;
+ border-radius: 20px;
overflow: hidden;
width: 2100px;
}
@@ -18,7 +18,7 @@
.largebox {
width: 600px;
height: 100px;
- background-color: red;
+ background-color: green;
}
</style>
</head>
diff --git a/LayoutTests/compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position.html b/LayoutTests/compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position.html
index e65a24e..b6cf49c 100644
--- a/LayoutTests/compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position.html
+++ b/LayoutTests/compositing/hidpi-compositing-layer-with-tile-layers-on-subpixel-position.html
@@ -10,16 +10,16 @@
}
.inner {
- -webkit-border-radius: 1px;
+ border-radius: 20px;
overflow: hidden;
width: 2100px;
- -webkit-transform: translateZ(0);
+ transform: translateZ(0);
}
.largebox {
width: 600px;
height: 100px;
- background-color: red;
+ background-color: green;
}
</style>
</head>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index d8aa3db..be93d88 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,24 @@
+2020-06-26 Simon Fraser <simon.fraser@apple.com>
+
+ Content sometimes missing in nested scrollers with border-radius
+ https://bugs.webkit.org/show_bug.cgi?id=213580
+ <rdar://problem/64460373>
+
+ Reviewed by Zalan Bujtas.
+
+ RenderLayer::clipToRect() has a special case for clips involving border-radius,
+ involving an ancestor tree walk which applies the rounded clips.
+
+ When inside composited overflow, don't include the border-radius from the scroller since
+ that does its own clipping.
+
+ Test: compositing/clipping/nested-overflow-with-border-radius.html
+
+ * rendering/RenderLayer.cpp:
+ (WebCore::RenderLayer::clipToRect):
+ (WebCore::RenderLayer::calculateClipRects const):
+ * rendering/RenderLayer.h:
+
2020-06-26 Geoffrey Garen <ggaren@apple.com>
Initializing the main thread should initialize the main run loop
diff --git a/Source/WebCore/rendering/RenderLayer.cpp b/Source/WebCore/rendering/RenderLayer.cpp
index 89c0cd9..66c2b90 100644
--- a/Source/WebCore/rendering/RenderLayer.cpp
+++ b/Source/WebCore/rendering/RenderLayer.cpp
@@ -4170,7 +4170,7 @@
m_containsDirtyOverlayScrollbars = false;
}
-void RenderLayer::clipToRect(GraphicsContext& context, const LayerPaintingInfo& paintingInfo, const ClipRect& clipRect, BorderRadiusClippingRule rule)
+void RenderLayer::clipToRect(GraphicsContext& context, const LayerPaintingInfo& paintingInfo, OptionSet<PaintBehavior> paintBehavior, const ClipRect& clipRect, BorderRadiusClippingRule rule)
{
float deviceScaleFactor = renderer().document().deviceScaleFactor();
bool needsClipping = !clipRect.isInfinite() && clipRect.rect() != paintingInfo.paintDirtyRect;
@@ -4192,6 +4192,9 @@
// any layers with overflow. The condition for being able to apply these clips is that the overflow object be in our
// containing block chain so we check that also.
for (RenderLayer* layer = rule == IncludeSelfForBorderRadius ? this : parent(); layer; layer = layer->parent()) {
+ if (paintBehavior.contains(PaintBehavior::CompositedOverflowScrollContent) && layer->usesCompositedScrolling())
+ break;
+
if (layer->renderer().hasOverflowClip() && layer->renderer().style().hasBorderRadius() && ancestorLayerIsInContainingBlockChain(*layer)) {
LayoutRect adjustedClipRect = LayoutRect(toLayoutPoint(layer->offsetFromAncestor(paintingInfo.rootLayer, AdjustForColumns)), layer->size());
adjustedClipRect.move(paintingInfo.subpixelOffset);
@@ -4340,8 +4343,12 @@
clipRect = backgroundClipRect(clipRectsContext);
clipRect.intersect(paintingInfo.paintDirtyRect);
+ OptionSet<PaintBehavior> paintBehavior = PaintBehavior::Normal;
+ if (paintFlags.contains(PaintLayerFlag::PaintingOverflowContents))
+ paintBehavior.add(PaintBehavior::CompositedOverflowScrollContent);
+
// Push the parent coordinate space's clip.
- parent()->clipToRect(context, paintingInfo, clipRect);
+ parent()->clipToRect(context, paintingInfo, paintBehavior, clipRect);
}
paintLayerByApplyingTransform(context, paintingInfo, paintFlags);
@@ -4517,11 +4524,11 @@
return filterContext;
}
-void RenderLayer::applyFilters(GraphicsContext& originalContext, const LayerPaintingInfo& paintingInfo, const LayerFragments& layerFragments)
+void RenderLayer::applyFilters(GraphicsContext& originalContext, const LayerPaintingInfo& paintingInfo, OptionSet<PaintBehavior> behavior, const LayerFragments& layerFragments)
{
// FIXME: Handle more than one fragment.
ClipRect backgroundRect = layerFragments.isEmpty() ? ClipRect() : layerFragments[0].backgroundRect;
- clipToRect(originalContext, paintingInfo, backgroundRect);
+ clipToRect(originalContext, paintingInfo, behavior, backgroundRect);
m_filters->applyFilterEffect(originalContext);
restoreClip(originalContext, paintingInfo, backgroundRect);
}
@@ -4695,7 +4702,7 @@
(isPaintingOverflowContents) ? IgnoreOverflowClip : RespectOverflowClip, offsetFromRoot);
updatePaintingInfoForFragments(layerFragments, paintingInfo, localPaintFlags, shouldPaintContent, offsetFromRoot);
- applyFilters(context, paintingInfo, layerFragments);
+ applyFilters(context, paintingInfo, paintBehavior, layerFragments);
}
}
@@ -4975,7 +4982,11 @@
clipRect.intersect(parentClipRect);
}
- parent()->clipToRect(context, paintingInfo, clipRect);
+ OptionSet<PaintBehavior> paintBehavior = PaintBehavior::Normal;
+ if (paintFlags.contains(PaintLayerFlag::PaintingOverflowContents))
+ paintBehavior.add(PaintBehavior::CompositedOverflowScrollContent);
+
+ parent()->clipToRect(context, paintingInfo, paintBehavior, clipRect);
paintLayerByApplyingTransform(context, paintingInfo, paintFlags, fragment.paginationOffset);
parent()->restoreClip(context, paintingInfo, clipRect);
}
@@ -4996,7 +5007,7 @@
if (localPaintingInfo.clipToDirtyRect) {
// Paint our background first, before painting any child layers.
// Establish the clip used to paint our background.
- clipToRect(context, localPaintingInfo, fragment.backgroundRect, DoNotIncludeSelfForBorderRadius); // Background painting will handle clipping to self.
+ clipToRect(context, localPaintingInfo, paintBehavior, fragment.backgroundRect, DoNotIncludeSelfForBorderRadius); // Background painting will handle clipping to self.
}
// Paint the background.
@@ -5049,7 +5060,7 @@
ClipRect clippedRect;
if (shouldClip) {
clippedRect = layerFragments[0].foregroundRect;
- clipToRect(context, localPaintingInfo, clippedRect);
+ clipToRect(context, localPaintingInfo, localPaintBehavior, clippedRect);
}
// We have to loop through every fragment multiple times, since we have to repaint in each specific phase in order for
@@ -5082,7 +5093,7 @@
continue;
if (shouldClip)
- clipToRect(context, localPaintingInfo, fragment.foregroundRect);
+ clipToRect(context, localPaintingInfo, paintBehavior, fragment.foregroundRect);
PaintInfo paintInfo(context, fragment.foregroundRect.rect(), phase, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), this, localPaintingInfo.requireSecurityOriginAccessForWidgets);
if (phase == PaintPhase::Foreground)
@@ -5103,7 +5114,7 @@
// Paint our own outline
PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhase::SelfOutline, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), this);
- clipToRect(context, localPaintingInfo, fragment.backgroundRect, DoNotIncludeSelfForBorderRadius);
+ clipToRect(context, localPaintingInfo, paintBehavior, fragment.backgroundRect, DoNotIncludeSelfForBorderRadius);
renderer().paint(paintInfo, toLayoutPoint(fragment.layerBounds.location() - renderBoxLocation() + localPaintingInfo.subpixelOffset));
restoreClip(context, localPaintingInfo, fragment.backgroundRect);
}
@@ -5117,7 +5128,7 @@
continue;
if (localPaintingInfo.clipToDirtyRect)
- clipToRect(context, localPaintingInfo, fragment.backgroundRect, DoNotIncludeSelfForBorderRadius); // Mask painting will handle clipping to self.
+ clipToRect(context, localPaintingInfo, paintBehavior, fragment.backgroundRect, DoNotIncludeSelfForBorderRadius); // Mask painting will handle clipping to self.
// Paint the mask.
// FIXME: Eventually we will collect the region from the fragment itself instead of just from the paint info.
@@ -5136,7 +5147,7 @@
continue;
if (localPaintingInfo.clipToDirtyRect)
- clipToRect(context, localPaintingInfo, fragment.foregroundRect, IncludeSelfForBorderRadius); // Child clipping mask painting will handle clipping to self.
+ clipToRect(context, localPaintingInfo, paintBehavior, fragment.foregroundRect, IncludeSelfForBorderRadius); // Child clipping mask painting will handle clipping to self.
// Paint the clipped mask.
PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhase::ClippingMask, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), this);
@@ -5152,7 +5163,7 @@
for (const auto& fragment : layerFragments) {
if (fragment.backgroundRect.isEmpty())
continue;
- clipToRect(context, localPaintingInfo, fragment.backgroundRect);
+ clipToRect(context, localPaintingInfo, { }, fragment.backgroundRect);
paintOverflowControls(context, roundedIntPoint(toLayoutPoint(fragment.layerBounds.location() - renderBoxLocation() + localPaintingInfo.subpixelOffset)),
snappedIntRect(fragment.backgroundRect.rect()), true);
restoreClip(context, localPaintingInfo, fragment.backgroundRect);
diff --git a/Source/WebCore/rendering/RenderLayer.h b/Source/WebCore/rendering/RenderLayer.h
index 95d219a..03eb61d 100644
--- a/Source/WebCore/rendering/RenderLayer.h
+++ b/Source/WebCore/rendering/RenderLayer.h
@@ -1001,7 +1001,7 @@
LayoutRect clipRectRelativeToAncestor(RenderLayer* ancestor, LayoutSize offsetFromAncestor, const LayoutRect& constrainingRect) const;
- void clipToRect(GraphicsContext&, const LayerPaintingInfo&, const ClipRect&, BorderRadiusClippingRule = IncludeSelfForBorderRadius);
+ void clipToRect(GraphicsContext&, const LayerPaintingInfo&, OptionSet<PaintBehavior>, const ClipRect&, BorderRadiusClippingRule = IncludeSelfForBorderRadius);
void restoreClip(GraphicsContext&, const LayerPaintingInfo&, const ClipRect&);
bool shouldRepaintAfterLayout() const;
@@ -1053,7 +1053,7 @@
RenderLayerFilters* filtersForPainting(GraphicsContext&, OptionSet<PaintLayerFlag>) const;
GraphicsContext* setupFilters(GraphicsContext& destinationContext, LayerPaintingInfo&, OptionSet<PaintLayerFlag>, const LayoutSize& offsetFromRoot, Optional<LayoutRect>& rootRelativeBounds);
- void applyFilters(GraphicsContext& originalContext, const LayerPaintingInfo&, const LayerFragments&);
+ void applyFilters(GraphicsContext& originalContext, const LayerPaintingInfo&, OptionSet<PaintBehavior>, const LayerFragments&);
void paintLayer(GraphicsContext&, const LayerPaintingInfo&, OptionSet<PaintLayerFlag>);
void paintLayerWithEffects(GraphicsContext&, const LayerPaintingInfo&, OptionSet<PaintLayerFlag>);