Setting border-radius on <video> element clips top and left sections of video
https://bugs.webkit.org/show_bug.cgi?id=202049
<rdar://problem/55570024>

Reviewed by Dean Jackson.

Source/WebCore:

updateClippingStrategy() is called both when we're setting a mask layer on m_layer,
and on m_contentsClippingLayer, and they disagreed on the coordinate space that
the rounded rect was in. r246845 fixed rounded-rect scrollers, but broke video.

Fix by declaring that the rounded rect is relative to the bounds of the layer
argument. We don't try to size the shape layer to the rounded rect, because in some
configurations (e.g. scroller with left scrollbar) the rounded rect hangs outside
the clipping layer.

Test: compositing/video/video-border-radius-clipping.html

* platform/graphics/FloatRoundedRect.h:
(WebCore::FloatRoundedRect::setLocation):
* platform/graphics/ca/GraphicsLayerCA.cpp:
(WebCore::GraphicsLayerCA::updateClippingStrategy):
(WebCore::GraphicsLayerCA::updateContentsRects): The rounded rect is relative to the contentsClippingLayer,
so set its location to zero.
* rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::updateChildClippingStrategy): negate the offset, as we do in updateGeometry which
has similar code.

LayoutTests:

* compositing/video/video-border-radius-clipping-expected.html: Added.
* compositing/video/video-border-radius-clipping.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251385 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 90ddb493..114f209 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -68,6 +68,17 @@
 
 2019-10-21  Simon Fraser  <simon.fraser@apple.com>
 
+        Setting border-radius on <video> element clips top and left sections of video
+        https://bugs.webkit.org/show_bug.cgi?id=202049
+        <rdar://problem/55570024>
+
+        Reviewed by Dean Jackson.
+
+        * compositing/video/video-border-radius-clipping-expected.html: Added.
+        * compositing/video/video-border-radius-clipping.html: Added.
+
+2019-10-21  Simon Fraser  <simon.fraser@apple.com>
+
         scrollingcoordinator/ios/ui-scrolling-tree.html is a Flaky Failure on iPad
         https://bugs.webkit.org/show_bug.cgi?id=203119
         rdar://problem/52970947
diff --git a/LayoutTests/compositing/video/video-border-radius-clipping-expected.html b/LayoutTests/compositing/video/video-border-radius-clipping-expected.html
new file mode 100644
index 0000000..b522806
--- /dev/null
+++ b/LayoutTests/compositing/video/video-border-radius-clipping-expected.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        video {
+            box-shadow: -50px -50px 0px black;
+            background-color: red;   
+            margin: 50px;
+            width: 500.25px;
+        }
+
+        .obscurer {
+            position: absolute;
+            width: 10px;
+            height: 10px;
+            background-color: gray;
+        }
+    </style>
+    <script src="../../media/media-file.js"></script>
+    <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+
+        function setupTest()
+        {
+            var totalCount = document.getElementsByTagName('video').length;
+            var count = totalCount;
+            document.addEventListener("canplaythrough", function () {
+                if (!--count && window.testRunner) {
+                    setTimeout(function() {
+                        testRunner.notifyDone();
+                    }, totalCount * 150);
+                }
+            }, true);
+
+            document.addEventListener("error", function (event) {
+                console.log("Video " + event.target.getAttribute("name") + " failed to load, error " + event.target.error.code);
+                if (window.testRunner)
+                    testRunner.notifyDone();
+            }, true);
+
+            setSrcByTagName("video", findMediaFile("video", "../../media/content/counting"));
+        }
+        
+        window.addEventListener('load', setupTest, false);
+    </script>
+</head>
+<body>
+    <video></video>
+
+    <div class="obscurer" style="left: 5px; top: 5px;"></div>
+    <div class="obscurer" style="left: 5px; top: 410px;"></div>
+    <div class="obscurer" style="left: 500px; top: 5px;"></div>
+
+    <div class="obscurer" style="left: 55px; top: 55px;"></div>
+    <div class="obscurer" style="left: 55px; top: 460px;"></div>
+    <div class="obscurer" style="left: 550px; top: 55px;"></div>
+    <div class="obscurer" style="left: 550px; top: 460px;"></div>
+</body>
+</html>
diff --git a/LayoutTests/compositing/video/video-border-radius-clipping.html b/LayoutTests/compositing/video/video-border-radius-clipping.html
new file mode 100644
index 0000000..e691d57
--- /dev/null
+++ b/LayoutTests/compositing/video/video-border-radius-clipping.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        video {
+            border-radius: 5px;
+            box-shadow: -50px -50px 0px black;
+            background-color: red;   
+            margin: 50px;
+            width: 500.25px;
+        }
+        
+        .obscurer {
+            position: absolute;
+            width: 10px;
+            height: 10px;
+            background-color: gray;
+        }
+    </style>
+    <script src="../../media/media-file.js"></script>
+    <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+
+        function setupTest()
+        {
+            var totalCount = document.getElementsByTagName('video').length;
+            var count = totalCount;
+            document.addEventListener("canplaythrough", function () {
+                if (!--count && window.testRunner) {
+                    setTimeout(function() {
+                        testRunner.notifyDone();
+                    }, totalCount * 150);
+                }
+            }, true);
+
+            document.addEventListener("error", function (event) {
+                console.log("Video " + event.target.getAttribute("name") + " failed to load, error " + event.target.error.code);
+                if (window.testRunner)
+                    testRunner.notifyDone();
+            }, true);
+
+            setSrcByTagName("video", findMediaFile("video", "../../media/content/counting"));
+        }
+        
+        window.addEventListener('load', setupTest, false);
+    </script>
+</head>
+<body>
+    <video></video>
+
+    <div class="obscurer" style="left: 5px; top: 5px;"></div>
+    <div class="obscurer" style="left: 5px; top: 410px;"></div>
+    <div class="obscurer" style="left: 500px; top: 5px;"></div>
+
+    <div class="obscurer" style="left: 55px; top: 55px;"></div>
+    <div class="obscurer" style="left: 55px; top: 460px;"></div>
+    <div class="obscurer" style="left: 550px; top: 55px;"></div>
+    <div class="obscurer" style="left: 550px; top: 460px;"></div>
+</body>
+</html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 4f99b2d..9b8cf2f 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -102,6 +102,35 @@
 
 2019-10-21  Simon Fraser  <simon.fraser@apple.com>
 
