Test if non-immediate descendants obscure background
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Mar 2013 01:00:57 +0000 (01:00 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Mar 2013 01:00:57 +0000 (01:00 +0000)
https://bugs.webkit.org/show_bug.cgi?id=113137

Reviewed by Simon Fraser.

Source/WebCore:

The current obscuration test only covers immediate children. We can find more cases by looking deeper into descendants.

The patch makes the test sufficiently smart to stop a heavy fully obscured gif animation on micrsoft.com.

* loader/cache/CachedImage.cpp:
(WebCore::CachedImage::animationAdvanced):
* rendering/RenderBox.cpp:
(WebCore::RenderBox::styleDidChange):

    Invalidate parents to max test depth.

(WebCore::RenderBox::backgroundPaintedExtent):

    Background painting is pixel snapped.

(WebCore::isCandidateForOpaquenessTest):
(WebCore::RenderBox::foregroundIsKnownToBeOpaqueInRect):

    Separate foreground testing and make it recursive.
    Add fast bailout for common static positioned case.
    Remove maximum child count, the fast bailouts should prevent long tests.
    Add maximum depth so we know how deep we need to invalidate in styleDidChange.

(WebCore::RenderBox::computeBackgroundIsKnownToBeObscured):
(WebCore):
* rendering/RenderBox.h:
(RenderBox):
* rendering/RenderImage.cpp:
(WebCore::RenderImage::foregroundIsKnownToBeOpaqueInRect):
(WebCore):
(WebCore::RenderImage::computeBackgroundIsKnownToBeObscured):

* rendering/RenderImage.h:
(RenderImage):

LayoutTests:

* fast/backgrounds/obscured-background-child-style-change-expected.html:
* fast/backgrounds/obscured-background-child-style-change.html:
* fast/repaint/obscured-background-no-repaint-expected.txt:
* fast/repaint/obscured-background-no-repaint.html:

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

LayoutTests/ChangeLog
LayoutTests/fast/backgrounds/obscured-background-child-style-change-expected.html
LayoutTests/fast/backgrounds/obscured-background-child-style-change.html
LayoutTests/fast/repaint/obscured-background-no-repaint-expected.txt
LayoutTests/fast/repaint/obscured-background-no-repaint.html
Source/WebCore/ChangeLog
Source/WebCore/rendering/RenderBox.cpp
Source/WebCore/rendering/RenderBox.h
Source/WebCore/rendering/RenderImage.cpp
Source/WebCore/rendering/RenderImage.h

index 70db58e..543676a 100644 (file)
@@ -1,3 +1,15 @@
+2013-03-26  Antti Koivisto  <antti@apple.com>
+
+        Test if non-immediate descendants obscure background
+        https://bugs.webkit.org/show_bug.cgi?id=113137
+
+        Reviewed by Simon Fraser.
+
+        * fast/backgrounds/obscured-background-child-style-change-expected.html:
+        * fast/backgrounds/obscured-background-child-style-change.html:
+        * fast/repaint/obscured-background-no-repaint-expected.txt:
+        * fast/repaint/obscured-background-no-repaint.html:
+
 2013-03-26  Chris Fleizach  <cfleizach@apple.com>
 
         WebKit does not expose @required or @aria-required as AXRequired on select elements
index d8e249f..b60a7c1 100644 (file)
@@ -7,7 +7,7 @@
     }
     .child {
         position: relative;
-        background-color: rgba(0,255,0,0.2);
+        background-color: rgba(0,255,0,0.8);
         width: 100px;
         height: 100px;
     }
     <div class=child>
     </div>
 </div>
+<div class=parent>
+    <div>
+        <div class=child>
+        </div>
+    </div>
+</div>
+<script>
+document.body.offsetTop;
+if (window.testRunner)
+    testRunner.display();
+var children = document.getElementsByClassName("child");
+children[0].style.backgroundColor = "rgba(0,255,0,0.5)";
+children[1].style.backgroundColor = "rgba(0,255,0,0.5)";
+</script>
index 2a42dd1..25a1999 100644 (file)
@@ -6,17 +6,26 @@
     height: 100px;
 }
 .child {
-    position: relative;
     background-color: red;
     width: 100px;
     height: 100px;
 }
 </style>
 <div class=parent>
