Bug 1549860 - Make display item merging nsDisplayWrapList specific operation r=mattwoodrow
authorMiko Mynttinen <mikokm@gmail.com>
Wed, 08 May 2019 15:51:23 +0000
changeset 531901 f6b5ab8f054ad48b1826c5af211a1a6c0bb214bb
parent 531900 37883d51ffd1e3809cfda2b9efd11df72878f20d
child 531902 c49518b4191c5985f6bfb21fab850c3b73313234
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1549860
milestone68.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 1549860 - Make display item merging nsDisplayWrapList specific operation r=mattwoodrow Differential Revision: https://phabricator.services.mozilla.com/D30283
layout/painting/FrameLayerBuilder.cpp
layout/painting/FrameLayerBuilder.h
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -280,19 +280,27 @@ void DisplayItemData::EndUpdate(nsAutoPt
   mChangedFrameInvalidations.SetEmpty();
 
   EndUpdate();
 }
 
 void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState,
                                   bool aFirstUpdate,
                                   nsDisplayItem* aItem /* = nullptr */) {
-  BeginUpdate(aLayer, aState, aItem,
-              (aItem && !aFirstUpdate) ? aItem->IsReused() : false,
-              aItem ? aItem->HasMergedFrames() : false);
+  bool isReused = false;
+  bool isMerged = false;
+
+  if (aItem) {
+    isReused = !aFirstUpdate ? aItem->IsReused() : false;
+
+    const nsDisplayWrapList* wraplist = aItem->AsDisplayWrapList();
+    isMerged = wraplist && wraplist->HasMergedFrames();
+  }
+
+  BeginUpdate(aLayer, aState, aItem, isReused, isMerged);
 }
 
 void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState,
                                   nsDisplayItem* aItem, bool aIsReused,
                                   bool aIsMerged) {
   MOZ_RELEASE_ASSERT(mLayer);
   MOZ_RELEASE_ASSERT(aLayer);
   mLayer = aLayer;
@@ -323,30 +331,32 @@ void DisplayItemData::BeginUpdate(Layer*
   // since we have to modify userdata each time
   AutoTArray<nsIFrame*, 4> copy(mFrameList);
   if (!copy.RemoveElement(aItem->Frame())) {
     AddFrame(aItem->Frame());
     mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
                                   aItem->Frame()->GetVisualOverflowRect());
   }
 
-  AutoTArray<nsIFrame*, 4> mergedFrames;
-  aItem->GetMergedFrames(&mergedFrames);
-  for (uint32_t i = 0; i < mergedFrames.Length(); ++i) {
-    if (!copy.RemoveElement(mergedFrames[i])) {
-      AddFrame(mergedFrames[i]);
-      mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
-                                    mergedFrames[i]->GetVisualOverflowRect());
-    }
-  }
-
-  for (uint32_t i = 0; i < copy.Length(); i++) {
-    RemoveFrame(copy[i]);
+  if (aIsMerged) {
+    MOZ_ASSERT(aItem->AsDisplayWrapList());
+
+    for (nsIFrame* frame : aItem->AsDisplayWrapList()->GetMergedFrames()) {
+      if (!copy.RemoveElement(frame)) {
+        AddFrame(frame);
+        mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
+                                      frame->GetVisualOverflowRect());
+      }
+    }
+  }
+
+  for (nsIFrame* frame : copy) {
+    RemoveFrame(frame);
     mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
-                                  copy[i]->GetVisualOverflowRect());
+                                  frame->GetVisualOverflowRect());
   }
 }
 
 static const nsIFrame* sDestroyedFrame = nullptr;
 DisplayItemData::~DisplayItemData() {
   MOZ_COUNT_DTOR(DisplayItemData);
 
   if (mItem) {
@@ -3735,35 +3745,38 @@ void PaintedLayerData::Accumulate(Contai
 
   const bool hasOpacity = aOpacityIndices.Length() > 0;
 
   const DisplayItemClip* oldClip = mItemClip;
   mItemClip = &aClip;
 
   UpdateEffectStatus(aType, aOpacityIndices);
 
+  const bool isMerged = aItem->AsDisplayWrapList() &&
+                        aItem->AsDisplayWrapList()->HasMergedFrames();
+
   if (IsEffectEndMarker(aType)) {
     mAssignedDisplayItems.emplace_back(aItem, aLayerState, nullptr,
                                        aContentRect, aType, hasOpacity,
-                                       aTransform);
+                                       aTransform, isMerged);
     return;
   }
 
   bool clipMatches =
       (oldClip == mItemClip) || (oldClip && *oldClip == *mItemClip);
 
   DisplayItemData* currentData =
-      aItem->HasMergedFrames() ? nullptr : aItem->GetDisplayItemData();
+      isMerged ? nullptr : aItem->GetDisplayItemData();
 
   DisplayItemData* oldData = aState->mLayerBuilder->GetOldLayerForFrame(
       aItem->Frame(), aItem->GetPerFrameKey(), currentData,
       aItem->GetDisplayItemDataLayerManager());
 
   mAssignedDisplayItems.emplace_back(aItem, aLayerState, oldData, aContentRect,
-                                     aType, hasOpacity, aTransform);
+                                     aType, hasOpacity, aTransform, isMerged);
 
   if (aState->mBuilder->NeedToForceTransparentSurfaceForItem(aItem)) {
     mForceTransparentSurface = true;
   }
 
   if (aState->mParameters.mDisableSubpixelAntialiasingInDescendants) {
     // Disable component alpha.
     // Note that the transform (if any) on the PaintedLayer is always an integer
@@ -5477,25 +5490,26 @@ void FrameLayerBuilder::StoreDataForFram
   data->BeginUpdate(aLayer, aState, true);
 
   lmd->mDisplayItems.push_back(data);
 }
 
 AssignedDisplayItem::AssignedDisplayItem(
     nsDisplayItem* aItem, LayerState aLayerState, DisplayItemData* aData,
     const nsRect& aContentRect, DisplayItemEntryType aType,
-    const bool aHasOpacity, const RefPtr<TransformClipNode>& aTransform)
+    const bool aHasOpacity, const RefPtr<TransformClipNode>& aTransform,
+    const bool aIsMerged)
     : mItem(aItem),
       mLayerState(aLayerState),
       mDisplayItemData(aData),
       mContentRect(aContentRect),
       mTransform(aTransform),
       mType(aType),
       mReused(aItem->IsReused()),
-      mMerged(aItem->HasMergedFrames()),
+      mMerged(aIsMerged),
       mHasOpacity(aHasOpacity),
       mHasTransform(aTransform),
       mHasPaintRect(aItem->HasPaintRect()) {}
 
 AssignedDisplayItem::~AssignedDisplayItem() {
   if (mInactiveLayerManager) {
     mInactiveLayerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
   }
--- a/layout/painting/FrameLayerBuilder.h
+++ b/layout/painting/FrameLayerBuilder.h
@@ -220,17 +220,18 @@ class RefCountedRegion {
   nsRegion mRegion;
   bool mIsInfinite;
 };
 
 struct AssignedDisplayItem {
   AssignedDisplayItem(nsDisplayItem* aItem, LayerState aLayerState,
                       DisplayItemData* aData, const nsRect& aContentRect,
                       DisplayItemEntryType aType, const bool aHasOpacity,
-                      const RefPtr<TransformClipNode>& aTransform);
+                      const RefPtr<TransformClipNode>& aTransform,
+                      const bool aIsMerged);
   ~AssignedDisplayItem();
 
   nsDisplayItem* mItem;
   LayerState mLayerState;
   DisplayItemData* mDisplayItemData;
   nsRect mContentRect;
 
   /**
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -947,24 +947,24 @@ void nsDisplayListBuilder::AddAnimations
   Send sendFlag = !aBuilder ? Send::NextTransaction : Send::Immediate;
   AnimationInfo& animationInfo = aLayer->GetAnimationInfo();
   AddAnimationsForDisplayItem(aFrame, aBuilder, aItem, aType, sendFlag,
                               layers::LayersBackend::LAYERS_CLIENT,
                               animationInfo);
   animationInfo.TransferMutatedFlagToLayer(aLayer);
 }
 
-nsDisplayItem* nsDisplayListBuilder::MergeItems(
-    nsTArray<nsDisplayItem*>& aMergedItems) {
+nsDisplayWrapList* nsDisplayListBuilder::MergeItems(
+    nsTArray<nsDisplayWrapList*>& aItems) {
   // For merging, we create a temporary item by cloning the last item of the
   // mergeable items list. This ensures that the temporary item will have the
   // correct frame and bounds.
-  nsDisplayItem* merged = nullptr;
-
-  for (nsDisplayItem* item : Reversed(aMergedItems)) {
+  nsDisplayWrapList* merged = nullptr;
+
+  for (nsDisplayWrapList* item : Reversed(aItems)) {
     MOZ_ASSERT(item);
 
     if (!merged) {
       // Create the temporary item.
       merged = item->Clone(this);
       MOZ_ASSERT(merged);
 
       AddTemporaryItem(merged);
@@ -5725,18 +5725,18 @@ nsDisplayWrapList::nsDisplayWrapList(nsD
   nsRect visible = aBuilder->GetVisibleRect() +
                    aBuilder->GetCurrentFrameOffsetToReferenceFrame();
 
   SetBuildingRect(visible);
 }
 
 nsDisplayWrapList::~nsDisplayWrapList() { MOZ_COUNT_DTOR(nsDisplayWrapList); }
 
-void nsDisplayWrapList::MergeDisplayListFromItem(nsDisplayListBuilder* aBuilder,
-                                                 const nsDisplayItem* aItem) {
+void nsDisplayWrapList::MergeDisplayListFromItem(
+    nsDisplayListBuilder* aBuilder, const nsDisplayWrapList* aItem) {
   const nsDisplayWrapList* wrappedItem = aItem->AsDisplayWrapList();
   MOZ_ASSERT(wrappedItem);
 
   // Create a new nsDisplayWrapList using a copy-constructor. This is done
   // to preserve the information about bounds.
   nsDisplayWrapList* wrapper =
       MakeClone<nsDisplayWrapList>(aBuilder, wrappedItem);
   MOZ_ASSERT(wrapper);
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -346,16 +346,18 @@ enum class nsDisplayListBuilderMode : ui
   Painting,
   EventDelivery,
   PluginGeometry,
   FrameVisibility,
   TransformComputation,
   GenerateGlyph,
 };
 
+class nsDisplayWrapList;
+
 /**
  * This manages a display list and is passed as a parameter to
  * nsIFrame::BuildDisplayList.
  * It contains the parameters that don't change from frame to frame and manages
  * the display list memory using an arena. It also establishes the reference
  * coordinate system for all display list items. Some of the parameters are
  * available from the prescontext/presshell, but we copy them into the builder
  * for faster/more convenient access.
@@ -1103,17 +1105,17 @@ class nsDisplayListBuilder {
                                                  nsIFrame* aFrame,
                                                  DisplayItemType aType);
 
   /**
    * Merges the display items in |aMergedItems| and returns a new temporary
    * display item.
    * The display items in |aMergedItems| have to be mergeable with each other.
    */
-  nsDisplayItem* MergeItems(nsTArray<nsDisplayItem*>& aMergedItems);
+  nsDisplayWrapList* MergeItems(nsTArray<nsDisplayWrapList*>& aItems);
 
   /**
    * A helper class used to temporarily set nsDisplayListBuilder properties for
    * building display items.
    * aVisibleRect and aDirtyRect are relative to aForChild.
    */
   class AutoBuildingDisplayList {
    public:
@@ -2107,17 +2109,16 @@ class nsDisplayItemLink {
   nsDisplayItemLink(const nsDisplayItemLink&) : mAbove(nullptr) {}
   ~nsDisplayItemLink() { MOZ_RELEASE_ASSERT(!mAbove); }
   nsDisplayItem* mAbove;
 
   friend class nsDisplayList;
 };
 
 class nsPaintedDisplayItem;
-class nsDisplayWrapList;
 
 /*
  * nsDisplayItemBase is a base-class for all display items. It is mainly
  * responsible for handling the frame-display item 1:n relationship, as well as
  * storing the state needed for display list merging.
  *
  * Display items are arena-allocated during display list construction.
  *
@@ -2810,47 +2811,16 @@ class nsDisplayItem : public nsDisplayIt
    *
    * @return true if the item is visible, false if no part of the item
    * is visible.
    */
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion);
 
   /**
-   * Checks if the given display item can be merged with this item.
-   * @return true if the merging is possible, otherwise false.
-   */
-  virtual bool CanMerge(const nsDisplayItem* aItem) const { return false; }
-
-  /**
-   * Try to merge with the other item (which is below us in the display
-   * list). This gets used by nsDisplayClip to coalesce clipping operations
-   * (optimization), by nsDisplayOpacity to merge rendering for the same
-   * content element into a single opacity group (correctness), and will be
-   * used by nsDisplayOutline to merge multiple outlines for the same element
-   * (also for correctness).
-   */
-  virtual void Merge(const nsDisplayItem* aItem) {}
-
-  /**
-   * Merges the given display list to this item.
-   */
-  virtual void MergeDisplayListFromItem(nsDisplayListBuilder* aBuilder,
-                                        const nsDisplayItem* aItem) {}
-
-  /**
-   * Appends the underlying frames of all display items that have been
-   * merged into this one (excluding  this item's own underlying frame)
-   * to aFrames.
-   */
-  virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) const {}
-
-  virtual bool HasMergedFrames() const { return false; }
-
-  /**
    * Returns true if this item needs to have its geometry updated, despite
    * returning empty invalidation region.
    */
   virtual bool NeedsGeometryUpdates() const { return false; }
 
   /**
    * Some items such as those calling into the native themed widget machinery
    * have to be painted on the content process. In this case it is best to avoid
@@ -3797,108 +3767,16 @@ class RetainedDisplayList : public nsDis
 
   DirectedAcyclicGraph<MergedListUnits> mDAG;
 
   // Temporary state initialized during the preprocess pass
   // of RetainedDisplayListBuilder and then used during merging.
   nsTArray<OldItemInfo> mOldItems;
 };
 
-class FlattenedDisplayListIterator {
- public:
-  FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder,
-                               nsDisplayList* aList)
-      : FlattenedDisplayListIterator(aBuilder, aList, true) {}
-
-  ~FlattenedDisplayListIterator() { MOZ_ASSERT(!HasNext()); }
-
-  virtual bool HasNext() const { return mNext || !mStack.IsEmpty(); }
-
-  nsDisplayItem* GetNextItem() {
-    MOZ_ASSERT(mNext);
-
-    nsDisplayItem* current = mNext;
-    nsDisplayItem* next = current->GetAbove();
-
-    // Attempt to merge |next| with |current|.
-    if (next && current->CanMerge(next)) {
-      // Merging is possible, collect all the successive mergeable items.
-      AutoTArray<nsDisplayItem*, 2> willMerge{current};
-
-      do {
-        willMerge.AppendElement(next);
-      } while ((next = next->GetAbove()) && current->CanMerge(next));
-
-      current = mBuilder->MergeItems(willMerge);
-    }
-
-    // |mNext| will be either the first item that could not be merged with
-    // |current|, or nullptr.
-    mNext = next;
-    ResolveFlattening();
-
-    return current;
-  }
-
-  nsDisplayItem* PeekNext() { return mNext; }
-
- protected:
-  FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder,
-                               nsDisplayList* aList,
-                               const bool aResolveFlattening)
-      : mBuilder(aBuilder), mNext(aList->GetBottom()) {
-    if (aResolveFlattening) {
-      // This is done conditionally in case subclass overrides
-      // ShouldFlattenNextItem().
-      ResolveFlattening();
-    }
-  }
-
-  virtual void EnterChildList(nsDisplayItem* aContainerItem) {}
-  virtual void ExitChildList() {}
-
-  bool AtEndOfNestedList() const { return !mNext && mStack.Length() > 0; }
-
-  virtual bool ShouldFlattenNextItem() {
-    return mNext && mNext->ShouldFlattenAway(mBuilder);
-  }
-
-  void ResolveFlattening() {
-    // Handle the case where we reach the end of a nested list, or the current
-    // item should start a new nested list. Repeat this until we find an actual
-    // item, or the very end of the outer list.
-    while (AtEndOfNestedList() || ShouldFlattenNextItem()) {
-      if (AtEndOfNestedList()) {
-        ExitChildList();
-
-        // We reached the end of the list, pop the next item from the stack.
-        mNext = mStack.PopLastElement();
-      } else {
-        EnterChildList(mNext);
-
-        // This item wants to be flattened. Store the next item on the stack,
-        // and use the first item in the child list instead.
-        mStack.AppendElement(mNext->GetAbove());
-
-        nsDisplayList* childItems =
-            mNext->GetType() != DisplayItemType::TYPE_TRANSFORM
-                ? mNext->GetSameCoordinateSystemChildren()
-                : mNext->GetChildren();
-
-        mNext = childItems->GetBottom();
-      }
-    }
-  }
-
- private:
-  nsDisplayListBuilder* mBuilder;
-  nsDisplayItem* mNext;
-  AutoTArray<nsDisplayItem*, 16> mStack;
-};
-
 struct HitTestInfo {
   HitTestInfo(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
               const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags)
       : mArea(aFrame->GetCompositorHitTestArea(aBuilder)),
         mFlags(aHitTestFlags),
         mAGR(aBuilder->FindAnimatedGeometryRootFor(aFrame)),
         mASR(aBuilder->CurrentActiveScrolledRoot()),
         mClipChain(aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder)),
@@ -5395,17 +5273,17 @@ class nsDisplayWrapList : public nsDispl
   }
 
   /**
    * Creates a new nsDisplayWrapList that holds a pointer to the display list
    * owned by the given nsDisplayItem. The new nsDisplayWrapList will be added
    * to the bottom of this item's contents.
    */
   void MergeDisplayListFromItem(nsDisplayListBuilder* aBuilder,
-                                const nsDisplayItem* aItem) override;
+                                const nsDisplayWrapList* aItem);
 
   /**
    * Call this if the wrapped list is changed.
    */
   void UpdateBounds(nsDisplayListBuilder* aBuilder) override {
     // Clear the clip chain up to the asr, but don't store it, so that we'll
     // recover it when we reuse the item.
     if (mClearingClipChain) {
@@ -5447,29 +5325,44 @@ class nsDisplayWrapList : public nsDispl
   mozilla::Maybe<nscolor> IsUniform(
       nsDisplayListBuilder* aBuilder) const override;
   void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                          nsRegion* aVisibleRegion) override;
 
   uint16_t CalculatePerFrameKey() const override { return mIndex; }
 
-  bool CanMerge(const nsDisplayItem* aItem) const override { return false; }
-
-  void Merge(const nsDisplayItem* aItem) override {
+  /**
+   * Checks if the given display item can be merged with this item.
+   * @return true if the merging is possible, otherwise false.
+   */
+  virtual bool CanMerge(const nsDisplayItem* aItem) const { return false; }
+
+  /**
+   * Try to merge with the other item (which is below us in the display
+   * list). This gets used by nsDisplayClip to coalesce clipping operations
+   * (optimization), by nsDisplayOpacity to merge rendering for the same
+   * content element into a single opacity group (correctness), and will be
+   * used by nsDisplayOutline to merge multiple outlines for the same element
+   * (also for correctness).
+   */
+  virtual void Merge(const nsDisplayItem* aItem) {
     MOZ_ASSERT(CanMerge(aItem));
     MOZ_ASSERT(Frame() != aItem->Frame());
     MergeFromTrackingMergedFrames(static_cast<const nsDisplayWrapList*>(aItem));
   }
 
-  void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) const override {
-    aFrames->AppendElements(mMergedFrames);
-  }
-
-  bool HasMergedFrames() const override { return !mMergedFrames.IsEmpty(); }
+  /**
+   * Returns the underlying frames of all display items that have been
+   * merged into this one (excluding this item's own underlying frame)
+   * to aFrames.
+   */
+  const nsTArray<nsIFrame*>& GetMergedFrames() const { return mMergedFrames; }
+
+  bool HasMergedFrames() const { return !mMergedFrames.IsEmpty(); }
 
   bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
     return true;
   }
 
   bool IsInvalid(nsRect& aRect) const override {
     if (mFrame->IsInvalid(aRect) && aRect.IsEmpty()) {
       return true;
@@ -5558,16 +5451,17 @@ class nsDisplayWrapList : public nsDispl
   nsRect mBaseBuildingRect;
   int32_t mOverrideZIndex;
   uint16_t mIndex;
   bool mHasZIndexOverride;
   bool mClearingClipChain = false;
 
  private:
   NS_DISPLAY_ALLOW_CLONING()
+  friend class nsDisplayListBuilder;
 };
 
 /**
  * We call WrapDisplayList on the in-flow lists: BorderBackground(),
  * BlockBorderBackgrounds() and Content().
  * We call WrapDisplayItem on each item of Outlines(), PositionedDescendants(),
  * and Floats(). This is done to support special wrapping processing for frames
  * that may not be in-flow descendants of the current frame.
@@ -6852,18 +6746,16 @@ class nsDisplayTransform : public nsDisp
       mozilla::layers::RenderRootStateManager* aManager,
       nsDisplayListBuilder* aDisplayListBuilder) override;
   bool UpdateScrollData(
       mozilla::layers::WebRenderScrollData* aData,
       mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
   bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                          nsRegion* aVisibleRegion) override;
 
-  bool CanMerge(const nsDisplayItem* aItem) const override { return false; }
-
   uint16_t CalculatePerFrameKey() const override { return mIndex; }
 
   nsDisplayItemGeometry* AllocateGeometry(
       nsDisplayListBuilder* aBuilder) override {
     return new nsDisplayTransformGeometry(
         this, aBuilder, GetTransformForRendering(),
         mFrame->PresContext()->AppUnitsPerDevPixel());
   }
@@ -7386,16 +7278,142 @@ class nsDisplayForeignObject : public ns
   bool CreateWebRenderCommands(
       mozilla::wr::DisplayListBuilder& aBuilder,
       mozilla::wr::IpcResourceUpdateQueue& aResources,
       const StackingContextHelper& aSc,
       mozilla::layers::RenderRootStateManager* aManager,
       nsDisplayListBuilder* aDisplayListBuilder) override;
 };
 
+class FlattenedDisplayListIterator {
+ public:
+  FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder,
+                               nsDisplayList* aList)
+      : FlattenedDisplayListIterator(aBuilder, aList, true) {}
+
+  ~FlattenedDisplayListIterator() { MOZ_ASSERT(!HasNext()); }
+
+  virtual bool HasNext() const { return mNext || !mStack.IsEmpty(); }
+
+  nsDisplayItem* GetNextItem() {
+    MOZ_ASSERT(mNext);
+
+    nsDisplayItem* next = mNext;
+    mNext = next->GetAbove();
+
+    if (mNext) {
+      // There are more items in the same list, merging might be possible.
+      next = TryMergingFrom(next);
+    }
+
+    ResolveFlattening();
+
+    return next;
+  }
+
+  nsDisplayItem* PeekNext() { return mNext; }
+
+ protected:
+  FlattenedDisplayListIterator(nsDisplayListBuilder* aBuilder,
+                               nsDisplayList* aList,
+                               const bool aResolveFlattening)
+      : mBuilder(aBuilder), mNext(aList->GetBottom()) {
+    if (aResolveFlattening) {
+      // This is done conditionally in case subclass overrides
+      // ShouldFlattenNextItem().
+      ResolveFlattening();
+    }
+  }
+
+  virtual void EnterChildList(nsDisplayItem* aContainerItem) {}
+  virtual void ExitChildList() {}
+
+  bool AtEndOfNestedList() const { return !mNext && mStack.Length() > 0; }
+
+  virtual bool ShouldFlattenNextItem() {
+    return mNext && mNext->ShouldFlattenAway(mBuilder);
+  }
+
+  void ResolveFlattening() {
+    // Handle the case where we reach the end of a nested list, or the current
+    // item should start a new nested list. Repeat this until we find an actual
+    // item, or the very end of the outer list.
+    while (AtEndOfNestedList() || ShouldFlattenNextItem()) {
+      if (AtEndOfNestedList()) {
+        ExitChildList();
+
+        // We reached the end of the list, pop the next item from the stack.
+        mNext = mStack.PopLastElement();
+      } else {
+        EnterChildList(mNext);
+
+        // This item wants to be flattened. Store the next item on the stack,
+        // and use the first item in the child list instead.
+        mStack.AppendElement(mNext->GetAbove());
+
+        nsDisplayList* childItems =
+            mNext->GetType() != DisplayItemType::TYPE_TRANSFORM
+                ? mNext->GetSameCoordinateSystemChildren()
+                : mNext->GetChildren();
+
+        mNext = childItems->GetBottom();
+      }
+    }
+  }
+
+  /**
+   * Returns the the display item above |aCurrent| casted to nsDisplayWrapList,
+   * if possible.
+   */
+  nsDisplayWrapList* GetNextAsWrapList(nsDisplayItem* aCurrent) const {
+    MOZ_ASSERT(aCurrent);
+    nsDisplayItem* next = aCurrent->GetAbove();
+    return next ? next->AsDisplayWrapList() : nullptr;
+  }
+
+  /**
+   * Tries to merge display items starting from |aCurrent|.
+   * Updates the internal pointer to the next display item.
+   */
+  nsDisplayItem* TryMergingFrom(nsDisplayItem* aCurrent) {
+    MOZ_ASSERT(aCurrent);
+    MOZ_ASSERT(aCurrent->GetAbove());
+
+    nsDisplayWrapList* current = aCurrent->AsDisplayWrapList();
+    nsDisplayWrapList* next = mNext->AsDisplayWrapList();
+
+    if (!current || !next) {
+      // Either the current or the next item do not support merging.
+      return aCurrent;
+    }
+
+    // Attempt to merge |next| with |current|.
+    if (current->CanMerge(next)) {
+      // Merging is possible, collect all the successive mergeable items.
+      AutoTArray<nsDisplayWrapList*, 2> willMerge{current};
+
+      do {
+        willMerge.AppendElement(next);
+      } while ((next = GetNextAsWrapList(next)) && current->CanMerge(next));
+
+      current = mBuilder->MergeItems(willMerge);
+    }
+
+    // |mNext| will be either the first item that could not be merged with
+    // |current|, or nullptr.
+    mNext = next;
+    return current;
+  }
+
+ private:
+  nsDisplayListBuilder* mBuilder;
+  nsDisplayItem* mNext;
+  AutoTArray<nsDisplayItem*, 16> mStack;
+};
+
 namespace mozilla {
 
 class PaintTelemetry {
  public:
   enum class Metric {
     DisplayList,
     Layerization,
     FlushRasterization,