+        Setting border-radius on <video> element clips top and left sections of video
+        https://bugs.webkit.org/show_bug.cgi?id=202049
+        <rdar://problem/55570024>
+
+        Reviewed by Dean Jackson.
+
+        updateClippingStrategy() is called both when we're setting a mask layer on m_layer,
+        and on m_contentsClippingLayer, and they disagreed on the coordinate space that
+        the rounded rect was in. r246845 fixed rounded-rect scrollers, but broke video.
+
+        Fix by declaring that the rounded rect is relative to the bounds of the layer
+        argument. We don't try to size the shape layer to the rounded rect, because in some
+        configurations (e.g. scroller with left scrollbar) the rounded rect hangs outside
+        the clipping layer.
+
+        Test: compositing/video/video-border-radius-clipping.html
+
+        * platform/graphics/FloatRoundedRect.h:
+        (WebCore::FloatRoundedRect::setLocation):
+        * platform/graphics/ca/GraphicsLayerCA.cpp:
+        (WebCore::GraphicsLayerCA::updateClippingStrategy):
+        (WebCore::GraphicsLayerCA::updateContentsRects): The rounded rect is relative to the contentsClippingLayer,
+        so set its location to zero.
+        * rendering/RenderLayerBacking.cpp:
+        (WebCore::RenderLayerBacking::updateChildClippingStrategy): negate the offset, as we do in updateGeometry which
+        has similar code.
+
+2019-10-21  Simon Fraser  <simon.fraser@apple.com>
+
         [iOS WK2] Support hiding iframe scrollbars via ::-webkit-scrollbar style
         https://bugs.webkit.org/show_bug.cgi?id=203178
 
diff --git a/Source/WebCore/platform/graphics/FloatRoundedRect.h b/Source/WebCore/platform/graphics/FloatRoundedRect.h
index a35b393..8efee57 100644
--- a/Source/WebCore/platform/graphics/FloatRoundedRect.h
+++ b/Source/WebCore/platform/graphics/FloatRoundedRect.h
@@ -103,6 +103,7 @@
     bool isEmpty() const { return m_rect.isEmpty(); }
 
     void setRect(const FloatRect& rect) { m_rect = rect; }
+    void setLocation(FloatPoint location) { m_rect.setLocation(location); }
     void setRadii(const Radii& radii) { m_radii = radii; }
 
     void move(const FloatSize& size) { m_rect.move(size); }