-<div class=child>
+    <div class=child>
+    </div>
 </div>
+<div class=parent>
+    <div>
+        <div class=child>
+        </div>
+    </div>
 </div>
 <script>
 document.body.offsetTop;
-document.getElementsByClassName("child")[0].style.backgroundColor = "rgba(0,255,0,0.2)";
+if (window.testRunner)
+    testRunner.display();
+var children = document.getElementsByClassName("child");
+children[0].style.backgroundColor = "rgba(0,255,0,0.5)";
+children[1].style.backgroundColor = "rgba(0,255,0,0.5)";
 </script>
index db88f23..32a03b1 100644 (file)
     #test3 img {
         background-image: url(resources/animated.gif)
     }
+    #test4 .parent {
+        position: relative;
+        height: 100px;
+        width: 100px;
+        background-color: red;
+        background-repeat: no-repeat;
+        background-position: center;
+        background-image: url(resources/animated.gif)
+    }
 </style>
 <script>
     description("Test that obscured animated gif does not trigger repaints. This test requires DRT.");
 <div id="test3">
     <img src="resources/apple.jpg">
 </div>
+<div id="test4">
+    <div class="parent">
+        <a>
+            <div></div>
+            <div>
+                <img src="resources/apple.jpg">
+            </div>
+        </a>
+    </div>
+</div>
 </body>
 <script src="../js/resources/js-test-post.js"></script>
 </html>
index d397c4e..a66a7fa 100644 (file)
@@ -1,3 +1,45 @@
+2013-03-26  Antti Koivisto  <antti@apple.com>
+
+        Test if non-immediate descendants obscure background
+        https://bugs.webkit.org/show_bug.cgi?id=113137
+
+        Reviewed by Simon Fraser.
+
+        The current obscuration test only covers immediate children. We can find more cases by looking deeper into descendants.
+        
+        The patch makes the test sufficiently smart to stop a heavy fully obscured gif animation on micrsoft.com.
+
+        * loader/cache/CachedImage.cpp:
+        (WebCore::CachedImage::animationAdvanced):
+        * rendering/RenderBox.cpp:
+        (WebCore::RenderBox::styleDidChange):
+        
+            Invalidate parents to max test depth.
+
+        (WebCore::RenderBox::backgroundPaintedExtent):
+        
+            Background painting is pixel snapped.
+
+        (WebCore::isCandidateForOpaquenessTest):
+        (WebCore::RenderBox::foregroundIsKnownToBeOpaqueInRect):
+        
+            Separate foreground testing and make it recursive.
+            Add fast bailout for common static positioned case.
+            Remove maximum child count, the fast bailouts should prevent long tests.
+            Add maximum depth so we know how deep we need to invalidate in styleDidChange.
+
+        (WebCore::RenderBox::computeBackgroundIsKnownToBeObscured):
+        (WebCore):
+        * rendering/RenderBox.h:
+        (RenderBox):
+        * rendering/RenderImage.cpp:
+        (WebCore::RenderImage::foregroundIsKnownToBeOpaqueInRect):
+        (WebCore):
+        (WebCore::RenderImage::computeBackgroundIsKnownToBeObscured):
+        
+        * rendering/RenderImage.h:
+        (RenderImage):
+
 2013-03-26  Benjamin Poulain  <bpoulain@apple.com>
 
         Regression (r145601): out-of-bounds read in line breaking / new width cache
index b8bc7c4..8fd3f2d 100644 (file)
@@ -89,6 +89,7 @@ static OverrideSizeMap* gOverrideContainingBlockLogicalWidthMap = 0;
 // Size of border belt for autoscroll. When mouse pointer in border belt,
 // autoscroll is started.
 static const int autoscrollBeltSize = 20;
+static const unsigned backgroundObscurationTestMaxDepth = 4;
 
 bool RenderBox::s_hadOverflowClip = false;
 
@@ -283,8 +284,13 @@ void RenderBox::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle
     }
 
     // Our opaqueness might have changed without triggering layout.
