Bug 640048 Fix edge case for z-ordering for async scrollable elements r=roc
☠☠ backed out by 44f2b2c318b0 ☠ ☠
authorBenjamin Stover <bstover@mozilla.com>
Fri, 29 Apr 2011 10:15:44 -0700
changeset 68783 c7f62d818af25b3e62e2f0c35b27214a4209a898
parent 68782 c1fe6531fa0abdda0713498cda348e0b98f32017
child 68784 0912e048403117c91f5be660c54a810aa7232835
push id19743
push userbstover@mozilla.com
push dateFri, 29 Apr 2011 17:12:21 +0000
treeherdermozilla-central@c7f62d818af2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs640048
milestone6.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 640048 Fix edge case for z-ordering for async scrollable elements r=roc
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsIFrame.h
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -184,17 +184,17 @@ static void UnmarkFrameForDisplay(nsIFra
        f = nsLayoutUtils::GetParentOrPlaceholderFor(frameManager, f)) {
     if (!(f->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO))
       return;
     f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
   }
 }
 
 static void RecordFrameMetrics(nsIFrame* aForFrame,
-                               nsIFrame* aViewportFrame,
+                               nsIFrame* aScrollFrame,
                                ContainerLayer* aRoot,
                                nsRect aVisibleRect,
                                nsRect aViewport,
                                nsRect* aDisplayPort,
                                ViewID aScrollId) {
   nsPresContext* presContext = aForFrame->PresContext();
 
   nsIntRect visible = aVisibleRect.ToNearestPixels(presContext->AppUnitsPerDevPixel());
@@ -204,18 +204,18 @@ static void RecordFrameMetrics(nsIFrame*
 
   PRInt32 auPerDevPixel = presContext->AppUnitsPerDevPixel();
   metrics.mViewport = aViewport.ToNearestPixels(auPerDevPixel);
   if (aDisplayPort) {
     metrics.mDisplayPort = aDisplayPort->ToNearestPixels(auPerDevPixel);
   }
 
   nsIScrollableFrame* scrollableFrame = nsnull;
-  if (aViewportFrame)
-    scrollableFrame = aViewportFrame->GetScrollTargetFrame();
+  if (aScrollFrame)
+    scrollableFrame = aScrollFrame->GetScrollTargetFrame();
 
   if (scrollableFrame) {
     nsSize contentSize =
       scrollableFrame->GetScrollRange().Size() +
       scrollableFrame->GetScrollPortRect().Size();
     metrics.mContentSize = nsIntSize(NSAppUnitsToIntPixels(contentSize.width, auPerDevPixel),
                                      NSAppUnitsToIntPixels(contentSize.height, auPerDevPixel));
 
@@ -459,16 +459,26 @@ nsDisplayList::ComputeVisibilityForSubli
     nsDisplayItem* belowItem = i < 1 ? nsnull : elements[i - 1];
 
     if (belowItem && item->TryMerge(aBuilder, belowItem)) {
       belowItem->~nsDisplayItem();
       elements.ReplaceElementsAt(i - 1, 1, item);
       continue;
     }
 
+    nsDisplayList* list = item->GetList();
+    if (list && item->ShouldFlattenAway(aBuilder)) {
+      // The elements on the list >= i no longer serve any use.
+      elements.SetLength(i);
+      list->FlattenTo(&elements);
+      i = elements.Length();
+      item->~nsDisplayItem();
+      continue;
+    }
+
     nsRect bounds = item->GetBounds(aBuilder);
 
     nsRegion itemVisible;
     itemVisible.And(*aVisibleRegion, bounds);
     item->mVisibleRect = itemVisible.GetBounds();
 
     if (ForceVisiblityForFixedItem(aBuilder, item)) {
       item->mVisibleRect = bounds;
@@ -1491,16 +1501,21 @@ nsDisplayWrapList::nsDisplayWrapList(nsD
 }
 
 nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame, nsDisplayItem* aItem)
   : nsDisplayItem(aBuilder, aFrame) {
   mList.AppendToTop(aItem);
 }
 
+nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
+                                     nsIFrame* aFrame)
+  : nsDisplayItem(aBuilder, aFrame) {
+}
+
 nsDisplayWrapList::~nsDisplayWrapList() {
   mList.DeleteAll();
 }
 
 void
 nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                            HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) {
   mList.HitTest(aBuilder, aRect, aState, aOutFrames);
@@ -1756,111 +1771,234 @@ nsDisplayOwnLayer::BuildLayer(nsDisplayL
   nsRefPtr<Layer> layer = aBuilder->LayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, mList);
   return layer.forget();
 }
 
 nsDisplayScrollLayer::nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
                                            nsDisplayList* aList,
                                            nsIFrame* aForFrame,
-                                           nsIFrame* aViewportFrame)
-  : nsDisplayOwnLayer(aBuilder, aForFrame, aList)
-  , mViewportFrame(aViewportFrame)
+                                           nsIFrame* aScrolledFrame,
+                                           nsIFrame* aScrollFrame)
+  : nsDisplayWrapList(aBuilder, aForFrame, aList)
+  , mScrollFrame(aScrollFrame)
+  , mScrolledFrame(aScrolledFrame)
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+  MOZ_COUNT_CTOR(nsDisplayScrollLayer);
+#endif
+
+  NS_ASSERTION(mScrolledFrame && mScrolledFrame->GetContent(),
+               "Need a child frame with content");
+}
+
+nsDisplayScrollLayer::nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
+                                           nsDisplayItem* aItem,
+                                           nsIFrame* aForFrame,
+                                           nsIFrame* aScrolledFrame,
+                                           nsIFrame* aScrollFrame)
+  : nsDisplayWrapList(aBuilder, aForFrame, aItem)
+  , mScrollFrame(aScrollFrame)
+  , mScrolledFrame(aScrolledFrame)
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   MOZ_COUNT_CTOR(nsDisplayScrollLayer);
 #endif
 
