Bug 1263349 (Part 2) - Cache the current scroll parent and information about scrollports and displayports on nsDisplayListBuilder. r=botond,mstange
☠☠ backed out by 2a26560c2888 ☠ ☠
authorSeth Fowler <mark.seth.fowler@gmail.com>
Tue, 24 May 2016 01:12:48 -0700
changeset 337707 4cfdd1649464cf1b80de560ca972c8ed1633b251
parent 337706 af8d93926a960ccec2d9e821c6e92005d1b6ee04
child 337708 1ae4cfe35d7157c4c0d3bc1b39224053e5cd84ba
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond, mstange
bugs1263349
milestone49.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 1263349 (Part 2) - Cache the current scroll parent and information about scrollports and displayports on nsDisplayListBuilder. r=botond,mstange
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/nsLayoutUtils.cpp
layout/generic/nsFrame.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsSubDocumentFrame.cpp
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -665,31 +665,98 @@ nsDisplayListBuilder::AddAnimationsAndTr
   } else if (aProperty == eCSSProperty_opacity) {
     data = null_t();
   }
 
   AddAnimationsForProperty(aFrame, aProperty, compositorAnimations,
                            aLayer, data, pending);
 }
 
+nsDisplayListBuilder::AutoCurrentScrollParentIdSetter::
+  AutoCurrentScrollParentIdSetter(nsDisplayListBuilder* aBuilder,
+                                  nsIFrame* aScrollParent)
+  : mBuilder(aBuilder)
+  , mOldScrollParent(aBuilder->mCurrentScrollParent)
+  , mOldDisplayPortConsideringAncestors(aBuilder->mDisplayPortConsideringAncestors)
+  , mOldScrollPortConsideringAncestors(aBuilder->mScrollPortConsideringAncestors)
+  , mOldScrollParentId(aBuilder->mCurrentScrollParentId)
+  , mOldForceLayer(aBuilder->mForceLayerForScrollParent)
+  , mChangedSubtrees(false)
+{
+  aBuilder->UpdateCurrentScrollParent(aScrollParent);
+  Init();
+}
+
+nsDisplayListBuilder::AutoCurrentScrollParentIdSetter::
+  AutoCurrentScrollParentIdSetter(nsDisplayListBuilder* aBuilder,
+                                  const OutOfFlowDisplayData* aOutOfFlowData)
+  : mBuilder(aBuilder)
+  , mOldScrollParent(aBuilder->mCurrentScrollParent)
+  , mOldDisplayPortConsideringAncestors(aBuilder->mDisplayPortConsideringAncestors)
+  , mOldScrollPortConsideringAncestors(aBuilder->mScrollPortConsideringAncestors)
+  , mOldScrollParentId(aBuilder->mCurrentScrollParentId)
+  , mOldForceLayer(aBuilder->mForceLayerForScrollParent)
+  , mChangedSubtrees(true)
+{
+  aBuilder->UpdateCurrentScrollParentForOutOfFlow(aOutOfFlowData);
+  Init();
+}
+
+void
+nsDisplayListBuilder::AutoCurrentScrollParentIdSetter::Init()
+{
+  // If this AutoCurrentScrollParentIdSetter has the same scrollId as the
+  // previous one on the stack, then that means the scrollframe that
+  // created this isn't actually scrollable and cannot participate in
+  // scroll handoff. We set mCanBeScrollParent to false to indicate this.
+  mCanBeScrollParent = mOldScrollParentId != mBuilder->mCurrentScrollParentId;
+
+  mBuilder->mForceLayerForScrollParent = false;
+}
+
+nsDisplayListBuilder::AutoCurrentScrollParentIdSetter::
+  ~AutoCurrentScrollParentIdSetter()
+{
+  mBuilder->mCurrentScrollParent = mOldScrollParent;
+  mBuilder->mDisplayPortConsideringAncestors = mOldDisplayPortConsideringAncestors;
+  mBuilder->mScrollPortConsideringAncestors = mOldScrollPortConsideringAncestors;
+  mBuilder->mCurrentScrollParentId = mOldScrollParentId;
+
+  if (mCanBeScrollParent || mChangedSubtrees) {
+    // If |mCanBeScrollParent| is set, caller code is responsible for
+    // having dealt with the current value of
+    // mBuilder->mForceLayerForScrollParent, so we can just restore the
+    // old value. If |mChangedSubtrees| is set, we don't propagate the
+    // flag because it doesn't make sense to propagate it through
+    // unrelated subtrees.
+    mBuilder->mForceLayerForScrollParent = mOldForceLayer;
+  } else {
+    // Otherwise we need to keep propagating the force-layerization flag
+    // upwards to the next ancestor scrollframe that does participate in
+    // scroll handoff.
+    mBuilder->mForceLayerForScrollParent |= mOldForceLayer;
+  }
+}
+
 nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
     nsDisplayListBuilderMode aMode, bool aBuildCaret)
     : mReferenceFrame(aReferenceFrame),
       mIgnoreScrollFrame(nullptr),
       mLayerEventRegions(nullptr),
       mCurrentTableItem(nullptr),
       mCurrentFrame(aReferenceFrame),
       mCurrentReferenceFrame(aReferenceFrame),
       mCurrentAGR(&mRootAGR),
       mRootAGR(aReferenceFrame, nullptr),
       mUsedAGRBudget(0),
       mDirtyRect(-1,-1,-1,-1),
       mGlassDisplayItem(nullptr),
       mScrollInfoItemsForHoisting(nullptr),
       mMode(aMode),
