Bug 1022612. Part 31: Perform layer-level occlusion culling in FrameLayerBuilder. r=mattwoodrow
☠☠ backed out by 2bcded4e3b4a ☠ ☠
authorRobert O'Callahan <robert@ocallahan.org>
Mon, 23 Jun 2014 16:24:00 +1200
changeset 216627 2763c4878de50e4b5078820ed7580312d46d3bfb
parent 216626 b72413ecc3851b5e550eec4e4f169f7ebd3d0b4b
child 216628 18eecc5b1ef786744358d74a58eaa4d3948aa6c0
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1022612
milestone33.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 1022612. Part 31: Perform layer-level occlusion culling in FrameLayerBuilder. r=mattwoodrow We need this to avoid constructing and painting unncecessarily large ThebesLayers.
layout/base/FrameLayerBuilder.cpp
layout/base/FrameLayerBuilder.h
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -32,18 +32,16 @@
 
 #include <algorithm>
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 namespace mozilla {
 
-class ContainerState;
-
 FrameLayerBuilder::DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey,
                                                     Layer* aLayer, LayerState aLayerState, uint32_t aGeneration)
 
   : mParent(aParent)
   , mLayer(aLayer)
   , mDisplayItemKey(aKey)
   , mContainerLayerGeneration(aGeneration)
   , mLayerState(aLayerState)
@@ -239,18 +237,20 @@ public:
     mAnimatedGeometryRoot(nullptr),
     mFixedPosFrameForLayerData(nullptr),
     mReferenceFrame(nullptr),
     mLayer(nullptr),
     mIsSolidColorInVisibleRegion(false),
     mSingleItemFixedToViewport(false),
     mNeedComponentAlpha(false),
     mForceTransparentSurface(false),
+    mHideAllLayersBelow(false),
     mImage(nullptr),
     mCommonClipCount(-1),
+    mNewChildLayersIndex(-1),
     mAllDrawingAbove(false)
   {}
   /**
    * Record that an item has been added to the ThebesLayer, so we
    * need to update our regions.
    * @param aVisibleRect the area of the item that's visible
    * @param aDrawRect the area of the item that would be drawn if it
    * was completely visible
@@ -419,16 +419,20 @@ public:
   bool mNeedComponentAlpha;
   /**
    * Set if the layer should be treated as transparent, even if its entire
    * area is covered by opaque display items. For example, this needs to
    * be set if something is going to "punch holes" in the layer by clearing
    * part of its surface.
    */
   bool mForceTransparentSurface;
+  /**
+   * Set if all layers below this ThebesLayer should be hidden.
+   */
+  bool mHideAllLayersBelow;
 
   /**
    * Stores the pointer to the nsDisplayImage if we want to
    * convert this to an ImageLayer.
    */
   nsDisplayImageContainer* mImage;
   /**
    * Stores the clip that we need to apply to the image or, if there is no
@@ -441,16 +445,20 @@ public:
   DisplayItemClip mItemClip;
   /**
    * The first mCommonClipCount rounded rectangle clips are identical for
    * all items in the layer.
    * -1 if there are no items in the layer; must be >=0 by the time that this
    * data is popped from the stack.
    */
   int32_t mCommonClipCount;
+  /**
+   * Index of this layer in mNewChildLayers.
+   */
+  int32_t mNewChildLayersIndex;
   /*
    * Updates mCommonClipCount by checking for rounded rect clips in common
    * between the clip on a new item (aCurrentClip) and the common clips
    * on items already in the layer (the first mCommonClipCount rounded rects
    * in mItemClip).
    */
   void UpdateCommonClipCount(const DisplayItemClip& aCurrentClip);
 
@@ -474,16 +482,49 @@ private:
   nsIntRegion  mDrawAboveRegion;
   /**
    * True if mDrawAboveRegion and mVisibleAboveRegion should be treated
    * as infinite, and all display items should be considered 'above' this layer.
    */
   bool mAllDrawingAbove;
 };
 
+struct NewLayerEntry {
+  NewLayerEntry()
+    : mAnimatedGeometryRoot(nullptr)
+    , mFixedPosFrameForLayerData(nullptr)
+    , mLayerContentsVisibleRect(0, 0, -1, -1)
+    , mHideAllLayersBelow(false)
+    , mOpaqueForAnimatedGeometryRootParent(false)
+  {}
+  // mLayer is null if the previous entry is for a ThebesLayer that hasn't
+  // been optimized to some other form (yet).
+  nsRefPtr<Layer> mLayer;
+  const nsIFrame* mAnimatedGeometryRoot;
+  const nsIFrame* mFixedPosFrameForLayerData;
+  // The following are only used for retained layers (for occlusion
+  // culling of those layers). These regions are all relative to the
+  // container reference frame.
+  nsIntRegion mVisibleRegion;
+  nsIntRegion mOpaqueRegion;
+  // This rect is in the layer's own coordinate space. The computed visible
+  // region for the layer cannot extend beyond this rect.
+  nsIntRect mLayerContentsVisibleRect;
+  bool mHideAllLayersBelow;
+  // When mOpaqueForAnimatedGeometryRootParent is true, the opaque region of
+  // this layer is opaque in the same position even subject to the animation of
+  // geometry of mAnimatedGeometryRoot. For example when mAnimatedGeometryRoot
+  // is a scrolled frame and the scrolled content is opaque everywhere in the
+  // displayport, we can set this flag.
+  // When this flag is set, we can treat this opaque region as covering
+  // content whose animated geometry root is the animated geometry root for
+  // mAnimatedGeometryRoot->GetParent().
+  bool mOpaqueForAnimatedGeometryRootParent;
+};
+
 /**
  * This is a helper object used to build up the layer children for
  * a ContainerLayer.
  */
 class ContainerState {
 public:
   ContainerState(nsDisplayListBuilder* aBuilder,
                  LayerManager* aManager,
@@ -504,16 +545,20 @@ public:
     nsPresContext* presContext = aContainerFrame->PresContext();
     mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
     mContainerReferenceFrame =
       const_cast<nsIFrame*>(aContainerItem ? aContainerItem->ReferenceFrameForChildren() :
                                              mBuilder->FindReferenceFrameFor(mContainerFrame));
     mContainerAnimatedGeometryRoot = aContainerItem
       ? nsLayoutUtils::GetAnimatedGeometryRootFor(aContainerItem, aBuilder)
       : mContainerReferenceFrame;
+    NS_ASSERTION(!aContainerItem || !aContainerItem->ShouldFixToViewport(aBuilder),
+                 "Container items never return true for ShouldFixToViewport");
+    mContainerFixedPosFrame =
+        FindFixedPosFrameForLayerData(mContainerAnimatedGeometryRoot, false);
     // When AllowResidualTranslation is false, display items will be drawn
     // scaled with a translation by integer pixels, so we know how the snapping
     // will work.
     mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() &&
       !mParameters.AllowResidualTranslation();
     CollectOldLayers();
   }
 
@@ -529,17 +574,19 @@ public:
   /**
    * This finalizes all the open ThebesLayers by popping every element off
    * mThebesLayerDataStack, then sets the children of the container layer
    * to be all the layers in mNewChildLayers in that order and removes any
    * layers as children of the container that aren't in mNewChildLayers.
    * @param aTextContentFlags if any child layer has CONTENT_COMPONENT_ALPHA,
    * set *aTextContentFlags to CONTENT_COMPONENT_ALPHA
    */
-  void Finish(uint32_t *aTextContentFlags, LayerManagerData* aData);
+  void Finish(uint32_t *aTextContentFlags, LayerManagerData* aData,
+              const nsIntRect& aContainerPixelBounds,
+              nsDisplayList* aChildItems);
 
   nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; }
 
   nsIntRect ScaleToNearestPixels(const nsRect& aRect)
   {
     return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale,
                                       mAppUnitsPerDevPixel);
   }
