Blur filter escapes an enclosing overflow:hidden
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 30 Apr 2016 04:13:16 +0000 (04:13 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 30 Apr 2016 04:13:16 +0000 (04:13 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155029

Reviewed by Zalan Bujtas.

Source/WebCore:

The clipping that was applied when drawing the results of filters was wrong for two reasons.

First, it used localPaintingInfo which has already been contaminated when setting up the filters.
When painting the result, we need to use the original paintingInfo, to get the right paintDirtyRect.

Secondly, when setting up the clip to paint the filter result, it was relying on layerFragments[0].backgroundRect.
However, that was also contaminated by filter setup, since calculateRects() intersects with paintDirtyRect to
compute that backgroundRect, and that paintDirtyRect came from filterPainter->repaintRect().

Fix this second issue by re-running collectFragments(), which computes a fragment backgroundRect using
the original paintDirtyRect.

Tests: css3/filters/blur-clipped-by-ancestor.html
       css3/filters/blur-clipped-with-overflow.html
       css3/filters/drop-shadow-with-overflow-hidden.html
       css3/filters/drop-shadow.html

* platform/graphics/filters/FilterEffect.cpp:
(WebCore::FilterEffect::clearResult): Unconditionally null these out.
* rendering/FilterEffectRenderer.cpp:
(WebCore::FilterEffectRendererHelper::beginFilterEffect): Typo fix.
* rendering/FilterEffectRenderer.h:
(WebCore::FilterEffectRendererHelper::FilterEffectRendererHelper): C++11 initialization.
* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::applyFilters):
(WebCore::RenderLayer::paintLayerContents):
* rendering/RenderLayer.h: const

LayoutTests:

* css3/filters/blur-clipped-by-ancestor-expected.html: Added.
* css3/filters/blur-clipped-by-ancestor.html: Added.
* css3/filters/blur-clipped-with-overflow-expected.html: Added.
* css3/filters/blur-clipped-with-overflow.html: Added.
* css3/filters/drop-shadow-expected.html: Added.
* css3/filters/drop-shadow-with-overflow-hidden-expected.html: Added.
* css3/filters/drop-shadow-with-overflow-hidden.html: Added.
* css3/filters/drop-shadow.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@200283 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
LayoutTests/ChangeLog
LayoutTests/css3/filters/blur-clipped-by-ancestor-expected.html [new file with mode: 0644]
LayoutTests/css3/filters/blur-clipped-by-ancestor.html [new file with mode: 0644]
LayoutTests/css3/filters/blur-clipped-with-overflow-expected.html [new file with mode: 0644]
LayoutTests/css3/filters/blur-clipped-with-overflow.html [new file with mode: 0644]
LayoutTests/css3/filters/drop-shadow-expected.html [new file with mode: 0644]
LayoutTests/css3/filters/drop-shadow-with-overflow-hidden-expected.html [new file with mode: 0644]
LayoutTests/css3/filters/drop-shadow-with-overflow-hidden.html [new file with mode: 0644]
LayoutTests/css3/filters/drop-shadow.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/filters/FilterEffect.cpp
Source/WebCore/rendering/FilterEffectRenderer.cpp
Source/WebCore/rendering/FilterEffectRenderer.h
Source/WebCore/rendering/RenderLayer.cpp
Source/WebCore/rendering/RenderLayer.h

index 4462910..c8d848f 100644 (file)
@@ -1,3 +1,19 @@
+2016-04-29  Simon Fraser  <simon.fraser@apple.com>
+
+        Blur filter escapes an enclosing overflow:hidden
+        https://bugs.webkit.org/show_bug.cgi?id=155029
+
+        Reviewed by Zalan Bujtas.
+
+        * css3/filters/blur-clipped-by-ancestor-expected.html: Added.
+        * css3/filters/blur-clipped-by-ancestor.html: Added.
+        * css3/filters/blur-clipped-with-overflow-expected.html: Added.
+        * css3/filters/blur-clipped-with-overflow.html: Added.
+        * css3/filters/drop-shadow-expected.html: Added.
+        * css3/filters/drop-shadow-with-overflow-hidden-expected.html: Added.
+        * css3/filters/drop-shadow-with-overflow-hidden.html: Added.
+        * css3/filters/drop-shadow.html: Added.
+
 2016-04-29  Myles C. Maxfield  <mmaxfield@apple.com>
 
         REGRESSION(194502): overflow: scroll; direction: rtl; divs jump horizontally when scrolled vertically
diff --git a/LayoutTests/css3/filters/blur-clipped-by-ancestor-expected.html b/LayoutTests/css3/filters/blur-clipped-by-ancestor-expected.html
new file mode 100644 (file)
index 0000000..5215650
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .wrapper {
+            height: 200px;
+            width: 200px;
+            margin: 50px;
+            border: 1px solid black;
+        }
+
+        .filtered {
+            -webkit-clip-path: inset(0px);
+            width: 100%;
+            height: 100%;
+            background-color: black;
+            filter: blur(10px);
+        }
+    </style>
+</head>
+<body>
+    <div class="wrapper">
+        <div class="filtered">
+        </div>
+    </div>
+</body>
+</html>
diff --git a/LayoutTests/css3/filters/blur-clipped-by-ancestor.html b/LayoutTests/css3/filters/blur-clipped-by-ancestor.html
new file mode 100644 (file)
index 0000000..5236f35
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .wrapper {
+            overflow: hidden;
+            height: 200px;
+            width: 200px;
+            margin: 50px;
+            border: 1px solid black;
+        }
+
+        .filtered {
+            width: 100%;
+            height: 100%;
+            background-color: black;
+            filter: blur(10px);
+        }
+    </style>
+</head>
+<body>
+    <div class="wrapper">
+        <div class="filtered">
+        </div>
+    </div>
+</body>
+</html>
diff --git a/LayoutTests/css3/filters/blur-clipped-with-overflow-expected.html b/LayoutTests/css3/filters/blur-clipped-with-overflow-expected.html
new file mode 100644 (file)
index 0000000..7ccb163
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .filtered {
+            overflow: hidden;
+            margin: 40px;
+            padding: 20px;
+            height: 200px;
+            width: 200px;
+            background-color: silver;
+            border: 1px solid black;
+            filter: blur(1px);
+            box-shadow: 0 0 20px black;
+        }
+
+        .contents {
+            width: 100%;
+            height: 220px;
+            background-image: repeating-linear-gradient(blue, blue 50px, green 50px, green 100px);
+        }
+    </style>
+</head>
+<body>
+    <div class="filtered">
+        <div class="contents"></div>
+    </div>
+</body>
+</html>
diff --git a/LayoutTests/css3/filters/blur-clipped-with-overflow.html b/LayoutTests/css3/filters/blur-clipped-with-overflow.html
new file mode 100644 (file)
index 0000000..8762d59
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .filtered {
+            overflow: hidden;
+            margin: 40px;
+            padding: 20px;
+            height: 200px;
+            width: 200px;
+            background-color: silver;
+            border: 1px solid black;
+            filter: blur(1px);
+            box-shadow: 0 0 20px black;
+        }
+
+        .contents {
+            width: 100%;
+            height: 200%;
+            background-image: repeating-linear-gradient(blue, blue 50px, green 50px, green 100px);
+        }
+    </style>
+</head>
+<body>
+    <div class="filtered">
+        <div class="contents"></div>
+    </div>
+</body>
+</html>
diff --git a/LayoutTests/css3/filters/drop-shadow-expected.html b/LayoutTests/css3/filters/drop-shadow-expected.html
new file mode 100644 (file)
index 0000000..68f6afb
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        .filtered {
+            position: absolute;
+            top: 0;
+            left: 0;
+            margin: 40px;
+            padding: 20px;
+            height: 200px;
+            width: 200px;
+            background-color: silver;
+            border: 1px solid black;
+            box-shadow: 0 0 20px transparent; /* Make layers bigger */
+        }
+        
+        .shadow {
+            position: absolute;
+            top: 110px;
+            left: 110px;
+            border: 1px solid blue;
+            background-color: blue;
+        }
+    </style>
+</head>
+<body>
+    <div class="filtered shadow"></div>
+    <div class="filtered"></div>
+</body>
+</html>
diff --git a/LayoutTests/css3/filters/drop-shadow-with-overflow-hidden-expected.html b/LayoutTests/css3/filters/drop-shadow-with-overflow-hidden-expected.html
new file mode 100644 (file)
index 0000000..ea4368e
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .filtered {
+            overflow: hidden;
+            margin: 40px;
+            padding: 20px;
+            height: 200px;
+            width: 200px;
+            background-color: silver;
+            border: 1px solid black;
+            filter: drop-shadow(110px 110px 0 blue);
+            box-shadow: 0 0 20px black;
+        }
+    </style>
+</head>
+<body>
+    <div class="filtered"></div>
+    <!-- Fixme -->
+</body>
+</html>
diff --git a/LayoutTests/css3/filters/drop-shadow-with-overflow-hidden.html b/LayoutTests/css3/filters/drop-shadow-with-overflow-hidden.html
new file mode 100644 (file)
index 0000000..01b3592
--- /dev/null
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .filtered {
+            overflow: hidden;
+            margin: 40px;
+            padding: 20px;
+            height: 200px;
+            width: 200px;
+            background-color: silver;
+            border: 1px solid black;
+            filter: drop-shadow(110px 110px 0 blue);
+            box-shadow: 0 0 20px black;
+        }
+    </style>
+</head>
+<body>
+    <div class="filtered"></div>
+</body>
+</html>
diff --git a/LayoutTests/css3/filters/drop-shadow.html b/LayoutTests/css3/filters/drop-shadow.html
new file mode 100644 (file)
index 0000000..e30df10
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        .filtered {
+            position: absolute;
+            top: 0;
+            left: 0;
+            margin: 40px;
+            padding: 20px;
+            height: 200px;
+            width: 200px;
+            background-color: silver;
+            border: 1px solid black;
+            filter: drop-shadow(110px 110px 0 blue);
+            box-shadow: 0 0 20px transparent; /* Make layers bigger */
+        }
+    </style>
+</head>
+<body>
+    <div class="filtered"></div>
+</body>
+</html>
index 15e51e8..0efd96f 100644 (file)
@@ -1,3 +1,38 @@
+2016-04-29  Simon Fraser  <simon.fraser@apple.com>
+
+        Blur filter escapes an enclosing overflow:hidden
+        https://bugs.webkit.org/show_bug.cgi?id=155029
+
+        Reviewed by Zalan Bujtas.
+
+        The clipping that was applied when drawing the results of filters was wrong for two reasons.
+
+        First, it used localPaintingInfo which has already been contaminated when setting up the filters.
+        When painting the result, we need to use the original paintingInfo, to get the right paintDirtyRect.
+
+        Secondly, when setting up the clip to paint the filter result, it was relying on layerFragments[0].backgroundRect.
+        However, that was also contaminated by filter setup, since calculateRects() intersects with paintDirtyRect to
+        compute that backgroundRect, and that paintDirtyRect came from filterPainter->repaintRect().
+        
+        Fix this second issue by re-running collectFragments(), which computes a fragment backgroundRect using
+        the original paintDirtyRect.
+
+        Tests: css3/filters/blur-clipped-by-ancestor.html
+               css3/filters/blur-clipped-with-overflow.html
+               css3/filters/drop-shadow-with-overflow-hidden.html
+               css3/filters/drop-shadow.html
+
+        * platform/graphics/filters/FilterEffect.cpp:
+        (WebCore::FilterEffect::clearResult): Unconditionally null these out.
+        * rendering/FilterEffectRenderer.cpp:
+        (WebCore::FilterEffectRendererHelper::beginFilterEffect): Typo fix.
+        * rendering/FilterEffectRenderer.h:
+        (WebCore::FilterEffectRendererHelper::FilterEffectRendererHelper): C++11 initialization.
+        * rendering/RenderLayer.cpp:
+        (WebCore::RenderLayer::applyFilters):
+        (WebCore::RenderLayer::paintLayerContents):
+        * rendering/RenderLayer.h: const
+
 2016-04-29  Myles C. Maxfield  <mmaxfield@apple.com>
 
         REGRESSION(194502): overflow: scroll; direction: rtl; divs jump horizontally when scrolled vertically
index 1dcb0cb..cbe5128 100644 (file)
@@ -197,10 +197,9 @@ void FilterEffect::clearResult()
 {
     if (m_imageBufferResult)
         m_imageBufferResult.reset();
-    if (m_unmultipliedImageResult)
-        m_unmultipliedImageResult = nullptr;
-    if (m_premultipliedImageResult)
-        m_premultipliedImageResult = nullptr;
+
+    m_unmultipliedImageResult = nullptr;
+    m_premultipliedImageResult = nullptr;
 }
 
 void FilterEffect::clearResultsRecursive()
index f4fe9e2..f47b3ba 100644 (file)
@@ -67,8 +67,6 @@ static inline void lastMatrixRow(Vector<float>& parameters)
 
 FilterEffectRenderer::FilterEffectRenderer()
     : Filter(AffineTransform())
-    , m_graphicsBufferAttached(false)
-    , m_hasFilterThatMovesPixels(false)
 {
     setFilterResolution(FloatSize(1, 1));
     m_sourceGraphic = SourceGraphic::create(*this);
@@ -407,7 +405,7 @@ bool FilterEffectRendererHelper::beginFilterEffect()
         return false;
     }
     
-    // Translate the context so that the contents of the layer is captuterd in the offscreen memory buffer.
+    // Translate the context so that the contents of the layer is captured in the offscreen memory buffer.
     sourceGraphicsContext->save();
     sourceGraphicsContext->translate(-m_paintOffset.x(), -m_paintOffset.y());
     sourceGraphicsContext->clearRect(m_repaintRect);
index 0f9b9ad..c731adb 100644 (file)
@@ -58,9 +58,7 @@ class FilterEffectRendererHelper {
     WTF_MAKE_FAST_ALLOCATED;
 public:
     FilterEffectRendererHelper(bool haveFilterEffect)
-        : m_renderLayer(0)
-        , m_haveFilterEffect(haveFilterEffect)
-        , m_startedFilterEffect(false)
+        : m_haveFilterEffect(haveFilterEffect)
     {
     }
     
@@ -76,11 +74,11 @@ public:
     const LayoutRect& repaintRect() const { return m_repaintRect; }
 
 private:
-    RenderLayer* m_renderLayer; // FIXME: this is mainly used to get the FilterEffectRenderer. FilterEffectRendererHelper should be weaned off it.
+    RenderLayer* m_renderLayer { nullptr }; // FIXME: this is mainly used to get the FilterEffectRenderer. FilterEffectRendererHelper should be weaned off it.
     LayoutPoint m_paintOffset;
     LayoutRect m_repaintRect;
-    bool m_haveFilterEffect;
-    bool m_startedFilterEffect;
+    bool m_haveFilterEffect { false };
+    bool m_startedFilterEffect { false };
 };
 
 class FilterEffectRenderer final : public Filter {
@@ -145,8 +143,8 @@ private:
     
     IntRectExtent m_outsets;
 
-    bool m_graphicsBufferAttached;
-    bool m_hasFilterThatMovesPixels;
+    bool m_graphicsBufferAttached { false };
+    bool m_hasFilterThatMovesPixels { false };
 };
 
 } // namespace WebCore
