Bug 1238564 - Anticipate async scrolling when computing the scroll clipped bounds of a display list. r?roc draft
authorMarkus Stange <mstange@themasta.com>
Mon, 22 Feb 2016 16:17:16 +0100
changeset 337027 51cab60bd27e1a7e3c2d6b8d791b79fe3b3baa94
parent 337024 5a2e0878d6c258b36b0ee8712a2afcde6ad94c78
child 337028 3ce68bd42b034820421f1c31e649978f5e88adb7
push id12253
push usermstange@themasta.com
push dateFri, 04 Mar 2016 20:13:14 +0000
reviewersroc
bugs1238564
milestone47.0a1
Bug 1238564 - Anticipate async scrolling when computing the scroll clipped bounds of a display list. r?roc This makes sure that for example the bounds of an opacity item are not empty if the opacity item contains a scroll frame whose contents are currently scrolled offscreen but still inside that scroll frame's display port. On its own, this changeset causes test failures due to missed optimizations because the bounds of many opacity items are now too large. That's because of the way we're setting scroll clips on opacity items at the moment: Even if the opacity is inside a scroll frame, we're currently only setting that scroll frame's scroll clip on the opacity item's contents, not on the opacity item itself, because the opacity item might also contain other items that are not scrolled by this scroll frame. The next patch in this bug will make us only do that when necessary. MozReview-Commit-ID: 9TtcJ7eQE7U
layout/base/FrameLayerBuilder.cpp
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/generic/nsPluginFrame.cpp
layout/reftests/async-scrolling/offscreen-prerendered-active-opacity-ref.html
layout/reftests/async-scrolling/offscreen-prerendered-active-opacity.html
layout/reftests/async-scrolling/reftest.list
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -3885,21 +3885,27 @@ ContainerState::ProcessDisplayItems(nsDi
     bool dummy;
     if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
       bounds = item->GetBounds(mBuilder, &dummy);
       if (itemClip.HasClip()) {
         bounds.IntersectRect(bounds, itemClip.GetClipRect());
       }
     }
     bounds = fixedToViewportClip.ApplyNonRoundedIntersection(bounds);
-    for (const DisplayItemScrollClip* scrollClip = itemScrollClip;
-         scrollClip && scrollClip != mContainerScrollClip;
-         scrollClip = scrollClip->mParent) {
-      if (scrollClip->mClip) {
-        bounds = scrollClip->mClip->ApplyNonRoundedIntersection(bounds);
+    if (!bounds.IsEmpty()) {
+      for (const DisplayItemScrollClip* scrollClip = itemScrollClip;
+           scrollClip && scrollClip != mContainerScrollClip;
+           scrollClip = scrollClip->mParent) {
+        if (scrollClip->mClip) {
+          if (scrollClip->mIsAsyncScrollable) {
+            bounds = scrollClip->mClip->GetClipRect();
+          } else {
+            bounds = scrollClip->mClip->ApplyNonRoundedIntersection(bounds);
+          }
+        }
       }
     }
     ((nsRect&)mAccumulatedChildBounds).UnionRect(mAccumulatedChildBounds, bounds);
 #endif
 
     nsIntRect itemVisibleRect = itemDrawRect;
     if (!shouldFixToViewport) {
       // We haven't computed visibility at this point, so item->GetVisibleRect()
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1466,17 +1466,31 @@ nsDisplayList::GetBounds(nsDisplayListBu
   return bounds;
 }
 
 nsRect
 nsDisplayList::GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
                                           const DisplayItemScrollClip* aIncludeScrollClipsUpTo) const {
   nsRect bounds;
   for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
-    bounds.UnionRect(bounds, i->GetScrollClippedBoundsUpTo(aBuilder, aIncludeScrollClipsUpTo));
+    nsRect r = i->GetClippedBounds(aBuilder);
+    if (r.IsEmpty()) {
+      continue;
+    }
+    for (auto* sc = i->ScrollClip(); sc && sc != aIncludeScrollClipsUpTo; sc = sc->mParent) {
+      if (sc->mClip && sc->mClip->HasClip()) {
+        if (sc->mIsAsyncScrollable) {
+          // Assume the item can move anywhere in the scroll clip's clip rect.
+          r = sc->mClip->GetClipRect();
+        } else {
+          r = sc->mClip->ApplyNonRoundedIntersection(r);
+        }
+      }
+    }
+    bounds.UnionRect(bounds, r);
   }
   return bounds;
 }
 
 nsRect
 nsDisplayList::GetVisibleRect() const {
   nsRect result;
   for (nsDisplayItem* i = GetBottom(); i != nullptr; i = i->GetAbove()) {
@@ -2255,31 +2269,16 @@ nsRect
 nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder)
 {
   bool snap;
   nsRect r = GetBounds(aBuilder, &snap);
   return GetClip().ApplyNonRoundedIntersection(r);
 }
 
 nsRect
-nsDisplayItem::GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
-                                          const DisplayItemScrollClip* aIncludeScrollClipsUpTo)
-{
-  nsRect r = GetClippedBounds(aBuilder);
-  for (const DisplayItemScrollClip* sc = mScrollClip;
-       sc && sc != aIncludeScrollClipsUpTo;
-       sc = sc->mParent) {
-    if (sc->mClip) {
-      r = sc->mClip->ApplyNonRoundedIntersection(r);
-    }
-  }
-  return r;
-}
-
-nsRect
 nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
 {
   *aSnap = true;
   return mBounds;
 }
 
 void
 nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder,
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1423,27 +1423,16 @@ public:
     return false;
   }
   /**
    * Returns the result of GetBounds intersected with the item's clip.
    * The intersection is approximate since rounded corners are not taking into
    * account.
    */
   nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder);
