Bug 967844 - Part 3: Setup FrameMetrics from FrameLayerBuilder based on animated geometry roots. r=mattwoodrow, a=bajaj
authorRobert O'Callahan <robert@ocallahan.org>
Sun, 31 Aug 2014 15:29:24 +1200
changeset 224583 8c7a4b462bf0c90b37977d75a7ef8e96e765c3a0
parent 224582 0b6742ebd50104b9921283f312d30ef06e1441e6
child 224584 3133016b4867bbd1b94b518c9ab6128224418be3
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, bajaj
bugs967844
milestone34.0a2
Bug 967844 - Part 3: Setup FrameMetrics from FrameLayerBuilder based on animated geometry roots. r=mattwoodrow, a=bajaj
dom/base/nsDOMWindowUtils.cpp
gfx/layers/LayersLogging.cpp
gfx/layers/ipc/LayerTransactionParent.cpp
gfx/layers/ipc/LayerTransactionParent.h
gfx/layers/ipc/PLayerTransaction.ipdl
layout/base/FrameLayerBuilder.cpp
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIScrollableFrame.h
layout/reftests/async-scrolling/nested-1-ref.html
layout/reftests/async-scrolling/nested-1.html
layout/reftests/async-scrolling/reftest.list
layout/reftests/async-scrolling/split-layers-1-ref.html
layout/reftests/async-scrolling/split-layers-1.html
layout/reftests/async-scrolling/split-layers-multi-scrolling-1-ref.html
layout/reftests/async-scrolling/split-layers-multi-scrolling-1.html
layout/reftests/async-scrolling/split-opacity-layers-1-ref.html
layout/reftests/async-scrolling/split-opacity-layers-1.html
modules/libpref/init/all.js
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2665,58 +2665,33 @@ nsDOMWindowUtils::GetIsTestControllingRe
 NS_IMETHODIMP
 nsDOMWindowUtils::SetAsyncScrollOffset(nsIDOMNode* aNode,
                                        int32_t aX, int32_t aY)
 {
   nsCOMPtr<Element> element = do_QueryInterface(aNode);
   if (!element) {
     return NS_ERROR_INVALID_ARG;
   }
-  nsIFrame* frame = element->GetPrimaryFrame();
-  if (!frame) {
-    return NS_ERROR_UNEXPECTED;
-  }
-  nsIScrollableFrame* scrollable = do_QueryFrame(frame);
-  nsPresContext* presContext = frame->PresContext();
-  nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
-  if (!scrollable) {
-    if (rootScrollFrame && rootScrollFrame->GetContent() == element) {
-      frame = rootScrollFrame;
-      scrollable = do_QueryFrame(frame);
-    }
-  }
-  if (!scrollable) {
-    return NS_ERROR_UNEXPECTED;
-  }
-  Layer* layer = FrameLayerBuilder::GetDedicatedLayer(scrollable->GetScrolledFrame(),
-    nsDisplayItem::TYPE_SCROLL_LAYER);
-  if (!layer) {
-    if (rootScrollFrame == frame && !presContext->GetParentPresContext()) {
-      nsIWidget* widget = GetWidget();
-      if (widget) {
-        LayerManager* manager = widget->GetLayerManager();
-        if (manager) {
-          layer = manager->GetRoot();
-        }
-      }
-    }
-    if (!layer) {
-      return NS_ERROR_UNEXPECTED;
-    }
-  }
   FrameMetrics::ViewID viewId;
   if (!nsLayoutUtils::FindIDFor(element, &viewId)) {
     return NS_ERROR_UNEXPECTED;
   }
-  ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
+  nsIWidget* widget = GetWidget();
+  if (!widget) {
+    return NS_ERROR_FAILURE;
+  }
+  LayerManager* manager = widget->GetLayerManager();
+  if (!manager) {
+    return NS_ERROR_FAILURE;
+  }
+  ShadowLayerForwarder* forwarder = manager->AsShadowForwarder();
   if (!forwarder || !forwarder->HasShadowManager()) {
     return NS_ERROR_UNEXPECTED;
   }
-  forwarder->GetShadowManager()->SendSetAsyncScrollOffset(
-    layer->AsShadowableLayer()->GetShadow(), viewId, aX, aY);
+  forwarder->GetShadowManager()->SendSetAsyncScrollOffset(viewId, aX, aY);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement,
                                            const nsAString& aProperty,
                                            const nsAString& aValue1,
                                            const nsAString& aValue2,
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -118,16 +118,17 @@ AppendToString(std::stringstream& aStrea
                const char* pfx, const char* sfx, bool detailed)
 {
   aStream << pfx;
   AppendToString(aStream, m.mCompositionBounds, "{ cb=");
   AppendToString(aStream, m.mScrollableRect, " sr=");
   AppendToString(aStream, m.GetScrollOffset(), " s=");
   AppendToString(aStream, m.mDisplayPort, " dp=");
   AppendToString(aStream, m.mCriticalDisplayPort, " cdp=");
+  AppendToString(aStream, m.GetBackgroundColor(), " color=");
   if (!detailed) {
     AppendToString(aStream, m.GetScrollId(), " scrollId=");
     if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) {
       AppendToString(aStream, m.GetScrollParentId(), " scrollParent=");
     }
     aStream << nsPrintfCString(" z=%.3f }", m.GetZoom().scale).get();
   } else {
     AppendToString(aStream, m.GetDisplayPortMargins(), " dpm=");
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -682,36 +682,45 @@ LayerTransactionParent::RecvGetAnimation
   transform._41 *= devPerCss;
   transform._42 *= devPerCss;
   transform._43 *= devPerCss;
 
   *aTransform = transform;
   return true;
 }
 
+static AsyncPanZoomController*
+GetAPZCForViewID(Layer* aLayer, FrameMetrics::ViewID aScrollID)
+{
+  for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
+    if (aLayer->GetFrameMetrics(i).GetScrollId() == aScrollID) {
+      return aLayer->GetAsyncPanZoomController(i);
+    }
+  }
+  ContainerLayer* container = aLayer->AsContainerLayer();
+  if (container) {
+    for (Layer* l = container->GetFirstChild(); l; l = l->GetNextSibling()) {
+      AsyncPanZoomController* c = GetAPZCForViewID(l, aScrollID);
+      if (c) {
+        return c;
+      }
+    }
+  }
+  return nullptr;
+}
+
 bool
-LayerTransactionParent::RecvSetAsyncScrollOffset(PLayerParent* aLayer,
-                                                 const FrameMetrics::ViewID& aId,
+LayerTransactionParent::RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aScrollID,
                                                  const int32_t& aX, const int32_t& aY)
 {
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     return false;
   }
 