-    if (parent() && (diff == StyleDifferenceRepaint || diff == StyleDifferenceRepaintLayer))
-        parent()->invalidateBackgroundObscurationStatus();
+    if (diff == StyleDifferenceRepaint || diff == StyleDifferenceRepaintLayer) {
+        RenderObject* parentToInvalidate = parent();
+        for (unsigned i = 0; i < backgroundObscurationTestMaxDepth && parentToInvalidate; ++i) {
+            parentToInvalidate->invalidateBackgroundObscurationStatus();
+            parentToInvalidate = parentToInvalidate->parent();
+        }
+    }
 
     bool isBodyRenderer = isBody();
     bool isRootRenderer = isRoot();
@@ -1150,7 +1156,7 @@ void RenderBox::paintBackground(const PaintInfo& paintInfo, const LayoutRect& pa
 LayoutRect RenderBox::backgroundPaintedExtent() const
 {
     ASSERT(hasBackground());
-    LayoutRect backgroundRect = borderBoxRect();
+    LayoutRect backgroundRect = pixelSnappedIntRect(borderBoxRect());
 
     Color backgroundColor = style()->visitedDependentColor(CSSPropertyBackgroundColor);
     if (backgroundColor.isValid() && backgroundColor.alpha())
@@ -1196,52 +1202,74 @@ bool RenderBox::backgroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect) c
     return backgroundRect.contains(localRect);
 }
 
-bool RenderBox::computeBackgroundIsKnownToBeObscured()
+static bool isCandidateForOpaquenessTest(RenderBox* childBox)
 {
-    // Test to see if the children trivially obscure the background.
-    // FIXME: This test can be much more comprehensive.
-    if (!hasBackground())
+    RenderStyle* childStyle = childBox->style();
+    if (childStyle->position() != StaticPosition && childBox->containingBlock() != childBox->parent())
         return false;
-    // Table and root background painting is special.
-    if (isTable() || isRoot())
+    if (childStyle->visibility() != VISIBLE || childStyle->shapeOutside())
         return false;
+    if (!childBox->width() || !childBox->height())
+        return false;
+    if (RenderLayer* childLayer = childBox->layer()) {
+#if USE(ACCELERATED_COMPOSITING)
+        if (childLayer->isComposited())
+            return false;
+#endif
+        // FIXME: Deal with z-index.
+        if (!childStyle->hasAutoZIndex())
+            return false;
+        if (childLayer->hasTransform() || childLayer->isTransparent() || childLayer->hasFilter())
+            return false;
+    }
+    return true;
+}
 
-    LayoutRect backgroundRect = backgroundPaintedExtent();
-    // If we don't find a covering child fast there probably isn't one.
-    static const unsigned maximumChildrenCountToTest = 4;
-    unsigned count = 0;
+bool RenderBox::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const
+{
+    if (!maxDepthToTest)
+        return false;
     for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
-        if (++count > maximumChildrenCountToTest)
-            break;
         if (!child->isBox())
             continue;
         RenderBox* childBox = toRenderBox(child);
-        RenderStyle* childStyle = child->style();
-        if (childStyle->visibility() != VISIBLE || childStyle->shapeOutside())
-            continue;
-        if (childStyle->position() != StaticPosition && childBox->containingBlock() != this)
+        if (!isCandidateForOpaquenessTest(childBox))
             continue;
         LayoutPoint childLocation = childBox->location();
         if (childBox->isRelPositioned())
             childLocation.move(childBox->relativePositionOffset());
-        LayoutRect childLocalBackgroundRect = backgroundRect;
-        childLocalBackgroundRect.moveBy(-childLocation);
-        if (RenderLayer* childLayer = childBox->layer()) {
-#if USE(ACCELERATED_COMPOSITING)
-            if (childLayer->isComposited())
-                continue;
-#endif
-            if (childLayer->zIndex() < 0)
-                continue;
-            if (childLayer->hasTransform() || childLayer->isTransparent())
-                continue;
+        LayoutRect childLocalRect = localRect;
+        childLocalRect.moveBy(-childLocation);
+        if (childLocalRect.y() < 0 || childLocalRect.x() < 0) {
+            // If there is unobscured area above/left of a static positioned box then the rect is probably not covered.
+            if (childBox->style()->position() == StaticPosition)
+                return false;
+            continue;
         }
-        if (childBox->backgroundIsKnownToBeOpaqueInRect(childLocalBackgroundRect))
+        if (childLocalRect.maxY() > childBox->height() || childLocalRect.maxX() > childBox->width())
+            continue;
+        if (childBox->backgroundIsKnownToBeOpaqueInRect(childLocalRect))
+            return true;
+        if (childBox->foregroundIsKnownToBeOpaqueInRect(childLocalRect, maxDepthToTest - 1))
             return true;
     }
     return false;
 }
 