-  /**
-   * Returns the result of GetClippedBounds, intersected with the item's
-   * scroll clips. The item walks up its chain of scroll clips, *not* crossing
-   * stacking contexts, applying each scroll clip until aIncludeScrollClipsUpTo
-   * is reached. aIncludeScrollClipsUpTo is *not* applied.
-   * The intersection is approximate since rounded corners are not taking into
-   * account.
-   */
-  nsRect GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
-                                    const DisplayItemScrollClip* aIncludeScrollClipsUpTo);
-
   nsRect GetBorderRect() {
     return nsRect(ToReferenceFrame(), Frame()->GetSize());
   }
   nsRect GetPaddingRect() {
     return Frame()->GetPaddingRectRelativeToSelf() + ToReferenceFrame();
   }
   nsRect GetContentRect() {
     return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
@@ -2150,17 +2139,24 @@ public:
                                            nsRenderingContext* aCtx,
                                            uint32_t aFlags);
   /**
    * Get the bounds. Takes the union of the bounds of all children.
    * The result is not cached.
    */
   nsRect GetBounds(nsDisplayListBuilder* aBuilder) const;
   /**
-   * Return the union of the scroll clipped bounds of all children.
+   * Return the union of the scroll clipped bounds of all children. To get the
+   * scroll clipped bounds of a child item, we start with the item's clipped
+   * bounds and walk its scroll clip chain up to (but not including)
+   * aIncludeScrollClipsUpTo, and take each scroll clip into account. For
+   * scroll clips from async scrollable frames we assume that the item can move
+   * anywhere inside that scroll frame.
+   * In other words, the return value from this method includes all pixels that
+   * could potentially be covered by items in this list under async scrolling.
    */
   nsRect GetScrollClippedBoundsUpTo(nsDisplayListBuilder* aBuilder,
                                     const DisplayItemScrollClip* aIncludeScrollClipsUpTo) const;
   /**
    * Find the topmost display item that returns a non-null frame, and return
    * the frame.
    */
   void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -49,16 +49,17 @@
 #include "ImageLayers.h"
 #include "nsPluginInstanceOwner.h"
 
 #ifdef XP_WIN
 #include "gfxWindowsNativeDrawing.h"
 #include "gfxWindowsSurface.h"
 #endif
 