-  Layer* layer = cast(aLayer)->AsLayer();
-  if (!layer) {
-    return false;
-  }
-  AsyncPanZoomController* controller = nullptr;
-  for (uint32_t i = 0; i < layer->GetFrameMetricsCount(); i++) {
-    if (layer->GetFrameMetrics(i).GetScrollId() == aId) {
-      controller = layer->GetAsyncPanZoomController(i);
-      break;
-    }
-  }
+  AsyncPanZoomController* controller = GetAPZCForViewID(mRoot, aScrollID);
   if (!controller) {
     return false;
   }
   controller->SetTestAsyncScrollOffset(CSSPoint(aX, aY));
   return true;
 }
 
 bool
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -121,17 +121,17 @@ protected:
   virtual bool RecvForceComposite() MOZ_OVERRIDE;
   virtual bool RecvSetTestSampleTime(const TimeStamp& aTime) MOZ_OVERRIDE;
   virtual bool RecvLeaveTestMode() MOZ_OVERRIDE;
   virtual bool RecvGetOpacity(PLayerParent* aParent,
                               float* aOpacity) MOZ_OVERRIDE;
   virtual bool RecvGetAnimationTransform(PLayerParent* aParent,
                                          MaybeTransform* aTransform)
                                          MOZ_OVERRIDE;
-  virtual bool RecvSetAsyncScrollOffset(PLayerParent* aLayer, const FrameMetrics::ViewID& aId,
+  virtual bool RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aId,
                                         const int32_t& aX, const int32_t& aY) MOZ_OVERRIDE;
   virtual bool RecvGetAPZTestData(APZTestData* aOutData);
 
   virtual PLayerParent* AllocPLayerParent() MOZ_OVERRIDE;
   virtual bool DeallocPLayerParent(PLayerParent* actor) MOZ_OVERRIDE;
 
   virtual PCompositableParent* AllocPCompositableParent(const TextureInfo& aInfo) MOZ_OVERRIDE;
   virtual bool DeallocPCompositableParent(PCompositableParent* actor) MOZ_OVERRIDE;
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -76,20 +76,20 @@ parent:
 
   // Returns the value of the transform applied to the layer by animation after
   // factoring out translation components introduced to account for the offset
   // of the corresponding frame and transform origin and after converting to CSS
   // pixels. If the layer is not transformed by animation, the return value will
   // be void_t.
   sync GetAnimationTransform(PLayer layer) returns (MaybeTransform transform);
 
-  // The next time this layer is composited, add this async scroll offset in
-  // CSS pixels.
+  // The next time the layer tree is composited, add this async scroll offset in
+  // CSS pixels for the given ViewID.
   // Useful for testing rendering of async scrolling.
-  async SetAsyncScrollOffset(PLayer layer, ViewID id, int32_t x, int32_t y);
+  async SetAsyncScrollOffset(ViewID id, int32_t x, int32_t y);
 
   // Drop any front buffers that might be retained on the compositor
   // side.
   async ClearCachedResources();
 
   // Schedule a composite if one isn't already scheduled.
   async ForceComposite();
 
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -254,16 +254,17 @@ public:
     mFixedPosFrameForLayerData(nullptr),
     mReferenceFrame(nullptr),
     mLayer(nullptr),
     mIsSolidColorInVisibleRegion(false),
     mSingleItemFixedToViewport(false),
     mNeedComponentAlpha(false),
     mForceTransparentSurface(false),
     mHideAllLayersBelow(false),
+    mOpaqueForAnimatedGeometryRootParent(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.
@@ -453,16 +454,23 @@ public:
    * 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;
+  /**
+   * Set if the opaque region for this layer can be applied to the parent
+   * animated geometry root of this layer's animated geometry root.
+   * We set this when a ThebesLayer's animated geometry root is a scrollframe
+   * and the ThebesLayer completely fills the displayport of the scrollframe.
+   */
+  bool mOpaqueForAnimatedGeometryRootParent;
 
   /**
    * 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
@@ -527,16 +535,19 @@ struct NewLayerEntry {
     , mOpaqueForAnimatedGeometryRootParent(false)
     , mPropagateComponentAlphaFlattening(true)
   {}
   // 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;
+  // If non-null, this FrameMetrics is set to the be the first FrameMetrics
+  // on the layer.
+  UniquePtr<FrameMetrics> mBaseFrameMetrics;
   // 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;
@@ -751,37 +762,46 @@ protected:
 
   /**
    * Returns true if aItem's opaque area (in aOpaque) covers the entire
    * scrollable area of its presshell.
    */
   bool ItemCoversScrollableArea(nsDisplayItem* aItem, const nsRegion& aOpaque);
 
   /**
+   * Set FrameMetrics and scroll-induced clipping on aEntry's layer.
+   */
+  void SetupScrollingMetadata(NewLayerEntry* aEntry);
+
+  /**
+   * Applies occlusion culling.
    * 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.
+   *
+   * Also sets scroll metadata on the layers.
    */
-  void ApplyOcclusionCulling(nsIntRegion* aOpaqueRegionForContainer);
+  void PostprocessRetainedLayers(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);
+                                bool* aHideAllLayersBelow,
+                                bool* aOpaqueForAnimatedGeometryRootParent);
 
   /**
    * 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();
   /**
@@ -2135,22 +2155,17 @@ ContainerState::PopThebesLayerData()
     layer->SetClipRect(nullptr);
     FLB_LOG_THEBES_DECISION(data, "  Selected thebes layer=%p\n", layer.get());
   }
 
   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;
-    }
+    newLayerEntry->mOpaqueForAnimatedGeometryRootParent = data->mOpaqueForAnimatedGeometryRootParent;
   } else {
     SetOuterVisibleRegionForLayer(layer, data->mVisibleRegion);
   }
 
 #ifdef MOZ_DUMP_PAINTING
   layer->AddExtraDumpInfo(nsCString(data->mLog));
 #endif
 
@@ -2657,17 +2672,18 @@ ContainerState::ItemCoversScrollableArea
 }
 
 nsIntRegion
 ContainerState::ComputeOpaqueRect(nsDisplayItem* aItem,
                                   const nsIFrame* aAnimatedGeometryRoot,
                                   const nsIFrame* aFixedPosFrame,
                                   const DisplayItemClip& aClip,
                                   nsDisplayList* aList,
-                                  bool* aHideAllLayersBelow)
+                                  bool* aHideAllLayersBelow,
+                                  bool* aOpaqueForAnimatedGeometryRootParent)
 {
   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()) {
@@ -2685,16 +2701,33 @@ ContainerState::ComputeOpaqueRect(nsDisp
     // not needed).
     if (!nsLayoutUtils::GetCrossDocParentFrame(mContainerFrame)) {
       mBuilder->AddWindowOpaqueRegion(opaqueClipped);
     }
     opaquePixels = ScaleRegionToInsidePixels(opaqueClipped, snapOpaque);
     if (aFixedPosFrame && ItemCoversScrollableArea(aItem, opaque)) {
       *aHideAllLayersBelow = true;
     }
+
+    nsIScrollableFrame* sf = nsLayoutUtils::GetScrollableFrameFor(aAnimatedGeometryRoot);
+    if (sf) {
+      nsRect displayport;
+      bool usingDisplayport =
+        nsLayoutUtils::GetDisplayPort(aAnimatedGeometryRoot->GetContent(), &displayport);
+      if (!usingDisplayport) {
+        // No async scrolling, so all that matters is that the layer contents
+        // cover the scrollport.
+        displayport = sf->GetScrollPortRect();
+      }
+      nsIFrame* scrollFrame = do_QueryFrame(sf);
+      displayport += scrollFrame->GetOffsetToCrossDoc(mContainerReferenceFrame);
+      if (opaque.Contains(displayport)) {
+        *aOpaqueForAnimatedGeometryRootParent = 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
@@ -2965,25 +2998,34 @@ ContainerState::ProcessDisplayItems(nsDi
       NS_ASSERTION(itemType != nsDisplayItem::TYPE_TRANSFORM ||
                    layerContentsVisibleRect.width >= 0,
                    "Transform items must set layerContentsVisibleRect!");
       if (mLayerBuilder->IsBuildingRetainedLayers()) {
         newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect;
         newLayerEntry->mVisibleRegion = itemVisibleRect;
         newLayerEntry->mOpaqueRegion = ComputeOpaqueRect(item,
           animatedGeometryRoot, fixedPosFrame, itemClip, aList,
-          &newLayerEntry->mHideAllLayersBelow);
+          &newLayerEntry->mHideAllLayersBelow,
+          &newLayerEntry->mOpaqueForAnimatedGeometryRootParent);
       } else {
         SetOuterVisibleRegionForLayer(ownLayer, itemVisibleRect,
             layerContentsVisibleRect.width >= 0 ? &layerContentsVisibleRect : nullptr);
       }
-      if (itemType == nsDisplayItem::TYPE_SCROLL_LAYER) {
+      if (itemType == nsDisplayItem::TYPE_SCROLL_LAYER ||
+          itemType == nsDisplayItem::TYPE_SCROLL_INFO_LAYER) {
         nsDisplayScrollLayer* scrollItem = static_cast<nsDisplayScrollLayer*>(item);
         newLayerEntry->mOpaqueForAnimatedGeometryRootParent =
             scrollItem->IsDisplayPortOpaque();
+        newLayerEntry->mBaseFrameMetrics =
+            scrollItem->ComputeFrameMetrics(ownLayer, mParameters);
+      } else if (itemType == nsDisplayItem::TYPE_SUBDOCUMENT ||
+                 itemType == nsDisplayItem::TYPE_ZOOM ||
+                 itemType == nsDisplayItem::TYPE_RESOLUTION) {
+        newLayerEntry->mBaseFrameMetrics =
+          static_cast<nsDisplaySubDocument*>(item)->ComputeFrameMetrics(ownLayer, mParameters);
       }
 
       /**
        * No need to allocate geometry for items that aren't
        * part of a ThebesLayer.
        */
       mLayerBuilder->AddLayerDisplayItem(ownLayer, item,
                                          layerState,
@@ -3006,17 +3048,18 @@ ContainerState::ProcessDisplayItems(nsDi
 
         InvalidateForLayerChange(item, thebesLayerData->mLayer);
 
         mLayerBuilder->AddThebesDisplayItem(thebesLayerData, item, itemClip, itemVisibleRect,
                                             *this, layerState, topLeft);
         nsIntRegion opaquePixels = ComputeOpaqueRect(item,
             animatedGeometryRoot, thebesLayerData->mFixedPosFrameForLayerData,
             itemClip, aList,
-            &thebesLayerData->mHideAllLayersBelow);
+            &thebesLayerData->mHideAllLayersBelow,
+            &thebesLayerData->mOpaqueForAnimatedGeometryRootParent);
         thebesLayerData->Accumulate(this, item, opaquePixels,
             itemVisibleRect, itemDrawRect, itemClip);
       }
     }
 
     if (itemSameCoordinateSystemChildren &&
         itemSameCoordinateSystemChildren->NeedsTransparentSurface()) {
       aList->SetNeedsTransparentSurface();
@@ -3432,17 +3475,73 @@ FindOpaqueRegionEntry(nsTArray<OpaqueReg
         d->mFixedPosFrameForLayerData == aFixedPosFrameForLayerData) {
       return d;
     }
   }
   return nullptr;
 }
 
 void