@@ -578,16 +625,28 @@ public:
   {
     if (aSnap && mSnappingEnabled) {
       return ScaleRegionToNearestPixels(aRegion);
     }
     return aRegion.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale,
                                         mAppUnitsPerDevPixel);
   }
 
+  nsIFrame* GetContainerFrame() const { return mContainerFrame; }
+
+  /**
+   * Sets aOuterVisibleRegion as aLayer's visible region. aOuterVisibleRegion
+   * is in the coordinate space of the container reference frame.
+   * aLayerContentsVisibleRect, if non-null, is in the layer's own
+   * coordinate system.
+   */
+  void SetOuterVisibleRegionForLayer(Layer* aLayer,
+                                     const nsIntRegion& aOuterVisibleRegion,
+                                     const nsIntRect* aLayerContentsVisibleRect = nullptr) const;
+
 protected:
   friend class ThebesLayerData;
 
   /**
    * Grab the next recyclable ThebesLayer, or create one if there are no
    * more recyclable ThebesLayers. Does any necessary invalidation of
    * a recycled ThebesLayer, and sets up the transform on the ThebesLayer
    * to account for scrolling.
@@ -650,16 +709,46 @@ protected:
                                           const nsIntRegion& aDrawRegion,
                                           nsIntRegion* aVisibleRegion,
                                           bool* aIsSolidColorInVisibleRegion = nullptr);
   /**
    * Set fixed-pos layer metadata on aLayer according to the data for aFixedPosFrame.
    */
   void SetFixedPositionLayerData(Layer* aLayer,
                                  const nsIFrame* aFixedPosFrame);
+
+  /**
+   * Returns true if aItem's opaque area (in aOpaque) covers the entire
+   * scrollable area of its presshell.
+   */
+  bool ItemCoversScrollableArea(nsDisplayItem* aItem, const nsRegion& aOpaque);
+
+  /**
+   * For each layer in mNewChildLayers, remove from its visible region the
+   * opaque regions of the layers at higher z-index, but only if they have
+   * the same animated geometry root and fixed-pos frame ancestor.
+   * The opaque region for the child layers that share the same animated
+   * geometry root as the container frame is returned in
+   * *aOpaqueRegionForContainer.
+   */
+  void ApplyOcclusionCulling(nsIntRegion* aOpaqueRegionForContainer);
+
+  /**
+   * Computes the snapped opaque area of aItem. Sets aList's opaque flag
+   * if it covers the entire list bounds. Sets *aHideAllLayersBelow to true
+   * this item covers the entire viewport so that all layers below are
+   * permanently invisible.
+   */
+  nsIntRegion ComputeOpaqueRect(nsDisplayItem* aItem,
+                                const nsIFrame* aAnimatedGeometryRoot,
+                                const nsIFrame* aFixedPosFrame,
+                                const DisplayItemClip& aClip,
+                                nsDisplayList* aList,
+                                bool* aHideAllLayersBelow);
+
   /**
    * Indicate that we are done adding items to the ThebesLayer at the top of
    * mThebesLayerDataStack. Set the final visible region and opaque-content
    * flag, and pop it off the stack.
    */
   void PopThebesLayerData();
   /**
    * Find the ThebesLayer to which we should assign the next display item.
@@ -692,48 +781,56 @@ protected:
         : mThebesLayerDataStack[mThebesLayerDataStack.Length() - 1].get();
   }
 
   /* Build a mask layer to represent the clipping region. Will return null if
    * there is no clipping specified or a mask layer cannot be built.
    * Builds an ImageLayer for the appropriate backend; the mask is relative to
    * aLayer's visible region.
    * aLayer is the layer to be clipped.
+   * aLayerVisibleRegion is the region that will be set as aLayer's visible region,
+   * relative to the container reference frame
    * aRoundedRectClipCount is used when building mask layers for ThebesLayers,
    * SetupMaskLayer will build a mask layer for only the first
    * aRoundedRectClipCount rounded rects in aClip
    */
   void SetupMaskLayer(Layer *aLayer, const DisplayItemClip& aClip,
+                      const nsIntRegion& aLayerVisibleRegion,
                       uint32_t aRoundedRectClipCount = UINT32_MAX);
 
   bool ChooseAnimatedGeometryRoot(const nsDisplayList& aList,
                                   const nsIFrame **aAnimatedGeometryRoot);
 
   nsDisplayListBuilder*            mBuilder;
   LayerManager*                    mManager;
   FrameLayerBuilder*               mLayerBuilder;
   nsIFrame*                        mContainerFrame;
   nsIFrame*                        mContainerReferenceFrame;
   const nsIFrame*                  mContainerAnimatedGeometryRoot;
+  const nsIFrame*                  mContainerFixedPosFrame;
   ContainerLayer*                  mContainerLayer;
   nsRect                           mContainerBounds;
   DebugOnly<nsRect>                mAccumulatedChildBounds;
   ContainerLayerParameters         mParameters;
   /**
    * The region of ThebesLayers that should be invalidated every time
    * we recycle one.
    */
   nsIntRegion                      mInvalidThebesContent;
   nsAutoTArray<nsAutoPtr<ThebesLayerData>,1>  mThebesLayerDataStack;
   /**
    * We collect the list of children in here. During ProcessDisplayItems,
    * the layers in this array either have mContainerLayer as their parent,
    * or no parent.
+   * ThebesLayers have two entries in this array: the second one is used only if
+   * the ThebesLayer is optimized away to a ColorLayer or ImageLayer.
+   * It's essential that this array is only appended to, since ThebesLayerData
+   * records the index of its ThebesLayer in this array.
    */
-  typedef nsAutoTArray<nsRefPtr<Layer>,1> AutoLayersArray;
+  typedef nsAutoTArray<NewLayerEntry,1> AutoLayersArray;
   AutoLayersArray                  mNewChildLayers;
   nsTArray<nsRefPtr<ThebesLayer> > mRecycledThebesLayers;
   nsDataHashtable<nsPtrHashKey<Layer>, nsRefPtr<ImageLayer> >
     mRecycledMaskImageLayers;
   uint32_t                         mNextFreeRecycledThebesLayer;
   nscoord                          mAppUnitsPerDevPixel;
   bool                             mSnappingEnabled;
 };
@@ -1599,48 +1696,59 @@ AppUnitsPerDevPixel(nsDisplayItem* aItem
   if (aItem->GetType() == nsDisplayItem::TYPE_ZOOM) {
     return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
   }
   return aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
 }
 #endif
 
 /**
- * Restrict the visible region of aLayer to the region that is actually visible.
- * Because we only reduce the visible region here, we don't need to worry
- * about whether CONTENT_OPAQUE is set; if layer was opaque in the old
- * visible region, it will still be opaque in the new one.
- * @param aLayerVisibleRect if non-null, the visible rect of the layer, in
- * the layer's coordinate space
- * @param aOuterVisibleRect the rect to restrict the visible region to, in the
- * parent's coordinate system
+ * Set the visible region for aLayer.
+ * aOuterVisibleRegion is the visible region relative to the parent layer.
+ * Consumes *aOuterVisibleRegion.
  */
 static void