+      mCurrentScrollParent(nullptr),
       mCurrentScrollParentId(FrameMetrics::NULL_SCROLL_ID),
       mCurrentScrollbarTarget(FrameMetrics::NULL_SCROLL_ID),
       mCurrentScrollbarFlags(0),
       mPerspectiveItemIndex(0),
       mSVGEffectsBuildingDepth(0),
       mContainsBlendMode(false),
       mIsBuildingScrollbar(false),
       mCurrentScrollbarWillHaveLayer(false),
@@ -727,16 +794,19 @@ nsDisplayListBuilder::nsDisplayListBuild
     if (selcon) {
       selcon->GetSelection(nsISelectionController::SELECTION_NORMAL,
                            getter_AddRefs(mBoundingSelection));
     }
   }
 
   mFrameToAnimatedGeometryRootMap.Put(aReferenceFrame, &mRootAGR);
 
+  UpdateCurrentScrollParent(
+      nsLayoutUtils::GetAsyncScrollableProperAncestorFrameOrFallback(aReferenceFrame));
+
   nsCSSRendering::BeginFrameTreesLocked();
   PR_STATIC_ASSERT(nsDisplayItem::TYPE_MAX < (1 << nsDisplayItem::TYPE_BITS));
 }
 
 static void MarkFrameForDisplay(nsIFrame* aFrame, nsIFrame* aStopAtFrame) {
   for (nsIFrame* f = aFrame; f;
        f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
     if (f->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)
@@ -849,17 +919,22 @@ void nsDisplayListBuilder::MarkOutOfFlow
     overflowRect.Inflate(nsPresContext::CSSPixelsToAppUnits(32));
   }
 
   if (!dirty.IntersectRect(dirty, overflowRect))
     return;
 
   const DisplayItemClip* oldClip = mClipState.GetClipForContainingBlockDescendants();
   const DisplayItemScrollClip* sc = mClipState.GetCurrentInnermostScrollClip();
-  OutOfFlowDisplayData* data = new OutOfFlowDisplayData(oldClip, sc, dirty);
+  OutOfFlowDisplayData* data =
+    new OutOfFlowDisplayData(oldClip, sc, dirty,
+                             mCurrentScrollParent, mCurrentScrollParentId,
+                             mDisplayPortConsideringAncestors,
+                             mScrollPortConsideringAncestors);
+
   aFrame->Properties().Set(nsDisplayListBuilder::OutOfFlowDisplayDataProperty(), data);
 
   MarkFrameForDisplay(aFrame, aDirtyFrame);
 }
 
 static void UnmarkFrameForDisplay(nsIFrame* aFrame) {
   nsPresContext* presContext = aFrame->PresContext();
   presContext->PropertyTable()->
@@ -1217,16 +1292,142 @@ nsDisplayListBuilder::FindAnimatedGeomet
     if (IsAnimatedGeometryRoot(cursor, &next))
       return cursor;
     cursor = next;
   }
   return cursor;
 }
 
 void
+nsDisplayListBuilder::UpdateCurrentScrollParent(nsIFrame* aScrollParent)
+{
+  // If the new scroll parent is the same as the old scroll parent, we don't
+  // have to do anything. This can happen because we sometimes instantiate an
+  // AutoCurrentScrollParentIdSetter unconditionally but only pass it a new
+  // scroll parent if certain conditions are true.
+  if (aScrollParent == mCurrentScrollParent) {
+    return;
+  }
+
+  // Update the current scroll parent.
+  nsIFrame* oldScrollParent = mCurrentScrollParent;
+  mCurrentScrollParent = aScrollParent;
+
+  // If there's no scroll parent now (which may be true because we're
+  // initializing this nsDisplayListBuilder for the first time, or because
+  // we're in a strange state where there's no pres shell or no root frame)
+  // then we just reset everything to its initial values.
+  if (!aScrollParent) {
+    mDisplayPortConsideringAncestors = nsRect();
+    mScrollPortConsideringAncestors = nsRect();
+    mCurrentScrollParentId = FrameMetrics::NULL_SCROLL_ID;
+    return;
+  }
+
+  // Compute the new scroll parent ID.
+  nsIContent* content = aScrollParent->GetContent();
+  mCurrentScrollParentId = content ? nsLayoutUtils::FindOrCreateIDFor(content)
+                                   : FrameMetrics::NULL_SCROLL_ID;
+
+  nsIScrollableFrame* newScrollParent = do_QueryFrame(aScrollParent);
+  if (!newScrollParent) {
+    // There's no scrollable parent frame, so we've been forced to fall back
+    // to the root frame. (Most likely this is a XUL document.) Just use its
+    // size for both the displayport and the scrollport.
+    MOZ_ASSERT(!oldScrollParent, "Falling back to the root frame when an ancestor "
+                                 "scrollable frame existed?");
+    nsRect frameRect(nsPoint(0, 0), aScrollParent->GetSize());
+    mDisplayPortConsideringAncestors = frameRect;
+    mScrollPortConsideringAncestors = frameRect;
+    return;
+  }
+
+  MOZ_ASSERT(mCurrentScrollParentId != FrameMetrics::NULL_SCROLL_ID,
+             "Couldn't get a scroll ID for a scrollable frame?");
+
+  // If we didn't have a scroll parent before, but we do now, the displayport
+  // and scrollport we're tracking are just those of the new scroll parent.
+  if (!oldScrollParent) {
+    mDisplayPortConsideringAncestors =
+      nsLayoutUtils::GetDisplayPortOrFallbackToScrollPort(newScrollParent);
+    mScrollPortConsideringAncestors = newScrollParent->GetScrollPortRect();
+    return;
+  }
+
+#ifdef DEBUG
+  {
+    nsIScrollableFrame* oldScrollableParent = do_QueryFrame(oldScrollParent);
+    if (oldScrollableParent && oldScrollableParent->WantAsyncScroll()) {
+      // Normally, the old scroll parent should be the nearest async
+      // scrollable ancestor of the new scroll parent.
+      MOZ_ASSERT(nsLayoutUtils::GetAsyncScrollableProperAncestorFrame(aScrollParent) ==
+                   oldScrollableParent);
+    } else {
+      // If the old scroll parent isn't async scrollable or isn't scrollable
+      // at all, (e.g., because we had to fall back to the root frame in a XUL
+      // document) then at least assert it's an ancestor of the new scroll parent.
+      MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrameCrossDoc(oldScrollParent,
+                                                              aScrollParent));
+    }
+  }
+#endif
+
+  // If the effective displayport, considering ancestors, is already empty, we
+  // can bail early as we know the intersection checks below will fail.
+  if (mDisplayPortConsideringAncestors.IsEmpty()) {
+    mDisplayPortConsideringAncestors = nsRect();
+    mScrollPortConsideringAncestors = nsRect();
+    return;
+  }
+
+  // The entire displayport for this subtree is the critical displayport of
+  // this scroll parent if any part of it is visible within the previous scroll
+  // parent's critical displayport. (What we're trying to do is capture
+  // everything that APZ could asynchronously scroll into view.) If it isn't
+  // visible, then the displayport for this subtree is empty and the
+  // scrollport, trivially, is too.
+  nsRect displayPort =
+    nsLayoutUtils::GetDisplayPortOrFallbackToScrollPort(newScrollParent);
+  nsRect displayPortIntersection =
+    nsLayoutUtils::TransformAndIntersectRect(oldScrollParent,
+                                             mDisplayPortConsideringAncestors,
+                                             aScrollParent,
+                                             displayPort);
+  if (displayPortIntersection.IsEmpty()) {
+    mDisplayPortConsideringAncestors = nsRect();
+    mScrollPortConsideringAncestors = nsRect();
+    return;
+  }
+
+  // See above: we take the whole thing, not the intersection with the
+  // previous scroll parent's critical displayport.
+  mDisplayPortConsideringAncestors = displayPort;
+
+  // The scrollport for this subtree is the intersection of aScrollParent's
+  // scrollport with all ancestor scrollports.
+  mScrollPortConsideringAncestors =
+    nsLayoutUtils::TransformAndIntersectRect(oldScrollParent,
+                                             mScrollPortConsideringAncestors,
+                                             aScrollParent,
+                                             newScrollParent->GetScrollPortRect());
+}
+
+void
+nsDisplayListBuilder::UpdateCurrentScrollParentForOutOfFlow(const OutOfFlowDisplayData* aOutOfFlowData)
+{
+  MOZ_ASSERT(aOutOfFlowData);
+
+  // Just set the information from |aOutOfFlowData| unconditionally.
+  mCurrentScrollParent = aOutOfFlowData->mCurrentScrollParent;
+  mCurrentScrollParentId = aOutOfFlowData->mCurrentScrollParentId;
+  mDisplayPortConsideringAncestors = aOutOfFlowData->mDisplayPortConsideringAncestors;
+  mScrollPortConsideringAncestors = aOutOfFlowData->mScrollPortConsideringAncestors;
+}
+
+void
 nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot()
 {
   if (*mCurrentAGR != mCurrentFrame &&
       IsAnimatedGeometryRoot(const_cast<nsIFrame*>(mCurrentFrame))) {
     AnimatedGeometryRoot* oldAGR = mCurrentAGR;
     mCurrentAGR = WrapAGRForFrame(const_cast<nsIFrame*>(mCurrentFrame), mCurrentAGR);
 
     // Iterate the AGR cache and look for any objects that reference the old AGR and check
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -364,16 +364,40 @@ public:
    * clipped by the viewport or drawing the viewport scrollbars.
    */
   void SetIgnoreScrollFrame(nsIFrame* aFrame) { mIgnoreScrollFrame = aFrame; }
   /**
    * Get the scrollframe to ignore, if any.
    */
   nsIFrame* GetIgnoreScrollFrame() { return mIgnoreScrollFrame; }
   /**
+   * Get the nearest ancestor scrollframe, or the root frame if there is no
+   * ancestor scrollframe, or null if neither exists.
+   */
+  nsIFrame* GetCurrentScrollParent() { return mCurrentScrollParent; }
+  /**
+   * Get the critical displayport of the nearest ancestor scrollframe, in the
+   * coordinate system of the frame returned by GetCurrentScrollParent(). If
+   * the ancestor scrollframe's critical displayport does not intersect the
+   * critical displayport of the scrollframe that encloses it, and so on
+   * transitively up the frame tree, returns the empty rect. The idea is that
+   * this method considers a displayport visible if its nearest ancestor is
+   * visible and some part of it intersects its nearest ancestor, which means
+   * that we capture everything that APZ could asynchronously scroll into
+   * view. Used for visibility tracking during painting.
+   */
+  nsRect GetDisplayPortConsideringAncestors() { return mDisplayPortConsideringAncestors; }
+  /**
+   * Get the intersection of all ancestor scrollframes' scrollports. Used for
+   * visibility tracking during painting. Unlike the displayport case, for
+   * scrollports we use strict intersection, because we only want to detect
+   * frames that are actually visible in the viewport.
+   */
+  nsRect GetScrollPortConsideringAncestors() { return mScrollPortConsideringAncestors; }
+  /**
    * Get the ViewID of the nearest scrolling ancestor frame.
    */
   ViewID GetCurrentScrollParentId() const { return mCurrentScrollParentId; }
   /**
    * Get and set the flag that indicates if scroll parents should have layers
    * forcibly created. This flag is set when a deeply nested scrollframe has
    * a displayport, and for scroll handoff to work properly the ancestor
    * scrollframes should also get their own scrollable layers.
@@ -821,59 +845,74 @@ public:
       }
     }
 
   private:
     nsDisplayListBuilder* mBuilder;
     uint32_t mCachedItemIndex;
   };
 
+  struct OutOfFlowDisplayData;
+
   /**
-   * A helper class to temporarily set the value of mCurrentScrollParentId.
+   * A helper class to temporarily set the value of mCurrentScrollParentId and
+   * cache information about the effective displayport and scrollport,
+   * considering all ancestor scrollframes. See
+   * GetDisplayPortConsideringAncestors() and
+   * GetScrollPortConsideringAncestors() for the details.
    */
   class AutoCurrentScrollParentIdSetter;
   friend class AutoCurrentScrollParentIdSetter;
   class AutoCurrentScrollParentIdSetter {
   public:
-    AutoCurrentScrollParentIdSetter(nsDisplayListBuilder* aBuilder, ViewID aScrollId)
-      : mBuilder(aBuilder)
-      , mOldValue(aBuilder->mCurrentScrollParentId)
-      , mOldForceLayer(aBuilder->mForceLayerForScrollParent) {
-      // If this AutoCurrentScrollParentIdSetter has the same scrollId as the
-      // previous one on the stack, then that means the scrollframe that
-      // created this isn't actually scrollable and cannot participate in
-      // scroll handoff. We set mCanBeScrollParent to false to indicate this.
-      mCanBeScrollParent = (mOldValue != aScrollId);
-      aBuilder->mCurrentScrollParentId = aScrollId;
-      aBuilder->mForceLayerForScrollParent = false;
-    }
+    /**
+     * Temporarily update the cached scroll parent information on @aBuilder
+     * from frame @aFrame. @aFrame should be a a scrollframe that is a
+     * descendant of @aBuilder's current scroll parent. (The exception is if
+     * there's no current scroll parent, in which case @aFrame does not have to
+     * be a scrollframe; this is fallback behavior for XUL documents, which
+     * may not have a root scrollframe.)
+     *
+     * If @aNewScrollParentFrom is SCROLLFRAMES_ON_PATH_TO_ROOT, @aFrame is
+     * interpreted as a scrollframe in a different subtree. In this case we
+     * have to walk up the frame tree all the way to the root to gather
+     * information about the effective displayport and scrollport.
+     */
+    AutoCurrentScrollParentIdSetter(nsDisplayListBuilder* aBuilder,
+                                    nsIFrame* aScrollParent);
+
+    /**
+     * Temporarily updated the cached scroll parent information on @aBuilder
+     * from saved information on @aOutOfFlowData. This is necessary when
+     * traversing out-of-flow frames via their placeholder frames, since their
+     * scroll parent may not be the same as the scroll parent of the
+     * placeholder frame.
+     */
+    AutoCurrentScrollParentIdSetter(nsDisplayListBuilder* aBuilder,
+                                    const OutOfFlowDisplayData* aOutOfFlowData);
+
+    ~AutoCurrentScrollParentIdSetter();
+
     bool ShouldForceLayerForScrollParent() const {
       // Only scrollframes participating in scroll handoff can be forced to
       // layerize
       return mCanBeScrollParent && mBuilder->mForceLayerForScrollParent;
-    };
-    ~AutoCurrentScrollParentIdSetter() {
-      mBuilder->mCurrentScrollParentId = mOldValue;
-      if (mCanBeScrollParent) {
-        // If this flag is set, caller code is responsible for having dealt
-        // with the current value of mBuilder->mForceLayerForScrollParent, so
-        // we can just restore the old value.
-        mBuilder->mForceLayerForScrollParent = mOldForceLayer;
-      } else {
-        // Otherwise we need to keep propagating the force-layerization flag
-        // upwards to the next ancestor scrollframe that does participate in
-        // scroll handoff.
-        mBuilder->mForceLayerForScrollParent |= mOldForceLayer;
-      }
     }
+
   private:
+    void Init();
+
     nsDisplayListBuilder* mBuilder;
-    ViewID                mOldValue;
-    bool                  mOldForceLayer;
-    bool                  mCanBeScrollParent;
+    nsIFrame*             mOldScrollParent;
+    nsRect                mOldDisplayPortConsideringAncestors;
+    nsRect                mOldScrollPortConsideringAncestors;
+    ViewID                mOldScrollParentId;
+    bool                  mOldForceLayer : 1;
+    bool                  mCanBeScrollParent : 1;
+    bool                  mChangedSubtrees : 1;
   };
 
   /**
    * A helper class to temporarily set the value of mCurrentScrollbarTarget
    * and mCurrentScrollbarFlags.
    */
   class AutoCurrentScrollbarInfoSetter;
   friend class AutoCurrentScrollbarInfoSetter;
@@ -984,24 +1023,36 @@ public:
 
   // Helpers for tables
   nsDisplayTableItem* GetCurrentTableItem() { return mCurrentTableItem; }
   void SetCurrentTableItem(nsDisplayTableItem* aTableItem) { mCurrentTableItem = aTableItem; }
 
   struct OutOfFlowDisplayData {
     OutOfFlowDisplayData(const DisplayItemClip* aContainingBlockClip,
                          const DisplayItemScrollClip* aContainingBlockScrollClip,
-                         const nsRect &aDirtyRect)
+                         const nsRect& aDirtyRect,
+                         nsIFrame* aCurrentScrollParent,
+                         ViewID aCurrentScrollParentId,
+                         const nsRect& aDisplayPortConsideringAncestors,
+                         const nsRect& aScrollPortConsideringAncestors)
       : mContainingBlockClip(aContainingBlockClip ? *aContainingBlockClip : DisplayItemClip())
       , mContainingBlockScrollClip(aContainingBlockScrollClip)
       , mDirtyRect(aDirtyRect)
+      , mCurrentScrollParent(aCurrentScrollParent)
+      , mCurrentScrollParentId(aCurrentScrollParentId)
+      , mDisplayPortConsideringAncestors(aDisplayPortConsideringAncestors)
+      , mScrollPortConsideringAncestors(aScrollPortConsideringAncestors)
     {}
     DisplayItemClip mContainingBlockClip;
     const DisplayItemScrollClip* mContainingBlockScrollClip;
     nsRect mDirtyRect;
+    nsIFrame* mCurrentScrollParent;
+    ViewID mCurrentScrollParentId;
+    nsRect mDisplayPortConsideringAncestors;
+    nsRect mScrollPortConsideringAncestors;
   };
 
   NS_DECLARE_FRAME_PROPERTY_DELETABLE(OutOfFlowDisplayDataProperty,
                                       OutOfFlowDisplayData)
 
   static OutOfFlowDisplayData* GetOutOfFlowData(nsIFrame* aFrame)
   {
     return static_cast<OutOfFlowDisplayData*>(
@@ -1148,16 +1199,33 @@ private:
   bool IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParent = nullptr);
 
   /**
    * Returns the nearest ancestor frame to aFrame that is considered to have
    * (or will have) animated geometry. This can return aFrame.
    */
   nsIFrame* FindAnimatedGeometryRootFrameFor(nsIFrame* aFrame);
 
+  /**
+   * Updates cached information about the nearest ancestor scrollable frame.
+   * @aScrollParent should be a descendant of the previous nearest ancestor
+   * scrollable frame. @aScrollParent may be null, indicating that there is
+   * no ancestor scrollable frame.
+   */
+  void UpdateCurrentScrollParent(nsIFrame* aScrollParent);
+
+  /**
+   * Updates cached information about the nearest ancestor scrollable frame.
+   * The new information is read from @aOutOfFlowData, and unconditionally
+   * replaces the cached information without reference to the current scroll
+   * parent, since we're descending into an out-of-flow frame that does not
+   * necessarily have the same scroll parent chain.
+   */
+  void UpdateCurrentScrollParentForOutOfFlow(const OutOfFlowDisplayData* aOutOfFlowData);
+
   friend class nsDisplayCanvasBackgroundImage;
   friend class nsDisplayBackgroundImage;
   friend class nsDisplayFixedPosition;
   AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsDisplayItem* aItem);
 
   AnimatedGeometryRoot* WrapAGRForFrame(nsIFrame* aAnimatedGeometryRoot,
                                         AnimatedGeometryRoot* aParent = nullptr);
 
@@ -1245,17 +1313,20 @@ private:
   // display items for the contents of frames with SVG effects.
   // Only non-null when ShouldBuildScrollInfoItemsForHoisting() is true.
   // This is a pointer and not a real nsDisplayList value because the
   // nsDisplayList class is defined below this class, so we can't use it here.
   nsDisplayList*                 mScrollInfoItemsForHoisting;
   nsTArray<DisplayItemScrollClip*> mScrollClipsToDestroy;
   nsTArray<DisplayItemClip*>     mDisplayItemClipsToDestroy;
   nsDisplayListBuilderMode       mMode;
+  nsIFrame*                      mCurrentScrollParent;
   ViewID                         mCurrentScrollParentId;
+  nsRect                         mDisplayPortConsideringAncestors;
+  nsRect                         mScrollPortConsideringAncestors;
   ViewID                         mCurrentScrollbarTarget;
   uint32_t                       mCurrentScrollbarFlags;
   Preserves3DContext             mPreserves3DCtx;
   uint32_t                       mPerspectiveItemIndex;
   int32_t                        mSVGEffectsBuildingDepth;
   bool                           mContainsBlendMode;
   bool                           mIsBuildingScrollbar;
   bool                           mCurrentScrollbarWillHaveLayer;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3529,44 +3529,16 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
       canvasArea.UnionRect(canvasArea,
         canvasFrame->CanvasArea() + builder.ToReferenceFrame(canvasFrame));
     }
   }
 
   builder.EnterPresShell(aFrame);
   nsRect dirtyRect = visibleRegion.GetBounds();
   {
-    // If a scrollable container layer is created in nsDisplayList::PaintForFrame,
-    // it will be the scroll parent for display items that are built in the
-    // BuildDisplayListForStackingContext call below. We need to set the scroll
-    // parent on the display list builder while we build those items, so that they
-    // can pick up their scroll parent's id.
-    ViewID id = FrameMetrics::NULL_SCROLL_ID;
-    if (ignoreViewportScrolling && presContext->IsRootContentDocument()) {
-      if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
-        if (nsIContent* content = rootScrollFrame->GetContent()) {
-          id = nsLayoutUtils::FindOrCreateIDFor(content);
-        }
-      }
-    }
-#if !defined(MOZ_WIDGET_ANDROID) || defined(MOZ_ANDROID_APZ)
-    else if (presShell->GetDocument() && presShell->GetDocument()->IsRootDisplayDocument()
-        && !presShell->GetRootScrollFrame()) {
-      // In cases where the root document is a XUL document, we want to take
-      // the ViewID from the root element, as that will be the ViewID of the
-      // root APZC in the tree. Skip doing this in cases where we know
-      // nsGfxScrollFrame::BuilDisplayList will do it instead.
-      if (dom::Element* element = presShell->GetDocument()->GetDocumentElement()) {
-        id = nsLayoutUtils::FindOrCreateIDFor(element);
-      }
-    }
-#endif
-
-    nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id);
-
     PROFILER_LABEL("nsLayoutUtils", "PaintFrame::BuildDisplayList",
       js::ProfileEntry::Category::GRAPHICS);
 
     aFrame->BuildDisplayListForStackingContext(&builder, dirtyRect, &list);
 #ifdef DEBUG
     if (builder.IsForGenerateGlyphMask() || builder.IsForPaintingSelectionBG()) {
       // PaintFrame is called to generate text glyph by
       // nsDisplayBackgroundImage::Paint or nsDisplayBackgroundColor::Paint.
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2777,21 +2777,27 @@ nsIFrame::BuildDisplayListForChild(nsDis
   NS_ASSERTION(!isStackingContext || pseudoStackingContext,
                "Stacking contexts must also be pseudo-stacking-contexts");
 
   nsDisplayListBuilder::AutoBuildingDisplayList
     buildingForChild(aBuilder, child, dirty, pseudoStackingContext);
   DisplayListClipState::AutoClipMultiple clipState(aBuilder);
   CheckForApzAwareEventHandlers(aBuilder, child);
 
+  Maybe<nsDisplayListBuilder::AutoCurrentScrollParentIdSetter> idSetter;
   if (savedOutOfFlowData) {
     clipState.SetClipForContainingBlockDescendants(
       &savedOutOfFlowData->mContainingBlockClip);
     clipState.SetScrollClipForContainingBlockDescendants(
       savedOutOfFlowData->mContainingBlockScrollClip);
+
+    // For out-of-flow frames, the current scroll parent is wrong, because it's
+    // the scroll parent for the placeholder frame rather than the actual
+    // out-of-flow frame, so we need to update the current scroll parent.
+    idSetter.emplace(aBuilder, savedOutOfFlowData);
   } else if (GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO &&
              isPlaceholder) {
     // If we have nested out-of-flow frames and the outer one isn't visible
     // then we won't have stored clip data for it. We can just clear the clip
     // instead since we know we won't render anything, and the inner out-of-flow
     // frame will setup the correct clip for itself.
     clipState.Clear();
   }
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3294,21 +3294,21 @@ ScrollFrameHelper::BuildDisplayList(nsDi
 
   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.
+    nsIContent* content = couldBuildLayer ? mScrolledFrame->GetContent()
+                                          : nullptr;
+
     nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(
-        aBuilder,
-        couldBuildLayer && mScrolledFrame->GetContent()
-            ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())
-            : aBuilder->GetCurrentScrollParentId());
+        aBuilder, content ? mOuter : aBuilder->GetCurrentScrollParent());
 
     nsRect clipRect = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
     // Our override of GetBorderRadii ensures we never have a radius at
     // the corners where we have a scrollbar.
     nscoord radii[8];
     bool haveRadii = mOuter->GetPaddingBoxBorderRadii(radii);
     if (mIsRoot) {
       clipRect.SizeTo(nsLayoutUtils::CalculateCompositionSizeForFrame(mOuter));
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -478,21 +478,22 @@ nsSubDocumentFrame::BuildDisplayList(nsD
       // the layer we will construct will be clipped by the current clip.
       // In fact for nsDisplayZoom propagating it down would be incorrect since
       // nsDisplayZoom changes the meaning of appunits.
       nestedClipState.EnterStackingContextContents(true);
     }
 
     if (subdocRootFrame) {
       nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+      nsIContent* content = ignoreViewportScrolling && rootScrollFrame
+                          ? rootScrollFrame->GetContent()
+                          : nullptr;
+
       nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(
-          aBuilder,
-          ignoreViewportScrolling && rootScrollFrame && rootScrollFrame->GetContent()
-              ? nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent())
-              : aBuilder->GetCurrentScrollParentId());
+          aBuilder, content ? rootScrollFrame : aBuilder->GetCurrentScrollParent());
 
       aBuilder->SetAncestorHasApzAwareEventHandler(false);
       subdocRootFrame->
         BuildDisplayListForStackingContext(aBuilder, dirty, &childItems);
     }
 
     if (!aBuilder->IsForEventDelivery()) {
       // If we are going to use a displayzoom below then any items we put under