index 676702a..504c85c 100644 (file)
@@ -4196,11 +4196,11 @@ std::unique_ptr<FilterEffectRendererHelper> RenderLayer::setupFilters(GraphicsCo
     return nullptr;
 }
 
-void RenderLayer::applyFilters(FilterEffectRendererHelper* filterPainter, GraphicsContext& originalContext, LayerPaintingInfo& paintingInfo, LayerFragments& layerFragments)
+void RenderLayer::applyFilters(FilterEffectRendererHelper* filterPainter, GraphicsContext& originalContext, const LayerPaintingInfo& paintingInfo, const LayerFragments& layerFragments)
 {
     ASSERT(filterPainter->hasStartedFilterEffect());
-    // Apply the correct clipping (ie. overflow: hidden).
-    // FIXME: It is incorrect to just clip to the damageRect here once multiple fragments are involved.
+
+    // FIXME: Handle more than one fragment.
     ClipRect backgroundRect = layerFragments.isEmpty() ? ClipRect() : layerFragments[0].backgroundRect;
     clipToRect(paintingInfo, originalContext, backgroundRect);
     filterPainter->applyFilterEffect(originalContext);
@@ -4291,14 +4291,13 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
     if (shouldApplyClipPath(paintingInfo.paintBehavior, localPaintFlags))
         hasClipPath = setupClipPath(context, paintingInfo, columnAwareOffsetFromRoot, rootRelativeBounds, rootRelativeBoundsComputed);
 
-    LayerPaintingInfo localPaintingInfo(paintingInfo);
-
-    bool selectionAndBackgroundsOnly = localPaintingInfo.paintBehavior & PaintBehaviorSelectionAndBackgroundsOnly;
-    bool selectionOnly = localPaintingInfo.paintBehavior & PaintBehaviorSelectionOnly;
+    bool selectionAndBackgroundsOnly = paintingInfo.paintBehavior & PaintBehaviorSelectionAndBackgroundsOnly;
+    bool selectionOnly = paintingInfo.paintBehavior & PaintBehaviorSelectionOnly;
     LayerFragments layerFragments;
     RenderObject* subtreePaintRootForRenderer = nullptr;
 
-    { // Scope for currentContext.
+    { // Scope for filter-related state changes.
+        LayerPaintingInfo localPaintingInfo(paintingInfo);
         std::unique_ptr<FilterEffectRendererHelper> filterPainter = setupFilters(context, localPaintingInfo, paintFlags, columnAwareOffsetFromRoot, rootRelativeBounds, rootRelativeBoundsComputed);
 
         GraphicsContext* filterContext = filterPainter ? filterPainter->filterContext() : nullptr;
@@ -4329,7 +4328,7 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
             // Collect the fragments. This will compute the clip rectangles and paint offsets for each layer fragment, as well as whether or not the content of each
             // fragment should paint. If the parent's filter dictates full repaint to ensure proper filter effect,
             // use the overflow clip as dirty rect, instead of no clipping. It maintains proper clipping for overflow::scroll.
-            if (!paintingInfo.clipToDirtyRect && renderer().hasOverflowClip()) {
+            if (!localPaintingInfo.clipToDirtyRect && renderer().hasOverflowClip()) {
                 // We can turn clipping back by requesting full repaint for the overflow area.
                 localPaintingInfo.clipToDirtyRect = true;
                 paintDirtyRect = selfClipRect();
@@ -4381,7 +4380,16 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
             paintOverflowControlsForFragments(layerFragments, currentContext, localPaintingInfo);
 
         if (filterContext) {
-            applyFilters(filterPainter.get(), context, localPaintingInfo, layerFragments);
+            // When we called collectFragments() last time, paintDirtyRect was reset to represent the filter bounds.
+            // Now we need to compute the backgroundRect uncontaminated by filters, in order to clip the filtered result.
+            // Note that we also use paintingInfo here, not localPaintingInfo which filters also contaminated.
+            LayerFragments layerFragments;
+            collectFragments(layerFragments, paintingInfo.rootLayer, paintingInfo.paintDirtyRect, ExcludeCompositedPaginatedLayers,
+                (localPaintFlags & PaintLayerTemporaryClipRects) ? TemporaryClipRects : PaintingClipRects, IgnoreOverlayScrollbarSize,
+                (isPaintingOverflowContents) ? IgnoreOverflowClip : RespectOverflowClip, offsetFromRoot);
+            updatePaintingInfoForFragments(layerFragments, paintingInfo, localPaintFlags, shouldPaintContent, offsetFromRoot);
+
+            applyFilters(filterPainter.get(), context, paintingInfo, layerFragments);
             filterPainter = nullptr;
         }
     }
@@ -4389,17 +4397,17 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
     if (shouldPaintContent && !(selectionOnly || selectionAndBackgroundsOnly)) {
         if (shouldPaintMask(paintingInfo.paintBehavior, localPaintFlags)) {
             // Paint the mask for the fragments.
-            paintMaskForFragments(layerFragments, context, localPaintingInfo, subtreePaintRootForRenderer);
+            paintMaskForFragments(layerFragments, context, paintingInfo, subtreePaintRootForRenderer);
         }
 
         if (!(paintFlags & PaintLayerPaintingCompositingMaskPhase) && (paintFlags & PaintLayerPaintingCompositingClipPathPhase)) {
             // Re-use paintChildClippingMaskForFragments to paint black for the compositing clipping mask.
-            paintChildClippingMaskForFragments(layerFragments, context, localPaintingInfo, subtreePaintRootForRenderer);
+            paintChildClippingMaskForFragments(layerFragments, context, paintingInfo, subtreePaintRootForRenderer);
         }
         
         if ((localPaintFlags & PaintLayerPaintingChildClippingMaskPhase)) {
             // Paint the border radius mask for the fragments.
-            paintChildClippingMaskForFragments(layerFragments, context, localPaintingInfo, subtreePaintRootForRenderer);
+            paintChildClippingMaskForFragments(layerFragments, context, paintingInfo, subtreePaintRootForRenderer);
         }
     }
 
index bd054d3..2c8de23 100644 (file)
@@ -786,7 +786,7 @@ private:
 
     bool hasFilterThatIsPainting(GraphicsContext&, PaintLayerFlags) const;
     std::unique_ptr<FilterEffectRendererHelper> setupFilters(GraphicsContext&, LayerPaintingInfo&, PaintLayerFlags, const LayoutSize& offsetFromRoot, LayoutRect& rootRelativeBounds, bool& rootRelativeBoundsComputed);
-    void applyFilters(FilterEffectRendererHelper*, GraphicsContext& originalContext, LayerPaintingInfo&, LayerFragments&);
+    void applyFilters(FilterEffectRendererHelper*, GraphicsContext& originalContext, const LayerPaintingInfo&, const LayerFragments&);
 
     void paintLayer(GraphicsContext&, const LayerPaintingInfo&, PaintLayerFlags);
     void paintFixedLayersInNamedFlows(GraphicsContext&, const LayerPaintingInfo&, PaintLayerFlags);