-  NS_ASSERTION(mFrame && mFrame->GetContent(),
+  NS_ASSERTION(mScrolledFrame && mScrolledFrame->GetContent(),
                "Need a child frame with content");
 }
 
+nsDisplayScrollLayer::nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
+                                           nsIFrame* aForFrame,
+                                           nsIFrame* aScrolledFrame,
+                                           nsIFrame* aScrollFrame)
+  : nsDisplayWrapList(aBuilder, aForFrame)
+  , mScrollFrame(aScrollFrame)
+  , mScrolledFrame(aScrolledFrame)
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+  MOZ_COUNT_CTOR(nsDisplayScrollLayer);
+#endif
+
+  NS_ASSERTION(mScrolledFrame && mScrolledFrame->GetContent(),
+               "Need a child frame with content");
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayScrollLayer::~nsDisplayScrollLayer()
+{
+  MOZ_COUNT_DTOR(nsDisplayScrollLayer);
+}
+#endif
+
 already_AddRefed<Layer>
 nsDisplayScrollLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
                                  LayerManager* aManager) {
   nsRefPtr<ContainerLayer> layer = aBuilder->LayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, mList);
 
   // Get the already set unique ID for scrolling this content remotely.
   // Or, if not set, generate a new ID.
-  nsIContent* content = mFrame->GetContent();
+  nsIContent* content = mScrolledFrame->GetContent();
   ViewID scrollId = nsLayoutUtils::FindIDFor(content);
 
-  nsRect viewport = mViewportFrame->GetRect() -
-                    mViewportFrame->GetPosition() +
-                    aBuilder->ToReferenceFrame(mViewportFrame);
+  nsRect viewport = mScrollFrame->GetRect() -
+                    mScrollFrame->GetPosition() +
+                    aBuilder->ToReferenceFrame(mScrollFrame);
 
   bool usingDisplayport = false;
   nsRect displayport;
   if (content) {
     usingDisplayport = nsLayoutUtils::GetDisplayPort(content, &displayport);
   }
-  RecordFrameMetrics(mFrame, mViewportFrame, layer, mVisibleRect, viewport,
+  RecordFrameMetrics(mScrolledFrame, mScrollFrame, layer, mVisibleRect, viewport,
                      (usingDisplayport ? &displayport : nsnull), scrollId);
 
   return layer.forget();
 }
 
 PRBool
 nsDisplayScrollLayer::ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                         nsRegion* aVisibleRegion,
                                         const nsRect& aAllowVisibleRegionExpansion,
                                         PRBool& aContainsRootContentDocBG)
 {
   nsRect displayport;
-  if (nsLayoutUtils::GetDisplayPort(mFrame->GetContent(), &displayport)) {
+  if (nsLayoutUtils::GetDisplayPort(mScrolledFrame->GetContent(), &displayport)) {
     // The visible region for the children may be much bigger than the hole we
     // are viewing the children from, so that the compositor process has enough
     // content to asynchronously pan while content is being refreshed.
 
-    nsRegion childVisibleRegion = displayport + aBuilder->ToReferenceFrame(mViewportFrame);
+    nsRegion childVisibleRegion = displayport + aBuilder->ToReferenceFrame(mScrollFrame);
 
     nsRect boundedRect;
     boundedRect.IntersectRect(childVisibleRegion.GetBounds(), mList.GetBounds(aBuilder));
     nsRect allowExpansion;
     allowExpansion.IntersectRect(allowExpansion, boundedRect);
     PRBool visible = mList.ComputeVisibilityForSublist(
       aBuilder, &childVisibleRegion, boundedRect, allowExpansion, aContainsRootContentDocBG);
     mVisibleRect = boundedRect;
 
     return visible;
 
   } else {
-    return nsDisplayOwnLayer::ComputeVisibility(aBuilder, aVisibleRegion,
+    return nsDisplayWrapList::ComputeVisibility(aBuilder, aVisibleRegion,
                                                 aAllowVisibleRegionExpansion,
                                                 aContainsRootContentDocBG);
   }
 }
 
-#ifdef NS_BUILD_REFCNT_LOGGING
-nsDisplayScrollLayer::~nsDisplayScrollLayer()
+LayerState
+nsDisplayScrollLayer::GetLayerState(nsDisplayListBuilder* aBuilder,
+                                    LayerManager* aManager)
+{
+  // Force this as a layer so we can scroll asynchronously.
+  // This causes incorrect rendering for rounded clips!
+  return LAYER_ACTIVE_FORCE;
+}
+
+PRBool
+nsDisplayScrollLayer::TryMerge(nsDisplayListBuilder* aBuilder,
+                               nsDisplayItem* aItem)
 {
-  MOZ_COUNT_DTOR(nsDisplayScrollLayer);
+  if (aItem->GetType() != TYPE_SCROLL_LAYER) {
+    return PR_FALSE;
+  }
+
+  nsDisplayScrollLayer* other = static_cast<nsDisplayScrollLayer*>(aItem);
+  if (other->mScrolledFrame != this->mScrolledFrame) {
+    return PR_FALSE;
+  }
+
+  FrameProperties props = mScrolledFrame->Properties();
+  props.Set(nsIFrame::ScrollLayerCount(),
+    reinterpret_cast<void*>(GetScrollLayerCount() - 1));
+
+  mList.AppendToBottom(&other->mList);
+  return PR_TRUE;
+}
+
+PRBool
+nsDisplayScrollLayer::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
+{
+  return GetScrollLayerCount() > 1;
 }
+
+PRWord
+nsDisplayScrollLayer::GetScrollLayerCount()
+{
+  FrameProperties props = mScrolledFrame->Properties();
+#ifdef DEBUG
+  PRBool hasCount = PR_FALSE;
+  PRWord result = reinterpret_cast<PRWord>(
+    props.Get(nsIFrame::ScrollLayerCount(), &hasCount));
+  // If this aborts, then the property was either not added before scroll
+  // layers were created or the property was deleted to early. If the latter,
+  // make sure that nsDisplayScrollInfoLayer is on the bottom of the list so
+  // that it is processed last.
+  NS_ABORT_IF_FALSE(hasCount, "nsDisplayScrollLayer should always be defined");
+  return result;
+#else
+  return reinterpret_cast<PRWord>(props.Get(nsIFrame::ScrollLayerCount()));
 #endif
+}
+
+PRWord
+nsDisplayScrollLayer::RemoveScrollLayerCount()
+{
+  PRWord result = GetScrollLayerCount();
+  FrameProperties props = mScrolledFrame->Properties();
+  props.Remove(nsIFrame::ScrollLayerCount());
+  return result;
+}
+
 
 nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer(
   nsDisplayListBuilder* aBuilder,
-  nsDisplayList* aList,
-  nsIFrame* aForFrame,
-  nsIFrame* aViewportFrame)
-  : nsDisplayScrollLayer(aBuilder, aList, aForFrame, aViewportFrame)
+  nsIFrame* aScrolledFrame,
+  nsIFrame* aScrollFrame)
+  : nsDisplayScrollLayer(aBuilder, aScrolledFrame, aScrolledFrame, aScrollFrame)
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer);
 #endif
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayScrollInfoLayer::~nsDisplayScrollInfoLayer()
 {
   MOZ_COUNT_DTOR(nsDisplayScrollInfoLayer);
 }
 #endif
 
