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>);