-ContainerState::ApplyOcclusionCulling(nsIntRegion* aOpaqueRegionForContainer)
+ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry)
+{
+  nsAutoTArray<FrameMetrics,2> metricsArray;
+  if (aEntry->mBaseFrameMetrics) {
+    metricsArray.AppendElement(*aEntry->mBaseFrameMetrics);
+  }
+  uint32_t baseLength = metricsArray.Length();
+
+  nsIntRect tmpClipRect;
+  const nsIntRect* layerClip = aEntry->mLayer->GetClipRect();
+  nsIFrame* fParent;
+  for (const nsIFrame* f = aEntry->mAnimatedGeometryRoot;
+       f != mContainerAnimatedGeometryRoot;
+       f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(
+           fParent, mContainerAnimatedGeometryRoot)) {
+    fParent = nsLayoutUtils::GetCrossDocParentFrame(f);
+    if (!fParent) {
+      // This means mContainerAnimatedGeometryRoot was not an ancestor
+      // of aEntry->mAnimatedGeometryRoot. This is a weird case but it
+      // can happen, e.g. when a scrolled frame contains a frame with opacity
+      // which contains a frame that is not scrolled by the scrolled frame.
+      // For now, we just don't apply any specific async scrolling to this layer.
+      // It will async-scroll with mContainerAnimatedGeometryRoot, which
+      // is substandard but not fatal.
+      metricsArray.SetLength(baseLength);
+      aEntry->mLayer->SetFrameMetrics(metricsArray);
+      return;
+    }
+
+    nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(f);
+    if (!scrollFrame) {
+      continue;
+    }
+
+    nsRect clipRect(0, 0, -1, -1);
+    scrollFrame->ComputeFrameMetrics(aEntry->mLayer, mContainerReferenceFrame,
+                                     mParameters, &clipRect, &metricsArray);
+    if (clipRect.width >= 0) {
+      nsIntRect pixClip = ScaleToNearestPixels(clipRect);
+      if (layerClip) {
+        tmpClipRect.IntersectRect(pixClip, *layerClip);
+      } else {
+        tmpClipRect = pixClip;
+      }
+      layerClip = &tmpClipRect;
+      // XXX this could cause IPC churn due to cliprects being updated
+      // twice during layer building --- for non-ThebesLayers that have
+      // both CSS and scroll clipping.
+    }
+  }
+  aEntry->mLayer->SetClipRect(layerClip);
+  // Watch out for FrameMetrics copies in profiles
+  aEntry->mLayer->SetFrameMetrics(metricsArray);
+}
+
+void
+ContainerState::PostprocessRetainedLayers(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) {
@@ -3453,16 +3552,20 @@ ContainerState::ApplyOcclusionCulling(ns
         e->mAnimatedGeometryRoot, e->mFixedPosFrameForLayerData);
 
     if (hideAll) {
       e->mVisibleRegion.SetEmpty();
     } else if (data) {
       e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion);
     }
 