+#include "DisplayItemScrollClip.h"
 #include "Layers.h"
 #include "ReadbackLayer.h"
 #include "ImageContainer.h"
 
 // accessibility support
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
@@ -997,16 +998,29 @@ void
 nsDisplayPlugin::Paint(nsDisplayListBuilder* aBuilder,
                        nsRenderingContext* aCtx)
 {
   nsPluginFrame* f = static_cast<nsPluginFrame*>(mFrame);
   bool snap;
   f->PaintPlugin(aBuilder, *aCtx, mVisibleRect, GetBounds(aBuilder, &snap));
 }
 
+static nsRect
+GetClippedBoundsIncludingAllScrollClips(nsDisplayItem* aItem,
+                                        nsDisplayListBuilder* aBuilder)
+{
+  nsRect r = aItem->GetClippedBounds(aBuilder);
+  for (auto* sc = aItem->ScrollClip(); sc; sc = sc->mParent) {
+    if (sc->mClip) {
+      r = sc->mClip->ApplyNonRoundedIntersection(r);
+    }
+  }
+  return r;
+}
+
 bool
 nsDisplayPlugin::ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                    nsRegion* aVisibleRegion)
 {
   if (aBuilder->IsForPluginGeometry()) {
     nsPluginFrame* f = static_cast<nsPluginFrame*>(mFrame);
     if (!aBuilder->IsInTransform() || f->IsPaintedByGecko()) {
       // Since transforms induce reference frames, we don't need to worry
@@ -1017,17 +1031,17 @@ nsDisplayPlugin::ComputeVisibility(nsDis
         ReferenceFrame()->PresContext()->AppUnitsPerDevPixel();
       f->mNextConfigurationBounds = LayoutDeviceIntRect::FromUnknownRect(
         rAncestor.ToNearestPixels(appUnitsPerDevPixel));
 
       nsRegion visibleRegion;
       // Apply all scroll clips when computing the clipped bounds of this item.
       // We hide windowed plugins during APZ scrolling, so there never is an
       // async transform that we need to take into account when clipping.
-      visibleRegion.And(*aVisibleRegion, GetScrollClippedBoundsUpTo(aBuilder, nullptr));
+      visibleRegion.And(*aVisibleRegion, GetClippedBoundsIncludingAllScrollClips(this, aBuilder));
       // Make visibleRegion relative to f
       visibleRegion.MoveBy(-ToReferenceFrame());
 
       f->mNextConfigurationClipRegion.Clear();
       for (auto iter = visibleRegion.RectIter(); !iter.Done(); iter.Next()) {
         nsRect rAncestor =
           nsLayoutUtils::TransformFrameRectToAncestor(f, iter.Get(), ReferenceFrame());
         LayoutDeviceIntRect rPixels =
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>Reference: Active opacity should be rendered if it's inside the display port, even if it's currently offscreen</title>
+
+<style>
+
+#scrollbox {
+  border: 1px solid black;
+  width: 200px;
+  height: 200px;
+  overflow: hidden;
+}
+
+#scrolledContent {
+  height: 1000px;
+}
+
+#opacity {
+  will-change: opacity;
+  opacity: 0.5;
+  margin-top: 250px;
+  width: 100px;
+  height: 100px;
+  box-sizing: border-box;
+  border: 1px solid blue;
+}
+
+
+</style>
+
+<div id="scrollbox">
+  <div id="scrolledContent">
+    <div id="opacity"></div>
+  </div>
+</div>
+
+<script>
+
+document.getElementById("scrollbox").scrollTop = 150;
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/offscreen-prerendered-active-opacity.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html reftest-async-scroll>
+<meta charset="utf-8">
+<title>Active opacity should be rendered if it's inside the display port, even if it's currently offscreen</title>
+
+<style>
+
+.scrollbox {
+  border: 1px solid black;
+  width: 200px;
+  height: 200px;
+  overflow: hidden;
+}
+
+.scrolledContent {
+  height: 1000px;
+}
+
+.opacity {
+  will-change: opacity;
+  opacity: 0.5;
+  margin-top: 250px;
+  width: 100px;
+  height: 100px;
+  box-sizing: border-box;
+  border: 1px solid blue;
+}
+
+
+</style>
+
+<div class="scrollbox"
+     reftest-displayport-x="0" reftest-displayport-y="0"
+     reftest-displayport-w="200" reftest-displayport-h="1000"
+     reftest-async-scroll-x="0" reftest-async-scroll-y="150">
+  <div class="scrolledContent">
+    <div class="opacity"></div>
+  </div>
+</div>
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -1,15 +1,15 @@
-== bg-fixed-1.html bg-fixed-1-ref.html
-== bg-fixed-cover-1.html bg-fixed-cover-1-ref.html
-== bg-fixed-cover-2.html bg-fixed-cover-2-ref.html
-== bg-fixed-cover-3.html bg-fixed-cover-3-ref.html
-== bg-fixed-child.html bg-fixed-child-ref.html
-== bg-fixed-child-clip-1.html bg-fixed-child-clip-ref.html
-== bg-fixed-child-clip-2.html bg-fixed-child-clip-ref.html
+skip-if(!asyncPan) == bg-fixed-1.html bg-fixed-1-ref.html
+skip-if(!asyncPan) == bg-fixed-cover-1.html bg-fixed-cover-1-ref.html
+skip-if(!asyncPan) == bg-fixed-cover-2.html bg-fixed-cover-2-ref.html
+skip-if(!asyncPan) == bg-fixed-cover-3.html bg-fixed-cover-3-ref.html
+skip-if(!asyncPan) == bg-fixed-child.html bg-fixed-child-ref.html
+skip-if(!asyncPan) == bg-fixed-child-clip-1.html bg-fixed-child-clip-ref.html
+skip-if(!asyncPan) == bg-fixed-child-clip-2.html bg-fixed-child-clip-ref.html
 fuzzy(1,246) fuzzy-if(skiaContent,2,160) skip-if(!asyncPan) == bg-fixed-child-mask.html bg-fixed-child-mask-ref.html
 == bg-fixed-in-opacity.html bg-fixed-in-opacity-ref.html
 == bg-fixed-child-no-culling.html bg-fixed-child-no-culling-ref.html
 fuzzy-if(B2G,2,5366) fuzzy-if(Android,2,4000) fuzzy-if(browserIsRemote&&cocoaWidget,2,179524) fuzzy-if(browserIsRemote&&winWidget,1,74590) skip-if(!asyncPan) == bg-fixed-transformed-image.html bg-fixed-transformed-image-ref.html
 == element-1.html element-1-ref.html
 pref(layers.force-active,true) == iframe-1.html iframe-1-ref.html
 == nested-1.html nested-1-ref.html
 == nested-2.html nested-2-ref.html
@@ -26,16 +26,17 @@ fuzzy-if(skiaContent,1,32000) fuzzy-if(b
 == sticky-pos-scrollable-1.html sticky-pos-scrollable-1-ref.html
 == fixed-pos-scrollable-1.html fixed-pos-scrollable-1-ref.html
 == culling-1.html culling-1-ref.html
 == position-fixed-iframe-1.html position-fixed-iframe-1-ref.html
 == position-fixed-iframe-2.html position-fixed-iframe-2-ref.html
 fuzzy-if(skiaContent||(browserIsRemote&&cocoaWidget),1,10000) skip-if(!asyncPan) == position-fixed-in-scroll-container.html position-fixed-in-scroll-container-ref.html
 fuzzy(1,60000) skip-if(!asyncPan) == group-opacity-surface-size-1.html group-opacity-surface-size-1-ref.html
 == position-sticky-transformed.html position-sticky-transformed-ref.html
+== offscreen-prerendered-active-opacity.html offscreen-prerendered-active-opacity-ref.html
 
 # for the following tests, we want to disable the low-precision buffer
 # as it will expand the displayport beyond what the test specifies in
 # its reftest-displayport attributes, and interfere with where we expect
 # checkerboarding to occur
 default-preferences pref(layers.low-precision-buffer,false)
 == checkerboard-1.html checkerboard-1-ref.html
 == checkerboard-2.html checkerboard-2-ref.html