+LayerState
+nsDisplayScrollInfoLayer::GetLayerState(nsDisplayListBuilder* aBuilder,
+                                        LayerManager* aManager)
+{
+  return LAYER_ACTIVE_EMPTY;
+}
+
+PRBool
+nsDisplayScrollInfoLayer::TryMerge(nsDisplayListBuilder* aBuilder,
+                                   nsDisplayItem* aItem)
+{
+  return PR_FALSE;
+}
+
+PRBool
+nsDisplayScrollInfoLayer::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
+{
+  // Layer metadata for a particular scroll frame needs to be unique. Only
+  // one nsDisplayScrollLayer (with rendered content) or one
+  // nsDisplayScrollInfoLayer (with only the metadata) should survive the
+  // visibility computation. 
+  return RemoveScrollLayerCount() == 1;
+}
+
 nsDisplayClip::nsDisplayClip(nsDisplayListBuilder* aBuilder,
                              nsIFrame* aFrame, nsDisplayItem* aItem,
                              const nsRect& aRect)
    : nsDisplayWrapList(aBuilder, aFrame, aItem) {
   MOZ_COUNT_CTOR(nsDisplayClip);
   mClip = SnapBounds(aBuilder->IsSnappingEnabled() && !aBuilder->IsInTransform(),
                      aBuilder->CurrentPresContext(), aRect);
 }
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -745,17 +745,27 @@ public:
    * content element into a single opacity group (correctness), and will be
    * used by nsDisplayOutline to merge multiple outlines for the same element
    * (also for correctness).
    * @return PR_TRUE if the merge was successful and the other item should be deleted
    */
   virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
     return PR_FALSE;
   }
-  
+
+  /**
+   * During the visibility computation and after TryMerge, display lists may
+   * return PR_TRUE here to flatten themselves away, removing them. This
+   * flattening is distinctly different from FlattenTo, which occurs before
+   * items are merged together.
+   */
+  virtual PRBool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
+    return PR_FALSE;
+  }
+
   /**
    * If this is a leaf item we return null, otherwise we return the wrapped
    * list.
    */
   virtual nsDisplayList* GetList() { return nsnull; }
 
   /**
    * Returns the visible rect. Should only be called after ComputeVisibility
@@ -1651,16 +1661,17 @@ class nsDisplayWrapList : public nsDispl
 public:
   /**
    * Takes all the items from aList and puts them in our list.
    */
   nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                     nsDisplayList* aList);
   nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                     nsDisplayItem* aItem);
+  nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
   virtual ~nsDisplayWrapList();
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames);
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                    PRBool* aForceTransparentSurface = nsnull);
   virtual PRBool IsUniform(nsDisplayListBuilder* aBuilder, nscolor* aColor);
   virtual PRBool IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder,
@@ -1783,85 +1794,117 @@ public:
   {
     // Don't allow merging, each sublist must have its own layer
     return PR_FALSE;
   }
   NS_DISPLAY_DECL_NAME("OwnLayer", TYPE_OWN_LAYER)
 };
 
 /**
- * This creates a layer for the given list of items, whose visibility is
- * determined by the displayport for the given frame instead of what is
- * passed in to ComputeVisibility.
+ * This potentially creates a layer for the given list of items, whose
+ * visibility is determined by the displayport for the given frame instead of
+ * what is passed in to ComputeVisibility.
  *
  * Here in content, we can use this to render more content than is actually
  * visible. Then, the compositing process can manipulate the generated layer
  * through transformations so that asynchronous scrolling can be implemented.
  *
  * Note that setting the displayport will not change any hit testing! The
  * content process will know nothing about what the user is actually seeing,
  * so it can only do hit testing for what is supposed to be the visible region.
+ *
+ * It is possible for scroll boxes to have content that can be both above and
+ * below content outside of the scroll box. We cannot create layers for these
+ * cases. This is accomplished by wrapping display items with
+ * nsDisplayScrollLayers. nsDisplayScrollLayers with the same scroll frame will
+ * be merged together. If more than one nsDisplayScrollLayer exists after
+ * merging, all nsDisplayScrollLayers will be flattened out so that no new
+ * layer is created at all.
  */
-class nsDisplayScrollLayer : public nsDisplayOwnLayer
+class nsDisplayScrollLayer : public nsDisplayWrapList
 {
 public:
   /**
-   * @param aForFrame This will determine what the displayport is. It should be
-   *                  the root content frame of the scrolled area.
-   * @param aViewportFrame The viewport frame you see this content through.
+   * @param aScrolledFrame This will determine what the displayport is. It should be
+   *                       the root content frame of the scrolled area. Note
+   *                       that nsDisplayScrollLayer will expect for
+   *                       ScrollLayerCount to be defined on aScrolledFrame.
+   * @param aScrollFrame The viewport frame you see this content through.
    */
   nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
-                       nsIFrame* aForFrame, nsIFrame* aViewportFrame);
+                       nsIFrame* aForFrame, nsIFrame* aScrolledFrame,
+                       nsIFrame* aScrollFrame);
+  nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
+                       nsIFrame* aForFrame, nsIFrame* aScrolledFrame,
+                       nsIFrame* aScrollFrame);
+  nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
+                       nsIFrame* aForFrame, nsIFrame* aScrolledFrame,
+                       nsIFrame* aScrollFrame);
   NS_DISPLAY_DECL_NAME("ScrollLayer", TYPE_SCROLL_LAYER)
 
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayScrollLayer();
 #endif
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager);
 
   virtual PRBool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                    nsRegion* aVisibleRegion,
                                    const nsRect& aAllowVisibleRegionExpansion,
                                    PRBool& aContainsRootContentDocBG);
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
-                                   LayerManager* aManager)
-  {
-    // Force this as a layer so we can scroll asynchronously.
-    // This causes incorrect rendering for rounded clips!
-    return mozilla::LAYER_ACTIVE_FORCE;
-  }
+                                   LayerManager* aManager);
+
+  virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder,
+                          nsDisplayItem* aItem);
+
+  virtual PRBool ShouldFlattenAway(nsDisplayListBuilder* aBuilder);
+
+  // Get the number of nsDisplayScrollLayers for a scroll frame. Note that this
+  // number does not include nsDisplayScrollInfoLayers. If this number is not 1
+  // after merging, all the nsDisplayScrollLayers should flatten away.
+  PRWord GetScrollLayerCount();
+  PRWord RemoveScrollLayerCount();
+
 private:
-  nsIFrame* mViewportFrame;
+  nsIFrame* mScrollFrame;
+  nsIFrame* mScrolledFrame;
 };
 
 /**
  * Like nsDisplayScrollLayer, but only has metadata on the scroll frame. This
  * creates a layer that has no Thebes child layer, but still allows the
  * compositor process to know of the scroll frame's existence.
+ *
+ * After visibility computation, nsDisplayScrollInfoLayers should only exist if
+ * nsDisplayScrollLayers were all flattened away.
+ *
+ * Important!! Add info layers to the bottom of the list so they are only
+ * considered after the others have flattened out!
  */
 class nsDisplayScrollInfoLayer : public nsDisplayScrollLayer
 {
 public:
-  nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
-                           nsIFrame* aForFrame, nsIFrame* aViewportFrame);
+  nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder,
+                           nsIFrame* aScrolledFrame, nsIFrame* aScrollFrame);
   NS_DISPLAY_DECL_NAME("ScrollInfoLayer", TYPE_SCROLL_INFO_LAYER)
 
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayScrollInfoLayer();
 #endif
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
-                                   LayerManager* aManager)
-  {
-    return mozilla::LAYER_ACTIVE_EMPTY;
-  }
+                                   LayerManager* aManager);
 
+  virtual PRBool TryMerge(nsDisplayListBuilder* aBuilder,
+                          nsDisplayItem* aItem);
+
+  virtual PRBool ShouldFlattenAway(nsDisplayListBuilder* aBuilder);
 };
 
 /**
  * nsDisplayClip can clip a list of items, but we take a single item
  * initially and then later merge other items into it when we merge
  * adjacent matching nsDisplayClips
  */
 class nsDisplayClip : public nsDisplayWrapList {
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1900,16 +1900,53 @@ nsGfxScrollFrameInner::AppendScrollParts
 }
 
 PRBool
 nsGfxScrollFrameInner::ShouldBuildLayer() const
 {
   return mShouldBuildLayer;
 }
 
+class ScrollLayerWrapper : public nsDisplayWrapper
+{
+public:
+  ScrollLayerWrapper(nsIFrame* aScrollFrame, nsIFrame* aScrolledFrame)
+    : mCount(0)
+    , mProps(aScrolledFrame->Properties())
+    , mScrollFrame(aScrollFrame)
+    , mScrolledFrame(aScrolledFrame)
+  {
+    SetCount(0);
+  }
+
+  virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
+                                  nsIFrame* aFrame,
+                                  nsDisplayList* aList) {
+    SetCount(++mCount);
+    return new (aBuilder) nsDisplayScrollLayer(aBuilder, aList, nsnull, mScrolledFrame, mScrollFrame);
+  }
+
+  virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
+                                  nsDisplayItem* aItem) {
+
+    SetCount(++mCount);
+    return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->GetUnderlyingFrame(), mScrolledFrame, mScrollFrame);
+  }
+
+protected:
+  void SetCount(PRWord aCount) {
+    mProps.Set(nsIFrame::ScrollLayerCount(), reinterpret_cast<void*>(aCount));
+  }
+
+  PRWord mCount;
+  FrameProperties mProps;
+  nsIFrame* mScrollFrame;
+  nsIFrame* mScrolledFrame;
+};
+
 nsresult
 nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                         const nsRect&           aDirtyRect,
                                         const nsDisplayListSet& aLists)
 {
   nsresult rv = mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1960,68 +1997,51 @@ nsGfxScrollFrameInner::BuildDisplayList(
   // dirty rect here.
   dirtyRect.IntersectRect(aDirtyRect, mScrollPort);
 
   // Override the dirty rectangle if the displayport has been set.
   PRBool usingDisplayport =
     nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &dirtyRect);
 
   nsDisplayListCollection set;
+  rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Since making new layers is expensive, only use nsDisplayScrollLayer
   // if the area is scrollable.
-  //
-  // Scroll frames can be generated with a scroll range that is 0, 0.
-  // Furthermore, it is not worth the memory tradeoff to allow asynchronous
-  // scrolling of small scroll frames. We use an arbitrary minimum scroll
-  // range of 20 pixels to eliminate many gfx scroll frames from becoming a
-  // layer.
-  //
   nsRect scrollRange = GetScrollRange();
   ScrollbarStyles styles = GetScrollbarStylesFromFrame();
   mShouldBuildLayer =
      (XRE_GetProcessType() == GeckoProcessType_Content &&
      (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN ||
       styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) &&
      (scrollRange.width > 0 ||
       scrollRange.height > 0) &&
      (!mIsRoot || !mOuter->PresContext()->IsRootContentDocument()));
 
   if (ShouldBuildLayer()) {
-    nsDisplayList list;
+    // ScrollLayerWrapper must always be created because it initializes the
+    // scroll layer count. The display lists depend on this.
+    ScrollLayerWrapper wrapper(mOuter, mScrolledFrame);
+
     if (usingDisplayport) {
       // Once a displayport is set, assume that scrolling needs to be fast
       // so create a layer with all the content inside. The compositor
       // process will be able to scroll the content asynchronously.
-      //
-      // Note that using StackingContext breaks z order, so the resulting
-      // rendering can be incorrect for weird edge cases!
-
-      rv = mScrolledFrame->BuildDisplayListForStackingContext(
-        aBuilder, dirtyRect + mOuter->GetOffsetTo(mScrolledFrame), &list);
-
-      nsDisplayScrollLayer* layerItem = new (aBuilder) nsDisplayScrollLayer(
-        aBuilder, &list, mScrolledFrame, mOuter);
-      set.Content()->AppendNewToTop(layerItem);
-    } else {
-      // If there is no displayport set, there is no reason here to force a
-      // layer that needs a memory-expensive allocation, but the compositor
-      // process would still like to know that it exists.
-
-      nsDisplayScrollLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer(
-        aBuilder, &list, mScrolledFrame, mOuter);
-      set.Content()->AppendNewToTop(layerItem);
-
-      rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
+      wrapper.WrapListsInPlace(aBuilder, mOuter, set);
     }
-  } else {
-    rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set);
+
+    // In case we are not using displayport or the nsDisplayScrollLayers are
+    // flattened during visibility computation, we still need to export the
+    // metadata about this scroll box to the compositor process.
+    nsDisplayScrollInfoLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer(
+      aBuilder, mScrolledFrame, mOuter);
+    set.Content()->AppendNewToBottom(layerItem);
   }
 
-  NS_ENSURE_SUCCESS(rv, rv);
   nsRect clip;
   clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
 
   nscoord radii[8];
   // Our override of GetBorderRadii ensures we never have a radius at
   // the corners where we have a scrollbar.
   mOuter->GetPaddingBoxBorderRadii(radii);
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -889,16 +889,18 @@ public:
   NS_DECLARE_FRAME_PROPERTY(OutlineInnerRectProperty, DestroyRect)
   NS_DECLARE_FRAME_PROPERTY(PreEffectsBBoxProperty, DestroyRect)
   NS_DECLARE_FRAME_PROPERTY(PreTransformBBoxProperty, DestroyRect)
 
   NS_DECLARE_FRAME_PROPERTY(UsedMarginProperty, DestroyMargin)
   NS_DECLARE_FRAME_PROPERTY(UsedPaddingProperty, DestroyMargin)
   NS_DECLARE_FRAME_PROPERTY(UsedBorderProperty, DestroyMargin)
 
+  NS_DECLARE_FRAME_PROPERTY(ScrollLayerCount, nsnull)
+
   /**
    * Return the distance between the border edge of the frame and the
    * margin edge of the frame.  Like GetRect(), returns the dimensions
    * as of the most recent reflow.
    *
    * This doesn't include any margin collapsing that may have occurred.
    *
    * It also treats 'auto' margins as zero, and treats any margins that