+bool RenderBox::computeBackgroundIsKnownToBeObscured()
+{
+    // Test to see if the children trivially obscure the background.
+    // FIXME: This test can be much more comprehensive.
+    if (!hasBackground())
+        return false;
+    // Table and root background painting is special.
+    if (isTable() || isRoot())
+        return false;
+
+    LayoutRect backgroundRect = backgroundPaintedExtent();
+    return foregroundIsKnownToBeOpaqueInRect(backgroundRect, backgroundObscurationTestMaxDepth);
+}
+
 bool RenderBox::backgroundHasOpaqueTopLayer() const
 {
     const FillLayer* fillLayer = style()->backgroundLayers();
index f1c19e8..d44c69e 100644 (file)
@@ -594,7 +594,9 @@ protected:
     virtual void updateFromStyle() OVERRIDE;
 
     LayoutRect backgroundPaintedExtent() const;
+    virtual bool foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const;
     virtual bool computeBackgroundIsKnownToBeObscured() OVERRIDE;
+
     void paintBackground(const PaintInfo&, const LayoutRect&, BackgroundBleedAvoidance = BackgroundBleedNone);
     
     void paintFillLayer(const PaintInfo&, const Color&, const FillLayer*, const LayoutRect&, BackgroundBleedAvoidance, CompositeOperator, RenderObject* backgroundObject);
index 7737b61..ced8a99 100644 (file)
@@ -484,13 +484,15 @@ bool RenderImage::boxShadowShouldBeAppliedToBackground(BackgroundBleedAvoidance
     return !const_cast<RenderImage*>(this)->backgroundIsKnownToBeObscured();
 }
 
-bool RenderImage::computeBackgroundIsKnownToBeObscured()
+bool RenderImage::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const
 {
+    UNUSED_PARAM(maxDepthToTest);
     if (!m_imageResource->hasImage() || m_imageResource->errorOccurred())
         return false;
     if (m_imageResource->cachedImage() && !m_imageResource->cachedImage()->isLoaded())
         return false;
-
+    if (!contentBoxRect().contains(localRect))
+        return false;
     EFillBox backgroundClip = style()->backgroundClip();
     // Background paints under borders.
     if (backgroundClip == BorderFillBox && style()->hasBorder() && !borderObscuresBackground())
@@ -498,11 +500,17 @@ bool RenderImage::computeBackgroundIsKnownToBeObscured()
     // Background shows in padding area.
     if ((backgroundClip == BorderFillBox || backgroundClip == PaddingFillBox) && style()->hasPadding())
         return false;
-
     // Check for image with alpha.
     return m_imageResource->cachedImage() && m_imageResource->cachedImage()->currentFrameKnownToBeOpaque(this);
 }
 
+bool RenderImage::computeBackgroundIsKnownToBeObscured()
+{
+    if (!hasBackground())
+        return false;
+    return foregroundIsKnownToBeOpaqueInRect(backgroundPaintedExtent(), 0);
+}
+
 LayoutUnit RenderImage::minimumReplacedHeight() const
 {
     return m_imageResource->errorOccurred() ? intrinsicSize().height() : LayoutUnit();
index 15c7906..48ef4cc 100644 (file)
@@ -88,6 +88,7 @@ private:
 
     virtual void paintReplaced(PaintInfo&, const LayoutPoint&);
 
+    virtual bool foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const OVERRIDE;
     virtual bool computeBackgroundIsKnownToBeObscured() OVERRIDE;
 
     virtual LayoutUnit minimumReplacedHeight() const OVERRIDE;