+    SetOuterVisibleRegionForLayer(e->mLayer, e->mVisibleRegion,
+      e->mLayerContentsVisibleRect.width >= 0 ? &e->mLayerContentsVisibleRect : nullptr);
+    SetupScrollingMetadata(e);
+
     if (!e->mOpaqueRegion.IsEmpty()) {
       const nsIFrame* animatedGeometryRootToCover = e->mAnimatedGeometryRoot;
       if (e->mOpaqueForAnimatedGeometryRootParent &&
           nsLayoutUtils::GetAnimatedGeometryRootForFrame(e->mAnimatedGeometryRoot->GetParent(),
                                                          mContainerAnimatedGeometryRoot)
             == mContainerAnimatedGeometryRoot) {
         animatedGeometryRootToCover = mContainerAnimatedGeometryRoot;
         data = FindOpaqueRegionEntry(opaqueRegions,
@@ -3480,19 +3583,16 @@ ContainerState::ApplyOcclusionCulling(ns
         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;
     }
@@ -3513,17 +3613,17 @@ ContainerState::Finish(uint32_t* aTextCo
     PopThebesLayerData();
   }
 
   NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds),
                "Bounds computation mismatch");
 
   if (mLayerBuilder->IsBuildingRetainedLayers()) {
     nsIntRegion containerOpaqueRegion;
-    ApplyOcclusionCulling(&containerOpaqueRegion);
+    PostprocessRetainedLayers(&containerOpaqueRegion);
     if (containerOpaqueRegion.Contains(aContainerPixelBounds)) {
       aChildItems->SetIsOpaque();
     }
   }
 
   uint32_t textContentFlags = 0;
 
   // Make sure that current/existing layers are added to the parent and are
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -642,25 +642,27 @@ static void UnmarkFrameForDisplay(nsIFra
   for (nsIFrame* f = aFrame; f;
        f = nsLayoutUtils::GetParentOrPlaceholderFor(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* aScrollFrame,
-                               const nsIFrame* aReferenceFrame,
-                               ContainerLayer* aRoot,
-                               ViewID aScrollParentId,
-                               const nsRect& aViewport,
-                               bool aForceNullScrollId,
-                               bool aIsRoot,
-                               const ContainerLayerParameters& aContainerParameters) {
+/* static */ FrameMetrics
+nsDisplayScrollLayer::ComputeFrameMetrics(nsIFrame* aForFrame,
+                                          nsIFrame* aScrollFrame,
+                                          const nsIFrame* aReferenceFrame,
+                                          Layer* aLayer,
+                                          ViewID aScrollParentId,
+                                          const nsRect& aViewport,
+                                          bool aForceNullScrollId,
+                                          bool aIsRoot,
+                                          const ContainerLayerParameters& aContainerParameters)
+{
   nsPresContext* presContext = aForFrame->PresContext();
   int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
   LayoutDeviceToLayerScale resolution(aContainerParameters.mXScale, aContainerParameters.mYScale);
 
   nsIPresShell* presShell = presContext->GetPresShell();
   FrameMetrics metrics;
   metrics.SetViewport(CSSRect::FromAppUnits(aViewport));
 
@@ -668,17 +670,17 @@ static void RecordFrameMetrics(nsIFrame*
   nsIContent* content = aScrollFrame ? aScrollFrame->GetContent() : nullptr;
   if (content) {
     if (!aForceNullScrollId) {
       scrollId = nsLayoutUtils::FindOrCreateIDFor(content);
     }
     nsRect dp;
     if (nsLayoutUtils::GetDisplayPort(content, &dp)) {
       metrics.mDisplayPort = CSSRect::FromAppUnits(dp);
-      nsLayoutUtils::LogTestDataForPaint(aRoot->Manager(), scrollId, "displayport",
+      nsLayoutUtils::LogTestDataForPaint(aLayer->Manager(), scrollId, "displayport",
           metrics.mDisplayPort);
     }
     if (nsLayoutUtils::GetCriticalDisplayPort(content, &dp)) {
       metrics.mCriticalDisplayPort = CSSRect::FromAppUnits(dp);
     }
     DisplayPortMarginsPropertyData* marginsData =
         static_cast<DisplayPortMarginsPropertyData*>(content->GetProperty(nsGkAtoms::DisplayPortMargins));
     if (marginsData) {
@@ -856,17 +858,17 @@ static void RecordFrameMetrics(nsIFrame*
     } else {
       nsStyleContext* backgroundStyle;
       if (nsCSSRendering::FindBackground(aScrollFrame, &backgroundStyle)) {
         metrics.SetBackgroundColor(backgroundStyle->StyleBackground()->mBackgroundColor);
       }
     }
   }
 
-  aRoot->SetFrameMetrics(metrics);
+  return metrics;
 }
 
 nsDisplayListBuilder::~nsDisplayListBuilder() {
   NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0,
                "All frames should have been unmarked");
   NS_ASSERTION(mPresShellStates.Length() == 0,
                "All presshells should have been exited");
   NS_ASSERTION(!mCurrentTableItem, "No table item should be active");
@@ -1287,20 +1289,21 @@ void nsDisplayList::PaintForFrame(nsDisp
                      1.0f/containerParameters.mYScale);
 
   bool isRoot = presContext->IsRootContentDocument();
 
   nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
 
   nsRect viewport(aBuilder->ToReferenceFrame(aForFrame), aForFrame->GetSize());
 
-  RecordFrameMetrics(aForFrame, rootScrollFrame,
-                     aBuilder->FindReferenceFrameFor(aForFrame),
-                     root, FrameMetrics::NULL_SCROLL_ID, viewport,
-                     !isRoot, isRoot, containerParameters);
+  root->SetFrameMetrics(
+    nsDisplayScrollLayer::ComputeFrameMetrics(aForFrame, rootScrollFrame,
+                       aBuilder->FindReferenceFrameFor(aForFrame),
+                       root, FrameMetrics::NULL_SCROLL_ID, viewport,
+                       !isRoot, isRoot, containerParameters));
 
   // NS_WARNING is debug-only, so don't even bother checking the conditions in
   // a release build.
 #ifdef DEBUG
   bool usingDisplayport = false;
   if (rootScrollFrame) {
     nsIContent* content = rootScrollFrame->GetContent();
     if (content) {
@@ -3663,38 +3666,45 @@ nsDisplaySubDocument::BuildLayer(nsDispl
   nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
   ContainerLayerParameters params = aContainerParameters;
   if ((mFlags & GENERATE_SCROLLABLE_LAYER) &&
       rootScrollFrame->GetContent() &&
       nsLayoutUtils::GetCriticalDisplayPort(rootScrollFrame->GetContent(), nullptr)) {
     params.mInLowPrecisionDisplayPort = true; 
   }
 
-  nsRefPtr<Layer> layer = nsDisplayOwnLayer::BuildLayer(
-    aBuilder, aManager, params);
-
+  return nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, params);
+}
+
+UniquePtr<FrameMetrics>
+nsDisplaySubDocument::ComputeFrameMetrics(Layer* aLayer,
+                                          const ContainerLayerParameters& aContainerParameters)
+{
   if (!(mFlags & GENERATE_SCROLLABLE_LAYER)) {
-    return layer.forget();
-  }
-
-  NS_ASSERTION(layer->AsContainerLayer(), "nsDisplayOwnLayer should have made a ContainerLayer");
-  if (ContainerLayer* container = layer->AsContainerLayer()) {
-    nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
-    bool isRootContentDocument = presContext->IsRootContentDocument();
-
-    nsRect viewport = mFrame->GetRect() -
-                      mFrame->GetPosition() +
-                      mFrame->GetOffsetToCrossDoc(ReferenceFrame());
-
-    RecordFrameMetrics(mFrame, rootScrollFrame, ReferenceFrame(),
-                       container, mScrollParentId, viewport,
-                       false, isRootContentDocument, params);
-  }
-
-  return layer.forget();
+    return UniquePtr<FrameMetrics>(nullptr);
+  }
+
+  nsPresContext* presContext = mFrame->PresContext();
+  nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
+  bool isRootContentDocument = presContext->IsRootContentDocument();
+  ContainerLayerParameters params = aContainerParameters;
+  if ((mFlags & GENERATE_SCROLLABLE_LAYER) &&
+      rootScrollFrame->GetContent() &&
+      nsLayoutUtils::GetCriticalDisplayPort(rootScrollFrame->GetContent(), nullptr)) {
+    params.mInLowPrecisionDisplayPort = true;
+  }
+
+  nsRect viewport = mFrame->GetRect() -
+                    mFrame->GetPosition() +
+                    mFrame->GetOffsetToCrossDoc(ReferenceFrame());
+
+  return MakeUnique<FrameMetrics>(
+    nsDisplayScrollLayer::ComputeFrameMetrics(mFrame, rootScrollFrame, ReferenceFrame(),
+                       aLayer, mScrollParentId, viewport,
+                       false, isRootContentDocument, params));
 }
 
 nsRect
 nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
 {
   bool usingDisplayPort =
     nsLayoutUtils::ViewportHasDisplayPort(mFrame->PresContext());
 
@@ -3973,53 +3983,56 @@ nsDisplayScrollLayer::GetScrolledContent
   }
   bool snap;
   return GetBounds(aBuilder, &snap);
 }
 
 already_AddRefed<Layer>
 nsDisplayScrollLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
                                  LayerManager* aManager,
-                                 const ContainerLayerParameters& aContainerParameters) {
-
+                                 const ContainerLayerParameters& aContainerParameters)
+{
   ContainerLayerParameters params = aContainerParameters;
   if (mScrolledFrame->GetContent() &&
       nsLayoutUtils::GetCriticalDisplayPort(mScrolledFrame->GetContent(), nullptr)) {
-    params.mInLowPrecisionDisplayPort = true; 
-  }
-
-  nsRefPtr<ContainerLayer> layer = aManager->GetLayerBuilder()->
-    BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
-                           params, nullptr);
-
-  nsRect viewport = mScrollFrame->GetRect() -
-                    mScrollFrame->GetPosition() +
-                    mScrollFrame->GetOffsetToCrossDoc(ReferenceFrame());
-
-  RecordFrameMetrics(mScrolledFrame, mScrollFrame, ReferenceFrame(), layer,
-                     mScrollParentId, viewport, false, false, params);
+    params.mInLowPrecisionDisplayPort = true;
+  }
 
   if (mList.IsOpaque()) {
     nsRect displayport;
     bool usingDisplayport =
       nsLayoutUtils::GetDisplayPort(mScrolledFrame->GetContent(), &displayport);
     mDisplayPortContentsOpaque = mList.GetBounds(aBuilder).Contains(
         GetScrolledContentRectToDraw(aBuilder, usingDisplayport ? &displayport : nullptr));
   } else {
     mDisplayPortContentsOpaque = false;
   }
 
-  return layer.forget();
-}
-
-bool
-nsDisplayScrollLayer::IsConstructingScrollLayerForScrolledFrame(const nsIFrame* aScrolledFrame)
+  return aManager->GetLayerBuilder()->
+    BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+                           params, nullptr);
+}
+
+UniquePtr<FrameMetrics>
+nsDisplayScrollLayer::ComputeFrameMetrics(Layer* aLayer,
+                                          const ContainerLayerParameters& aContainerParameters)
 {
-  FrameProperties props = aScrolledFrame->Properties();
-  return reinterpret_cast<intptr_t>(props.Get(nsIFrame::ScrollLayerCount())) != 0;
+  ContainerLayerParameters params = aContainerParameters;
+  if (mScrolledFrame->GetContent() &&
+      nsLayoutUtils::GetCriticalDisplayPort(mScrolledFrame->GetContent(), nullptr)) {
+    params.mInLowPrecisionDisplayPort = true; 
+  }
+
+  nsRect viewport = mScrollFrame->GetRect() -
+                    mScrollFrame->GetPosition() +
+                    mScrollFrame->GetOffsetToCrossDoc(ReferenceFrame());
+
+  return UniquePtr<FrameMetrics>(new FrameMetrics(
+    ComputeFrameMetrics(mScrolledFrame, mScrollFrame, ReferenceFrame(), aLayer,
+                        mScrollParentId, viewport, false, false, params)));
 }
 
 bool
 nsDisplayScrollLayer::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
 {
   if (nsLayoutUtils::GetDisplayPort(mScrolledFrame->GetContent(), nullptr)) {
     return true;
   }
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -19,16 +19,18 @@
 #include "nsPoint.h"
 #include "nsRect.h"
 #include "plarena.h"
 #include "Layers.h"
 #include "nsRegion.h"
 #include "nsDisplayListInvalidation.h"
 #include "DisplayListClipState.h"
 #include "LayerState.h"
+#include "FrameMetrics.h"
+#include "mozilla/UniquePtr.h"
 
 #include <stdint.h>
 
 #include <stdlib.h>
 #include <algorithm>
 
 class nsIContent;
 class nsRenderingContext;
@@ -38,18 +40,18 @@ class nsDisplayLayerEventRegions;
 class nsCaret;
 
 namespace mozilla {
 class FrameLayerBuilder;
 namespace layers {
 class Layer;
 class ImageLayer;
 class ImageContainer;
-} //namepsace
-} //namepsace
+} //namespace
+} //namespace
 
 // A set of blend modes, that never includes OP_OVER (since it's
 // considered the default, rather than a specific blend mode).
 typedef mozilla::EnumSet<mozilla::gfx::CompositionOp> BlendModeSet;
 
 /*
  * An nsIFrame can have many different visual parts. For example an image frame
  * can have a background, border, and outline, the image itself, and a
@@ -801,16 +803,17 @@ protected:
  * 
  * Display items belong to a list at all times (except temporarily as they
  * move from one list to another).
  */
 class nsDisplayItem : public nsDisplayItemLink {
 public:
   typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
   typedef mozilla::DisplayItemClip DisplayItemClip;
+  typedef mozilla::layers::FrameMetrics FrameMetrics;
   typedef mozilla::layers::FrameMetrics::ViewID ViewID;
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::layers::LayerManager LayerManager;
   typedef mozilla::LayerState LayerState;
 
   // This is never instantiated directly (it has pure virtual methods), so no
   // need to count constructors and destructors.
   nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
@@ -2880,16 +2883,20 @@ public:
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) MOZ_OVERRIDE;
 
   virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE;
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) MOZ_OVERRIDE;
 
   NS_DISPLAY_DECL_NAME("SubDocument", TYPE_SUBDOCUMENT)
+
+  mozilla::UniquePtr<FrameMetrics> ComputeFrameMetrics(Layer* aLayer,
+                                                       const ContainerLayerParameters& aContainerParameters);
+
 protected:
   ViewID mScrollParentId;
 };
 
 /**
  * A display item for subdocuments to capture the resolution from the presShell
  * and ensure that it gets applied to all the right elements. This item creates
  * a container layer.
@@ -3006,27 +3013,38 @@ public:
 
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE;
 
   // 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.
   intptr_t GetScrollLayerCount();
 
-  static bool IsConstructingScrollLayerForScrolledFrame(const nsIFrame* aScrolledFrame);
-
   virtual nsIFrame* GetScrollFrame() { return mScrollFrame; }
   virtual nsIFrame* GetScrolledFrame() { return mScrolledFrame; }
 
 #ifdef MOZ_DUMP_PAINTING
   virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
 #endif
 
   bool IsDisplayPortOpaque() { return mDisplayPortContentsOpaque; }
 
+  static FrameMetrics ComputeFrameMetrics(nsIFrame* aForFrame,
+                                          nsIFrame* aScrollFrame,
+                                          const nsIFrame* aReferenceFrame,
+                                          Layer* aLayer,
+                                          ViewID aScrollParentId,
+                                          const nsRect& aViewport,
+                                          bool aForceNullScrollId,
+                                          bool aIsRoot,
+                                          const ContainerLayerParameters& aContainerParameters);
+
+  mozilla::UniquePtr<FrameMetrics> ComputeFrameMetrics(Layer* aLayer,
+                                                       const ContainerLayerParameters& aContainerParameters);
+
 protected:
   nsRect GetScrolledContentRectToDraw(nsDisplayListBuilder* aBuilder,
                                       nsRect* aDisplayPort);
 
   nsIFrame* mScrollFrame;
   nsIFrame* mScrolledFrame;
   ViewID mScrollParentId;
   bool mDisplayPortContentsOpaque;
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -58,16 +58,32 @@
 #include <algorithm>
 #include <cstdlib> // for std::abs(int/long)
 #include <cmath> // for std::abs(float/double)
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layout;
 
+static bool
+BuildScrollContainerLayers()
+{
+  static bool sContainerlessScrollingEnabled;
+  static bool sContainerlessScrollingPrefCached = false;
+
+  if (!sContainerlessScrollingPrefCached) {
+    sContainerlessScrollingPrefCached = true;
+    Preferences::AddBoolVarCache(&sContainerlessScrollingEnabled,
+                                 "layout.async-containerless-scrolling.enabled",
+                                 true);
+  }
+
+  return !sContainerlessScrollingEnabled;
+}
+
 //----------------------------------------------------------------------
 
 //----------nsHTMLScrollFrame-------------------------------------------
 
 nsHTMLScrollFrame*
 NS_NewHTMLScrollFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, bool aIsRoot)
 {
   return new (aPresShell) nsHTMLScrollFrame(aPresShell, aContext, aIsRoot);
@@ -2881,16 +2897,18 @@ ScrollFrameHelper::BuildDisplayList(nsDi
       nsLayoutUtils::WantSubAPZC() &&
       WantAsyncScroll() &&
       // If we are the root scroll frame for the display root then we don't need a scroll
       // info layer to make a RecordFrameMetrics call for us as
       // nsDisplayList::PaintForFrame already calls RecordFrameMetrics for us.
       (!mIsRoot || aBuilder->RootReferenceFrame()->PresContext() != mOuter->PresContext());
   }
 
+  mScrollParentID = aBuilder->GetCurrentScrollParentId();
+
   nsDisplayListCollection scrolledContent;
   {
     // Note that setting the current scroll parent id here means that positioned children
     // of this scroll info layer will pick up the scroll info layer as their scroll handoff
     // parent. This is intentional because that is what happens for positioned children
     // of scroll layers, and we want to maintain consistent behaviour between scroll layers
     // and scroll info layers.
     nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(
@@ -2949,17 +2967,17 @@ ScrollFrameHelper::BuildDisplayList(nsDi
     }
   }
 
   if (shouldBuildLayer) {
     // ScrollLayerWrapper must always be created because it initializes the
     // scroll layer count. The display lists depend on this.
     ScrollLayerWrapper wrapper(mOuter, mScrolledFrame);
 
-    if (mShouldBuildScrollableLayer) {
+    if (mShouldBuildScrollableLayer && BuildScrollContainerLayers()) {
       DisplayListClipState::AutoSaveRestore clipState(aBuilder);
 
       // For root scrollframes in documents where the CSS viewport has been
       // modified, the CSS viewport no longer corresponds to what is visible,
       // so we don't want to clip the content to it. For root scrollframes
       // in documents where the CSS viewport is NOT modified, the mScrollPort
       // is the same as the CSS viewport, modulo scrollbars.
       if (!(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden())) {
@@ -2991,16 +3009,41 @@ ScrollFrameHelper::BuildDisplayList(nsDi
     }
   }
   // Now display overlay scrollbars and the resizer, if we have one.
   AppendScrollPartsTo(aBuilder, scrollbarDirty, scrolledContent,
                       createLayersForScrollbars, true);
   scrolledContent.MoveTo(aLists);
 }
 
+void
+ScrollFrameHelper::ComputeFrameMetrics(Layer* aLayer,
+                                       nsIFrame* aContainerReferenceFrame,
+                                       const ContainerLayerParameters& aParameters,
+                                       nsRect* aClipRect,
+                                       nsTArray<FrameMetrics>* aOutput) const
+{
+  if (!mShouldBuildScrollableLayer || BuildScrollContainerLayers()) {
+    return;
+  }
+
+  MOZ_ASSERT(mScrolledFrame->GetContent());
+
+  nsRect viewport = mScrollPort +
+    mOuter->GetOffsetToCrossDoc(aContainerReferenceFrame);
+
+  if (!(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden())) {
+    *aClipRect = viewport;
+  }
+  *aOutput->AppendElement() =
+      nsDisplayScrollLayer::ComputeFrameMetrics(mScrolledFrame, mOuter,
+        aContainerReferenceFrame, aLayer, mScrollParentID,
+        viewport, false, false, aParameters);
+}
+
 bool
 ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const
 {
   // Use the right rect depending on if a display port is set.
   nsRect displayPort;
   bool usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort);
   return aRect.Intersects(ExpandRectToNearlyVisible(usingDisplayport ? displayPort : mScrollPort));
 }
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -26,28 +26,33 @@ class nsIPresShell;
 class nsIContent;
 class nsIAtom;
 class nsIScrollFrameInternal;
 class nsPresState;
 class nsIScrollPositionListener;
 struct ScrollReflowState;
 
 namespace mozilla {
+namespace layers {
+class Layer;
+}
 namespace layout {
 class ScrollbarActivity;
 }
 }
 
 namespace mozilla {
 
 class ScrollFrameHelper : public nsIReflowCallback {
 public:
   typedef nsIFrame::Sides Sides;
   typedef mozilla::CSSIntPoint CSSIntPoint;
   typedef mozilla::layout::ScrollbarActivity ScrollbarActivity;
+  typedef mozilla::layers::FrameMetrics FrameMetrics;
+  typedef mozilla::layers::Layer Layer;
 
   class AsyncScroll;
   class AsyncSmoothMSDScroll;
 
   ScrollFrameHelper(nsContainerFrame* aOuter, bool aIsRoot);
   ~ScrollFrameHelper();
 
   mozilla::ScrollbarStyles GetScrollbarStylesFromFrame() const;
@@ -320,16 +325,20 @@ public:
   nsIAtom* OriginOfLastScroll() const { return mOriginOfLastScroll; }
   uint32_t CurrentScrollGeneration() const { return mScrollGeneration; }
   void ResetOriginIfScrollAtGeneration(uint32_t aGeneration) {
     if (aGeneration == mScrollGeneration) {
       mOriginOfLastScroll = nullptr;
     }
   }
   bool WantAsyncScroll() const;
+  void ComputeFrameMetrics(Layer* aLayer, nsIFrame* aContainerReferenceFrame,
+                           const ContainerLayerParameters& aParameters,
+                           nsRect* aClipRect,
+                           nsTArray<FrameMetrics>* aOutput) const;
 
   // nsIScrollbarMediator
   void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection);
   void ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection);
   void ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection);
   void RepeatButtonScroll(nsScrollbarFrame* aScrollbar);
   void ThumbMoved(nsScrollbarFrame* aScrollbar,
                   nscoord aOldPos,
@@ -384,16 +393,18 @@ public:
   nsExpirationState mActivityExpirationState;
 
   nsCOMPtr<nsITimer> mScrollActivityTimer;
   nsPoint mScrollPosForLayerPixelAlignment;
 
   // The scroll position where we last updated image visibility.
   nsPoint mLastUpdateImagesPos;
 
+  FrameMetrics::ViewID mScrollParentID;
+
   bool mNeverHasVerticalScrollbar:1;
   bool mNeverHasHorizontalScrollbar:1;
   bool mHasVerticalScrollbar:1;
   bool mHasHorizontalScrollbar:1;
   bool mFrameIsUpdatingScrollbar:1;
   bool mDidHistoryRestore:1;
   // Is this the scrollframe for the document's viewport?
   bool mIsRoot:1;
@@ -703,16 +714,23 @@ public:
     return mHelper.CurrentScrollGeneration();
   }
   virtual void ResetOriginIfScrollAtGeneration(uint32_t aGeneration) MOZ_OVERRIDE {
     mHelper.ResetOriginIfScrollAtGeneration(aGeneration);
   }
   virtual bool WantAsyncScroll() const MOZ_OVERRIDE {
     return mHelper.WantAsyncScroll();
   }
+  virtual void ComputeFrameMetrics(Layer* aLayer, nsIFrame* aContainerReferenceFrame,
+                                   const ContainerLayerParameters& aParameters,
+                                   nsRect* aClipRect,
+                                   nsTArray<FrameMetrics>* aOutput) const MOZ_OVERRIDE {
+    mHelper.ComputeFrameMetrics(aLayer, aContainerReferenceFrame,
+                                aParameters, aClipRect, aOutput);
+  }
 
   // nsIStatefulFrame
   NS_IMETHOD SaveState(nsPresState** aState) MOZ_OVERRIDE {
     NS_ENSURE_ARG_POINTER(aState);
     *aState = mHelper.SaveState();
     return NS_OK;
   }
   NS_IMETHOD RestoreState(nsPresState* aState) MOZ_OVERRIDE {
@@ -1042,16 +1060,23 @@ public:
     return mHelper.CurrentScrollGeneration();
   }
   virtual void ResetOriginIfScrollAtGeneration(uint32_t aGeneration) MOZ_OVERRIDE {
     mHelper.ResetOriginIfScrollAtGeneration(aGeneration);
   }
   virtual bool WantAsyncScroll() const MOZ_OVERRIDE {
     return mHelper.WantAsyncScroll();
   }
+  virtual void ComputeFrameMetrics(Layer* aLayer, nsIFrame* aContainerReferenceFrame,
+                                   const ContainerLayerParameters& aParameters,
+                                   nsRect* aClipRect,
+                                   nsTArray<FrameMetrics>* aOutput) const MOZ_OVERRIDE {
+    mHelper.ComputeFrameMetrics(aLayer, aContainerReferenceFrame,
+                                aParameters, aClipRect, aOutput);
+  }
 
   // nsIStatefulFrame
   NS_IMETHOD SaveState(nsPresState** aState) MOZ_OVERRIDE {
     NS_ENSURE_ARG_POINTER(aState);
     *aState = mHelper.SaveState();
     return NS_OK;
   }
   NS_IMETHOD RestoreState(nsPresState* aState) MOZ_OVERRIDE {
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -10,36 +10,46 @@
 #ifndef nsIScrollFrame_h___
 #define nsIScrollFrame_h___
 
 #include "nsCoord.h"
 #include "ScrollbarStyles.h"
 #include "mozilla/gfx/Point.h"
 #include "nsIScrollbarMediator.h"
 #include "Units.h"
+#include "FrameMetrics.h"
 
 #define NS_DEFAULT_VERTICAL_SCROLL_DISTANCE   3
 #define NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE 5
 
 class nsBoxLayoutState;
 class nsIScrollPositionListener;
 class nsIFrame;
 class nsPresContext;
 class nsIContent;
 class nsRenderingContext;
 class nsIAtom;
 
+namespace mozilla {
+struct ContainerLayerParameters;
+namespace layers {
+class Layer;
+}
+}
+
 /**
  * Interface for frames that are scrollable. This interface exposes
  * APIs for examining scroll state, observing changes to scroll state,
  * and triggering scrolling.
  */
 class nsIScrollableFrame : public nsIScrollbarMediator {
 public:
   typedef mozilla::CSSIntPoint CSSIntPoint;
+  typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
+  typedef mozilla::layers::FrameMetrics FrameMetrics;
 
   NS_DECL_QUERYFRAME_TARGET(nsIScrollableFrame)
 
   /**
    * Get the frame for the content that we are scrolling within
    * this scrollable frame.
    */
   virtual nsIFrame* GetScrolledFrame() const = 0;
@@ -335,11 +345,20 @@ public:
    * counter.
    */
   virtual void ResetOriginIfScrollAtGeneration(uint32_t aGeneration) = 0;
   /**
    * Determine whether it is desirable to be able to asynchronously scroll this
    * scroll frame.
    */
   virtual bool WantAsyncScroll() const = 0;
+  /**
+   * aLayer's animated geometry root is this frame. If there needs to be a
+   * FrameMetrics contributed by this frame, append it to aOutput.
+   */
+  virtual void ComputeFrameMetrics(mozilla::layers::Layer* aLayer,
+                                   nsIFrame* aContainerReferenceFrame,
+                                   const ContainerLayerParameters& aParameters,
+                                   nsRect* aOutClipRect,
+                                   nsTArray<FrameMetrics>* aOutput) const = 0;
 };
 
 #endif
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/nested-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <div style="width:400px; height:500px; overflow:hidden; border:2px solid black">
+    <div style="width:300px; height:800px; overflow:hidden; border:2px solid blue; margin-top:-50px">
+      <div style="height:450px"></div>
+      <div style="height:200px; background:purple"></div>
+    </div>
+  </div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/nested-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<body>
+  <!-- Test that nested active scrolling elements work -->
+  <div style="width:400px; height:500px; overflow:hidden; border:2px solid black"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="800" reftest-displayport-h="2000"
+       reftest-async-scroll-x="0" reftest-async-scroll-y="50">
+    <div style="width:300px; height:800px; overflow:hidden; border:2px solid blue"
+         reftest-displayport-x="0" reftest-displayport-y="0"
+         reftest-displayport-w="800" reftest-displayport-h="2000"
+         reftest-async-scroll-x="0" reftest-async-scroll-y="50">
+      <div style="height:500px"></div>
+      <div style="height:200px; background:purple"></div>
+    </div>
+  </div>
+</html>
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -1,10 +1,26 @@
-skip-if(!asyncPanZoom) == bg-fixed-1.html bg-fixed-1-ref.html
-skip-if(!asyncPanZoom) == bg-fixed-cover-1.html bg-fixed-cover-1-ref.html
-skip-if(!asyncPanZoom) == bg-fixed-cover-2.html bg-fixed-cover-2-ref.html
-skip-if(!asyncPanZoom) == bg-fixed-cover-3.html bg-fixed-cover-3-ref.html
-skip-if(!asyncPanZoom) == element-1.html element-1-ref.html
-skip-if(!asyncPanZoom) == position-fixed-1.html position-fixed-1-ref.html
-skip-if(!asyncPanZoom) == position-fixed-2.html position-fixed-2-ref.html
-skip-if(!asyncPanZoom) == position-fixed-cover-1.html position-fixed-cover-1-ref.html
-skip-if(!asyncPanZoom) == position-fixed-cover-2.html position-fixed-cover-2-ref.html
-skip-if(!asyncPanZoom) == position-fixed-cover-3.html position-fixed-cover-3-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == bg-fixed-1.html bg-fixed-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == bg-fixed-cover-1.html bg-fixed-cover-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == bg-fixed-cover-2.html bg-fixed-cover-2-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == bg-fixed-cover-3.html bg-fixed-cover-3-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == element-1.html element-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == nested-1.html nested-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == position-fixed-1.html position-fixed-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == position-fixed-2.html position-fixed-2-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == position-fixed-cover-1.html position-fixed-cover-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == position-fixed-cover-2.html position-fixed-cover-2-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == position-fixed-cover-3.html position-fixed-cover-3-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == split-layers-1.html split-layers-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == split-layers-multi-scrolling-1.html split-layers-multi-scrolling-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == split-opacity-layers-1.html split-opacity-layers-1-ref.html
+
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == bg-fixed-1.html bg-fixed-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == bg-fixed-cover-1.html bg-fixed-cover-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == bg-fixed-cover-2.html bg-fixed-cover-2-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == bg-fixed-cover-3.html bg-fixed-cover-3-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == element-1.html element-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == position-fixed-1.html position-fixed-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == position-fixed-2.html position-fixed-2-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == position-fixed-cover-1.html position-fixed-cover-1-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == position-fixed-cover-2.html position-fixed-cover-2-ref.html
+pref(layout.async-containerless-scrolling.enabled,false) skip-if(!asyncPanZoom) == position-fixed-cover-3.html position-fixed-cover-3-ref.html
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/split-layers-1-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <!-- Test that element content scrolls asynchronously even when content
+       has to be split into separate layers with non-scrolling content in
+       between -->
+  <div style="width:400px; height:500px; overflow:hidden; border:2px solid black">
+    <div style="height:450px"></div>
+    <div style="height:100px; width:200px; float:left; background:purple"></div>
+    <div style="left:200px; top:0; height:800px; width:300px; float:left; background:yellow; position:absolute; z-index:1;"></div>
+    <div style="height:100px; width:200px; float:left; background:purple; position:relative; z-index:2;"></div>
+  </div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/split-layers-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<body>
+  <!-- Test that element content scrolls asynchronously even when content
+       has to be split into separate layers with non-scrolling content in
+       between -->
+  <div style="width:400px; height:500px; overflow:hidden; border:2px solid black"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="800" reftest-displayport-h="2000"
+       reftest-async-scroll-x="0" reftest-async-scroll-y="50">
+    <div style="height:500px"></div>
+    <div style="height:100px; width:200px; float:left; background:purple"></div>
+    <div style="left:200px; top:0; height:800px; width:300px; float:left; background:yellow; position:absolute; z-index:1;"></div>
+    <div style="height:100px; width:200px; float:left; background:purple; position:relative; z-index:2;"></div>
+  </div>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/split-layers-multi-scrolling-1-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <!-- Test that element content scrolls asynchronously even when content
+       has to be split into separate layers with non-scrolling content in
+       between -->
+  <div style="width:400px; height:500px; overflow:hidden; border:2px solid black; margin-top:-50px;">
+    <div style="height:450px"></div>
+    <div style="height:100px; width:200px; float:left; background:purple"></div>
+    <div style="left:200px; top:-50px; height:800px; width:300px; float:left; background:yellow; position:absolute; z-index:1;"></div>
+    <div style="height:100px; width:200px; float:left; background:purple; position:relative; z-index:2;"></div>
+  </div>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/split-layers-multi-scrolling-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll
+      reftest-displayport-x="0" reftest-displayport-y="0"
+      reftest-displayport-w="800" reftest-displayport-h="2000"
+      reftest-async-scroll-x="0" reftest-async-scroll-y="50">
+<body style="height:3000px; overflow:hidden">
+  <!-- Test that element content scrolls asynchronously even when content
+       has to be split into separate layers with non-scrolling content in
+       between -->
+  <div style="width:400px; height:500px; overflow:hidden; border:2px solid black"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="800" reftest-displayport-h="2000"
+       reftest-async-scroll-x="0" reftest-async-scroll-y="50">
+    <div style="height:500px"></div>
+    <div style="height:100px; width:200px; float:left; background:purple"></div>
+    <div style="left:200px; top:0; height:800px; width:300px; float:left; background:yellow; position:absolute; z-index:1;"></div>
+    <div style="height:100px; width:200px; float:left; background:purple; position:relative; z-index:2;"></div>
+  </div>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/split-opacity-layers-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <div style="width:400px; height:500px; overflow:hidden; border:2px solid black">
+    <div style="height:450px"></div>
+    <div style="height:100px; width:200px; float:left; background:purple"></div>
+    <div style="opacity:0.5">
+      <div style="left:200px; top:0; height:800px; width:300px; float:left; background:yellow; position:absolute; z-index:1;"></div>
+      <div style="height:100px; width:200px; float:left; background:purple; position:relative; z-index:2;"></div>
+    </div>
+  </div>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/split-opacity-layers-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<body>
+  <div style="width:400px; height:500px; overflow:hidden; border:2px solid black"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="800" reftest-displayport-h="2000"
+       reftest-async-scroll-x="0" reftest-async-scroll-y="50">
+    <div style="height:500px"></div>
+    <div style="height:100px; width:200px; float:left; background:purple"></div>
+    <div style="opacity:0.5">
+      <div style="left:200px; top:0; height:800px; width:300px; float:left; background:yellow; position:absolute; z-index:1;"></div>
+      <div style="height:100px; width:200px; float:left; background:purple; position:relative; z-index:2;"></div>
+    </div>
+  </div>
+
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -421,16 +421,19 @@ pref("media.video-queue.default-size", 1
 pref("media.video_stats.enabled", true);
 
 // Whether to enable the audio writing APIs on the audio element
 pref("media.audio_data.enabled", false);
 
 // Whether to use async panning and zooming
 pref("layers.async-pan-zoom.enabled", false);
 
+// Whether to enable containerless async scrolling
+pref("layout.async-containerless-scrolling.enabled", true);
+
 // APZ preferences. For documentation/details on what these prefs do, check 
 // gfx/layers/apz/src/AsyncPanZoomController.cpp.
 pref("apz.allow_checkerboarding", true);
 pref("apz.asyncscroll.throttle", 100);
 pref("apz.asyncscroll.timeout", 300);
 
 // Whether to lock touch scrolling to one axis at a time
 // 0 = FREE (No locking at all)