-SetVisibleRegionForLayer(Layer* aLayer, const nsIntRect* aLayerVisibleRect,
-                         const nsIntRect& aOuterVisibleRect)
+SetOuterVisibleRegion(Layer* aLayer, nsIntRegion* aOuterVisibleRegion,
+                      const nsIntRect* aLayerContentsVisibleRect = nullptr)
 {
   gfx3DMatrix transform;
   To3DMatrix(aLayer->GetTransform(), transform);
-
-  // if 'transform' is not invertible, then nothing will be displayed
-  // for the layer, so it doesn't really matter what we do here
-  gfxRect outerVisible(aOuterVisibleRect.x, aOuterVisibleRect.y,
-                       aOuterVisibleRect.width, aOuterVisibleRect.height);
-  gfxRect layerVisible = transform.Inverse().ProjectRectBounds(outerVisible);
-  layerVisible.RoundOut();
-
-  nsIntRect visibleRect;
-  if (!gfxUtils::GfxRectToIntRect(layerVisible, &visibleRect)) {
-    aLayer->SetVisibleRegion(nsIntRegion());
+  gfxMatrix transform2D;
+  if (transform.Is2D(&transform2D) && !transform2D.HasNonIntegerTranslation()) {
+    aOuterVisibleRegion->MoveBy(-int(transform2D._31), -int(transform2D._32));
   } else {
-    if (aLayerVisibleRect) {
-      visibleRect.IntersectRect(visibleRect, *aLayerVisibleRect);
+    nsIntRect outerRect = aOuterVisibleRegion->GetBounds();
+    // if 'transform' is not invertible, then nothing will be displayed
+    // for the layer, so it doesn't really matter what we do here
+    gfxRect outerVisible(outerRect.x, outerRect.y, outerRect.width, outerRect.height);
+    gfxRect layerVisible = transform.Inverse().ProjectRectBounds(outerVisible);
+    layerVisible.RoundOut();
+    nsIntRect visRect;
+    if (gfxUtils::GfxRectToIntRect(layerVisible, &visRect)) {
+      *aOuterVisibleRegion = visRect;
+    } else  {
+      aOuterVisibleRegion->SetEmpty();
     }
-    aLayer->SetVisibleRegion(nsIntRegion(visibleRect));
+  }
+
+  if (aLayerContentsVisibleRect && aLayerContentsVisibleRect->width >= 0) {
+    aOuterVisibleRegion->And(*aOuterVisibleRegion, *aLayerContentsVisibleRect);
   }
+
+  aLayer->SetVisibleRegion(*aOuterVisibleRegion);
+}
+
+void
+ContainerState::SetOuterVisibleRegionForLayer(Layer* aLayer,
+                                              const nsIntRegion& aOuterVisibleRegion,
+                                              const nsIntRect* aLayerContentsVisibleRect) const
+{
+  nsIntRegion visRegion = aOuterVisibleRegion;
+  visRegion.MoveBy(mParameters.mOffset);
+  SetOuterVisibleRegion(aLayer, &visRegion, aLayerContentsVisibleRect);
 }
 
 nscolor
 ContainerState::FindOpaqueBackgroundColorFor(int32_t aThebesLayerIndex)
 {
   ThebesLayerData* target = mThebesLayerDataStack[aThebesLayerIndex];
   for (int32_t i = aThebesLayerIndex - 1; i >= 0; --i) {
     ThebesLayerData* candidate = mThebesLayerDataStack[i];
@@ -1839,18 +1947,17 @@ ContainerState::SetFixedPositionLayerDat
   nsLayoutUtils::SetFixedPositionLayerData(aLayer,
       viewportFrame, anchorRect, aFixedPosFrame, presContext, mParameters);
 }
 
 static bool
 CanOptimizeAwayThebesLayer(ThebesLayerData* aData,
                            FrameLayerBuilder* aLayerBuilder)
 {
-  bool isRetained = aData->mLayer->Manager()->IsWidgetLayerManager();
-  if (!isRetained) {
+  if (!aLayerBuilder->IsBuildingRetainedLayers()) {
     return false;
   }
 
   // If there's no thebes layer with valid content in it that we can reuse,
   // always create a color or image layer (and potentially throw away an
   // existing completely invalid thebes layer).
   if (aData->mLayer->GetValidRegion().IsEmpty()) {
     return true;
@@ -1858,31 +1965,45 @@ CanOptimizeAwayThebesLayer(ThebesLayerDa
 
   // There is an existing thebes layer we can reuse. Throwing it away can make
   // compositing cheaper (see bug 946952), but it might cause us to re-allocate
   // the thebes layer frequently due to an animation. So we only discard it if
   // we're in tree compression mode, which is triggered at a low frequency.
   return aLayerBuilder->CheckInLayerTreeCompressionMode();
 }
 
+#ifdef DEBUG
+static int32_t FindIndexOfLayerIn(nsTArray<NewLayerEntry>& aArray,
+                                  Layer* aLayer)
+{
+  for (uint32_t i = 0; i < aArray.Length(); ++i) {
+    if (aArray[i].mLayer == aLayer) {
+      return i;
+    }
+  }
+  return -1;
+}
+#endif
+
 void
 ContainerState::PopThebesLayerData()
 {
   NS_ASSERTION(!mThebesLayerDataStack.IsEmpty(), "Can't pop");
 
   int32_t lastIndex = mThebesLayerDataStack.Length() - 1;
   ThebesLayerData* data = mThebesLayerDataStack[lastIndex];
 
   AdjustLayerDataForFixedPositioning(data->mFixedPosFrameForLayerData,
                                      data->mDrawRegion,
                                      &data->mVisibleRegion,
                                      &data->mIsSolidColorInVisibleRegion);
+
+  NewLayerEntry* newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex];
   nsRefPtr<Layer> layer;
   nsRefPtr<ImageContainer> imageContainer = data->CanOptimizeImageLayer(mBuilder);
-
   if ((data->mIsSolidColorInVisibleRegion || imageContainer) &&
       CanOptimizeAwayThebesLayer(data, mLayerBuilder)) {
     NS_ASSERTION(!(data->mIsSolidColorInVisibleRegion && imageContainer),
                  "Can't be a solid color as well as an image!");
     if (imageContainer) {
       nsRefPtr<ImageLayer> imageLayer = CreateOrRecycleImageLayer(data->mLayer);
       imageLayer->SetContainer(imageContainer);
       data->mImage->ConfigureLayer(imageLayer, mParameters.mOffset);
@@ -1908,47 +2029,51 @@ ContainerState::PopThebesLayerData()
 
       nsIntRect visibleRect = data->mVisibleRegion.GetBounds();
       visibleRect.MoveBy(-GetTranslationForThebesLayer(data->mLayer));
       colorLayer->SetBounds(visibleRect);
 
       layer = colorLayer;
     }
 
-    NS_ASSERTION(!mNewChildLayers.Contains(layer), "Layer already in list???");
-    AutoLayersArray::index_type index = mNewChildLayers.IndexOf(data->mLayer);
-    NS_ASSERTION(index != AutoLayersArray::NoIndex, "Thebes layer not found?");
-    mNewChildLayers.InsertElementAt(index + 1, layer);
+    NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
+                 "Layer already in list???");
+    NS_ASSERTION(newLayerEntry->mLayer == data->mLayer,
+                 "Thebes layer at wrong index");
+    // Store optimized layer in reserved slot
+    newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex + 1];
+    NS_ASSERTION(!newLayerEntry->mLayer, "Slot already occupied?");
+    newLayerEntry->mLayer = layer;
+    newLayerEntry->mAnimatedGeometryRoot = data->mAnimatedGeometryRoot;
+    newLayerEntry->mFixedPosFrameForLayerData = data->mFixedPosFrameForLayerData;
 
     // Hide the ThebesLayer. We leave it in the layer tree so that we
     // can find and recycle it later.
     nsIntRect emptyRect;
     data->mLayer->SetClipRect(&emptyRect);
     data->mLayer->SetVisibleRegion(nsIntRegion());
     data->mLayer->SetEventRegions(EventRegions());
   } else {
     layer = data->mLayer;
     imageContainer = nullptr;
     layer->SetClipRect(nullptr);
   }
 
-  Matrix transform;
-  if (!layer->GetTransform().Is2D(&transform)) {
-    NS_ERROR("Only 2D transformations currently supported");
-  }
-
-  // ImageLayers are already configured with a visible region
-  if (!imageContainer) {
-    NS_ASSERTION(!transform.HasNonIntegerTranslation(),
-                 "Matrix not just an integer translation?");
-    // Convert from relative to the container to relative to the
-    // ThebesLayer itself.
-    nsIntRegion rgn = data->mVisibleRegion;
-    rgn.MoveBy(-GetTranslationForThebesLayer(data->mLayer));
-    layer->SetVisibleRegion(rgn);
+  if (mLayerBuilder->IsBuildingRetainedLayers()) {
+    newLayerEntry->mVisibleRegion = data->mVisibleRegion;
+    newLayerEntry->mOpaqueRegion = data->mOpaqueRegion;
+    newLayerEntry->mHideAllLayersBelow = data->mHideAllLayersBelow;
+    if (nsLayoutUtils::GetScrollableFrameFor(newLayerEntry->mAnimatedGeometryRoot) &&
+        !nsDisplayScrollLayer::IsConstructingScrollLayerForScrolledFrame(newLayerEntry->mAnimatedGeometryRoot)) {
+      // Async scrolling not currently active so we can propagate our opaque region
+      // up to the parent animated geometry root.
+      newLayerEntry->mOpaqueForAnimatedGeometryRootParent = true;
+    }
+  } else {
+    SetOuterVisibleRegionForLayer(layer, data->mVisibleRegion);
   }
 
   nsIntRegion transparentRegion;
   transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion);
   bool isOpaque = transparentRegion.IsEmpty();
   // For translucent ThebesLayers, try to find an opaque background
   // color that covers the entire area beneath it so we can pull that
   // color into this layer to make it opaque.
@@ -1980,24 +2105,24 @@ ContainerState::PopThebesLayerData()
       data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion());
     }
     userData->mForcedBackgroundColor = backgroundColor;
 
     // use a mask layer for rounded rect clipping.
     // data->mCommonClipCount may be -1 if we haven't put any actual
     // drawable items in this layer (i.e. it's only catching events).
     int32_t commonClipCount = std::max(0, data->mCommonClipCount);
-    SetupMaskLayer(layer, data->mItemClip, commonClipCount);
+    SetupMaskLayer(layer, data->mItemClip, data->mVisibleRegion, commonClipCount);
     // copy commonClipCount to the entry
     FrameLayerBuilder::ThebesLayerItemsEntry* entry = mLayerBuilder->
       GetThebesLayerItemsEntry(static_cast<ThebesLayer*>(layer.get()));
     entry->mCommonClipCount = commonClipCount;
   } else {
     // mask layer for image and color layers
-    SetupMaskLayer(layer, data->mItemClip);
+    SetupMaskLayer(layer, data->mItemClip, data->mVisibleRegion);
   }
 
   uint32_t flags = 0;
   nsIWidget* widget = mContainerReferenceFrame->PresContext()->GetRootWidget();
   // See bug 941095. Not quite ready to disable this.
   bool hidpi = false && widget && widget->GetDefaultScale().scale >= 2;
   if (hidpi) {
     flags |= Layer::CONTENT_DISABLE_SUBPIXEL_AA;
@@ -2275,18 +2400,28 @@ ContainerState::FindThebesLayerFor(nsDis
     mThebesLayerDataStack.AppendElement(thebesLayerData);
     thebesLayerData->mLayer = layer;
     thebesLayerData->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
     thebesLayerData->mFixedPosFrameForLayerData =
       FindFixedPosFrameForLayerData(aAnimatedGeometryRoot, aShouldFixToViewport);
     thebesLayerData->mReferenceFrame = aItem->ReferenceFrame();
     thebesLayerData->mSingleItemFixedToViewport = aShouldFixToViewport;
 
-    NS_ASSERTION(!mNewChildLayers.Contains(layer), "Layer already in list???");
-    *mNewChildLayers.AppendElement() = layer.forget();
+    NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
+                 "Layer already in list???");
+    thebesLayerData->mNewChildLayersIndex = mNewChildLayers.Length();
+    NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
+    newLayerEntry->mLayer = layer.forget();
+    newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
+    newLayerEntry->mFixedPosFrameForLayerData = thebesLayerData->mFixedPosFrameForLayerData;
+    // newLayerEntry->mOpaqueRegion is filled in later from
+    // thebesLayerData->mOpaqueRegion, if necessary.
+
+    // Allocate another entry for this layer's optimization to ColorLayer/ImageLayer
+    mNewChildLayers.AppendElement();
   } else {
     thebesLayerData = mThebesLayerDataStack[lowestUsableLayerWithScrolledRoot];
   }
 
   return thebesLayerData;
 }
 
 #ifdef MOZ_DUMP_PAINTING