diff --git a/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp b/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp
index 9a4918c..8d8c203 100644
--- a/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp
+++ b/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp
@@ -2589,9 +2589,10 @@
 }
 
 // The clipping strategy depends on whether the rounded rect has equal corner radii.
+// roundedRect is in the coordinate space of clippingLayer.
 void GraphicsLayerCA::updateClippingStrategy(PlatformCALayer& clippingLayer, RefPtr<PlatformCALayer>& shapeMaskLayer, const FloatRoundedRect& roundedRect)
 {
-    if (roundedRect.radii().isUniformCornerRadius()) {
+    if (roundedRect.radii().isUniformCornerRadius() && clippingLayer.bounds() == roundedRect.rect()) {
         clippingLayer.setMask(nullptr);
         if (shapeMaskLayer) {
             shapeMaskLayer->setOwner(nullptr);
@@ -2609,11 +2610,8 @@
         shapeMaskLayer->setName("shape mask");
     }
     
-    shapeMaskLayer->setPosition(roundedRect.rect().location());
-    FloatRect shapeBounds({ }, roundedRect.rect().size());
-    shapeMaskLayer->setBounds(shapeBounds);
-    FloatRoundedRect offsetRoundedRect(shapeBounds, roundedRect.radii());
-    shapeMaskLayer->setShapeRoundedRect(offsetRoundedRect);
+    shapeMaskLayer->setBounds(clippingLayer.bounds());
+    shapeMaskLayer->setShapeRoundedRect(roundedRect);
 
     clippingLayer.setCornerRadius(0);
     clippingLayer.setMask(shapeMaskLayer.get());
@@ -2628,13 +2626,13 @@
     const FloatRect contentBounds(0, 0, m_contentsRect.width(), m_contentsRect.height());
 
     FloatPoint clippingOrigin(m_contentsClippingRect.rect().location());
-    FloatRect clippingBounds(FloatPoint(), m_contentsClippingRect.rect().size());
+    FloatRect clippingBounds({ }, m_contentsClippingRect.rect().size());
     
     bool gainedOrLostClippingLayer = false;
     if (m_contentsClippingRect.isRounded() || !m_contentsClippingRect.rect().contains(m_contentsRect)) {
         if (!m_contentsClippingLayer) {
             m_contentsClippingLayer = createPlatformCALayer(PlatformCALayer::LayerTypeLayer, this);
-            m_contentsClippingLayer->setAnchorPoint(FloatPoint());
+            m_contentsClippingLayer->setAnchorPoint({ });
 #if ENABLE(TREE_DEBUGGING)
             m_contentsClippingLayer->setName(makeString("contents clipping ", m_contentsClippingLayer->layerID()));
 #else
@@ -2645,8 +2643,11 @@
 
         m_contentsClippingLayer->setPosition(clippingOrigin);
         m_contentsClippingLayer->setBounds(clippingBounds);
+        
+        auto clippingRectRelativeToClippingLayer = m_contentsClippingRect;
+        clippingRectRelativeToClippingLayer.setLocation({ });
 
-        updateClippingStrategy(*m_contentsClippingLayer, m_contentsShapeMaskLayer, m_contentsClippingRect);
+        updateClippingStrategy(*m_contentsClippingLayer, m_contentsShapeMaskLayer, clippingRectRelativeToClippingLayer);
 
         if (gainedOrLostClippingLayer) {
             m_contentsLayer->removeFromSuperlayer();
diff --git a/Source/WebCore/rendering/RenderLayerBacking.cpp b/Source/WebCore/rendering/RenderLayerBacking.cpp
index 214f609..7d9978f 100644
--- a/Source/WebCore/rendering/RenderLayerBacking.cpp
+++ b/Source/WebCore/rendering/RenderLayerBacking.cpp
@@ -1931,7 +1931,7 @@
             auto* clipLayer = clippingLayer();
             LayoutRect boxRect(LayoutPoint(), downcast<RenderBox>(renderer()).size());
             FloatRoundedRect contentsClippingRect = renderer().style().getRoundedInnerBorderFor(boxRect).pixelSnappedRoundedRectForPainting(deviceScaleFactor());
-            contentsClippingRect.move(LayoutSize(clipLayer->offsetFromRenderer()));
+            contentsClippingRect.move(LayoutSize(-clipLayer->offsetFromRenderer()));
             // Note that we have to set this rounded rect again during the geometry update (clipLayer->offsetFromRenderer() may be stale here).
             if (clipLayer->setMasksToBoundsRect(contentsClippingRect)) {
                 clipLayer->setMaskLayer(nullptr);