@@ -2406,16 +2541,65 @@ IsScrollLayerItemAndOverlayScrollbarForS
           nsDisplayOwnLayer::HORIZONTAL_SCROLLBAR)) &&
         layerItem->Frame()->GetParent() == scrollItem->GetScrollFrame()) {
       return true;
     }
   }
   return false;
 }
 
+bool
+ContainerState::ItemCoversScrollableArea(nsDisplayItem* aItem, const nsRegion& aOpaque)
+{
+  nsIPresShell* presShell = aItem->Frame()->PresContext()->PresShell();
+  nsIFrame* rootFrame = presShell->GetRootFrame();
+  if (mContainerFrame != rootFrame) {
+    return false;
+  }
+  nsRect scrollableArea;
+  if (presShell->IsScrollPositionClampingScrollPortSizeSet()) {
+    scrollableArea = nsRect(nsPoint(0, 0),
+      presShell->GetScrollPositionClampingScrollPortSize());
+  } else {
+    scrollableArea = rootFrame->GetRectRelativeToSelf();
+  }
+  return aOpaque.Contains(scrollableArea);
+}
+
+nsIntRegion
+ContainerState::ComputeOpaqueRect(nsDisplayItem* aItem,
+                                  const nsIFrame* aAnimatedGeometryRoot,
+                                  const nsIFrame* aFixedPosFrame,
+                                  const DisplayItemClip& aClip,
+                                  nsDisplayList* aList,
+                                  bool* aHideAllLayersBelow)
+{
+  bool snapOpaque;
+  nsRegion opaque = aItem->GetOpaqueRegion(mBuilder, &snapOpaque);
+  nsIntRegion opaquePixels;
+  if (!opaque.IsEmpty()) {
+    nsRegion opaqueClipped;
+    nsRegionRectIterator iter(opaque);
+    for (const nsRect* r = iter.Next(); r; r = iter.Next()) {
+      opaqueClipped.Or(opaqueClipped, aClip.ApproximateIntersectInward(*r));
+    }
+    if (aAnimatedGeometryRoot == mContainerAnimatedGeometryRoot &&
+        aFixedPosFrame == mContainerFixedPosFrame &&
+        !aList->IsOpaque() &&
+        opaqueClipped.Contains(mContainerBounds)) {
+      aList->SetIsOpaque();
+    }
+    opaquePixels = ScaleRegionToInsidePixels(opaqueClipped, snapOpaque);
+    if (aFixedPosFrame && ItemCoversScrollableArea(aItem, opaque)) {
+      *aHideAllLayersBelow = true;
+    }
+  }
+  return opaquePixels;
+}
+
 /*
  * Iterate through the non-clip items in aList and its descendants.
  * For each item we compute the effective clip rect. Each item is assigned
  * to a layer. We invalidate the areas in ThebesLayers where an item
  * has moved from one ThebesLayer to another. Also,
  * aState->mInvalidThebesContent is invalidated in every ThebesLayer.
  * We set the clip rect for items that generated their own layer, and
  * create a mask layer to do any rounded rect clipping.
@@ -2499,17 +2683,16 @@ ContainerState::ProcessDisplayItems(nsDi
       clipRect = ScaleToNearestPixels(itemClip.GetClipRect());
       itemDrawRect.IntersectRect(itemDrawRect, clipRect);
       clipRect.MoveBy(mParameters.mOffset);
     }
 #ifdef DEBUG
     ((nsRect&)mAccumulatedChildBounds).UnionRect(mAccumulatedChildBounds, itemContent);
 #endif
     itemVisibleRect.IntersectRect(itemVisibleRect, itemDrawRect);
-    ThebesLayerData* accumulateIntoThebesLayerData = nullptr;
 
     LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters);
     if (layerState == LAYER_INACTIVE &&
         nsDisplayItem::ForceActiveLayers()) {
       layerState = LAYER_ACTIVE;
     }
 
     bool forceInactive;
@@ -2564,23 +2747,19 @@ ContainerState::ProcessDisplayItems(nsDi
       // Note that items without their own layers can't be skipped this
       // way, since their ThebesLayer may decide it wants to draw them
       // into its buffer even if they're currently covered.
       if (itemVisibleRect.IsEmpty() &&
           !item->ShouldBuildLayerEvenIfInvisible(mBuilder)) {
         continue;
       }
 
-      if (itemType == nsDisplayItem::TYPE_TRANSFORM) {
-        mParameters.mAncestorClipRect = itemClip.HasClip() ? &clipRect : nullptr;
-      } else {
-        mParameters.mAncestorClipRect = nullptr;
-      }
-
       // Just use its layer.
+      nsIntRect layerContentsVisibleRect(0, 0, -1, -1);
+      mParameters.mLayerContentsVisibleRect = &layerContentsVisibleRect;
       nsRefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager, mParameters);
       if (!ownLayer) {
         continue;
       }
 
       NS_ASSERTION(!ownLayer->AsThebesLayer(),
                    "Should never have created a dedicated Thebes layer!");
 
@@ -2645,35 +2824,49 @@ ContainerState::ProcessDisplayItems(nsDi
 
           // Add the entire bounds rect to the mDrawAboveRegion.
           // The visible region may be excluding opaque content above the
           // item, and we need to ensure that that content is not placed
           // in a ThebesLayer below the item!
           data->AddDrawAboveRegion(itemDrawRect);
         }
       }
-      itemVisibleRect.MoveBy(mParameters.mOffset);
-      if (item->SetVisibleRegionOnLayer()) {
-        SetVisibleRegionForLayer(ownLayer, nullptr, itemVisibleRect);
-      }
 
       // rounded rectangle clipping using mask layers
       // (must be done after visible rect is set on layer)
       if (itemClip.IsRectClippedByRoundedCorner(itemContent)) {
-        SetupMaskLayer(ownLayer, itemClip);
+        SetupMaskLayer(ownLayer, itemClip, itemVisibleRect);
       }
 
       ContainerLayer* oldContainer = ownLayer->GetParent();
       if (oldContainer && oldContainer != mContainerLayer) {
         oldContainer->RemoveChild(ownLayer);
       }
-      NS_ASSERTION(!mNewChildLayers.Contains(ownLayer),
+      NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0,
                    "Layer already in list???");
 
-      mNewChildLayers.AppendElement(ownLayer);
+      NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
+      newLayerEntry->mLayer = ownLayer;
+      newLayerEntry->mAnimatedGeometryRoot = animatedGeometryRoot;
+      newLayerEntry->mFixedPosFrameForLayerData = fixedPosFrame;
+      if (mLayerBuilder->IsBuildingRetainedLayers()) {
+        newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect;
+        newLayerEntry->mVisibleRegion = itemVisibleRect;
+        newLayerEntry->mOpaqueRegion = ComputeOpaqueRect(item,
+          animatedGeometryRoot, fixedPosFrame, itemClip, aList,
+          &newLayerEntry->mHideAllLayersBelow);
+      } else {
+        SetOuterVisibleRegionForLayer(ownLayer, itemVisibleRect,
+                                      &layerContentsVisibleRect);
+      }
+      if (itemType == nsDisplayItem::TYPE_SCROLL_LAYER) {
+        nsDisplayScrollLayer* scrollItem = static_cast<nsDisplayScrollLayer*>(item);
+        newLayerEntry->mOpaqueForAnimatedGeometryRootParent =
+            scrollItem->IsDisplayPortOpaque();
+      }
 
       /**
        * No need to allocate geometry for items that aren't
        * part of a ThebesLayer.
        */
       nsAutoPtr<nsDisplayItemGeometry> dummy;
       mLayerBuilder->AddLayerDisplayItem(ownLayer, item,
                                          itemClip, layerState,
@@ -2693,43 +2886,27 @@ ContainerState::ProcessDisplayItems(nsDi
       } else {
         // check to see if the new item has rounded rect clips in common with
         // other items in the layer
         thebesLayerData->UpdateCommonClipCount(itemClip);
 
         nsAutoPtr<nsDisplayItemGeometry> geometry(item->AllocateGeometry(mBuilder));
         InvalidateForLayerChange(item, thebesLayerData->mLayer, itemClip, topLeft, geometry);
 
-        mLayerBuilder->AddThebesDisplayItem(thebesLayerData, item, itemClip,
-                                            mContainerFrame,
-                                            layerState, topLeft,
-                                            geometry);
-        accumulateIntoThebesLayerData = thebesLayerData;
+        mLayerBuilder->AddThebesDisplayItem(thebesLayerData, item, itemClip, itemVisibleRect,
+                                            *this, layerState, topLeft, geometry);
+        nsIntRegion opaquePixels = ComputeOpaqueRect(item,
+            animatedGeometryRoot, thebesLayerData->mFixedPosFrameForLayerData,
+            itemClip, aList,
+            &thebesLayerData->mHideAllLayersBelow);
+        thebesLayerData->Accumulate(this, item, opaquePixels,
+            itemVisibleRect, itemDrawRect, itemClip);
       }
     }
 
-    bool snapOpaque;
-    nsRegion opaque = item->GetOpaqueRegion(mBuilder, &snapOpaque);
-    nsRegion opaqueClipped;
-    if (!opaque.IsEmpty()) {
-      nsRegionRectIterator iter(opaque);
-      for (const nsRect* r = iter.Next(); r; r = iter.Next()) {
-        opaqueClipped.Or(opaqueClipped, itemClip.ApproximateIntersectInward(*r));
-      }
-      if (opaqueClipped.Contains(mContainerBounds)) {
-        aList->SetIsOpaque();
-      }
-    }
-    if (accumulateIntoThebesLayerData) {
-      nsIntRegion opaquePixels =
-          ScaleRegionToInsidePixels(opaqueClipped, snapOpaque);
-      accumulateIntoThebesLayerData->Accumulate(this, item,
-          opaquePixels, itemVisibleRect, itemDrawRect, itemClip);
-    }
-
     if (itemSameCoordinateSystemChildren &&
         itemSameCoordinateSystemChildren->NeedsTransparentSurface()) {
       aList->SetNeedsTransparentSurface();
     }
   }
 
   aList->AppendToTop(&savedItems);
 }
@@ -2868,17 +3045,18 @@ ContainerState::InvalidateForLayerChange
         GetTranslationForThebesLayer(newThebesLayer));
   }
 }
 
 void
 FrameLayerBuilder::AddThebesDisplayItem(ThebesLayerData* aLayerData,
                                         nsDisplayItem* aItem,
                                         const DisplayItemClip& aClip,
-                                        nsIFrame* aContainerLayerFrame,
+                                        const nsIntRect& aItemVisibleRect,
+                                        const ContainerState& aContainerState,
                                         LayerState aLayerState,
                                         const nsPoint& aTopLeft,
                                         nsAutoPtr<nsDisplayItemGeometry> aGeometry)
 {
   ThebesLayer* layer = aLayerData->mLayer;
   ThebesDisplayItemLayerUserData* thebesData =
     static_cast<ThebesDisplayItemLayerUserData*>
       (layer->GetUserData(&gThebesDisplayItemLayerUserData));
@@ -2908,17 +3086,17 @@ FrameLayerBuilder::AddThebesDisplayItem(
                                                       thebesData->mAppUnitsPerDevPixel);
     }
   }
 
   AddLayerDisplayItem(layer, aItem, aClip, aLayerState, aTopLeft, tempManager, aGeometry);
 
   ThebesLayerItemsEntry* entry = mThebesLayerItems.PutEntry(layer);
   if (entry) {
-    entry->mContainerLayerFrame = aContainerLayerFrame;
+    entry->mContainerLayerFrame = aContainerState.GetContainerFrame();
     if (entry->mContainerLayerGeneration == 0) {
       entry->mContainerLayerGeneration = mContainerLayerGeneration;
     }
     if (tempManager) {
       FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
       layerBuilder->Init(mDisplayListBuilder, tempManager, aLayerData);
 
       tempManager->BeginTransaction();
@@ -2932,16 +3110,22 @@ FrameLayerBuilder::AddThebesDisplayItem(
       // We have no easy way of detecting if this transaction will ever actually get finished.
       // For now, I've just silenced the warning with nested transactions in BasicLayers.cpp
       if (!tmpLayer) {
         tempManager->EndTransaction(nullptr, nullptr);
         tempManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
         return;
       }
 
+      bool snap;
+      nsRect visibleRect =
+        aItem->GetVisibleRect().Intersect(aItem->GetBounds(mDisplayListBuilder, &snap));
+      nsIntRegion rgn = visibleRect.ToOutsidePixels(thebesData->mAppUnitsPerDevPixel);
+      SetOuterVisibleRegion(tmpLayer, &rgn);
+
       // If BuildLayer didn't call BuildContainerLayerFor, then our new layer won't have been
       // stored in layerBuilder. Manually add it now.
       if (mRetainingManager) {
 #ifdef DEBUG_DISPLAY_ITEM_DATA
         LayerManagerData* parentLmd = static_cast<LayerManagerData*>
           (layer->Manager()->GetUserData(&gLayerManagerUserData));
         LayerManagerData* lmd = static_cast<LayerManagerData*>
           (tempManager->GetUserData(&gLayerManagerUserData));
@@ -3124,33 +3308,136 @@ ContainerState::CollectOldLayers()
     if (Layer* maskLayer = layer->GetMaskLayer()) {
       NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
                    "Could not recycle mask layer, unsupported layer type.");
       mRecycledMaskImageLayers.Put(layer, static_cast<ImageLayer*>(maskLayer));
     }
   }
 }
 
+struct OpaqueRegionEntry {
+  const nsIFrame* mAnimatedGeometryRoot;
+  const nsIFrame* mFixedPosFrameForLayerData;
+  nsIntRegion mOpaqueRegion;
+};
+
+static OpaqueRegionEntry*
+FindOpaqueRegionEntry(nsTArray<OpaqueRegionEntry>& aEntries,
+                      const nsIFrame* aAnimatedGeometryRoot,
+                      const nsIFrame* aFixedPosFrameForLayerData)
+{
+  for (uint32_t i = 0; i < aEntries.Length(); ++i) {
+    OpaqueRegionEntry* d = &aEntries[i];
+    if (d->mAnimatedGeometryRoot == aAnimatedGeometryRoot &&
+        d->mFixedPosFrameForLayerData == aFixedPosFrameForLayerData) {
+      return d;
+    }
+  }
+  return nullptr;
+}
+
 void
-ContainerState::Finish(uint32_t* aTextContentFlags, LayerManagerData* aData)
+ContainerState::ApplyOcclusionCulling(nsIntRegion* aOpaqueRegionForContainer)
+{
+  nsAutoTArray<OpaqueRegionEntry,4> opaqueRegions;
+  bool hideAll = false;
+  int32_t opaqueRegionForContainer = -1;
+
+  for (int32_t i = mNewChildLayers.Length() - 1; i >= 0; --i) {
+    NewLayerEntry* e = &mNewChildLayers.ElementAt(i);
+    if (!e->mLayer) {
+      continue;
+    }
+
+    OpaqueRegionEntry* data = FindOpaqueRegionEntry(opaqueRegions,
+        e->mAnimatedGeometryRoot, e->mFixedPosFrameForLayerData);
+
+    if (hideAll) {
+      e->mVisibleRegion.SetEmpty();
+    } else if (data) {
+      e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion);
+    }
+
+    if (!e->mOpaqueRegion.IsEmpty()) {
+      const nsIFrame* animatedGeometryRootToCover = e->mAnimatedGeometryRoot;
+      if (e->mOpaqueForAnimatedGeometryRootParent &&
+          nsLayoutUtils::GetAnimatedGeometryRootForFrame(e->mAnimatedGeometryRoot->GetParent(),
+                                                         mContainerAnimatedGeometryRoot)
+            == mContainerAnimatedGeometryRoot) {
+        animatedGeometryRootToCover = mContainerAnimatedGeometryRoot;
+        data = FindOpaqueRegionEntry(opaqueRegions,
+            animatedGeometryRootToCover, e->mFixedPosFrameForLayerData);
+      }
+
+      if (!data) {
+        if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot &&
+            !e->mFixedPosFrameForLayerData) {
+          NS_ASSERTION(opaqueRegionForContainer == -1, "Already found it?");
+          opaqueRegionForContainer = opaqueRegions.Length();
+        }
+        data = opaqueRegions.AppendElement();
+        data->mAnimatedGeometryRoot = animatedGeometryRootToCover;
+        data->mFixedPosFrameForLayerData = e->mFixedPosFrameForLayerData;
+      }
+      data->mOpaqueRegion.Or(data->mOpaqueRegion, e->mOpaqueRegion);
+      if (e->mHideAllLayersBelow) {
+        hideAll = true;
+      }
+    }
+
+    SetOuterVisibleRegionForLayer(e->mLayer, e->mVisibleRegion,
+	  e->mLayerContentsVisibleRect.width >= 0 ? &e->mLayerContentsVisibleRect : nullptr);
+
+    if (e->mLayer->GetType() == Layer::TYPE_READBACK) {
+      // ReadbackLayers need to accurately read what's behind them. So,
+      // we don't want to do any occlusion culling of layers behind them.
+      // Theoretically we could just punch out the ReadbackLayer's rectangle
+      // from all mOpaqueRegions, but that's probably not worth doing.
+      opaqueRegions.Clear();
+      opaqueRegionForContainer = -1;
+    }
+  }
+
+  if (opaqueRegionForContainer >= 0) {
+    aOpaqueRegionForContainer->Or(*aOpaqueRegionForContainer,
+        opaqueRegions[opaqueRegionForContainer].mOpaqueRegion);
+  }
+}
+
+void
+ContainerState::Finish(uint32_t* aTextContentFlags, LayerManagerData* aData,
+                       const nsIntRect& aContainerPixelBounds,
+                       nsDisplayList* aChildItems)
 {
   while (!mThebesLayerDataStack.IsEmpty()) {
     PopThebesLayerData();
   }
 
   NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds),
                "Bounds computation mismatch");
   uint32_t textContentFlags = 0;
 
+  if (mLayerBuilder->IsBuildingRetainedLayers()) {
+    nsIntRegion containerOpaqueRegion;
+    ApplyOcclusionCulling(&containerOpaqueRegion);
+    if (containerOpaqueRegion.Contains(aContainerPixelBounds)) {
+      aChildItems->SetIsOpaque();
+    }
+  }
+
   // Make sure that current/existing layers are added to the parent and are
   // in the correct order.
   Layer* layer = nullptr;
-  for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i) {
-    Layer* prevChild = i == 0 ? nullptr : mNewChildLayers[i - 1].get();
-    layer = mNewChildLayers[i];
+  Layer* prevChild = nullptr;
+  for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i, prevChild = layer) {
+    if (!mNewChildLayers[i].mLayer) {
+      continue;
+    }
+
+    layer = mNewChildLayers[i].mLayer;
 
     if (!layer->GetVisibleRegion().IsEmpty()) {
       textContentFlags |= layer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA;
     }
 
     if (!layer->GetParent()) {
       // This is not currently a child of the container, so just add it
       // now.
@@ -3328,18 +3615,18 @@ ChooseScaleAndSetTransform(FrameLayerBui
   aOutgoingScale =
     ContainerLayerParameters(scale.width, scale.height, -offset, aIncomingScale);
   if (aTransform) {
     aOutgoingScale.mInTransformedSubtree = true;
     if (ActiveLayerTracker::IsStyleAnimated(aContainerFrame, eCSSProperty_transform)) {
       aOutgoingScale.mInActiveTransformedSubtree = true;
     }
   }
-  bool isRetained = aLayer->Manager()->IsWidgetLayerManager();
-  if (isRetained && (!canDraw2D || transform2d.HasNonIntegerTranslation())) {
+  if (aLayerBuilder->IsBuildingRetainedLayers() &&
+      (!canDraw2D || transform2d.HasNonIntegerTranslation())) {
     aOutgoingScale.mDisableSubpixelAntialiasingInDescendants = true;
   }
   return true;
 }
 
 /* static */ PLDHashOperator
 FrameLayerBuilder::RestoreDisplayItemData(nsRefPtrHashKey<DisplayItemData>* aEntry, void* aUserArg)
 {
@@ -3471,37 +3758,36 @@ FrameLayerBuilder::BuildContainerLayerFo
       StoreDataForFrame(aContainerFrame, containerDisplayItemKey, containerLayer, LAYER_ACTIVE);
     }
   }
 
   LayerManagerData* data = static_cast<LayerManagerData*>
     (aManager->GetUserData(&gLayerManagerUserData));
 
   nsIntRect pixBounds;
-  int32_t appUnitsPerDevPixel;
+  nscoord appUnitsPerDevPixel;
   uint32_t stateFlags = 0;
   if ((aContainerFrame->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) &&
       mRetainingManager && !mRetainingManager->AreComponentAlphaLayersEnabled()) {
     stateFlags = ContainerState::NO_COMPONENT_ALPHA;
   }
   uint32_t flags;
   while (true) {
     ContainerState state(aBuilder, aManager, aManager->GetLayerBuilder(),
                          aContainerFrame, aContainerItem, bounds,
                          containerLayer, scaleParameters);
 
     state.ProcessDisplayItems(aChildren, stateFlags);
 
     // Set CONTENT_COMPONENT_ALPHA if any of our children have it.
     // This is suboptimal ... a child could have text that's over transparent
     // pixels in its own layer, but over opaque parts of previous siblings.
-    state.Finish(&flags, data);
-
     pixBounds = state.ScaleToOutsidePixels(bounds, false);
     appUnitsPerDevPixel = state.GetAppUnitsPerDevPixel();
+    state.Finish(&flags, data, pixBounds, aChildren);
 
     if ((flags & Layer::CONTENT_COMPONENT_ALPHA) &&
         mRetainingManager &&
         !mRetainingManager->AreComponentAlphaLayersEnabled() &&
         !stateFlags) {
       // Since we don't want any component alpha layers on BasicLayers, we repeat
       // the layer building process with this explicitely forced off.
       // We restore the previous FrameLayerBuilder state since the first set
@@ -3512,33 +3798,34 @@ FrameLayerBuilder::BuildContainerLayerFo
       mThebesLayerItems.EnumerateEntries(RestoreThebesLayerItemEntries,
                                          &mContainerLayerGeneration);
       aContainerFrame->AddStateBits(NS_FRAME_NO_COMPONENT_ALPHA);
       continue;
     }
     break;
   }
 
-  pixBounds.MoveBy(nsIntPoint(scaleParameters.mOffset.x, scaleParameters.mOffset.y));
-  if (aParameters.mAncestorClipRect && !(aFlags & CONTAINER_NOT_CLIPPED_BY_ANCESTORS)) {
-    SetVisibleRegionForLayer(containerLayer, &pixBounds,
-                             *aParameters.mAncestorClipRect);
-  } else {
-    containerLayer->SetVisibleRegion(pixBounds);
-  }
   // Make sure that rounding the visible region out didn't add any area
   // we won't paint
   if (aChildren->IsOpaque() && !aChildren->NeedsTransparentSurface()) {
     bounds.ScaleRoundIn(scaleParameters.mXScale, scaleParameters.mYScale);
     if (bounds.Contains(pixBounds.ToAppUnits(appUnitsPerDevPixel))) {
       // Clear CONTENT_COMPONENT_ALPHA
       flags = Layer::CONTENT_OPAQUE;
     }
   }
   containerLayer->SetContentFlags(flags);
+  // If aContainerItem is non-null some BuildContainerLayer further up the
+  // call stack is responsible for setting containerLayer's visible region.
+  if (!aContainerItem) {
+    containerLayer->SetVisibleRegion(pixBounds);
+  }
+  if (aParameters.mLayerContentsVisibleRect) {
+    *aParameters.mLayerContentsVisibleRect = pixBounds + scaleParameters.mOffset;
+  }
 
   mContainerLayerGeneration = oldGeneration;
   nsPresContext::ClearNotifySubDocInvalidationData(containerLayer);
 
   return containerLayer.forget();
 }
 
 Layer*
@@ -4052,31 +4339,33 @@ SetClipCount(ThebesDisplayItemLayerUserD
              uint32_t aClipCount)
 {
   if (aThebesData) {
     aThebesData->mMaskClipCount = aClipCount;
   }
 }
 
 void
-ContainerState::SetupMaskLayer(Layer *aLayer, const DisplayItemClip& aClip,
+ContainerState::SetupMaskLayer(Layer *aLayer,
+                               const DisplayItemClip& aClip,
+                               const nsIntRegion& aLayerVisibleRegion,
                                uint32_t aRoundedRectClipCount)
 {
   // if the number of clips we are going to mask has decreased, then aLayer might have
   // cached graphics which assume the existence of a soon-to-be non-existent mask layer
   // in that case, invalidate the whole layer.
   ThebesDisplayItemLayerUserData* thebesData = GetThebesDisplayItemLayerUserData(aLayer);
   if (thebesData &&
       aRoundedRectClipCount < thebesData->mMaskClipCount) {
     ThebesLayer* thebes = aLayer->AsThebesLayer();
     thebes->InvalidateRegion(thebes->GetValidRegion().GetBounds());
   }
 
   // don't build an unnecessary mask
-  nsIntRect layerBounds = aLayer->GetVisibleRegion().GetBounds();
+  nsIntRect layerBounds = aLayerVisibleRegion.GetBounds();
   if (aClip.GetRoundedRectCount() == 0 ||
       aRoundedRectClipCount == 0 ||
       layerBounds.IsEmpty()) {
     SetClipCount(thebesData, 0);
     return;
   }
 
   // check if we can re-use the mask layer
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -27,16 +27,17 @@ class ContainerLayer;
 class LayerManager;
 class BasicLayerManager;
 class ThebesLayer;
 }
 
 class FrameLayerBuilder;
 class LayerManagerData;
 class ThebesLayerData;
+class ContainerState;
 
 enum LayerState {
   LAYER_NONE,
   LAYER_INACTIVE,
   LAYER_ACTIVE,
   // Force an active layer even if it causes incorrect rendering, e.g.
   // when the layer has rounded rect clips.
   LAYER_ACTIVE_FORCE,
@@ -52,44 +53,54 @@ private:
 public:
   NS_INLINE_DECL_REFCOUNTING(RefCountedRegion)
 
   RefCountedRegion() : mIsInfinite(false) {}
   nsRegion mRegion;
   bool mIsInfinite;
 };
 
+struct NewLayerEntry;
+
 struct ContainerLayerParameters {
-  ContainerLayerParameters() :
-    mXScale(1), mYScale(1), mAncestorClipRect(nullptr),
-    mInTransformedSubtree(false), mInActiveTransformedSubtree(false),
-    mDisableSubpixelAntialiasingInDescendants(false)
+  ContainerLayerParameters()
+    : mXScale(1)
+    , mYScale(1)
+    , mLayerContentsVisibleRect(nullptr)
+    , mInTransformedSubtree(false)
+    , mInActiveTransformedSubtree(false)
+    , mDisableSubpixelAntialiasingInDescendants(false)
   {}
-  ContainerLayerParameters(float aXScale, float aYScale) :
-    mXScale(aXScale), mYScale(aYScale), mAncestorClipRect(nullptr),
-    mInTransformedSubtree(false), mInActiveTransformedSubtree(false),
-    mDisableSubpixelAntialiasingInDescendants(false)
+  ContainerLayerParameters(float aXScale, float aYScale)
+    : mXScale(aXScale)
+    , mYScale(aYScale)
+    , mLayerContentsVisibleRect(nullptr)
+    , mInTransformedSubtree(false)
+    , mInActiveTransformedSubtree(false)
+    , mDisableSubpixelAntialiasingInDescendants(false)
   {}
   ContainerLayerParameters(float aXScale, float aYScale,
                            const nsIntPoint& aOffset,
-                           const ContainerLayerParameters& aParent) :
-    mXScale(aXScale), mYScale(aYScale), mAncestorClipRect(nullptr),
-    mOffset(aOffset),
-    mInTransformedSubtree(aParent.mInTransformedSubtree),
-    mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree),
-    mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants)
+                           const ContainerLayerParameters& aParent)
+    : mXScale(aXScale)
+    , mYScale(aYScale)
+    , mLayerContentsVisibleRect(nullptr)
+    , mOffset(aOffset)
+    , mInTransformedSubtree(aParent.mInTransformedSubtree)
+    , mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree)
+    , mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants)
   {}
   float mXScale, mYScale;
   /**
-   * An ancestor clip rect that can be applied to restrict the visibility
-   * of this container. Null if none available.
+   * If non-null, the rectangle in which BuildContainerLayerFor stores the
+   * visible rect of the layer, in the coordinate system of the created layer.
    */
-  const nsIntRect* mAncestorClipRect;
+  nsIntRect* mLayerContentsVisibleRect;
   /**
-   * An offset to append to the transform set on all child layers created.
+   * An offset to apply to all child layers created.
    */
   nsIntPoint mOffset;
 
   bool mInTransformedSubtree;
   bool mInActiveTransformedSubtree;
   bool mDisableSubpixelAntialiasingInDescendants;
   /**
    * When this is false, ThebesLayer coordinates are drawn to with an integer
@@ -203,16 +214,18 @@ public:
    * children of the new container, and assigning all other items to
    * ThebesLayer children created and managed by the FrameLayerBuilder.
    * Returns a layer with clip rect cleared; it is the
    * caller's responsibility to add any clip rect. The visible region
    * is set based on what's in the layer.
    * The container layer is transformed by aTransform (if non-null), and
    * the result is transformed by the scale factors in aContainerParameters.
    * aChildren is modified due to display item merging and flattening.
+   * The visible region of the returned layer is set only if aContainerItem
+   * is null.
    */
   already_AddRefed<ContainerLayer>
   BuildContainerLayerFor(nsDisplayListBuilder* aBuilder,
                          LayerManager* aManager,
                          nsIFrame* aContainerFrame,
                          nsDisplayItem* aContainerItem,
                          nsDisplayList* aChildren,
                          const ContainerLayerParameters& aContainerParameters,
@@ -295,17 +308,18 @@ public:
    * aLayer, with aClipRect, where aContainerLayerFrame is the frame
    * for the container layer this ThebesItem belongs to.
    * aItem must have an underlying frame.
    * @param aTopLeft offset from active scrolled root to reference frame
    */
   void AddThebesDisplayItem(ThebesLayerData* aLayer,
                             nsDisplayItem* aItem,
                             const DisplayItemClip& aClip,
-                            nsIFrame* aContainerLayerFrame,
+                            const nsIntRect& aItemVisibleRect,
+                            const ContainerState& aContainerState,
                             LayerState aLayerState,
                             const nsPoint& aTopLeft,
                             nsAutoPtr<nsDisplayItemGeometry> aGeometry);
 
   /**
    * Gets the frame property descriptor for the given manager, or for the current
    * widget layer manager if nullptr is passed.
    */
@@ -592,16 +606,21 @@ public:
     return mThebesLayerItems.GetEntry(aLayer);
   }
 
   ThebesLayerData* GetContainingThebesLayerData()
   {
     return mContainingThebesLayer;
   }
 
+  bool IsBuildingRetainedLayers()
+  {
+    return !mContainingThebesLayer && mRetainingManager;
+  }
+
   /**
    * Attempt to build the most compressed layer tree possible, even if it means
    * throwing away existing retained buffers.
    */
   void SetLayerTreeCompressionMode() { mInLayerTreeCompressionMode = true; }
   bool CheckInLayerTreeCompressionMode();
 
 protected: