layout/painting/FrameLayerBuilder.cpp
author Kris Maglione <maglione.k@gmail.com>
Wed, 13 Feb 2019 20:50:10 -0800
changeset 516999 68cffca6927a90ace041bd3f938954fab0d0c586
parent 513865 e2cbda47cf40da6f8407a4ac5149e7e3133488db
child 517570 bbe983a2f15e08752393bb28e8ecedebab0f1a61
permissions -rw-r--r--
Bug 1524687: Follow-up: Fix sporadic Windows PGO build bustages. r=bustage CLOSED TREE

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/DebugOnly.h"

#include "FrameLayerBuilder.h"

#include "gfxContext.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Maybe.h"
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
#include "mozilla/gfx/Matrix.h"
#include "ActiveLayerTracker.h"
#include "BasicLayers.h"
#include "ImageContainer.h"
#include "ImageLayers.h"
#include "LayerTreeInvalidation.h"
#include "Layers.h"
#include "LayerUserData.h"
#include "MatrixStack.h"
#include "MaskLayerImageCache.h"
#include "UnitTransforms.h"
#include "Units.h"
#include "gfx2DGlue.h"
#include "gfxEnv.h"
#include "gfxUtils.h"
#include "nsAutoPtr.h"
#include "nsAnimationManager.h"
#include "nsDisplayList.h"
#include "nsDocShell.h"
#include "nsIScrollableFrame.h"
#include "nsImageFrame.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsPrintfCString.h"
#include "nsSVGIntegrationUtils.h"
#include "nsTransitionManager.h"
#include "mozilla/LayerTimelineMarker.h"

#include "mozilla/EffectCompositor.h"
#include "mozilla/Move.h"
#include "mozilla/ReverseIterator.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Tools.h"
#include "mozilla/layers/ShadowLayers.h"
#include "mozilla/layers/TextureClient.h"
#include "mozilla/layers/TextureWrapperImage.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "mozilla/Unused.h"
#include "GeckoProfiler.h"
#include "LayersLogging.h"
#include "gfxPrefs.h"

#include <algorithm>
#include <functional>
#include <deque>

using namespace mozilla::layers;
using namespace mozilla::gfx;

// PaintedLayerData::mAssignedDisplayItems is a std::vector, which is
// non-memmovable
DECLARE_USE_COPY_CONSTRUCTORS(mozilla::PaintedLayerData);

namespace mozilla {

class PaintedDisplayItemLayerUserData;

static nsTHashtable<nsPtrHashKey<DisplayItemData>>* sAliveDisplayItemDatas;

/**
 * The address of gPaintedDisplayItemLayerUserData is used as the user
 * data key for PaintedLayers created by FrameLayerBuilder.
 * It identifies PaintedLayers used to draw non-layer content, which are
 * therefore eligible for recycling. We want display items to be able to
 * create their own dedicated PaintedLayers in BuildLayer, if necessary,
 * and we wouldn't want to accidentally recycle those.
 * The user data is a PaintedDisplayItemLayerUserData.
 */
uint8_t gPaintedDisplayItemLayerUserData;
/**
 * The address of gColorLayerUserData is used as the user
 * data key for ColorLayers created by FrameLayerBuilder.
 * The user data is null.
 */
uint8_t gColorLayerUserData;
/**
 * The address of gImageLayerUserData is used as the user
 * data key for ImageLayers created by FrameLayerBuilder.
 * The user data is null.
 */
uint8_t gImageLayerUserData;
/**
 * The address of gLayerManagerUserData is used as the user
 * data key for retained LayerManagers managed by FrameLayerBuilder.
 * The user data is a LayerManagerData.
 */
uint8_t gLayerManagerUserData;
/**
 * The address of gMaskLayerUserData is used as the user
 * data key for mask layers managed by FrameLayerBuilder.
 * The user data is a MaskLayerUserData.
 */
uint8_t gMaskLayerUserData;
/**
 * The address of gCSSMaskLayerUserData is used as the user
 * data key for mask layers of css masking managed by FrameLayerBuilder.
 * The user data is a CSSMaskLayerUserData.
 */
uint8_t gCSSMaskLayerUserData;

// a global cache of image containers used for mask layers
static MaskLayerImageCache* gMaskLayerImageCache = nullptr;

static inline MaskLayerImageCache* GetMaskLayerImageCache() {
  if (!gMaskLayerImageCache) {
    gMaskLayerImageCache = new MaskLayerImageCache();
  }

  return gMaskLayerImageCache;
}

struct DisplayItemEntry {
  DisplayItemEntry(nsDisplayItem* aItem, DisplayItemEntryType aType)
      : mItem(aItem), mType(aType) {}

  nsDisplayItem* mItem;
  DisplayItemEntryType mType;
};

/**
 * Returns true if the given |aType| is an effect start marker.
 */
static bool IsEffectStartMarker(DisplayItemEntryType aType) {
  return aType == DisplayItemEntryType::PUSH_OPACITY ||
         aType == DisplayItemEntryType::PUSH_OPACITY_WITH_BG ||
         aType == DisplayItemEntryType::PUSH_TRANSFORM;
}

/**
 * Returns true if the given |aType| is an effect end marker.
 */
static bool IsEffectEndMarker(DisplayItemEntryType aType) {
  return aType == DisplayItemEntryType::POP_OPACITY ||
         aType == DisplayItemEntryType::POP_TRANSFORM;
}

enum class MarkerType { StartMarker, EndMarker };

/**
 * Adds the effect marker to |aMarkers| based on the type of |aItem| and whether
 * |markerType| is a start or end marker.
 */
template <MarkerType markerType>
static bool AddMarkerIfNeeded(nsDisplayItem* aItem,
                              std::deque<DisplayItemEntry>& aMarkers) {
  const DisplayItemType type = aItem->GetType();
  if (type != DisplayItemType::TYPE_OPACITY &&
      type != DisplayItemType::TYPE_TRANSFORM) {
    return false;
  }

  DisplayItemEntryType marker;

// Just a fancy way to avoid writing two separate functions to select between
// PUSH and POP markers. This is done during compile time based on |markerType|.
#define GET_MARKER(start_marker, end_marker)                      \
  std::conditional<                                               \
      markerType == MarkerType::StartMarker,                      \
      std::integral_constant<DisplayItemEntryType, start_marker>, \
      std::integral_constant<DisplayItemEntryType, end_marker>>::type::value;

  switch (type) {
    case DisplayItemType::TYPE_OPACITY:
      marker = GET_MARKER(DisplayItemEntryType::PUSH_OPACITY,
                          DisplayItemEntryType::POP_OPACITY);
      break;
    case DisplayItemType::TYPE_TRANSFORM:
      marker = GET_MARKER(DisplayItemEntryType::PUSH_TRANSFORM,
                          DisplayItemEntryType::POP_TRANSFORM);
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Invalid display item type!");
      break;
  }

  aMarkers.emplace_back(aItem, marker);
  return true;
}

DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey,
                                 Layer* aLayer, nsIFrame* aFrame)

    : mRefCnt(0),
      mParent(aParent),
      mLayer(aLayer),
      mDisplayItemKey(aKey),
      mItem(nullptr),
      mUsed(true),
      mIsInvalid(false),
      mReusedItem(false) {
  MOZ_COUNT_CTOR(DisplayItemData);

  if (!sAliveDisplayItemDatas) {
    sAliveDisplayItemDatas = new nsTHashtable<nsPtrHashKey<DisplayItemData>>();
  }
  MOZ_RELEASE_ASSERT(!sAliveDisplayItemDatas->Contains(this));
  sAliveDisplayItemDatas->PutEntry(this);

  MOZ_RELEASE_ASSERT(mLayer);
  if (aFrame) {
    AddFrame(aFrame);
  }
}

void DisplayItemData::AddFrame(nsIFrame* aFrame) {
  MOZ_RELEASE_ASSERT(mLayer);
  MOZ_RELEASE_ASSERT(!mFrameList.Contains(aFrame));
  mFrameList.AppendElement(aFrame);

  SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
  array.AppendElement(this);
}

void DisplayItemData::RemoveFrame(nsIFrame* aFrame) {
  MOZ_RELEASE_ASSERT(mLayer);
  bool result = mFrameList.RemoveElement(aFrame);
  MOZ_RELEASE_ASSERT(result, "Can't remove a frame that wasn't added!");

  SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
  array.RemoveElement(this);
}

void DisplayItemData::EndUpdate() {
  MOZ_RELEASE_ASSERT(mLayer);
  mIsInvalid = false;
  mUsed = false;
  mReusedItem = false;
  mOldTransform = nullptr;
}

void DisplayItemData::EndUpdate(nsAutoPtr<nsDisplayItemGeometry> aGeometry) {
  MOZ_RELEASE_ASSERT(mLayer);
  MOZ_ASSERT(mItem);
  MOZ_ASSERT(mGeometry || aGeometry);

  if (aGeometry) {
    mGeometry = aGeometry;
  }
  mClip = mItem->GetClip();
  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);
}

void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState,
                                  nsDisplayItem* aItem, bool aIsReused,
                                  bool aIsMerged) {
  MOZ_RELEASE_ASSERT(mLayer);
  MOZ_RELEASE_ASSERT(aLayer);
  mLayer = aLayer;
  mOptLayer = nullptr;
  mInactiveManager = nullptr;
  mLayerState = aState;
  mUsed = true;

  if (aLayer->AsPaintedLayer()) {
    if (aItem != mItem) {
      aItem->SetDisplayItemData(this, aLayer->Manager());
    } else {
      MOZ_ASSERT(aItem->GetDisplayItemData() == this);
    }
    mReusedItem = aIsReused;
  }

  if (!aItem) {
    return;
  }

  if (!aIsMerged && mFrameList.Length() == 1) {
    MOZ_ASSERT(mFrameList[0] == aItem->Frame());
    return;
  }

  // We avoid adding or removing element unnecessarily
  // 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]);
    mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
                                  copy[i]->GetVisualOverflowRect());
  }
}

static const nsIFrame* sDestroyedFrame = nullptr;
DisplayItemData::~DisplayItemData() {
  MOZ_COUNT_DTOR(DisplayItemData);

  if (mItem) {
    MOZ_ASSERT(mItem->GetDisplayItemData() == this);
    mItem->SetDisplayItemData(nullptr, nullptr);
  }

  for (uint32_t i = 0; i < mFrameList.Length(); i++) {
    nsIFrame* frame = mFrameList[i];
    if (frame == sDestroyedFrame) {
      continue;
    }

    SmallPointerArray<DisplayItemData>& array = frame->DisplayItemData();
    array.RemoveElement(this);
  }

  MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas);
  nsPtrHashKey<mozilla::DisplayItemData>* entry =
      sAliveDisplayItemDatas->GetEntry(this);
  MOZ_RELEASE_ASSERT(entry);

  sAliveDisplayItemDatas->RemoveEntry(entry);

  if (sAliveDisplayItemDatas->Count() == 0) {
    delete sAliveDisplayItemDatas;
    sAliveDisplayItemDatas = nullptr;
  }
}

void DisplayItemData::ClearAnimationCompositorState() {
  if (mDisplayItemKey !=
          static_cast<uint32_t>(DisplayItemType::TYPE_TRANSFORM) &&
      mDisplayItemKey != static_cast<uint32_t>(DisplayItemType::TYPE_OPACITY)) {
    return;
  }

  for (nsIFrame* frame : mFrameList) {
    nsCSSPropertyID prop =
        mDisplayItemKey ==
                static_cast<uint32_t>(DisplayItemType::TYPE_TRANSFORM)
            ? eCSSProperty_transform
            : eCSSProperty_opacity;
    EffectCompositor::ClearIsRunningOnCompositor(frame, prop);
  }
}

const nsRegion& DisplayItemData::GetChangedFrameInvalidations() {
  return mChangedFrameInvalidations;
}

DisplayItemData* DisplayItemData::AssertDisplayItemData(
    DisplayItemData* aData) {
  MOZ_RELEASE_ASSERT(aData);
  MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas &&
                     sAliveDisplayItemDatas->Contains(aData));
  MOZ_RELEASE_ASSERT(aData->mLayer);
  return aData;
}

/**
 * This is the userdata we associate with a layer manager.
 */
class LayerManagerData : public LayerUserData {
 public:
  explicit LayerManagerData(LayerManager* aManager)
      : mLayerManager(aManager),
#ifdef DEBUG_DISPLAY_ITEM_DATA
        mParent(nullptr),
#endif
        mInvalidateAllLayers(false) {
    MOZ_COUNT_CTOR(LayerManagerData);
  }
  ~LayerManagerData() override { MOZ_COUNT_DTOR(LayerManagerData); }

#ifdef DEBUG_DISPLAY_ITEM_DATA
  void Dump(const char* aPrefix = "") {
    printf_stderr("%sLayerManagerData %p\n", aPrefix, this);

    for (auto& data : mDisplayItems) {
      nsAutoCString prefix;
      prefix += aPrefix;
      prefix += "  ";

      const char* layerState;
      switch (data->mLayerState) {
        case LAYER_NONE:
          layerState = "LAYER_NONE";
          break;
        case LAYER_INACTIVE:
          layerState = "LAYER_INACTIVE";
          break;
        case LAYER_ACTIVE:
          layerState = "LAYER_ACTIVE";
          break;
        case LAYER_ACTIVE_FORCE:
          layerState = "LAYER_ACTIVE_FORCE";
          break;
        case LAYER_ACTIVE_EMPTY:
          layerState = "LAYER_ACTIVE_EMPTY";
          break;
        case LAYER_SVG_EFFECTS:
          layerState = "LAYER_SVG_EFFECTS";
          break;
      }
      uint32_t mask = (1 << TYPE_BITS) - 1;

      nsAutoCString str;
      str += prefix;
      str += nsPrintfCString("Frame %p ", data->mFrameList[0]);
      str += nsDisplayItem::DisplayItemTypeName(
          static_cast<nsDisplayItem::Type>(data->mDisplayItemKey & mask));
      if ((data->mDisplayItemKey >> TYPE_BITS)) {
        str += nsPrintfCString("(%i)", data->mDisplayItemKey >> TYPE_BITS);
      }
      str += nsPrintfCString(", %s, Layer %p", layerState, data->mLayer.get());
      if (data->mOptLayer) {
        str += nsPrintfCString(", OptLayer %p", data->mOptLayer.get());
      }
      if (data->mInactiveManager) {
        str += nsPrintfCString(", InactiveLayerManager %p",
                               data->mInactiveManager.get());
      }
      str += "\n";

      printf_stderr("%s", str.get());

      if (data->mInactiveManager) {
        prefix += "  ";
        printf_stderr("%sDumping inactive layer info:\n", prefix.get());
        LayerManagerData* lmd = static_cast<LayerManagerData*>(
            data->mInactiveManager->GetUserData(&gLayerManagerUserData));
        lmd->Dump(prefix.get());
      }
    }
  }
#endif

  /**
   * Tracks which frames have layers associated with them.
   */
  LayerManager* mLayerManager;
#ifdef DEBUG_DISPLAY_ITEM_DATA
  LayerManagerData* mParent;
#endif
  std::vector<RefPtr<DisplayItemData>> mDisplayItems;
  bool mInvalidateAllLayers;
};

/* static */ void FrameLayerBuilder::DestroyDisplayItemDataFor(
    nsIFrame* aFrame) {
  RemoveFrameFromLayerManager(aFrame, aFrame->DisplayItemData());
  aFrame->DisplayItemData().Clear();
  aFrame->DeleteProperty(WebRenderUserDataProperty::Key());
}

/**
 * We keep a stack of these to represent the PaintedLayers that are
 * currently available to have display items added to.
 * We use a stack here because as much as possible we want to
 * assign display items to existing PaintedLayers, and to the lowest
 * PaintedLayer in z-order. This reduces the number of layers and
 * makes it more likely a display item will be rendered to an opaque
 * layer, giving us the best chance of getting subpixel AA.
 */
class PaintedLayerData {
 public:
  PaintedLayerData()
      : mAnimatedGeometryRoot(nullptr),
        mASR(nullptr),
        mClipChain(nullptr),
        mReferenceFrame(nullptr),
        mLayer(nullptr),
        mSolidColor(NS_RGBA(0, 0, 0, 0)),
        mIsSolidColorInVisibleRegion(false),
        mNeedComponentAlpha(false),
        mForceTransparentSurface(false),
        mHideAllLayersBelow(false),
        mOpaqueForAnimatedGeometryRootParent(false),
        mBackfaceHidden(false),
        mShouldPaintOnContentSide(false),
        mDTCRequiresTargetConfirmation(false),
        mImage(nullptr),
        mItemClip(nullptr),
        mNewChildLayersIndex(-1)
#ifdef DEBUG
        ,
        mTransformLevel(0)
#endif
  {
  }

  ~PaintedLayerData() { MOZ_ASSERT(mTransformLevel == 0); }

#ifdef MOZ_DUMP_PAINTING
  /**
   * Keep track of important decisions for debugging.
   */
  nsCString mLog;

#  define FLB_LOG_PAINTED_LAYER_DECISION(pld, ...) \
    if (gfxPrefs::LayersDumpDecision()) {          \
      pld->mLog.AppendPrintf("\t\t\t\t");          \
      pld->mLog.AppendPrintf(__VA_ARGS__);         \
    }
#else
#  define FLB_LOG_PAINTED_LAYER_DECISION(...)
#endif

  /**
   * Disables component alpha for |aItem| if the component alpha bounds are not
   * contained in |mOpaqueRegion|. Alternatively if possible, sets
   * |mNeedComponentAlpha| to true for this PaintedLayerData.
   */
  bool SetupComponentAlpha(ContainerState* aState, nsDisplayItem* aItem,
                           const nsIntRect& aVisibleRect,
                           const TransformClipNode* aTransform);

  /**
   * Record that an item has been added to the PaintedLayer, so we
   * need to update our regions.
   * @param aVisibleRect the area of the item that's visible
   * @param aSolidColor if non-null, the visible area of the item is
   * a constant color given by *aSolidColor
   */
  void Accumulate(ContainerState* aState, nsDisplayItem* aItem,
                  const nsIntRect& aVisibleRect, const nsRect& aContentRect,
                  const DisplayItemClip& aClip, LayerState aLayerState,
                  nsDisplayList* aList, DisplayItemEntryType aType,
                  nsTArray<size_t>& aOpacityIndices,
                  const RefPtr<TransformClipNode>& aTransform);

  /**
   * Updates the status of |mTransform| and |aOpacityIndices|, based on |aType|.
   */
  void UpdateEffectStatus(DisplayItemEntryType aType,
                          nsTArray<size_t>& aOpacityIndices);

  AnimatedGeometryRoot* GetAnimatedGeometryRoot() {
    return mAnimatedGeometryRoot;
  }

  /**
   * A region including the horizontal pan, vertical pan, and no action regions.
   */
  nsRegion CombinedTouchActionRegion();

  /**
   * Add the given hit test info to the hit regions for this PaintedLayer.
   */
  void AccumulateHitTestItem(ContainerState* aState, nsDisplayItem* aItem,
                             const DisplayItemClip& aClip,
                             TransformClipNode* aTransform);

  /**
   * If this represents only a nsDisplayImage, and the image type supports being
   * optimized to an ImageLayer, returns true.
   */
  bool CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder);

  /**
   * If this represents only a nsDisplayImage, and the image type supports being
   * optimized to an ImageLayer, returns an ImageContainer for the underlying
   * image if one is available.
   */
  already_AddRefed<ImageContainer> GetContainerForImageLayer(
      nsDisplayListBuilder* aBuilder);

  bool VisibleAboveRegionIntersects(const nsIntRegion& aRegion) const {
    return !mVisibleAboveRegion.Intersect(aRegion).IsEmpty();
  }
  bool VisibleRegionIntersects(const nsIntRegion& aRegion) const {
    return !mVisibleRegion.Intersect(aRegion).IsEmpty();
  }

  /**
   * The region of visible content in the layer, relative to the
   * container layer (which is at the snapped top-left of the display
   * list reference frame).
   */
  nsIntRegion mVisibleRegion;
  /**
   * The region of visible content in the layer that is opaque.
   * Same coordinate system as mVisibleRegion.
   */
  nsIntRegion mOpaqueRegion;
  /**
   * The definitely-hit region for this PaintedLayer.
   */
  nsRegion mHitRegion;
  /**
   * The maybe-hit region for this PaintedLayer.
   */
  nsRegion mMaybeHitRegion;
  /**
   * The dispatch-to-content hit region for this PaintedLayer.
   */
  nsRegion mDispatchToContentHitRegion;
  /**
   * The region for this PaintedLayer that is sensitive to events
   * but disallows panning and zooming. This is an approximation
   * and any deviation from the true region will be part of the
   * mDispatchToContentHitRegion.
   */
  nsRegion mNoActionRegion;
  /**
   * The region for this PaintedLayer that is sensitive to events and
   * allows horizontal panning but not zooming. This is an approximation
   * and any deviation from the true region will be part of the
   * mDispatchToContentHitRegion.
   */
  nsRegion mHorizontalPanRegion;
  /**
   * The region for this PaintedLayer that is sensitive to events and
   * allows vertical panning but not zooming. This is an approximation
   * and any deviation from the true region will be part of the
   * mDispatchToContentHitRegion.
   */
  nsRegion mVerticalPanRegion;

  bool mCollapsedTouchActions = false;
  /**
   * Scaled versions of the bounds of mHitRegion and mMaybeHitRegion.
   * We store these because FindPaintedLayerFor() needs to consume them
   * in this form, and it's a hot code path so we don't want to scale
   * them inside that function.
   */
  nsIntRect mScaledHitRegionBounds;
  nsIntRect mScaledMaybeHitRegionBounds;
  /**
   * The "active scrolled root" for all content in the layer. Must
   * be non-null; all content in a PaintedLayer must have the same
   * active scrolled root.
   */
  AnimatedGeometryRoot* mAnimatedGeometryRoot;
  const ActiveScrolledRoot* mASR;
  /**
   * The chain of clips that should apply to this layer.
   */
  const DisplayItemClipChain* mClipChain;
  /**
   * The offset between mAnimatedGeometryRoot and the reference frame.
   */
  nsPoint mAnimatedGeometryRootOffset;
  /**
   * If non-null, the frame from which we'll extract "fixed positioning"
   * metadata for this layer. This can be a position:fixed frame or a viewport
   * frame; the latter case is used for background-attachment:fixed content.
   */
  const nsIFrame* mReferenceFrame;
  PaintedLayer* mLayer;
  /**
   * If mIsSolidColorInVisibleRegion is true, this is the color of the visible
   * region.
   */
  nscolor mSolidColor;
  /**
   * True if every pixel in mVisibleRegion will have color mSolidColor.
   */
  bool mIsSolidColorInVisibleRegion;
  /**
   * True if there is any text visible in the layer that's over
   * transparent pixels in the layer.
   */
  bool mNeedComponentAlpha;
  /**
   * Set if the layer should be treated as transparent, even if its entire
   * area is covered by opaque display items. For example, this needs to
   * be set if something is going to "punch holes" in the layer by clearing
   * part of its surface.
   */
  bool mForceTransparentSurface;
  /**
   * Set if all layers below this PaintedLayer 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 PaintedLayer's animated geometry root is a scrollframe
   * and the PaintedLayer completely fills the displayport of the scrollframe.
   */
  bool mOpaqueForAnimatedGeometryRootParent;
  /**
   * Set if the backface of this region is hidden to the user.
   * Content that backface is hidden should not be draw on the layer
   * with visible backface.
   */
  bool mBackfaceHidden;
  /**
   * Set if it is better to render this layer on the content process, for
   * example if it contains native theme widgets.
   */
  bool mShouldPaintOnContentSide;
  /**
   * Set to true if events targeting the dispatch-to-content region
   * require target confirmation.
   * See CompositorHitTestFlags::eRequiresTargetConfirmation and
   * EventRegions::mDTCRequiresTargetConfirmation.
   */
  bool mDTCRequiresTargetConfirmation;
  /**
   * 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
   * image, a clip for SOME item in the layer. There is no guarantee which
   * item's clip will be stored here and mItemClip should not be used to clip
   * the whole layer - only some part of the clip should be used, as determined
   * by PaintedDisplayItemLayerUserData::GetCommonClipCount() - which may even
   * be no part at all.
   */
  const DisplayItemClip* mItemClip;
  /**
   * Index of this layer in mNewChildLayers.
   */
  int32_t mNewChildLayersIndex;
  /**
   * The region of visible content above the layer and below the
   * next PaintedLayerData currently in the stack, if any.
   * This is a conservative approximation: it contains the true region.
   */
  nsIntRegion mVisibleAboveRegion;
  /**
   * All the display items that have been assigned to this painted layer.
   * These items get added by Accumulate().
   */
  std::vector<AssignedDisplayItem> mAssignedDisplayItems;

#ifdef DEBUG
  /**
   * Tracks the level of transform to ensure balanced PUSH/POP markers.
   */
  int mTransformLevel;
#endif
};

struct NewLayerEntry {
  NewLayerEntry()
      : mAnimatedGeometryRoot(nullptr),
        mASR(nullptr),
        mClipChain(nullptr),
        mScrollMetadataASR(nullptr),
        mLayerContentsVisibleRect(0, 0, -1, -1),
        mLayerState(LAYER_INACTIVE),
        mHideAllLayersBelow(false),
        mOpaqueForAnimatedGeometryRootParent(false),
        mPropagateComponentAlphaFlattening(true),
        mUntransformedVisibleRegion(false),
        mIsFixedToRootScrollFrame(false) {}
  // mLayer is null if the previous entry is for a PaintedLayer that hasn't
  // been optimized to some other form (yet).
  RefPtr<Layer> mLayer;
  AnimatedGeometryRoot* mAnimatedGeometryRoot;
  const ActiveScrolledRoot* mASR;
  const DisplayItemClipChain* mClipChain;
  const ActiveScrolledRoot* mScrollMetadataASR;
  // If non-null, this ScrollMetadata is set to the be the first ScrollMetadata
  // on the layer.
  UniquePtr<ScrollMetadata> mBaseScrollMetadata;
  // 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;
  LayerState mLayerState;
  bool mHideAllLayersBelow;
  // When mOpaqueForAnimatedGeometryRootParent is true, the opaque region of
  // this layer is opaque in the same position even subject to the animation of
  // geometry of mAnimatedGeometryRoot. For example when mAnimatedGeometryRoot
  // is a scrolled frame and the scrolled content is opaque everywhere in the
  // displayport, we can set this flag.
  // When this flag is set, we can treat this opaque region as covering
  // content whose animated geometry root is the animated geometry root for
  // mAnimatedGeometryRoot->GetParent().
  bool mOpaqueForAnimatedGeometryRootParent;

  // If true, then the content flags for this layer should contribute
  // to our decision to flatten component alpha layers, false otherwise.
  bool mPropagateComponentAlphaFlattening;
  // mVisibleRegion is relative to the associated frame before
  // transform.
  bool mUntransformedVisibleRegion;
  bool mIsFixedToRootScrollFrame;
};

class PaintedLayerDataTree;

/**
 * This is tree node type for PaintedLayerDataTree.
 * Each node corresponds to a different animated geometry root, and contains
 * a stack of PaintedLayerDatas, in bottom-to-top order.
 * There is at most one node per animated geometry root. The ancestor and
 * descendant relations in PaintedLayerDataTree tree mirror those in the frame
 * tree.
 * Each node can have clip that describes the potential extents that items in
 * this node can cover. If mHasClip is false, it means that the node's contents
 * can move anywhere.
 * Testing against the clip instead of the node's actual contents has the
 * advantage that the node's contents can move or animate without affecting
 * content in other nodes. So we don't need to re-layerize during animations
 * (sync or async), and during async animations everything is guaranteed to
 * look correct.
 * The contents of a node's PaintedLayerData stack all share the node's
 * animated geometry root. The child nodes are on top of the PaintedLayerData
 * stack, in z-order, and the clip rects of the child nodes are allowed to
 * intersect with the visible region or visible above region of their parent
 * node's PaintedLayerDatas.
 */
class PaintedLayerDataNode {
 public:
  PaintedLayerDataNode(PaintedLayerDataTree& aTree,
                       PaintedLayerDataNode* aParent,
                       AnimatedGeometryRoot* aAnimatedGeometryRoot);
  ~PaintedLayerDataNode();

  AnimatedGeometryRoot* GetAnimatedGeometryRoot() const {
    return mAnimatedGeometryRoot;
  }

  /**
   * Whether this node's contents can potentially intersect aRect.
   * aRect is in our tree's ContainerState's coordinate space.
   */
  bool Intersects(const nsIntRect& aRect) const {
    return !mHasClip || mClipRect.Intersects(aRect);
  }

  /**
   * Create a PaintedLayerDataNode for aAnimatedGeometryRoot, add it to our
   * children, and return it.
   */
  PaintedLayerDataNode* AddChildNodeFor(
      AnimatedGeometryRoot* aAnimatedGeometryRoot);

  /**
   * Find a PaintedLayerData in our mPaintedLayerDataStack that aItem can be
   * added to. Creates a new PaintedLayerData by calling
   * aNewPaintedLayerCallback if necessary.
   */
  template <typename NewPaintedLayerCallbackType>
  PaintedLayerData* FindPaintedLayerFor(
      const nsIntRect& aVisibleRect, bool aBackfaceHidden,
      const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
      NewPaintedLayerCallbackType aNewPaintedLayerCallback);

  /**
   * Find an opaque background color for aRegion. Pulls a color from the parent
   * geometry root if appropriate, but only if that color is present underneath
   * the whole clip of this node, so that this node's contents can animate or
   * move (possibly async) without having to change the background color.
   * @param aUnderIndex Searching will start in mPaintedLayerDataStack right
   *                    below aUnderIndex.
   */
  enum { ABOVE_TOP = -1 };
  nscolor FindOpaqueBackgroundColor(const nsIntRegion& aRegion,
                                    int32_t aUnderIndex = ABOVE_TOP) const;
  /**
   * Same as FindOpaqueBackgroundColor, but only returns a color if absolutely
   * nothing is in between, so that it can be used for a layer that can move
   * anywhere inside our clip.
   */
  nscolor FindOpaqueBackgroundColorCoveringEverything() const;

  /**
   * Adds aRect to this node's top PaintedLayerData's mVisibleAboveRegion,
   * or mVisibleAboveBackgroundRegion if mPaintedLayerDataStack is empty.
   */
  void AddToVisibleAboveRegion(const nsIntRect& aRect);
  /**
   * Call this if all of our existing content can potentially be covered, so
   * nothing can merge with it and all new content needs to create new items
   * on top. This will finish all of our children and pop our whole
   * mPaintedLayerDataStack.
   */
  void SetAllDrawingAbove();

  /**
   * Finish this node: Finish all children, finish our PaintedLayer contents,
   * and (if requested) adjust our parent's visible above region to include
   * our clip.
   */
  void Finish(bool aParentNeedsAccurateVisibleAboveRegion);

  /**
   * Finish any children that intersect aRect.
   */
  void FinishChildrenIntersecting(const nsIntRect& aRect);

  /**
   * Finish all children.
   */
  void FinishAllChildren() { FinishAllChildren(true); }

 protected:
  /**
   * Finish all items in mPaintedLayerDataStack and clear the stack.
   */
  void PopAllPaintedLayerData();
  /**
   * Finish all of our child nodes, but don't touch mPaintedLayerDataStack.
   */
  void FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion);
  /**
   * Pass off opaque background color searching to our parent node, if we have
   * one.
   */
  nscolor FindOpaqueBackgroundColorInParentNode() const;

  PaintedLayerDataTree& mTree;
  PaintedLayerDataNode* mParent;
  AnimatedGeometryRoot* mAnimatedGeometryRoot;

  /**
   * Our contents: a PaintedLayerData stack and our child nodes.
   */
  AutoTArray<PaintedLayerData, 3> mPaintedLayerDataStack;

  /**
   * UniquePtr is used here in the sense of "unique ownership", i.e. there is
   * only one owner. Not in the sense of "this is the only pointer to the
   * node": There are two other, non-owning, pointers to our child nodes: The
   * node's respective children point to their parent node with their mParent
   * pointer, and the tree keeps a map of animated geometry root to node in its
   * mNodes member. These outside pointers are the reason that mChildren isn't
   * just an nsTArray<PaintedLayerDataNode> (since the pointers would become
   * invalid whenever the array expands its capacity).
   */
  nsTArray<UniquePtr<PaintedLayerDataNode>> mChildren;

  /**
   * The region that's covered between our "background" and the bottom of
   * mPaintedLayerDataStack. This is used to indicate whether we can pull
   * a background color from our parent node. If mVisibleAboveBackgroundRegion
   * should be considered infinite, mAllDrawingAboveBackground will be true and
   * the value of mVisibleAboveBackgroundRegion will be meaningless.
   */
  nsIntRegion mVisibleAboveBackgroundRegion;

  /**
   * Our clip, if we have any. If not, that means we can move anywhere, and
   * mHasClip will be false and mClipRect will be meaningless.
   */
  nsIntRect mClipRect;
  bool mHasClip;

  /**
   * Whether mVisibleAboveBackgroundRegion should be considered infinite.
   */
  bool mAllDrawingAboveBackground;
};

class ContainerState;

/**
 * A tree of PaintedLayerDataNodes. At any point in time, the tree only
 * contains nodes for animated geometry roots that new items can potentially
 * merge into. Any time content is added on top that overlaps existing things
 * in such a way that we no longer want to merge new items with some existing
 * content, that existing content gets "finished".
 * The public-facing methods of this class are FindPaintedLayerFor,
 * AddingOwnLayer, and Finish. The other public methods are for
 * PaintedLayerDataNode.
 * The tree calls out to its containing ContainerState for some things.
 * All coordinates / rects in the tree or the tree nodes are in the
 * ContainerState's coordinate space, i.e. relative to the reference frame and
 * in layer pixels.
 * The clip rects of sibling nodes never overlap. This is ensured by finishing
 * existing nodes before adding new ones, if this property were to be violated.
 * The root tree node doesn't get finished until the ContainerState is
 * finished.
 * The tree's root node is always the root reference frame of the builder. We
 * don't stop at the container state's mContainerAnimatedGeometryRoot because
 * some of our contents can have animated geometry roots that are not
 * descendants of the container's animated geometry root. Every animated
 * geometry root we encounter for our contents needs to have a defined place in
 * the tree.
 */
class PaintedLayerDataTree {
 public:
  PaintedLayerDataTree(ContainerState& aContainerState,
                       nscolor& aBackgroundColor)
      : mContainerState(aContainerState),
        mContainerUniformBackgroundColor(aBackgroundColor),
        mForInactiveLayer(false) {}

  ~PaintedLayerDataTree() {
    MOZ_ASSERT(!mRoot);
    MOZ_ASSERT(mNodes.Count() == 0);
  }

  void InitializeForInactiveLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot);

  /**
   * Notify our contents that some non-PaintedLayer content has been added.
   * *aRect needs to be a rectangle that doesn't move with respect to
   * aAnimatedGeometryRoot and that contains the added item.
   * If aRect is null, the extents will be considered infinite.
   * If aOutUniformBackgroundColor is non-null, it will be set to an opaque
   * color that can be pulled into the background of the added content, or
   * transparent if that is not possible.
   */
  void AddingOwnLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot,
                      const nsIntRect* aRect,
                      nscolor* aOutUniformBackgroundColor);

  /**
   * Find a PaintedLayerData for aItem. This can either be an existing
   * PaintedLayerData from inside a node in our tree, or a new one that gets
   * created by a call out to aNewPaintedLayerCallback.
   */
  template <typename NewPaintedLayerCallbackType>
  PaintedLayerData* FindPaintedLayerFor(
      AnimatedGeometryRoot* aAnimatedGeometryRoot,
      const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
      const nsIntRect& aVisibleRect, const bool aBackfaceHidden,
      NewPaintedLayerCallbackType aNewPaintedLayerCallback);

  /**
   * Finish everything.
   */
  void Finish();

  /**
   * Get the parent animated geometry root of aAnimatedGeometryRoot.
   * That's either aAnimatedGeometryRoot's animated geometry root, or, if
   * that's aAnimatedGeometryRoot itself, then it's the animated geometry
   * root for aAnimatedGeometryRoot's cross-doc parent frame.
   */
  AnimatedGeometryRoot* GetParentAnimatedGeometryRoot(
      AnimatedGeometryRoot* aAnimatedGeometryRoot);

  /**
   * Whether aAnimatedGeometryRoot has an intrinsic clip that doesn't move with
   * respect to aAnimatedGeometryRoot's parent animated geometry root.
   * If aAnimatedGeometryRoot is a scroll frame, this will be the scroll frame's
   * scroll port, otherwise there is no clip.
   * This method doesn't have much to do with PaintedLayerDataTree, but this is
   * where we have easy access to a display list builder, which we use to get
   * the clip rect result into the right coordinate space.
   */
  bool IsClippedWithRespectToParentAnimatedGeometryRoot(
      AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip);

  /**
   * Called by PaintedLayerDataNode when it is finished, so that we can drop
   * our pointers to it.
   */
  void NodeWasFinished(AnimatedGeometryRoot* aAnimatedGeometryRoot);

  nsDisplayListBuilder* Builder() const;
  ContainerState& ContState() const { return mContainerState; }
  nscolor UniformBackgroundColor() const {
    return mContainerUniformBackgroundColor;
  }

 protected:
  /**
   * Finish all nodes that potentially intersect *aRect, where *aRect is a rect
   * that doesn't move with respect to aAnimatedGeometryRoot.
   * If aRect is null, *aRect will be considered infinite.
   */
  void FinishPotentiallyIntersectingNodes(
      AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect);

  /**
   * Make sure that there is a node for aAnimatedGeometryRoot and all of its
   * ancestor geometry roots. Return the node for aAnimatedGeometryRoot.
   */
  PaintedLayerDataNode* EnsureNodeFor(
      AnimatedGeometryRoot* aAnimatedGeometryRoot);

  /**
   * Find an existing node in the tree for an ancestor of aAnimatedGeometryRoot.
   * *aOutAncestorChild will be set to the last ancestor that was encountered
   * in the search up from aAnimatedGeometryRoot; it will be a child animated
   * geometry root of the result, if neither are null.
   */
  PaintedLayerDataNode* FindNodeForAncestorAnimatedGeometryRoot(
      AnimatedGeometryRoot* aAnimatedGeometryRoot,
      AnimatedGeometryRoot** aOutAncestorChild);

  ContainerState& mContainerState;
  Maybe<PaintedLayerDataNode> mRoot;

  /**
   * The uniform opaque color from behind this container layer, or
   * NS_RGBA(0,0,0,0) if the background behind this container layer is not
   * uniform and opaque. This color can be pulled into PaintedLayers that are
   * directly above the background.
   */
  nscolor mContainerUniformBackgroundColor;

  /**
   * A hash map for quick access the node belonging to a particular animated
   * geometry root.
   */
  nsDataHashtable<nsPtrHashKey<AnimatedGeometryRoot>, PaintedLayerDataNode*>
      mNodes;

  bool mForInactiveLayer;
};

/**
 * This is a helper object used to build up the layer children for
 * a ContainerLayer.
 */
class ContainerState {
 public:
  ContainerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
                 FrameLayerBuilder* aLayerBuilder, nsIFrame* aContainerFrame,
                 nsDisplayItem* aContainerItem, const nsRect& aContainerBounds,
                 ContainerLayer* aContainerLayer,
                 const ContainerLayerParameters& aParameters,
                 nscolor aBackgroundColor,
                 const ActiveScrolledRoot* aContainerASR,
                 const ActiveScrolledRoot* aContainerScrollMetadataASR,
                 const ActiveScrolledRoot* aContainerCompositorASR)
      : mBuilder(aBuilder),
        mManager(aManager),
        mLayerBuilder(aLayerBuilder),
        mContainerFrame(aContainerFrame),
        mContainerLayer(aContainerLayer),
        mContainerBounds(aContainerBounds),
        mContainerASR(aContainerASR),
        mContainerScrollMetadataASR(aContainerScrollMetadataASR),
        mContainerCompositorASR(aContainerCompositorASR),
        mParameters(aParameters),
        mPaintedLayerDataTree(*this, aBackgroundColor),
        mLastDisplayPortAGR(nullptr),
        mContainerItem(aContainerItem) {
    nsPresContext* presContext = aContainerFrame->PresContext();
    mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
    mContainerReferenceFrame = const_cast<nsIFrame*>(
        aContainerItem ? aContainerItem->ReferenceFrameForChildren()
                       : mBuilder->FindReferenceFrameFor(mContainerFrame));
    bool isAtRoot = !aContainerItem ||
                    (aContainerItem->Frame() == mBuilder->RootReferenceFrame());
    MOZ_ASSERT(!isAtRoot ||
               mContainerReferenceFrame == mBuilder->RootReferenceFrame());
    mContainerAnimatedGeometryRoot =
        isAtRoot ? aBuilder->GetRootAnimatedGeometryRoot()
                 : aContainerItem->GetAnimatedGeometryRoot();
    MOZ_ASSERT(
        !mBuilder->IsPaintingToWindow() ||
        nsLayoutUtils::IsAncestorFrameCrossDoc(
            mBuilder->RootReferenceFrame(), *mContainerAnimatedGeometryRoot));
    // When AllowResidualTranslation is false, display items will be drawn
    // scaled with a translation by integer pixels, so we know how the snapping
    // will work.
    mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() &&
                       !mParameters.AllowResidualTranslation();
    CollectOldLayers();
  }

  /**
   * This is the method that actually walks a display list and builds
   * the child layers.
   */
  void ProcessDisplayItems(nsDisplayList* aList);
  /**
   * This finalizes all the open PaintedLayers by popping every element off
   * mPaintedLayerDataStack, then sets the children of the container layer
   * to be all the layers in mNewChildLayers in that order and removes any
   * layers as children of the container that aren't in mNewChildLayers.
   * @param aTextContentFlags if any child layer has CONTENT_COMPONENT_ALPHA,
   * set *aTextContentFlags to CONTENT_COMPONENT_ALPHA
   */
  void Finish(uint32_t* aTextContentFlags,
              const nsIntRect& aContainerPixelBounds,
              nsDisplayList* aChildItems);

  nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; }

  nsIntRect ScaleToNearestPixels(const nsRect& aRect) const {
    return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale,
                                      mAppUnitsPerDevPixel);
  }
  nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion) const {
    return aRegion.ScaleToNearestPixels(
        mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel);
  }
  nsIntRect ScaleToOutsidePixels(const nsRect& aRect,
                                 bool aSnap = false) const {
    if (aSnap && mSnappingEnabled) {
      return ScaleToNearestPixels(aRect);
    }
    return aRect.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale,
                                      mAppUnitsPerDevPixel);
  }
  nsIntRegion ScaleToOutsidePixels(const nsRegion& aRegion,
                                   bool aSnap = false) const {
    if (aSnap && mSnappingEnabled) {
      return ScaleRegionToNearestPixels(aRegion);
    }
    return aRegion.ScaleToOutsidePixels(
        mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel);
  }
  nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false) const {
    if (aSnap && mSnappingEnabled) {
      return ScaleToNearestPixels(aRect);
    }
    return aRect.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
                                     mAppUnitsPerDevPixel);
  }

  nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion,
                                        bool aSnap = false) const {
    if (aSnap && mSnappingEnabled) {
      return ScaleRegionToNearestPixels(aRegion);
    }
    return aRegion.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
                                       mAppUnitsPerDevPixel);
  }

  nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion,
                                         bool aSnap = false) const {
    if (aSnap && mSnappingEnabled) {
      return ScaleRegionToNearestPixels(aRegion);
    }
    return aRegion.ScaleToOutsidePixels(
        mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel);
  }

  nsIFrame* GetContainerFrame() const { return mContainerFrame; }
  nsDisplayListBuilder* Builder() const { return mBuilder; }

  /**
   * Check if we are currently inside an inactive layer.
   */
  bool IsInInactiveLayer() const {
    return mLayerBuilder->GetContainingPaintedLayerData();
  }

  /**
   * Sets aOuterVisibleRegion as aLayer's visible region.
   * @param aOuterVisibleRegion
   *   is in the coordinate space of the container reference frame.
   * @param aLayerContentsVisibleRect, if non-null, is in the layer's own
   *   coordinate system.
   * @param aOuterUntransformed is true if the given aOuterVisibleRegion
   *   is already untransformed with the matrix of the layer.
   */
  void SetOuterVisibleRegionForLayer(
      Layer* aLayer, const nsIntRegion& aOuterVisibleRegion,
      const nsIntRect* aLayerContentsVisibleRect = nullptr,
      bool aOuterUntransformed = false) const;

  /**
   * Try to determine whether the PaintedLayer aData has a single opaque color
   * covering aRect. If successful, return that color, otherwise return
   * NS_RGBA(0,0,0,0).
   * If aRect turns out not to intersect any content in the layer,
   * *aOutIntersectsLayer will be set to false.
   */
  nscolor FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData,
                                           const nsIntRect& aRect,
                                           bool* aOutIntersectsLayer) const;

  /**
   * Indicate that we are done adding items to the PaintedLayer represented by
   * aData. Make sure that a real PaintedLayer exists for it, and set the final
   * visible region and opaque-content.
   */
  template <typename FindOpaqueBackgroundColorCallbackType>
  void FinishPaintedLayerData(
      PaintedLayerData& aData,
      FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor);

 protected:
  friend class PaintedLayerData;
  friend class FLBDisplayItemIterator;

  LayerManager::PaintedLayerCreationHint GetLayerCreationHint(
      AnimatedGeometryRoot* aAnimatedGeometryRoot);

  /**
   * Creates a new PaintedLayer and sets up the transform on the PaintedLayer
   * to account for scrolling.
   */
  already_AddRefed<PaintedLayer> CreatePaintedLayer(PaintedLayerData* aData);

  /**
   * Find a PaintedLayer for recycling, recycle it and prepare it for use, or
   * return null if no suitable layer was found.
   */
  already_AddRefed<PaintedLayer> AttemptToRecyclePaintedLayer(
      AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem,
      const nsPoint& aTopLeft, const nsIFrame* aReferenceFrame);
  /**
   * Recycle aLayer and do any necessary invalidation.
   */
  PaintedDisplayItemLayerUserData* RecyclePaintedLayer(
      PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
      bool& didResetScrollPositionForLayerPixelAlignment);

  /**
   * Perform the last step of CreatePaintedLayer / AttemptToRecyclePaintedLayer:
   * Initialize aData, set up the layer's transform for scrolling, and
   * invalidate the layer for layer pixel alignment changes if necessary.
   */
  void PreparePaintedLayerForUse(
      PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData,
      AnimatedGeometryRoot* aAnimatedGeometryRoot,
      const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft,
      bool aDidResetScrollPositionForLayerPixelAlignment);

  /**
   * Attempt to prepare an ImageLayer based upon the provided PaintedLayerData.
   * Returns nullptr on failure.
   */
  already_AddRefed<Layer> PrepareImageLayer(PaintedLayerData* aData);

  /**
   * Attempt to prepare a ColorLayer based upon the provided PaintedLayerData.
   * Returns nullptr on failure.
   */
  already_AddRefed<Layer> PrepareColorLayer(PaintedLayerData* aData);

  /**
   * Grab the next recyclable ColorLayer, or create one if there are no
   * more recyclable ColorLayers.
   */
  already_AddRefed<ColorLayer> CreateOrRecycleColorLayer(
      PaintedLayer* aPainted);
  /**
   * Grab the next recyclable ImageLayer, or create one if there are no
   * more recyclable ImageLayers.
   */
  already_AddRefed<ImageLayer> CreateOrRecycleImageLayer(
      PaintedLayer* aPainted);
  /**
   * Grab a recyclable ImageLayer for use as a mask layer for aLayer (that is a
   * mask layer which has been used for aLayer before), or create one if such
   * a layer doesn't exist.
   *
   * Since mask layers can exist either on the layer directly, or as a side-
   * attachment to FrameMetrics (for ancestor scrollframe clips), we key the
   * recycle operation on both the originating layer and the mask layer's
   * index in the layer, if any.
   */
  struct MaskLayerKey;
  template <typename UserData>
  already_AddRefed<ImageLayer> CreateOrRecycleMaskImageLayerFor(
      const MaskLayerKey& aKey, UserData* (*aGetUserData)(Layer* aLayer),
      void (*aSetDefaultUserData)(Layer* aLayer));
  /**
   * Grabs all PaintedLayers and ColorLayers from the ContainerLayer and makes
   * them available for recycling.
   */
  void CollectOldLayers();
  /**
   * If aItem used to belong to a PaintedLayer, invalidates the area of
   * aItem in that layer. If aNewLayer is a PaintedLayer, invalidates the area
   * of aItem in that layer.
   */
  void InvalidateForLayerChange(nsDisplayItem* aItem, PaintedLayer* aNewLayer,
                                DisplayItemData* aData);
  /**
   * 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 ScrollMetadata 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 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,
                                AnimatedGeometryRoot* aAnimatedGeometryRoot,
                                const ActiveScrolledRoot* aASR,
                                const DisplayItemClip& aClip,
                                nsDisplayList* aList, bool* aHideAllLayersBelow,
                                bool* aOpaqueForAnimatedGeometryRootParent);

  /**
   * Fills a PaintedLayerData object that is initialized for a layer that the
   * current item will be assigned to. Also creates mNewChildLayers entries.
   * @param  aData                 The PaintedLayerData that will be filled.
   * @param  aVisibleRect          The visible rect of the item.
   * @param  aAnimatedGeometryRoot The item's animated geometry root.
   * @param  aASR                  The active scrolled root that moves this
   * PaintedLayer.
   * @param  aClipChain            The clip chain that the compositor needs to
   *                               apply to this layer.
   * @param  aScrollMetadataASR    The leaf ASR for which scroll metadata needs
   * to be set on the layer, because either the layer itself or its scrolled
   * clip need to move with that ASR.
   * @param  aTopLeft              The offset between aAnimatedGeometryRoot and
   *                               the reference frame.
   * @param  aReferenceFrame       The reference frame for the item.
   * @param  aBackfaceHidden       The backface visibility for the item frame.
   */
  void NewPaintedLayerData(
      PaintedLayerData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot,
      const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
      const ActiveScrolledRoot* aScrollMetadataASR, const nsPoint& aTopLeft,
      const nsIFrame* aReferenceFrame, const bool aBackfaceHidden);

  /* Build a mask layer to represent the clipping region. Will return null if
   * there is no clipping specified or a mask layer cannot be built.
   * Builds an ImageLayer for the appropriate backend; the mask is relative to
   * aLayer's visible region.
   * aLayer is the layer to be clipped.
   * relative to the container reference frame
   * aRoundedRectClipCount is used when building mask layers for PaintedLayers,
   */
  void SetupMaskLayer(Layer* aLayer, const DisplayItemClip& aClip);

  /**
   * If |aClip| has rounded corners, create a mask layer for them, and
   * add it to |aLayer|'s ancestor mask layers, returning an index into
   * the array of ancestor mask layers. Returns an empty Maybe if
   * |aClip| does not have rounded corners, or if no mask layer could
   * be created.
   */
  Maybe<size_t> SetupMaskLayerForScrolledClip(Layer* aLayer,
                                              const DisplayItemClip& aClip);

  /**
   * Create/find a mask layer with suitable size for aMaskItem to paint
   * css-positioned-masking onto.
   */
  void SetupMaskLayerForCSSMask(Layer* aLayer,
                                nsDisplayMasksAndClipPaths* aMaskItem);

  already_AddRefed<Layer> CreateMaskLayer(
      Layer* aLayer, const DisplayItemClip& aClip,
      const Maybe<size_t>& aForAncestorMaskLayer);

  /**
   * Get the display port for an AGR.
   * The result would be cached for later reusing.
   */
  nsRect GetDisplayPortForAnimatedGeometryRoot(
      AnimatedGeometryRoot* aAnimatedGeometryRoot);

  nsDisplayListBuilder* mBuilder;
  LayerManager* mManager;
  FrameLayerBuilder* mLayerBuilder;
  nsIFrame* mContainerFrame;
  nsIFrame* mContainerReferenceFrame;
  AnimatedGeometryRoot* mContainerAnimatedGeometryRoot;
  ContainerLayer* mContainerLayer;
  nsRect mContainerBounds;

  // Due to the way we store scroll annotations in the layer tree, we need to
  // keep track of three (possibly different) ASRs here.
  // mContainerASR is the ASR of the container display item that this
  // ContainerState was created for.
  // mContainerScrollMetadataASR is the ASR of the leafmost scroll metadata
  // that's in effect on mContainerLayer.
  // mContainerCompositorASR is the ASR that mContainerLayer moves with on
  // the compositor / APZ side, taking into account both the scroll meta data
  // and the fixed position annotation on itself and its ancestors.
  const ActiveScrolledRoot* mContainerASR;
  const ActiveScrolledRoot* mContainerScrollMetadataASR;
  const ActiveScrolledRoot* mContainerCompositorASR;
#ifdef DEBUG
  nsRect mAccumulatedChildBounds;
#endif
  ContainerLayerParameters mParameters;
  /**
   * The region of PaintedLayers that should be invalidated every time
   * we recycle one.
   */
  nsIntRegion mInvalidPaintedContent;
  PaintedLayerDataTree mPaintedLayerDataTree;
  /**
   * We collect the list of children in here. During ProcessDisplayItems,
   * the layers in this array either have mContainerLayer as their parent,
   * or no parent.
   * PaintedLayers have two entries in this array: the second one is used only
   * if the PaintedLayer is optimized away to a ColorLayer or ImageLayer. It's
   * essential that this array is only appended to, since PaintedLayerData
   * records the index of its PaintedLayer in this array.
   */
  typedef AutoTArray<NewLayerEntry, 1> AutoLayersArray;
  AutoLayersArray mNewChildLayers;
  nsTHashtable<nsRefPtrHashKey<PaintedLayer>>
      mPaintedLayersAvailableForRecycling;
  nscoord mAppUnitsPerDevPixel;
  bool mSnappingEnabled;

  struct MaskLayerKey {
    MaskLayerKey() : mLayer(nullptr) {}
    MaskLayerKey(Layer* aLayer, const Maybe<size_t>& aAncestorIndex)
        : mLayer(aLayer), mAncestorIndex(aAncestorIndex) {}

    PLDHashNumber Hash() const {
      // Hash the layer and add the layer index to the hash.
      return (NS_PTR_TO_UINT32(mLayer) >> 2) +
             (mAncestorIndex ? (*mAncestorIndex + 1) : 0);
    }
    bool operator==(const MaskLayerKey& aOther) const {
      return mLayer == aOther.mLayer && mAncestorIndex == aOther.mAncestorIndex;
    }

    Layer* mLayer;
    Maybe<size_t> mAncestorIndex;
  };

  nsDataHashtable<nsGenericHashKey<MaskLayerKey>, RefPtr<ImageLayer>>
      mRecycledMaskImageLayers;
  // Keep display port of AGR to avoid wasting time on doing the same
  // thing repeatly.
  AnimatedGeometryRoot* mLastDisplayPortAGR;
  nsRect mLastDisplayPortRect;

  nsDisplayItem* mContainerItem;

  // Cache ScrollMetadata so it doesn't need recomputed if the ASR and clip are
  // unchanged. If mASR == nullptr then mMetadata is not valid.
  struct CachedScrollMetadata {
    const ActiveScrolledRoot* mASR;
    const DisplayItemClip* mClip;
    Maybe<ScrollMetadata> mMetadata;

    CachedScrollMetadata() : mASR(nullptr), mClip(nullptr) {}
  };
  CachedScrollMetadata mCachedScrollMetadata;
};

class FLBDisplayItemIterator : protected FlattenedDisplayItemIterator {
 public:
  FLBDisplayItemIterator(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
                         ContainerState* aState)
      : FlattenedDisplayItemIterator(aBuilder, aList, false),
        mState(aState),
        mAddingEffectMarker(false) {
    MOZ_ASSERT(mState);

    if (aState->mContainerItem) {
      // Add container item hit test information for processing, if needed.
      AddHitTestMarker(aState->mContainerItem);
    }

    ResolveFlattening();
  }

  void AddHitTestMarker(nsDisplayItem* aItem) {
    if (aItem->HasHitTestInfo()) {
      mMarkers.emplace_back(aItem, DisplayItemEntryType::HIT_TEST_INFO);
    }
  }

  DisplayItemEntry GetNextEntry() {
    if (!mMarkers.empty()) {
      DisplayItemEntry entry = mMarkers.front();
      mMarkers.pop_front();
      return entry;
    }

    nsDisplayItem* next = GetNext();
    return DisplayItemEntry{next, DisplayItemEntryType::ITEM};
  }

  nsDisplayItem* GetNext() {
    // This function is only supposed to be called if there are no markers set.
    // Breaking this invariant can potentially break effect flattening and/or
    // display item merging.
    MOZ_ASSERT(mMarkers.empty());

    nsDisplayItem* next = mNext;

    // Advance mNext to the following item
    if (next) {
      nsDisplayItem* peek = next->GetAbove();

      // Peek ahead to the next item and see if it can be merged with the
      // current item.
      if (peek && next->CanMerge(peek)) {
        // Create a list of consecutive items that can be merged together.
        AutoTArray<nsDisplayItem*, 2> mergedItems{next, peek};
        while ((peek = peek->GetAbove())) {
          if (!next->CanMerge(peek)) {
            break;
          }

          mergedItems.AppendElement(peek);
        }

        // We have items that can be merged together.
        // Merge them into a temporary item and process that item immediately.
        MOZ_ASSERT(mergedItems.Length() > 1);
        next = mState->mBuilder->MergeItems(mergedItems);
      }

      // |mNext| is either the first item that could not be merged with |next|,
      // or a nullptr.
      mNext = peek;

      ResolveFlattening();
    }

    return next;
  }

  bool HasNext() const {
    return FlattenedDisplayItemIterator::HasNext() || !mMarkers.empty();
  }

  nsDisplayItem* PeekNext() { return mNext; }

 private:
  bool ShouldFlattenNextItem() override {
    if (!mNext) {
      return false;
    }

    if (!mNext->ShouldFlattenAway(mBuilder)) {
      return false;
    }

    const DisplayItemType type = mNext->GetType();

    if (type == DisplayItemType::TYPE_SVG_WRAPPER) {
      // We mark SetContainsSVG for the CONTENT_FRAME_TIME_WITH_SVG metric
      if (RefPtr<LayerManager> lm = mBuilder->GetWidgetLayerManager()) {
        lm->SetContainsSVG(true);
      }
    }

    if (type != DisplayItemType::TYPE_OPACITY &&
        type != DisplayItemType::TYPE_TRANSFORM) {
      return true;
    }

    if (type == DisplayItemType::TYPE_OPACITY) {
      nsDisplayOpacity* opacity = static_cast<nsDisplayOpacity*>(mNext);

      if (opacity->OpacityAppliedToChildren()) {
        // This is the previous opacity flattening path, where the opacity has
        // been applied to children.
        return true;
      }
    }

    if (mState->IsInInactiveLayer() || !NextItemWantsInactiveLayer()) {
      // Do not flatten nested inactive display items, or display items that
      // want an active layer.
      return false;
    }

    // Flatten inactive nsDisplayOpacity and nsDisplayTransform.
    mAddingEffectMarker = true;
    return true;
  }

  void EnterChildList(nsDisplayItem* aItem) override {
    if (!mAddingEffectMarker) {
      // A container item will be flattened but no effect marker is needed.
      AddHitTestMarker(aItem);
      return;
    }

    if (AddMarkerIfNeeded<MarkerType::StartMarker>(aItem, mMarkers)) {
      mActiveMarkers.AppendElement(aItem);
    }

    // Place the hit test marker between the effect markers.
    AddHitTestMarker(aItem);

    mAddingEffectMarker = false;
  }

  void ExitChildList(nsDisplayItem* aItem) override {
    if (mActiveMarkers.IsEmpty() || mActiveMarkers.LastElement() != aItem) {
      // Do not emit an end marker if this item did not emit a start marker.
      return;
    }

    if (AddMarkerIfNeeded<MarkerType::EndMarker>(aItem, mMarkers)) {
      mActiveMarkers.RemoveLastElement();
    }
  }

  bool NextItemWantsInactiveLayer() {
    LayerState layerState = mNext->GetLayerState(
        mState->mBuilder, mState->mManager, mState->mParameters);

    return layerState == LayerState::LAYER_INACTIVE;
  }

  std::deque<DisplayItemEntry> mMarkers;
  AutoTArray<nsDisplayItem*, 4> mActiveMarkers;
  ContainerState* mState;
  bool mAddingEffectMarker;
};

class PaintedDisplayItemLayerUserData : public LayerUserData {
 public:
  PaintedDisplayItemLayerUserData()
      : mForcedBackgroundColor(NS_RGBA(0, 0, 0, 0)),
        mXScale(1.f),
        mYScale(1.f),
        mAppUnitsPerDevPixel(0),
        mTranslation(0, 0),
        mAnimatedGeometryRootPosition(0, 0),
        mLastItemCount(0),
        mContainerLayerFrame(nullptr),
        mHasExplicitLastPaintOffset(false),
        mDisabledAlpha(false) {}

  NS_INLINE_DECL_REFCOUNTING(PaintedDisplayItemLayerUserData);

  /**
   * A color that should be painted over the bounds of the layer's visible
   * region before any other content is painted.
   */
  nscolor mForcedBackgroundColor;

  /**
   * The resolution scale used.
   */
  float mXScale, mYScale;

  /**
   * The appunits per dev pixel for the items in this layer.
   */
  nscoord mAppUnitsPerDevPixel;

  /**
   * The offset from the PaintedLayer's 0,0 to the
   * reference frame. This isn't necessarily the same as the transform
   * set on the PaintedLayer since we might also be applying an extra
   * offset specified by the parent ContainerLayer/
   */
  nsIntPoint mTranslation;

  /**
   * We try to make 0,0 of the PaintedLayer be the top-left of the
   * border-box of the "active scrolled root" frame (i.e. the nearest ancestor
   * frame for the display items that is being actively scrolled). But
   * we force the PaintedLayer transform to be an integer translation, and we
   * may have a resolution scale, so we have to snap the PaintedLayer transform,
   * so 0,0 may not be exactly the top-left of the active scrolled root. Here we
   * store the coordinates in PaintedLayer space of the top-left of the
   * active scrolled root.
   */
  gfxPoint mAnimatedGeometryRootPosition;

  nsIntRegion mRegionToInvalidate;

  // The offset between the active scrolled root of this layer
  // and the root of the container for the previous and current
  // paints respectively.
  nsPoint mLastAnimatedGeometryRootOrigin;
  nsPoint mAnimatedGeometryRootOrigin;

  RefPtr<ColorLayer> mColorLayer;
  RefPtr<ImageLayer> mImageLayer;

  // The region for which display item visibility for this layer has already
  // been calculated. Used to reduce the number of calls to
  // RecomputeVisibilityForItems if it is known in advance that a larger
  // region will be painted during a transaction than in a single call to
  // DrawPaintedLayer, for example when progressive paint is enabled.
  nsIntRegion mVisibilityComputedRegion;

  // The area for which we called RecomputeVisibilityForItems on the
  // previous paint.
  nsRect mPreviousRecomputeVisibilityRect;

  // The number of items assigned to this layer on the previous paint.
  size_t mLastItemCount;

  // The translation set on this PaintedLayer before we started updating the
  // layer tree.
  nsIntPoint mLastPaintOffset;

  // Temporary state only valid during the FrameLayerBuilder's lifetime.
  // FLB's mPaintedLayerItems is responsible for cleaning these up when
  // we finish painting to avoid dangling pointers.
  std::vector<AssignedDisplayItem> mItems;
  nsIFrame* mContainerLayerFrame;

  bool mHasExplicitLastPaintOffset;

  /**
   * This is set when the painted layer has no component alpha.
   */
  bool mDisabledAlpha;

 protected:
  ~PaintedDisplayItemLayerUserData() override = default;
};

FrameLayerBuilder::FrameLayerBuilder()
    : mRetainingManager(nullptr),
      mDisplayListBuilder(nullptr),
      mContainingPaintedLayer(nullptr),
      mInactiveLayerClip(nullptr),
      mInvalidateAllLayers(false),
      mInLayerTreeCompressionMode(false),
      mIsInactiveLayerManager(false) {
  MOZ_COUNT_CTOR(FrameLayerBuilder);
}

FrameLayerBuilder::~FrameLayerBuilder() {
  GetMaskLayerImageCache()->Sweep();
  for (PaintedDisplayItemLayerUserData* userData : mPaintedLayerItems) {
    userData->mItems.clear();
    userData->mContainerLayerFrame = nullptr;
  }
  MOZ_COUNT_DTOR(FrameLayerBuilder);
}

void FrameLayerBuilder::AddPaintedLayerItemsEntry(
    PaintedDisplayItemLayerUserData* aData) {
  mPaintedLayerItems.AppendElement(aData);
}

/*
 * User data for layers which will be used as masks.
 */
struct MaskLayerUserData : public LayerUserData {
  MaskLayerUserData()
      : mScaleX(-1.0f), mScaleY(-1.0f), mAppUnitsPerDevPixel(-1) {}
  MaskLayerUserData(const DisplayItemClip& aClip, int32_t aAppUnitsPerDevPixel,
                    const ContainerLayerParameters& aParams)
      : mScaleX(aParams.mXScale),
        mScaleY(aParams.mYScale),
        mOffset(aParams.mOffset),
        mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {
    aClip.AppendRoundedRects(&mRoundedClipRects);
  }

  void operator=(MaskLayerUserData&& aOther) {
    mScaleX = aOther.mScaleX;
    mScaleY = aOther.mScaleY;
    mOffset = aOther.mOffset;
    mAppUnitsPerDevPixel = aOther.mAppUnitsPerDevPixel;
    mRoundedClipRects.SwapElements(aOther.mRoundedClipRects);
  }

  bool operator==(const MaskLayerUserData& aOther) const {
    return mRoundedClipRects == aOther.mRoundedClipRects &&
           mScaleX == aOther.mScaleX && mScaleY == aOther.mScaleY &&
           mOffset == aOther.mOffset &&
           mAppUnitsPerDevPixel == aOther.mAppUnitsPerDevPixel;
  }

  // Keeps a MaskLayerImageKey alive by managing its mLayerCount member-var
  MaskLayerImageCache::MaskLayerImageKeyRef mImageKey;
  // properties of the mask layer; the mask layer may be re-used if these
  // remain unchanged.
  nsTArray<DisplayItemClip::RoundedRect> mRoundedClipRects;
  // scale from the masked layer which is applied to the mask
  float mScaleX, mScaleY;
  // The ContainerLayerParameters offset which is applied to the mask's
  // transform.
  nsIntPoint mOffset;
  int32_t mAppUnitsPerDevPixel;
};

/*
 * User data for layers which will be used as masks for css positioned mask.
 */
struct CSSMaskLayerUserData : public LayerUserData {
  CSSMaskLayerUserData() : mMaskStyle(nsStyleImageLayers::LayerType::Mask) {}

  CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aMaskBounds,
                       const nsPoint& aMaskLayerOffset)
      : mMaskBounds(aMaskBounds),
        mMaskStyle(aFrame->StyleSVGReset()->mMask),
        mMaskLayerOffset(aMaskLayerOffset) {}

  void operator=(CSSMaskLayerUserData&& aOther) {
    mMaskBounds = aOther.mMaskBounds;
    mMaskStyle = std::move(aOther.mMaskStyle);
    mMaskLayerOffset = aOther.mMaskLayerOffset;
  }

  bool operator==(const CSSMaskLayerUserData& aOther) const {
    if (!mMaskBounds.IsEqualInterior(aOther.mMaskBounds)) {
      return false;
    }

    // Make sure we draw the same portion of the mask onto mask layer.
    if (mMaskLayerOffset != aOther.mMaskLayerOffset) {
      return false;
    }

    return mMaskStyle == aOther.mMaskStyle;
  }

 private:
  nsIntRect mMaskBounds;
  nsStyleImageLayers mMaskStyle;
  nsPoint mMaskLayerOffset;  // The offset from the origin of mask bounds to
                             // the origin of mask layer.
};

/*
 * A helper object to create a draw target for painting mask and create a
 * image container to hold the drawing result. The caller can then bind this
 * image container with a image mask layer via ImageLayer::SetContainer.
 */
class MaskImageData {
 public:
  MaskImageData(const gfx::IntSize& aSize, LayerManager* aLayerManager)
      : mTextureClientLocked(false),
        mSize(aSize),
        mLayerManager(aLayerManager) {
    MOZ_ASSERT(!mSize.IsEmpty());
    MOZ_ASSERT(mLayerManager);
  }

  ~MaskImageData() {
    if (mTextureClientLocked) {
      MOZ_ASSERT(mTextureClient);
      // Clear DrawTarget before Unlock.
      mDrawTarget = nullptr;
      mTextureClient->Unlock();
    }
  }

  gfx::DrawTarget* CreateDrawTarget() {
    if (mDrawTarget) {
      return mDrawTarget;
    }

    if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
      mDrawTarget = mLayerManager->CreateOptimalMaskDrawTarget(mSize);
      return mDrawTarget;
    }

    MOZ_ASSERT(mLayerManager->GetBackendType() ==
                   LayersBackend::LAYERS_CLIENT ||
               mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR);

    KnowsCompositor* knowsCompositor = mLayerManager->AsKnowsCompositor();
    if (!knowsCompositor) {
      return nullptr;
    }
    mTextureClient = TextureClient::CreateForDrawing(
        knowsCompositor, SurfaceFormat::A8, mSize, BackendSelector::Content,
        TextureFlags::DISALLOW_BIGIMAGE,
        TextureAllocationFlags::ALLOC_CLEAR_BUFFER);
    if (!mTextureClient) {
      return nullptr;
    }

    mTextureClientLocked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
    if (!mTextureClientLocked) {
      return nullptr;
    }

    mDrawTarget = mTextureClient->BorrowDrawTarget();
    return mDrawTarget;
  }

  already_AddRefed<ImageContainer> CreateImageAndImageContainer() {
    RefPtr<ImageContainer> container = LayerManager::CreateImageContainer();
    RefPtr<Image> image = CreateImage();

    if (!image) {
      return nullptr;
    }
    container->SetCurrentImageInTransaction(image);

    return container.forget();
  }

 private:
  already_AddRefed<Image> CreateImage() {
    if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC &&
        mDrawTarget) {
      RefPtr<SourceSurface> surface = mDrawTarget->Snapshot();
      RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(mSize, surface);
      // Disallow BIGIMAGE (splitting into multiple textures) for mask
      // layer images
      image->SetTextureFlags(TextureFlags::DISALLOW_BIGIMAGE);
      return image.forget();
    }

    if ((mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT ||
         mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR) &&
        mTextureClient && mDrawTarget) {
      RefPtr<TextureWrapperImage> image = new TextureWrapperImage(
          mTextureClient, gfx::IntRect(gfx::IntPoint(0, 0), mSize));
      return image.forget();
    }

    return nullptr;
  }

  bool mTextureClientLocked;
  gfx::IntSize mSize;
  LayerManager* mLayerManager;
  RefPtr<gfx::DrawTarget> mDrawTarget;
  RefPtr<TextureClient> mTextureClient;
};

static PaintedDisplayItemLayerUserData* GetPaintedDisplayItemLayerUserData(
    Layer* aLayer) {
  return static_cast<PaintedDisplayItemLayerUserData*>(
      aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
}

/* static */ void FrameLayerBuilder::Shutdown() {
  if (gMaskLayerImageCache) {
    delete gMaskLayerImageCache;
    gMaskLayerImageCache = nullptr;
  }
}

void FrameLayerBuilder::Init(nsDisplayListBuilder* aBuilder,
                             LayerManager* aManager,
                             PaintedLayerData* aLayerData,
                             bool aIsInactiveLayerManager,
                             const DisplayItemClip* aInactiveLayerClip) {
  mDisplayListBuilder = aBuilder;
  mRootPresContext =
      aBuilder->RootReferenceFrame()->PresContext()->GetRootPresContext();
  mContainingPaintedLayer = aLayerData;
  mIsInactiveLayerManager = aIsInactiveLayerManager;
  mInactiveLayerClip = aInactiveLayerClip;
  aManager->SetUserData(&gLayerManagerLayerBuilder, this);
}

void FrameLayerBuilder::FlashPaint(gfxContext* aContext) {
  float r = float(rand()) / RAND_MAX;
  float g = float(rand()) / RAND_MAX;
  float b = float(rand()) / RAND_MAX;
  aContext->SetColor(Color(r, g, b, 0.4f));
  aContext->Paint();
}

DisplayItemData* FrameLayerBuilder::GetDisplayItemData(nsIFrame* aFrame,
                                                       uint32_t aKey) {
  const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
  for (uint32_t i = 0; i < array.Length(); i++) {
    DisplayItemData* item =
        DisplayItemData::AssertDisplayItemData(array.ElementAt(i));
    if (item->mDisplayItemKey == aKey &&
        item->mLayer->Manager() == mRetainingManager) {
      return item;
    }
  }
  return nullptr;
}

#ifdef MOZ_DUMP_PAINTING
static nsACString& AppendToString(nsACString& s, const nsIntRect& r,
                                  const char* pfx = "", const char* sfx = "") {
  s += pfx;
  s += nsPrintfCString("(x=%d, y=%d, w=%d, h=%d)", r.x, r.y, r.width, r.height);
  return s += sfx;
}

static nsACString& AppendToString(nsACString& s, const nsIntRegion& r,
                                  const char* pfx = "", const char* sfx = "") {
  s += pfx;

  s += "< ";
  for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
    AppendToString(s, iter.Get()) += "; ";
  }
  s += ">";

  return s += sfx;
}
#endif  // MOZ_DUMP_PAINTING

/**
 * Invalidate aRegion in aLayer. aLayer is in the coordinate system
 * *after* aTranslation has been applied, so we need to
 * apply the inverse of that transform before calling InvalidateRegion.
 */
static void InvalidatePostTransformRegion(PaintedLayer* aLayer,
                                          const nsIntRegion& aRegion,
                                          const nsIntPoint& aTranslation) {
  // Convert the region from the coordinates of the container layer
  // (relative to the snapped top-left of the display list reference frame)
  // to the PaintedLayer's own coordinates
  nsIntRegion rgn = aRegion;

  rgn.MoveBy(-aTranslation);
  aLayer->InvalidateRegion(rgn);
#ifdef MOZ_DUMP_PAINTING
  if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
    nsAutoCString str;
    AppendToString(str, rgn);
    printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
  }
#endif
}

static void InvalidatePreTransformRect(PaintedLayer* aLayer,
                                       const nsRect& aRect,
                                       const DisplayItemClip& aClip,
                                       const nsIntPoint& aTranslation,
                                       TransformClipNode* aTransform) {
  PaintedDisplayItemLayerUserData* data =
      static_cast<PaintedDisplayItemLayerUserData*>(
          aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));

  nsRect rect = aClip.ApplyNonRoundedIntersection(aRect);

  if (aTransform) {
    rect = aTransform->TransformRect(rect, data->mAppUnitsPerDevPixel);
  }

  nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale,
                                                  data->mAppUnitsPerDevPixel);

  InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation);
}

static nsIntPoint GetTranslationForPaintedLayer(PaintedLayer* aLayer) {
  PaintedDisplayItemLayerUserData* data =
      static_cast<PaintedDisplayItemLayerUserData*>(
          aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
  NS_ASSERTION(data, "Must be a tracked painted layer!");

  return data->mTranslation;
}

/**
 * Some frames can have multiple, nested, retaining layer managers
 * associated with them (normal manager, inactive managers, SVG effects).
 * In these cases we store the 'outermost' LayerManager data property
 * on the frame since we can walk down the chain from there.
 *
 * If one of these frames has just been destroyed, we will free the inner
 * layer manager when removing the entry from mFramesWithLayers. Destroying
 * the layer manager destroys the LayerManagerData and calls into
 * the DisplayItemData destructor. If the inner layer manager had any
 * items with the same frame, then we attempt to retrieve properties
 * from the deleted frame.
 *
 * Cache the destroyed frame pointer here so we can avoid crashing in this case.
 */

/* static */ void FrameLayerBuilder::RemoveFrameFromLayerManager(
    const nsIFrame* aFrame, SmallPointerArray<DisplayItemData>& aArray) {
  MOZ_RELEASE_ASSERT(!sDestroyedFrame);
  sDestroyedFrame = aFrame;

  // Hold a reference to all the items so that they don't get
  // deleted from under us.
  nsTArray<RefPtr<DisplayItemData>> arrayCopy;
  for (DisplayItemData* data : aArray) {
    arrayCopy.AppendElement(data);
  }

#ifdef DEBUG_DISPLAY_ITEM_DATA
  if (aArray->Length()) {
    LayerManagerData* rootData = aArray->ElementAt(0)->mParent;
    while (rootData->mParent) {
      rootData = rootData->mParent;
    }
    printf_stderr("Removing frame %p - dumping display data\n", aFrame);
    rootData->Dump();
  }
#endif

  for (DisplayItemData* data : aArray) {
    PaintedLayer* t = data->mLayer ? data->mLayer->AsPaintedLayer() : nullptr;
    if (t) {
      PaintedDisplayItemLayerUserData* paintedData =
          static_cast<PaintedDisplayItemLayerUserData*>(
              t->GetUserData(&gPaintedDisplayItemLayerUserData));
      if (paintedData && data->mGeometry) {
        const int32_t appUnitsPerDevPixel = paintedData->mAppUnitsPerDevPixel;
        nsRegion rgn = data->mGeometry->ComputeInvalidationRegion();
        nsIntRegion pixelRgn = rgn.ToOutsidePixels(appUnitsPerDevPixel);

        if (data->mTransform) {
          pixelRgn = data->mTransform->TransformRegion(pixelRgn);
        }

        pixelRgn =
            pixelRgn.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale);

        pixelRgn.MoveBy(-GetTranslationForPaintedLayer(t));

        paintedData->mRegionToInvalidate.Or(paintedData->mRegionToInvalidate,
                                            pixelRgn);
        paintedData->mRegionToInvalidate.SimplifyOutward(8);
      }
    }

    auto it = std::find(data->mParent->mDisplayItems.begin(),
                        data->mParent->mDisplayItems.end(), data);
    MOZ_ASSERT(it != data->mParent->mDisplayItems.end());
    std::iter_swap(it, data->mParent->mDisplayItems.end() - 1);
    data->mParent->mDisplayItems.pop_back();
  }

  arrayCopy.Clear();
  sDestroyedFrame = nullptr;
}

void FrameLayerBuilder::DidBeginRetainedLayerTransaction(
    LayerManager* aManager) {
  mRetainingManager = aManager;
  LayerManagerData* data = static_cast<LayerManagerData*>(
      aManager->GetUserData(&gLayerManagerUserData));
  if (data) {
    mInvalidateAllLayers = data->mInvalidateAllLayers;
  } else {
    data = new LayerManagerData(aManager);
    aManager->SetUserData(&gLayerManagerUserData, data);
  }
}

void FrameLayerBuilder::DidEndTransaction() {
  GetMaskLayerImageCache()->Sweep();
}

void FrameLayerBuilder::WillEndTransaction() {
  if (!mRetainingManager) {
    return;
  }

  // We need to save the data we'll need to support retaining.
  LayerManagerData* data = static_cast<LayerManagerData*>(
      mRetainingManager->GetUserData(&gLayerManagerUserData));
  NS_ASSERTION(data, "Must have data!");

  // Update all the frames that used to have layers.
  auto iter = data->mDisplayItems.begin();
  while (iter != data->mDisplayItems.end()) {
    DisplayItemData* did = iter->get();
    if (!did->mUsed) {
      // This item was visible, but isn't anymore.
      PaintedLayer* t = did->mLayer->AsPaintedLayer();
      if (t && did->mGeometry) {
#ifdef MOZ_DUMP_PAINTING
        if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
          printf_stderr(
              "Invalidating unused display item (%i) belonging to "
              "frame %p from layer %p\n",
              did->mDisplayItemKey, did->mFrameList[0], t);
        }
#endif
        InvalidatePreTransformRect(
            t, did->mGeometry->ComputeInvalidationRegion(), did->mClip,
            GetLastPaintOffset(t), did->mTransform);
      }

      did->ClearAnimationCompositorState();

      // Remove this item. Swapping it with the last element first is
      // quicker than erasing from the middle.
      if (iter != data->mDisplayItems.end() - 1) {
        std::iter_swap(iter, data->mDisplayItems.end() - 1);
        data->mDisplayItems.pop_back();
      } else {
        data->mDisplayItems.pop_back();
        break;
      }

      // Don't increment iter because we still need to process the item which
      // was moved.

    } else {
      ComputeGeometryChangeForItem(did);
      iter++;
    }
  }

  data->mInvalidateAllLayers = false;
}

/* static */ DisplayItemData* FrameLayerBuilder::GetDisplayItemDataForManager(
    nsDisplayItem* aItem, LayerManager* aManager) {
  const SmallPointerArray<DisplayItemData>& array =
      aItem->Frame()->DisplayItemData();
  for (uint32_t i = 0; i < array.Length(); i++) {
    DisplayItemData* item =
        DisplayItemData::AssertDisplayItemData(array.ElementAt(i));
    if (item->mDisplayItemKey == aItem->GetPerFrameKey() &&
        item->mLayer->Manager() == aManager) {
      return item;
    }
  }
  return nullptr;
}

bool FrameLayerBuilder::HasRetainedDataFor(nsIFrame* aFrame,
                                           uint32_t aDisplayItemKey) {
  const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
  for (uint32_t i = 0; i < array.Length(); i++) {
    if (DisplayItemData::AssertDisplayItemData(array.ElementAt(i))
            ->mDisplayItemKey == aDisplayItemKey) {
      return true;
    }
  }
  if (RefPtr<WebRenderUserData> data =
          GetWebRenderUserData<WebRenderFallbackData>(aFrame,
                                                      aDisplayItemKey)) {
    return true;
  }
  return false;
}

DisplayItemData* FrameLayerBuilder::GetOldLayerForFrame(
    nsIFrame* aFrame, uint32_t aDisplayItemKey,
    DisplayItemData* aOldData, /* = nullptr */
    LayerManager* aOldLayerManager /* = nullptr */) {
  // If we need to build a new layer tree, then just refuse to recycle
  // anything.
  if (!mRetainingManager || mInvalidateAllLayers) return nullptr;

  MOZ_ASSERT(!aOldData || aOldLayerManager,
             "You must provide aOldLayerManager to check aOldData's validity.");
  MOZ_ASSERT_IF(aOldData, aOldLayerManager == aOldData->mLayer->Manager());

  DisplayItemData* data = aOldData;
  if (!data || aOldLayerManager != mRetainingManager) {
    data = GetDisplayItemData(aFrame, aDisplayItemKey);
  }

  MOZ_ASSERT(data == GetDisplayItemData(aFrame, aDisplayItemKey));

  return data;
}

Layer* FrameLayerBuilder::GetOldLayerFor(nsDisplayItem* aItem,
                                         nsDisplayItemGeometry** aOldGeometry,
                                         DisplayItemClip** aOldClip) {
  uint32_t key = aItem->GetPerFrameKey();
  nsIFrame* frame = aItem->Frame();

  DisplayItemData* oldData = GetOldLayerForFrame(frame, key);
  if (oldData) {
    if (aOldGeometry) {
      *aOldGeometry = oldData->mGeometry.get();
    }
    if (aOldClip) {
      *aOldClip = &oldData->mClip;
    }
    return oldData->mLayer;
  }

  return nullptr;
}

/* static */ DisplayItemData* FrameLayerBuilder::GetOldDataFor(
    nsDisplayItem* aItem) {
  const SmallPointerArray<DisplayItemData>& array =
      aItem->Frame()->DisplayItemData();

  for (uint32_t i = 0; i < array.Length(); i++) {
    DisplayItemData* data =
        DisplayItemData::AssertDisplayItemData(array.ElementAt(i));

    if (data->mDisplayItemKey == aItem->GetPerFrameKey()) {
      return data;
    }
  }
  return nullptr;
}

// Reset state that should not persist when a layer is recycled.
static void ResetLayerStateForRecycling(Layer* aLayer) {
  // Currently, this clears the mask layer and ancestor mask layers.
  // Other cleanup may be added here.
  aLayer->SetMaskLayer(nullptr);
  aLayer->SetAncestorMaskLayers({});
}

already_AddRefed<ColorLayer> ContainerState::CreateOrRecycleColorLayer(
    PaintedLayer* aPainted) {
  PaintedDisplayItemLayerUserData* data =
      static_cast<PaintedDisplayItemLayerUserData*>(
          aPainted->GetUserData(&gPaintedDisplayItemLayerUserData));
  RefPtr<ColorLayer> layer = data->mColorLayer;
  if (layer) {
    ResetLayerStateForRecycling(layer);
    layer->ClearExtraDumpInfo();
  } else {
    // Create a new layer
    layer = mManager->CreateColorLayer();
    if (!layer) return nullptr;
    // Mark this layer as being used for painting display items
    data->mColorLayer = layer;
    layer->SetUserData(&gColorLayerUserData, nullptr);

    // Remove other layer types we might have stored for this PaintedLayer
    data->mImageLayer = nullptr;
  }
  return layer.forget();
}

already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleImageLayer(
    PaintedLayer* aPainted) {
  PaintedDisplayItemLayerUserData* data =
      static_cast<PaintedDisplayItemLayerUserData*>(
          aPainted->GetUserData(&gPaintedDisplayItemLayerUserData));
  RefPtr<ImageLayer> layer = data->mImageLayer;
  if (layer) {
    ResetLayerStateForRecycling(layer);
    layer->ClearExtraDumpInfo();
  } else {
    // Create a new layer
    layer = mManager->CreateImageLayer();
    if (!layer) return nullptr;
    // Mark this layer as being used for painting display items
    data->mImageLayer = layer;
    layer->SetUserData(&gImageLayerUserData, nullptr);

    // Remove other layer types we might have stored for this PaintedLayer
    data->mColorLayer = nullptr;
  }
  return layer.forget();
}

template <typename UserData>
already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleMaskImageLayerFor(
    const MaskLayerKey& aKey, UserData* (*aGetUserData)(Layer* aLayer),
    void (*aSetDefaultUserData)(Layer* aLayer)) {
  RefPtr<ImageLayer> result = mRecycledMaskImageLayers.Get(aKey);

  if (result && aGetUserData(result.get())) {
    mRecycledMaskImageLayers.Remove(aKey);
    aKey.mLayer->ClearExtraDumpInfo();
    // XXX if we use clip on mask layers, null it out here
  } else {
    // Create a new layer
    result = mManager->CreateImageLayer();
    if (!result) {
      return nullptr;
    }
    aSetDefaultUserData(result);
  }

  return result.forget();
}

static const double SUBPIXEL_OFFSET_EPSILON = 0.02;

/**
 * This normally computes NSToIntRoundUp(aValue). However, if that would
 * give a residual near 0.5 while aOldResidual is near -0.5, or
 * it would give a residual near -0.5 while aOldResidual is near 0.5, then
 * instead we return the integer in the other direction so that the residual
 * is close to aOldResidual.
 */
static int32_t RoundToMatchResidual(double aValue, double aOldResidual) {
  int32_t v = NSToIntRoundUp(aValue);
  double residual = aValue - v;
  if (aOldResidual < 0) {
    if (residual > 0 &&
        fabs(residual - 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) {
      // Round up instead
      return int32_t(ceil(aValue));
    }
  } else if (aOldResidual > 0) {
    if (residual < 0 &&
        fabs(residual + 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) {
      // Round down instead
      return int32_t(floor(aValue));
    }
  }
  return v;
}

static void ResetScrollPositionForLayerPixelAlignment(
    AnimatedGeometryRoot* aAnimatedGeometryRoot) {
  nsIScrollableFrame* sf =
      nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
  if (sf) {
    sf->ResetScrollPositionForLayerPixelAlignment();
  }
}

static void InvalidateEntirePaintedLayer(
    PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
    const char* aReason) {
#ifdef MOZ_DUMP_PAINTING
  if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
    printf_stderr("Invalidating entire layer %p: %s\n", aLayer, aReason);
  }
#endif
  aLayer->InvalidateWholeLayer();
  aLayer->SetInvalidRectToVisibleRegion();
  ResetScrollPositionForLayerPixelAlignment(aAnimatedGeometryRoot);
}

LayerManager::PaintedLayerCreationHint ContainerState::GetLayerCreationHint(
    AnimatedGeometryRoot* aAnimatedGeometryRoot) {
  // Check whether the layer will be scrollable. This is used as a hint to
  // influence whether tiled layers are used or not.

  // Check creation hint inherited from our parent.
  if (mParameters.mLayerCreationHint == LayerManager::SCROLLABLE) {
    return LayerManager::SCROLLABLE;
  }

  // Check whether there's any active scroll frame on the animated geometry
  // root chain.
  for (AnimatedGeometryRoot* agr = aAnimatedGeometryRoot;
       agr && agr != mContainerAnimatedGeometryRoot; agr = agr->mParentAGR) {
    nsIFrame* fParent = nsLayoutUtils::GetCrossDocParentFrame(*agr);
    if (!fParent) {
      break;
    }
    nsIScrollableFrame* scrollable = do_QueryFrame(fParent);
    if (scrollable) {
      return LayerManager::SCROLLABLE;
    }
  }
  return LayerManager::NONE;
}

already_AddRefed<PaintedLayer> ContainerState::AttemptToRecyclePaintedLayer(
    AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem,
    const nsPoint& aTopLeft, const nsIFrame* aReferenceFrame) {
  Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem);
  if (!oldLayer || !oldLayer->AsPaintedLayer()) {
    return nullptr;
  }

  if (!mPaintedLayersAvailableForRecycling.EnsureRemoved(
          oldLayer->AsPaintedLayer())) {
    // Not found.
    return nullptr;
  }

  // Try to recycle the layer.
  RefPtr<PaintedLayer> layer = oldLayer->AsPaintedLayer();

  // Check if the layer hint has changed and whether or not the layer should
  // be recreated because of it.
  if (!layer->IsOptimizedFor(GetLayerCreationHint(aAnimatedGeometryRoot))) {
    return nullptr;
  }

  bool didResetScrollPositionForLayerPixelAlignment = false;
  PaintedDisplayItemLayerUserData* data =
      RecyclePaintedLayer(layer, aAnimatedGeometryRoot,
                          didResetScrollPositionForLayerPixelAlignment);
  PreparePaintedLayerForUse(layer, data, aAnimatedGeometryRoot, aReferenceFrame,
                            aTopLeft,
                            didResetScrollPositionForLayerPixelAlignment);

  return layer.forget();
}

void ReleaseLayerUserData(void* aData) {
  PaintedDisplayItemLayerUserData* userData =
      static_cast<PaintedDisplayItemLayerUserData*>(aData);
  userData->Release();
}

already_AddRefed<PaintedLayer> ContainerState::CreatePaintedLayer(
    PaintedLayerData* aData) {
  LayerManager::PaintedLayerCreationHint creationHint =
      GetLayerCreationHint(aData->mAnimatedGeometryRoot);

  // Create a new painted layer
  RefPtr<PaintedLayer> layer =
      mManager->CreatePaintedLayerWithHint(creationHint);
  if (!layer) {
    return nullptr;
  }

  // Mark this layer as being used for painting display items
  RefPtr<PaintedDisplayItemLayerUserData> userData =
      new PaintedDisplayItemLayerUserData();
  userData->mDisabledAlpha =
      mParameters.mDisableSubpixelAntialiasingInDescendants;
  userData.get()->AddRef();
  layer->SetUserData(&gPaintedDisplayItemLayerUserData, userData,
                     ReleaseLayerUserData);
  ResetScrollPositionForLayerPixelAlignment(aData->mAnimatedGeometryRoot);

  PreparePaintedLayerForUse(layer, userData, aData->mAnimatedGeometryRoot,
                            aData->mReferenceFrame,
                            aData->mAnimatedGeometryRootOffset, true);

  return layer.forget();
}

PaintedDisplayItemLayerUserData* ContainerState::RecyclePaintedLayer(
    PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
    bool& didResetScrollPositionForLayerPixelAlignment) {
  // Clear clip rect and mask layer so we don't accidentally stay clipped.
  // We will reapply any necessary clipping.
  ResetLayerStateForRecycling(aLayer);
  aLayer->ClearExtraDumpInfo();

  PaintedDisplayItemLayerUserData* data =
      static_cast<PaintedDisplayItemLayerUserData*>(
          aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
  NS_ASSERTION(data, "Recycled PaintedLayers must have user data");

  // This gets called on recycled PaintedLayers that are going to be in the
  // final layer tree, so it's a convenient time to invalidate the
  // content that changed where we don't know what PaintedLayer it belonged
  // to, or if we need to invalidate the entire layer, we can do that.
  // This needs to be done before we update the PaintedLayer to its new
  // transform. See nsGfxScrollFrame::InvalidateInternal, where
  // we ensure that mInvalidPaintedContent is updated according to the
  // scroll position as of the most recent paint.
  if (!FuzzyEqual(data->mXScale, mParameters.mXScale, 0.00001f) ||
      !FuzzyEqual(data->mYScale, mParameters.mYScale, 0.00001f) ||
      data->mAppUnitsPerDevPixel != mAppUnitsPerDevPixel) {
#ifdef MOZ_DUMP_PAINTING
    if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
      printf_stderr("Recycled layer %p changed scale\n", aLayer);
    }
#endif
    InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
                                 "recycled layer changed state");
    didResetScrollPositionForLayerPixelAlignment = true;
  }
  if (!data->mRegionToInvalidate.IsEmpty()) {
#ifdef MOZ_DUMP_PAINTING
    if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
      printf_stderr("Invalidating deleted frame content from layer %p\n",
                    aLayer);
    }
#endif
    aLayer->InvalidateRegion(data->mRegionToInvalidate);
#ifdef MOZ_DUMP_PAINTING
    if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
      nsAutoCString str;
      AppendToString(str, data->mRegionToInvalidate);
      printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
    }
#endif
    data->mRegionToInvalidate.SetEmpty();
  }
  return data;
}

void ContainerState::PreparePaintedLayerForUse(
    PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData,
    AnimatedGeometryRoot* aAnimatedGeometryRoot,
    const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft,
    bool didResetScrollPositionForLayerPixelAlignment) {
  aData->mXScale = mParameters.mXScale;
  aData->mYScale = mParameters.mYScale;
  aData->mLastAnimatedGeometryRootOrigin = aData->mAnimatedGeometryRootOrigin;
  aData->mAnimatedGeometryRootOrigin = aTopLeft;
  aData->mAppUnitsPerDevPixel = mAppUnitsPerDevPixel;
  aLayer->SetAllowResidualTranslation(mParameters.AllowResidualTranslation());

  aData->mLastPaintOffset = GetTranslationForPaintedLayer(aLayer);
  aData->mHasExplicitLastPaintOffset = true;

  // Set up transform so that 0,0 in the PaintedLayer corresponds to the
  // (pixel-snapped) top-left of the aAnimatedGeometryRoot.
  nsPoint offset =
      (*aAnimatedGeometryRoot)->GetOffsetToCrossDoc(aReferenceFrame);
  nscoord appUnitsPerDevPixel =
      (*aAnimatedGeometryRoot)->PresContext()->AppUnitsPerDevPixel();
  gfxPoint scaledOffset(
      NSAppUnitsToDoublePixels(offset.x, appUnitsPerDevPixel) *
          mParameters.mXScale,
      NSAppUnitsToDoublePixels(offset.y, appUnitsPerDevPixel) *
          mParameters.mYScale);
  // We call RoundToMatchResidual here so that the residual after rounding
  // is close to aData->mAnimatedGeometryRootPosition if possible.
  nsIntPoint pixOffset(
      RoundToMatchResidual(scaledOffset.x,
                           aData->mAnimatedGeometryRootPosition.x),
      RoundToMatchResidual(scaledOffset.y,
                           aData->mAnimatedGeometryRootPosition.y));
  aData->mTranslation = pixOffset;
  pixOffset += mParameters.mOffset;
  Matrix matrix = Matrix::Translation(pixOffset.x, pixOffset.y);
  aLayer->SetBaseTransform(Matrix4x4::From2D(matrix));

  aData->mVisibilityComputedRegion.SetEmpty();

  // Calculate exact position of the top-left of the active scrolled root.
  // This might not be 0,0 due to the snapping in ScaleToNearestPixels.
  gfxPoint animatedGeometryRootTopLeft =
      scaledOffset - ThebesPoint(matrix.GetTranslation()) + mParameters.mOffset;
  const bool disableAlpha =
      mParameters.mDisableSubpixelAntialiasingInDescendants;
  if (aData->mDisabledAlpha != disableAlpha) {
    aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
    InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
                                 "change of subpixel-AA");
    aData->mDisabledAlpha = disableAlpha;
    return;
  }

  // FIXME: Temporary workaround for bug 681192 and bug 724786.
#ifndef MOZ_WIDGET_ANDROID
  // If it has changed, then we need to invalidate the entire layer since the
  // pixels in the layer buffer have the content at a (subpixel) offset
  // from what we need.
  if (!animatedGeometryRootTopLeft.WithinEpsilonOf(
          aData->mAnimatedGeometryRootPosition, SUBPIXEL_OFFSET_EPSILON)) {
    aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
    InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
                                 "subpixel offset");
  } else if (didResetScrollPositionForLayerPixelAlignment) {
    aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
  }
#else
  Unused << didResetScrollPositionForLayerPixelAlignment;
#endif
}

#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
/**
 * Returns the appunits per dev pixel for the item's frame
 */
static int32_t AppUnitsPerDevPixel(nsDisplayItem* aItem) {
  // The underlying frame for zoom items is the root frame of the subdocument.
  // But zoom display items report their bounds etc using the parent document's
  // APD because zoom items act as a conversion layer between the two different
  // APDs.
  if (aItem->GetType() == DisplayItemType::TYPE_ZOOM) {
    return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
  }
  return aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
}
#endif

/**
 * Set the visible region for aLayer.
 * aOuterVisibleRegion is the visible region relative to the parent layer.
 * aLayerContentsVisibleRect, if non-null, is a rectangle in the layer's
 * own coordinate system to which the layer's visible region is restricted.
 * Consumes *aOuterVisibleRegion.
 */
static void SetOuterVisibleRegion(
    Layer* aLayer, nsIntRegion* aOuterVisibleRegion,
    const nsIntRect* aLayerContentsVisibleRect = nullptr,
    bool aOuterUntransformed = false) {
  Matrix4x4 transform = aLayer->GetTransform();
  Matrix transform2D;
  if (aOuterUntransformed) {
    if (aLayerContentsVisibleRect) {
      aOuterVisibleRegion->And(*aOuterVisibleRegion,
                               *aLayerContentsVisibleRect);
    }
  } else if (transform.Is2D(&transform2D) &&
             !transform2D.HasNonIntegerTranslation()) {
    aOuterVisibleRegion->MoveBy(-int(transform2D._31), -int(transform2D._32));
    if (aLayerContentsVisibleRect) {
      aOuterVisibleRegion->And(*aOuterVisibleRegion,
                               *aLayerContentsVisibleRect);
    }
  } else {
    nsIntRect outerRect = aOuterVisibleRegion->GetBounds();
    // if 'transform' is not invertible, then nothing will be displayed
    // for the layer, so it doesn't really matter what we do here
    Rect outerVisible(outerRect.x, outerRect.y, outerRect.width,
                      outerRect.height);
    transform.Invert();

    Rect layerContentsVisible = Rect::MaxIntRect();

    if (aLayerContentsVisibleRect) {
      NS_ASSERTION(aLayerContentsVisibleRect->width >= 0 &&
                       aLayerContentsVisibleRect->height >= 0,
                   "Bad layer contents rectangle");
      // restrict to aLayerContentsVisibleRect before call GfxRectToIntRect,
      // in case layerVisible is extremely large (as it can be when
      // projecting through the inverse of a 3D transform)
      layerContentsVisible = Rect(
          aLayerContentsVisibleRect->x, aLayerContentsVisibleRect->y,
          aLayerContentsVisibleRect->width, aLayerContentsVisibleRect->height);
    }

    Rect layerVisible =
        transform.ProjectRectBounds(outerVisible, layerContentsVisible);

    layerVisible.RoundOut();

    IntRect intRect;
    if (!layerVisible.ToIntRect(&intRect)) {
      intRect = IntRect::MaxIntRect();
    }

    *aOuterVisibleRegion = intRect;
  }

  aLayer->SetVisibleRegion(
      LayerIntRegion::FromUnknownRegion(*aOuterVisibleRegion));
}

void ContainerState::SetOuterVisibleRegionForLayer(
    Layer* aLayer, const nsIntRegion& aOuterVisibleRegion,
    const nsIntRect* aLayerContentsVisibleRect,
    bool aOuterUntransformed) const {
  nsIntRegion visRegion = aOuterVisibleRegion;
  if (!aOuterUntransformed) {
    visRegion.MoveBy(mParameters.mOffset);
  }
  SetOuterVisibleRegion(aLayer, &visRegion, aLayerContentsVisibleRect,
                        aOuterUntransformed);
}

nscolor ContainerState::FindOpaqueBackgroundColorInLayer(
    const PaintedLayerData* aData, const nsIntRect& aRect,
    bool* aOutIntersectsLayer) const {
  *aOutIntersectsLayer = true;

  // Scan the candidate's display items.
  nsIntRect deviceRect = aRect;
  nsRect appUnitRect = ToAppUnits(deviceRect, mAppUnitsPerDevPixel);
  appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale);

  for (auto& assignedItem : Reversed(aData->mAssignedDisplayItems)) {
    if (assignedItem.mHasOpacity || assignedItem.mHasTransform) {
      // We cannot easily calculate the opaque background color for items inside
      // a flattened effect.
      continue;
    }

    if (IsEffectEndMarker(assignedItem.mType)) {
      // An optimization: the underlying display item for effect markers is the
      // same for both start and end markers. Skip the effect end markers.
      continue;
    }

    nsDisplayItem* item = assignedItem.mItem;
    bool snap;
    nsRect bounds = item->GetBounds(mBuilder, &snap);
    if (snap && mSnappingEnabled) {
      nsIntRect snappedBounds = ScaleToNearestPixels(bounds);
      if (!snappedBounds.Intersects(deviceRect)) continue;

      if (!snappedBounds.Contains(deviceRect)) return NS_RGBA(0, 0, 0, 0);

    } else {
      // The layer's visible rect is already (close enough to) pixel
      // aligned, so no need to round out and in here.
      if (!bounds.Intersects(appUnitRect)) continue;

      if (!bounds.Contains(appUnitRect)) return NS_RGBA(0, 0, 0, 0);
    }

    if (item->IsInvisibleInRect(appUnitRect)) {
      continue;
    }

    if (item->GetClip().IsRectAffectedByClip(deviceRect, mParameters.mXScale,
                                             mParameters.mYScale,
                                             mAppUnitsPerDevPixel)) {
      return NS_RGBA(0, 0, 0, 0);
    }

    MOZ_ASSERT(!assignedItem.mHasOpacity && !assignedItem.mHasTransform);
    Maybe<nscolor> color = item->IsUniform(mBuilder);

    if (color && NS_GET_A(*color) == 255) {
      return *color;
    }

    return NS_RGBA(0, 0, 0, 0);
  }

  *aOutIntersectsLayer = false;
  return NS_RGBA(0, 0, 0, 0);
}

nscolor PaintedLayerDataNode::FindOpaqueBackgroundColor(
    const nsIntRegion& aTargetVisibleRegion, int32_t aUnderIndex) const {
  if (aUnderIndex == ABOVE_TOP) {
    aUnderIndex = mPaintedLayerDataStack.Length();
  }
  for (int32_t i = aUnderIndex - 1; i >= 0; --i) {
    const PaintedLayerData* candidate = &mPaintedLayerDataStack[i];
    if (candidate->VisibleAboveRegionIntersects(aTargetVisibleRegion)) {
      // Some non-PaintedLayer content between target and candidate; this is
      // hopeless
      return NS_RGBA(0, 0, 0, 0);
    }

    if (!candidate->VisibleRegionIntersects(aTargetVisibleRegion)) {
      // The layer doesn't intersect our target, ignore it and move on
      continue;
    }

    bool intersectsLayer = true;
    nsIntRect rect = aTargetVisibleRegion.GetBounds();
    nscolor color = mTree.ContState().FindOpaqueBackgroundColorInLayer(
        candidate, rect, &intersectsLayer);
    if (!intersectsLayer) {
      continue;
    }
    return color;
  }
  if (mAllDrawingAboveBackground ||
      !mVisibleAboveBackgroundRegion.Intersect(aTargetVisibleRegion)
           .IsEmpty()) {
    // Some non-PaintedLayer content is between this node's background and
    // target.
    return NS_RGBA(0, 0, 0, 0);
  }
  return FindOpaqueBackgroundColorInParentNode();
}

nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorCoveringEverything()
    const {
  if (!mPaintedLayerDataStack.IsEmpty() || mAllDrawingAboveBackground ||
      !mVisibleAboveBackgroundRegion.IsEmpty()) {
    return NS_RGBA(0, 0, 0, 0);
  }
  return FindOpaqueBackgroundColorInParentNode();
}

nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorInParentNode() const {
  if (mParent) {
    if (mHasClip) {
      // Check whether our parent node has uniform content behind our whole
      // clip.
      // There's one tricky case here: If our parent node is also a scrollable,
      // and is currently scrolled in such a way that this inner one is
      // clipped by it, then it's not really clear how we should determine
      // whether we have a uniform background in the parent: There might be
      // non-uniform content in the parts that our scroll port covers in the
      // parent and that are currently outside the parent's clip.
      // For now, we'll fail to pull a background color in that case.
      return mParent->FindOpaqueBackgroundColor(mClipRect);
    }
    return mParent->FindOpaqueBackgroundColorCoveringEverything();
  }
  // We are the root.
  return mTree.UniformBackgroundColor();
}

bool PaintedLayerData::CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder) {
  if (!mImage) {
    return false;
  }

  return mImage->CanOptimizeToImageLayer(mLayer->Manager(), aBuilder);
}

already_AddRefed<ImageContainer> PaintedLayerData::GetContainerForImageLayer(
    nsDisplayListBuilder* aBuilder) {
  if (!mImage) {
    return nullptr;
  }

  return mImage->GetContainer(mLayer->Manager(), aBuilder);
}

PaintedLayerDataNode::PaintedLayerDataNode(
    PaintedLayerDataTree& aTree, PaintedLayerDataNode* aParent,
    AnimatedGeometryRoot* aAnimatedGeometryRoot)
    : mTree(aTree),
      mParent(aParent),
      mAnimatedGeometryRoot(aAnimatedGeometryRoot),
      mAllDrawingAboveBackground(false) {
  MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(
      mTree.Builder()->RootReferenceFrame(), *mAnimatedGeometryRoot));
  mHasClip = mTree.IsClippedWithRespectToParentAnimatedGeometryRoot(
      mAnimatedGeometryRoot, &mClipRect);
}

PaintedLayerDataNode::~PaintedLayerDataNode() {
  MOZ_ASSERT(mPaintedLayerDataStack.IsEmpty());
  MOZ_ASSERT(mChildren.IsEmpty());
}

PaintedLayerDataNode* PaintedLayerDataNode::AddChildNodeFor(
    AnimatedGeometryRoot* aAnimatedGeometryRoot) {
  MOZ_ASSERT(aAnimatedGeometryRoot->mParentAGR == mAnimatedGeometryRoot);
  UniquePtr<PaintedLayerDataNode> child =
      MakeUnique<PaintedLayerDataNode>(mTree, this, aAnimatedGeometryRoot);
  mChildren.AppendElement(std::move(child));
  return mChildren.LastElement().get();
}

template <typename NewPaintedLayerCallbackType>
PaintedLayerData* PaintedLayerDataNode::FindPaintedLayerFor(
    const nsIntRect& aVisibleRect, const bool aBackfaceHidden,
    const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
    NewPaintedLayerCallbackType aNewPaintedLayerCallback) {
  if (!mPaintedLayerDataStack.IsEmpty()) {
    PaintedLayerData* lowestUsableLayer = nullptr;
    for (auto& data : Reversed(mPaintedLayerDataStack)) {
      if (data.mVisibleAboveRegion.Intersects(aVisibleRect)) {
        break;
      }
      if (data.mBackfaceHidden == aBackfaceHidden && data.mASR == aASR &&
          data.mClipChain == aClipChain) {
        lowestUsableLayer = &data;
      }
      // Also check whether the event-regions intersect the visible rect,
      // unless we're in an inactive layer, in which case the event-regions
      // will be hoisted out into their own layer.
      // For performance reasons, we check the intersection with the bounds
      // of the event-regions.
      if (!mTree.ContState().IsInInactiveLayer() &&
          (data.mScaledHitRegionBounds.Intersects(aVisibleRect) ||
           data.mScaledMaybeHitRegionBounds.Intersects(aVisibleRect))) {
        break;
      }
      // If the visible region intersects with the current layer then we
      // can't possibly use any of the layers below it, so stop the search
      // now.
      //
      // If we're trying to minimize painted layer size and we don't
      // intersect the current visible region, then make sure we don't
      // use this painted layer.
      if (data.mVisibleRegion.Intersects(aVisibleRect)) {
        break;
      }

      if (gfxPrefs::LayoutSmallerPaintedLayers()) {
        lowestUsableLayer = nullptr;
      }
    }
    if (lowestUsableLayer) {
      return lowestUsableLayer;
    }
  }
  PaintedLayerData* data = mPaintedLayerDataStack.AppendElement();
  aNewPaintedLayerCallback(data);

  return data;
}

void PaintedLayerDataNode::FinishChildrenIntersecting(const nsIntRect& aRect) {
  for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
    if (mChildren[i]->Intersects(aRect)) {
      mChildren[i]->Finish(true);
      mChildren.RemoveElementAt(i);
    }
  }
}

void PaintedLayerDataNode::FinishAllChildren(
    bool aThisNodeNeedsAccurateVisibleAboveRegion) {
  for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
    mChildren[i]->Finish(aThisNodeNeedsAccurateVisibleAboveRegion);
  }
  mChildren.Clear();
}

void PaintedLayerDataNode::Finish(bool aParentNeedsAccurateVisibleAboveRegion) {
  // Skip "visible above region" maintenance, because this node is going away.
  FinishAllChildren(false);

  PopAllPaintedLayerData();

  if (mParent && aParentNeedsAccurateVisibleAboveRegion) {
    if (mHasClip) {
      mParent->AddToVisibleAboveRegion(mClipRect);
    } else {
      mParent->SetAllDrawingAbove();
    }
  }
  mTree.NodeWasFinished(mAnimatedGeometryRoot);
}

void PaintedLayerDataNode::AddToVisibleAboveRegion(const nsIntRect& aRect) {
  nsIntRegion& visibleAboveRegion =
      mPaintedLayerDataStack.IsEmpty()
          ? mVisibleAboveBackgroundRegion
          : mPaintedLayerDataStack.LastElement().mVisibleAboveRegion;
  visibleAboveRegion.Or(visibleAboveRegion, aRect);
  visibleAboveRegion.SimplifyOutward(8);
}

void PaintedLayerDataNode::SetAllDrawingAbove() {
  PopAllPaintedLayerData();
  mAllDrawingAboveBackground = true;
  mVisibleAboveBackgroundRegion.SetEmpty();
}

void PaintedLayerDataNode::PopAllPaintedLayerData() {
  for (int32_t index = mPaintedLayerDataStack.Length() - 1; index >= 0;
       index--) {
    PaintedLayerData& data = mPaintedLayerDataStack[index];
    mTree.ContState().FinishPaintedLayerData(data, [this, &data, index]() {
      return this->FindOpaqueBackgroundColor(data.mVisibleRegion, index);
    });
  }
  mPaintedLayerDataStack.Clear();
}

void PaintedLayerDataTree::InitializeForInactiveLayer(
    AnimatedGeometryRoot* aAnimatedGeometryRoot) {
  mForInactiveLayer = true;
  mRoot.emplace(*this, nullptr, aAnimatedGeometryRoot);
}

nsDisplayListBuilder* PaintedLayerDataTree::Builder() const {
  return mContainerState.Builder();
}

void PaintedLayerDataTree::Finish() {
  if (mRoot) {
    mRoot->Finish(false);
  }
  MOZ_ASSERT(mNodes.Count() == 0);
  mRoot.reset();
}

void PaintedLayerDataTree::NodeWasFinished(
    AnimatedGeometryRoot* aAnimatedGeometryRoot) {
  mNodes.Remove(aAnimatedGeometryRoot);
}

void PaintedLayerDataTree::AddingOwnLayer(
    AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect,
    nscolor* aOutUniformBackgroundColor) {
  PaintedLayerDataNode* node = nullptr;
  if (mForInactiveLayer) {
    node = mRoot.ptr();
  } else {
    FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, aRect);
    node = EnsureNodeFor(aAnimatedGeometryRoot);
  }
  if (aRect) {
    if (aOutUniformBackgroundColor) {
      *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColor(*aRect);
    }
    node->AddToVisibleAboveRegion(*aRect);
  } else {
    if (aOutUniformBackgroundColor) {
      *aOutUniformBackgroundColor =
          node->FindOpaqueBackgroundColorCoveringEverything();
    }
    node->SetAllDrawingAbove();
  }
}

template <typename NewPaintedLayerCallbackType>
PaintedLayerData* PaintedLayerDataTree::FindPaintedLayerFor(
    AnimatedGeometryRoot* aAnimatedGeometryRoot, const ActiveScrolledRoot* aASR,
    const DisplayItemClipChain* aClipChain, const nsIntRect& aVisibleRect,
    const bool aBackfaceHidden,
    NewPaintedLayerCallbackType aNewPaintedLayerCallback) {
  const nsIntRect* bounds = &aVisibleRect;
  PaintedLayerDataNode* node = nullptr;
  if (mForInactiveLayer) {
    node = mRoot.ptr();
  } else {
    FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, bounds);
    node = EnsureNodeFor(aAnimatedGeometryRoot);
  }

  PaintedLayerData* data =
      node->FindPaintedLayerFor(aVisibleRect, aBackfaceHidden, aASR, aClipChain,
                                aNewPaintedLayerCallback);
  return data;
}

void PaintedLayerDataTree::FinishPotentiallyIntersectingNodes(
    AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect) {
  AnimatedGeometryRoot* ancestorThatIsChildOfCommonAncestor = nullptr;
  PaintedLayerDataNode* ancestorNode = FindNodeForAncestorAnimatedGeometryRoot(
      aAnimatedGeometryRoot, &ancestorThatIsChildOfCommonAncestor);
  if (!ancestorNode) {
    // None of our ancestors are in the tree. This should only happen if this
    // is the very first item we're looking at.
    MOZ_ASSERT(!mRoot);
    return;
  }

  if (ancestorNode->GetAnimatedGeometryRoot() == aAnimatedGeometryRoot) {
    // aAnimatedGeometryRoot already has a node in the tree.
    // This is the common case.
    MOZ_ASSERT(!ancestorThatIsChildOfCommonAncestor);
    if (aRect) {
      ancestorNode->FinishChildrenIntersecting(*aRect);
    } else {
      ancestorNode->FinishAllChildren();
    }
    return;
  }

  // We have found an existing ancestor, but it's a proper ancestor of our
  // animated geometry root.
  // ancestorThatIsChildOfCommonAncestor is the last animated geometry root
  // encountered on the way up from aAnimatedGeometryRoot to ancestorNode.
  MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor);
  MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(
      *ancestorThatIsChildOfCommonAncestor, *aAnimatedGeometryRoot));
  MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor->mParentAGR ==
             ancestorNode->GetAnimatedGeometryRoot());

  // ancestorThatIsChildOfCommonAncestor is not in the tree yet!
  MOZ_ASSERT(!mNodes.Get(ancestorThatIsChildOfCommonAncestor));

  // We're about to add a node for ancestorThatIsChildOfCommonAncestor, so we
  // finish all intersecting siblings.
  nsIntRect clip;
  if (IsClippedWithRespectToParentAnimatedGeometryRoot(
          ancestorThatIsChildOfCommonAncestor, &clip)) {
    ancestorNode->FinishChildrenIntersecting(clip);
  } else {
    ancestorNode->FinishAllChildren();
  }
}

PaintedLayerDataNode* PaintedLayerDataTree::EnsureNodeFor(
    AnimatedGeometryRoot* aAnimatedGeometryRoot) {
  MOZ_ASSERT(aAnimatedGeometryRoot);
  PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
  if (node) {
    return node;
  }

  AnimatedGeometryRoot* parentAnimatedGeometryRoot =
      aAnimatedGeometryRoot->mParentAGR;
  if (!parentAnimatedGeometryRoot) {
    MOZ_ASSERT(!mRoot);
    MOZ_ASSERT(*aAnimatedGeometryRoot == Builder()->RootReferenceFrame());
    mRoot.emplace(*this, nullptr, aAnimatedGeometryRoot);
    node = mRoot.ptr();
  } else {
    PaintedLayerDataNode* parentNode =
        EnsureNodeFor(parentAnimatedGeometryRoot);
    MOZ_ASSERT(parentNode);
    node = parentNode->AddChildNodeFor(aAnimatedGeometryRoot);
  }
  MOZ_ASSERT(node);
  mNodes.Put(aAnimatedGeometryRoot, node);
  return node;
}

bool PaintedLayerDataTree::IsClippedWithRespectToParentAnimatedGeometryRoot(
    AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip) {
  if (mForInactiveLayer) {
    return false;
  }
  nsIScrollableFrame* scrollableFrame =
      nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
  if (!scrollableFrame) {
    return false;
  }
  nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame);
  nsRect scrollPort = scrollableFrame->GetScrollPortRect() +
                      Builder()->ToReferenceFrame(scrollFrame);
  *aOutClip = mContainerState.ScaleToNearestPixels(scrollPort);
  return true;
}

PaintedLayerDataNode*
PaintedLayerDataTree::FindNodeForAncestorAnimatedGeometryRoot(
    AnimatedGeometryRoot* aAnimatedGeometryRoot,
    AnimatedGeometryRoot** aOutAncestorChild) {
  if (!aAnimatedGeometryRoot) {
    return nullptr;
  }
  PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
  if (node) {
    return node;
  }
  *aOutAncestorChild = aAnimatedGeometryRoot;
  return FindNodeForAncestorAnimatedGeometryRoot(
      aAnimatedGeometryRoot->mParentAGR, aOutAncestorChild);
}

static bool CanOptimizeAwayPaintedLayer(PaintedLayerData* aData,
                                        FrameLayerBuilder* aLayerBuilder) {
  if (!aLayerBuilder->IsBuildingRetainedLayers()) {
    return false;
  }

  // If there's no painted layer with valid content in it that we can reuse,
  // always create a color or image layer (and potentially throw away an
  // existing completely invalid painted layer).
  if (aData->mLayer->GetValidRegion().IsEmpty()) {
    return true;
  }

  // There is an existing painted layer we can reuse. Throwing it away can make
  // compositing cheaper (see bug 946952), but it might cause us to re-allocate
  // the painted layer frequently due to an animation. So we only discard it if
  // we're in tree compression mode, which is triggered at a low frequency.
  return aLayerBuilder->CheckInLayerTreeCompressionMode();
}

#ifdef DEBUG
static int32_t FindIndexOfLayerIn(nsTArray<NewLayerEntry>& aArray,
                                  Layer* aLayer) {
  for (uint32_t i = 0; i < aArray.Length(); ++i) {
    if (aArray[i].mLayer == aLayer) {
      return i;
    }
  }
  return -1;
}
#endif

already_AddRefed<Layer> ContainerState::PrepareImageLayer(
    PaintedLayerData* aData) {
  RefPtr<ImageContainer> imageContainer =
      aData->GetContainerForImageLayer(mBuilder);
  if (!imageContainer) {
    return nullptr;
  }

  RefPtr<ImageLayer> imageLayer = CreateOrRecycleImageLayer(aData->mLayer);
  imageLayer->SetContainer(imageContainer);
  aData->mImage->ConfigureLayer(imageLayer, mParameters);
  imageLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale);

  if (aData->mItemClip->HasClip()) {
    ParentLayerIntRect clip = ViewAs<ParentLayerPixel>(
        ScaleToNearestPixels(aData->mItemClip->GetClipRect()));
    clip.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
    imageLayer->SetClipRect(Some(clip));
  } else {
    imageLayer->SetClipRect(Nothing());
  }

  FLB_LOG_PAINTED_LAYER_DECISION(aData, "  Selected image layer=%p\n",
                                 imageLayer.get());

  return imageLayer.forget();
}

already_AddRefed<Layer> ContainerState::PrepareColorLayer(
    PaintedLayerData* aData) {
  RefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer(aData->mLayer);
  colorLayer->SetColor(Color::FromABGR(aData->mSolidColor));

  // Copy transform
  colorLayer->SetBaseTransform(aData->mLayer->GetBaseTransform());
  colorLayer->SetPostScale(aData->mLayer->GetPostXScale(),
                           aData->mLayer->GetPostYScale());

  nsIntRect visibleRect = aData->mVisibleRegion.GetBounds();
  visibleRect.MoveBy(-GetTranslationForPaintedLayer(aData->mLayer));
  colorLayer->SetBounds(visibleRect);
  colorLayer->SetClipRect(Nothing());

  FLB_LOG_PAINTED_LAYER_DECISION(aData, "  Selected color layer=%p\n",
                                 colorLayer.get());

  return colorLayer.forget();
}

static void SetBackfaceHiddenForLayer(bool aBackfaceHidden, Layer* aLayer) {
  if (aBackfaceHidden) {
    aLayer->SetContentFlags(aLayer->GetContentFlags() |
                            Layer::CONTENT_BACKFACE_HIDDEN);
  } else {
    aLayer->SetContentFlags(aLayer->GetContentFlags() &
                            ~Layer::CONTENT_BACKFACE_HIDDEN);
  }
}

template <typename FindOpaqueBackgroundColorCallbackType>
void ContainerState::FinishPaintedLayerData(
    PaintedLayerData& aData,
    FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor) {
  PaintedLayerData* data = &aData;

  if (!data->mLayer) {
    // No layer was recycled, so we create a new one.
    RefPtr<PaintedLayer> paintedLayer = CreatePaintedLayer(data);
    data->mLayer = paintedLayer;

    NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0,
                 "Layer already in list???");
    mNewChildLayers[data->mNewChildLayersIndex].mLayer = paintedLayer.forget();
  }

  PaintedDisplayItemLayerUserData* userData =
      GetPaintedDisplayItemLayerUserData(data->mLayer);
  NS_ASSERTION(userData, "where did our user data go?");
  userData->mLastItemCount = data->mAssignedDisplayItems.size();

  NewLayerEntry* newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex];

  RefPtr<Layer> layer;
  bool canOptimizeToImageLayer = data->CanOptimizeToImageLayer(mBuilder);

  FLB_LOG_PAINTED_LAYER_DECISION(data, "Selecting layer for pld=%p\n", data);
  FLB_LOG_PAINTED_LAYER_DECISION(
      data, "  Solid=%i, hasImage=%c, canOptimizeAwayPaintedLayer=%i\n",
      data->mIsSolidColorInVisibleRegion, canOptimizeToImageLayer ? 'y' : 'n',
      CanOptimizeAwayPaintedLayer(data, mLayerBuilder));

  if ((data->mIsSolidColorInVisibleRegion || canOptimizeToImageLayer) &&
      CanOptimizeAwayPaintedLayer(data, mLayerBuilder)) {
    NS_ASSERTION(
        !(data->mIsSolidColorInVisibleRegion && canOptimizeToImageLayer),
        "Can't be a solid color as well as an image!");

    layer = canOptimizeToImageLayer ? PrepareImageLayer(data)
                                    : PrepareColorLayer(data);

    if (layer) {
      NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
                   "Layer already in list???");
      NS_ASSERTION(newLayerEntry->mLayer == data->mLayer,
                   "Painted layer at wrong index");
      // Store optimized layer in reserved slot
      NewLayerEntry* paintedLayerEntry = newLayerEntry;
      newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex + 1];
      NS_ASSERTION(!newLayerEntry->mLayer, "Slot already occupied?");
      newLayerEntry->mLayer = layer;
      newLayerEntry->mAnimatedGeometryRoot = data->mAnimatedGeometryRoot;
      newLayerEntry->mASR = paintedLayerEntry->mASR;
      newLayerEntry->mClipChain = paintedLayerEntry->mClipChain;
      newLayerEntry->mScrollMetadataASR = paintedLayerEntry->mScrollMetadataASR;

      // Hide the PaintedLayer. We leave it in the layer tree so that we
      // can find and recycle it later.
      ParentLayerIntRect emptyRect;
      data->mLayer->SetClipRect(Some(emptyRect));
      data->mLayer->SetVisibleRegion(LayerIntRegion());
      data->mLayer->InvalidateWholeLayer();
      data->mLayer->SetEventRegions(EventRegions());
    }
  }

  if (!layer) {
    // We couldn't optimize to an image layer or a color layer above.
    layer = data->mLayer;
    layer->SetClipRect(Nothing());
    FLB_LOG_PAINTED_LAYER_DECISION(data, "  Selected painted layer=%p\n",
                                   layer.get());
  }

  for (auto& item : data->mAssignedDisplayItems) {
    MOZ_ASSERT(item.mItem->GetType() !=
               DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO);

    if (IsEffectEndMarker(item.mType)) {
      // Do not invalidate for end markers.
      continue;
    }

    InvalidateForLayerChange(item.mItem, data->mLayer, item.mDisplayItemData);
    mLayerBuilder->AddPaintedDisplayItem(data, item, *this, layer);
    item.mDisplayItemData = nullptr;
  }

  if (mLayerBuilder->IsBuildingRetainedLayers()) {
    newLayerEntry->mVisibleRegion = data->mVisibleRegion;
    newLayerEntry->mOpaqueRegion = data->mOpaqueRegion;
    newLayerEntry->mHideAllLayersBelow = data->mHideAllLayersBelow;
    newLayerEntry->mOpaqueForAnimatedGeometryRootParent =
        data->mOpaqueForAnimatedGeometryRootParent;
  } else {
    SetOuterVisibleRegionForLayer(layer, data->mVisibleRegion);
  }

#ifdef MOZ_DUMP_PAINTING
  if (!data->mLog.IsEmpty()) {
    if (PaintedLayerData* containingPld =
            mLayerBuilder->GetContainingPaintedLayerData()) {
      containingPld->mLayer->AddExtraDumpInfo(nsCString(data->mLog));
    } else {
      layer->AddExtraDumpInfo(nsCString(data->mLog));
    }
  }
#endif

  mLayerBuilder->AddPaintedLayerItemsEntry(userData);

  nsIntRegion transparentRegion;
  transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion);
  bool isOpaque = transparentRegion.IsEmpty();
  // For translucent PaintedLayers, try to find an opaque background
  // color that covers the entire area beneath it so we can pull that
  // color into this layer to make it opaque.
  if (layer == data->mLayer) {
    nscolor backgroundColor = NS_RGBA(0, 0, 0, 0);
    if (!isOpaque) {
      backgroundColor = aFindOpaqueBackgroundColor();
      if (NS_GET_A(backgroundColor) == 255) {
        isOpaque = true;
      }
    }

    // Store the background color
    if (userData->mForcedBackgroundColor != backgroundColor) {
      // Invalidate the entire target PaintedLayer since we're changing
      // the background color
#ifdef MOZ_DUMP_PAINTING
      if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
        printf_stderr(
            "Forced background color has changed from #%08X to #%08X "
            "on layer %p\n",
            userData->mForcedBackgroundColor, backgroundColor, data->mLayer);
        nsAutoCString str;
        AppendToString(str, data->mLayer->GetValidRegion());
        printf_stderr("Invalidating layer %p: %s\n", data->mLayer, str.get());
      }
#endif
      data->mLayer->InvalidateWholeLayer();
    }
    userData->mForcedBackgroundColor = backgroundColor;
  } else {
    // mask layer for image and color layers
    SetupMaskLayer(layer, *data->mItemClip);
  }

  uint32_t flags = 0;
  nsIWidget* widget = mContainerReferenceFrame->PresContext()->GetRootWidget();
  // See bug 941095. Not quite ready to disable this.
  bool hidpi = false && widget && widget->GetDefaultScale().scale >= 2;
  if (hidpi) {
    flags |= Layer::CONTENT_DISABLE_SUBPIXEL_AA;
  }
  if (isOpaque && !data->mForceTransparentSurface) {
    flags |= Layer::CONTENT_OPAQUE;
  } else if (data->mNeedComponentAlpha && !hidpi) {
    flags |= Layer::CONTENT_COMPONENT_ALPHA;
  }
  layer->SetContentFlags(flags);

  userData->mItems = std::move(data->mAssignedDisplayItems);
  userData->mContainerLayerFrame = GetContainerFrame();

  PaintedLayerData* containingPaintedLayerData =
      mLayerBuilder->GetContainingPaintedLayerData();
  // If we're building layers for an inactive layer, the event regions are
  // clipped to the inactive layer's clip prior to being combined into the
  // event regions of the containing PLD.
  // For the dispatch-to-content and maybe-hit regions, rounded corners on
  // the clip are ignored, since these are approximate regions. For the
  // remaining regions, rounded corners in the clip cause the region to
  // be combined into the corresponding "imprecise" region of the
  // containing's PLD (e.g. the maybe-hit region instead of the hit region).
  const DisplayItemClip* inactiveLayerClip =
      mLayerBuilder->GetInactiveLayerClip();
  if (containingPaintedLayerData) {
    if (!data->mDispatchToContentHitRegion.GetBounds().IsEmpty()) {
      nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
          mContainerReferenceFrame,
          data->mDispatchToContentHitRegion.GetBounds(),
          containingPaintedLayerData->mReferenceFrame);
      if (inactiveLayerClip) {
        rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect);
      }
      containingPaintedLayerData->mDispatchToContentHitRegion.Or(
          containingPaintedLayerData->mDispatchToContentHitRegion, rect);
      containingPaintedLayerData->mDispatchToContentHitRegion.SimplifyOutward(
          8);
      if (data->mDTCRequiresTargetConfirmation) {
        containingPaintedLayerData->mDTCRequiresTargetConfirmation = true;
      }
    }
    if (!data->mMaybeHitRegion.GetBounds().IsEmpty()) {
      nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
          mContainerReferenceFrame, data->mMaybeHitRegion.GetBounds(),
          containingPaintedLayerData->mReferenceFrame);
      if (inactiveLayerClip) {
        rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect);
      }
      containingPaintedLayerData->mMaybeHitRegion.Or(
          containingPaintedLayerData->mMaybeHitRegion, rect);
      containingPaintedLayerData->mMaybeHitRegion.SimplifyOutward(8);
    }
    Maybe<Matrix4x4Flagged> matrixCache;
    nsLayoutUtils::TransformToAncestorAndCombineRegions(
        data->mHitRegion, mContainerReferenceFrame,
        containingPaintedLayerData->mReferenceFrame,
        &containingPaintedLayerData->mHitRegion,
        &containingPaintedLayerData->mMaybeHitRegion, &matrixCache,
        inactiveLayerClip);
    // See the comment in nsDisplayList::AddFrame, where the touch action
    // regions are handled. The same thing applies here.
    bool alreadyHadRegions =
        !containingPaintedLayerData->mNoActionRegion.IsEmpty() ||
        !containingPaintedLayerData->mHorizontalPanRegion.IsEmpty() ||
        !containingPaintedLayerData->mVerticalPanRegion.IsEmpty();
    nsLayoutUtils::TransformToAncestorAndCombineRegions(
        data->mNoActionRegion, mContainerReferenceFrame,
        containingPaintedLayerData->mReferenceFrame,
        &containingPaintedLayerData->mNoActionRegion,
        &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
        inactiveLayerClip);
    nsLayoutUtils::TransformToAncestorAndCombineRegions(
        data->mHorizontalPanRegion, mContainerReferenceFrame,
        containingPaintedLayerData->mReferenceFrame,
        &containingPaintedLayerData->mHorizontalPanRegion,
        &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
        inactiveLayerClip);
    nsLayoutUtils::TransformToAncestorAndCombineRegions(
        data->mVerticalPanRegion, mContainerReferenceFrame,
        containingPaintedLayerData->mReferenceFrame,
        &containingPaintedLayerData->mVerticalPanRegion,
        &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
        inactiveLayerClip);
    if (alreadyHadRegions) {
      containingPaintedLayerData->mDispatchToContentHitRegion.OrWith(
          containingPaintedLayerData->CombinedTouchActionRegion());
    }
  } else {
    EventRegions regions(
        ScaleRegionToOutsidePixels(data->mHitRegion),
        ScaleRegionToOutsidePixels(data->mMaybeHitRegion),
        ScaleRegionToOutsidePixels(data->mDispatchToContentHitRegion),
        ScaleRegionToOutsidePixels(data->mNoActionRegion),
        ScaleRegionToOutsidePixels(data->mHorizontalPanRegion),
        ScaleRegionToOutsidePixels(data->mVerticalPanRegion),
        data->mDTCRequiresTargetConfirmation);

    Matrix mat = layer->GetTransform().As2D();
    mat.Invert();
    regions.ApplyTranslationAndScale(mat._31, mat._32, mat._11, mat._22);

    layer->SetEventRegions(regions);
  }

  SetBackfaceHiddenForLayer(data->mBackfaceHidden, data->mLayer);
  if (layer != data->mLayer) {
    SetBackfaceHiddenForLayer(data->mBackfaceHidden, layer);
  }
}

static bool IsItemAreaInWindowOpaqueRegion(
    nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
    const nsRect& aComponentAlphaBounds) {
  if (!aItem->Frame()->PresContext()->IsChrome()) {
    // Assume that Web content is always in the window opaque region.
    return true;
  }
  if (aItem->ReferenceFrame() != aBuilder->RootReferenceFrame()) {
    // aItem is probably in some transformed subtree.
    // We're not going to bother figuring out where this landed, we're just
    // going to assume it might have landed over a transparent part of
    // the window.
    return false;
  }
  return aBuilder->GetWindowOpaqueRegion().Contains(aComponentAlphaBounds);
}

void PaintedLayerData::UpdateEffectStatus(DisplayItemEntryType aType,
                                          nsTArray<size_t>& aOpacityIndices) {
  switch (aType) {
    case DisplayItemEntryType::PUSH_OPACITY:
      // The index of the new assigned display item in |mAssignedDisplayItems|
      // array will be the current length of the array.
      aOpacityIndices.AppendElement(mAssignedDisplayItems.size());
      break;
    case DisplayItemEntryType::POP_OPACITY:
      MOZ_ASSERT(!aOpacityIndices.IsEmpty());
      aOpacityIndices.RemoveLastElement();
      break;
#ifdef DEBUG
    case DisplayItemEntryType::POP_TRANSFORM:
      MOZ_ASSERT(mTransformLevel >= 0);
      mTransformLevel--;
      break;
    case DisplayItemEntryType::PUSH_TRANSFORM:
      mTransformLevel++;
      break;
#endif
    default:
      break;
  }
}

bool PaintedLayerData::SetupComponentAlpha(
    ContainerState* aState, nsDisplayItem* aItem, const nsIntRect& aVisibleRect,
    const TransformClipNode* aTransform) {
  nsRect componentAlphaBounds =
      aItem->GetComponentAlphaBounds(aState->mBuilder);

  if (componentAlphaBounds.IsEmpty()) {
    // The item does not require component alpha, nothing do do here.
    return false;
  }

  if (aTransform) {
    componentAlphaBounds = aTransform->TransformRect(
        componentAlphaBounds, aState->mAppUnitsPerDevPixel);
  }

  const nsIntRect pixelBounds =
      aState->ScaleToOutsidePixels(componentAlphaBounds, false);

  const nsIntRect visibleRect = pixelBounds.Intersect(aVisibleRect);

  if (!mOpaqueRegion.Contains(visibleRect)) {
    nsRect buildingRect = aItem->GetBuildingRect();

    if (aTransform) {
      buildingRect =
          aTransform->TransformRect(buildingRect, aState->mAppUnitsPerDevPixel);
    }

    const nsRect tightBounds = componentAlphaBounds.Intersect(buildingRect);

    if (IsItemAreaInWindowOpaqueRegion(aState->mBuilder, aItem, tightBounds)) {
      mNeedComponentAlpha = true;
    } else {
      // There is no opaque background below the item, disable component alpha.
      aItem->DisableComponentAlpha();
      return false;
    }
  }

  return true;
}

void PaintedLayerData::Accumulate(ContainerState* aState, nsDisplayItem* aItem,
                                  const nsIntRect& aVisibleRect,
                                  const nsRect& aContentRect,
                                  const DisplayItemClip& aClip,
                                  LayerState aLayerState, nsDisplayList* aList,
                                  DisplayItemEntryType aType,
                                  nsTArray<size_t>& aOpacityIndices,
                                  const RefPtr<TransformClipNode>& aTransform) {
  MOZ_ASSERT(aType != DisplayItemEntryType::HIT_TEST_INFO,
             "Should have handled hit test items earlier!");

  FLB_LOG_PAINTED_LAYER_DECISION(
      this, "Accumulating dp=%s(%p), f=%p against pld=%p\n", aItem->Name(),
      aItem, aItem->Frame(), this);

  const bool hasOpacity = aOpacityIndices.Length() > 0;

  const DisplayItemClip* oldClip = mItemClip;
  mItemClip = &aClip;

  UpdateEffectStatus(aType, aOpacityIndices);

  if (IsEffectEndMarker(aType)) {
    mAssignedDisplayItems.emplace_back(aItem, aLayerState, nullptr,
                                       aContentRect, aType, hasOpacity,
                                       aTransform);
    return;
  }

  bool clipMatches =
      (oldClip == mItemClip) || (oldClip && *oldClip == *mItemClip);

  DisplayItemData* currentData =
      aItem->HasMergedFrames() ? 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);

  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
    // translation so we don't have to factor that in here.
    aItem->DisableComponentAlpha();
  } else {
    const bool needsComponentAlpha =
        SetupComponentAlpha(aState, aItem, aVisibleRect, aTransform);

    if (needsComponentAlpha) {
      // This display item needs background copy when pushing opacity group.
      for (size_t i : aOpacityIndices) {
        AssignedDisplayItem& item = mAssignedDisplayItems[i];
        MOZ_ASSERT(item.mType == DisplayItemEntryType::PUSH_OPACITY ||
                   item.mType == DisplayItemEntryType::PUSH_OPACITY_WITH_BG);
        item.mType = DisplayItemEntryType::PUSH_OPACITY_WITH_BG;
      }
    }
  }

  if (aItem->MustPaintOnContentSide()) {
    mShouldPaintOnContentSide = true;
  }

  if (aTransform && aType == DisplayItemEntryType::ITEM) {
    // Bounds transformed with axis-aligned transforms could be included in the
    // opaque region calculations. For simplicity, this is currently not done.
    return;
  }

  if (!mIsSolidColorInVisibleRegion && mOpaqueRegion.Contains(aVisibleRect) &&
      mVisibleRegion.Contains(aVisibleRect) && !mImage) {
    // A very common case! Most pages have a PaintedLayer with the page
    // background (opaque) visible and most or all of the page content over the
    // top of that background.
    // The rest of this method won't do anything. mVisibleRegion and
    // mOpaqueRegion don't need updating. mVisibleRegion contains aVisibleRect
    // already, mOpaqueRegion contains aVisibleRect and therefore whatever the
    // opaque region of the item is. mVisibleRegion must contain mOpaqueRegion
    // and therefore aVisibleRect.
    return;
  }

  nsIntRegion opaquePixels;

  // Active opacity means no opaque pixels.
  if (!hasOpacity) {
    opaquePixels = aState->ComputeOpaqueRect(
        aItem, mAnimatedGeometryRoot, mASR, aClip, aList, &mHideAllLayersBelow,
        &mOpaqueForAnimatedGeometryRootParent);
    opaquePixels.AndWith(aVisibleRect);
  }

  /* Mark as available for conversion to image layer if this is a nsDisplayImage
   * and it's the only thing visible in this layer.
   */
  if (nsIntRegion(aVisibleRect).Contains(mVisibleRegion) &&
      opaquePixels.Contains(mVisibleRegion) &&
      aItem->SupportsOptimizingToImage()) {
    mImage = static_cast<nsDisplayImageContainer*>(aItem);
    FLB_LOG_PAINTED_LAYER_DECISION(
        this, "  Tracking image: nsDisplayImageContainer covers the layer\n");
  } else if (mImage) {
    FLB_LOG_PAINTED_LAYER_DECISION(this, "  No longer tracking image\n");
    mImage = nullptr;
  }

  bool isFirstVisibleItem = mVisibleRegion.IsEmpty();

  Maybe<nscolor> uniformColor;
  if (!hasOpacity) {
    uniformColor = aItem->IsUniform(aState->mBuilder);
  }

  // Some display items have to exist (so they can set forceTransparentSurface
  // below) but don't draw anything. They'll return true for isUniform but
  // a color with opacity 0.
  if (!uniformColor || NS_GET_A(*uniformColor) > 0) {
    // Make sure that the visible area is covered by uniform pixels. In
    // particular this excludes cases where the edges of the item are not
    // pixel-aligned (thus the item will not be truly uniform).
    if (uniformColor) {
      bool snap;
      nsRect bounds = aItem->GetBounds(aState->mBuilder, &snap);
      if (!aState->ScaleToInsidePixels(bounds, snap).Contains(aVisibleRect)) {
        uniformColor = Nothing();
        FLB_LOG_PAINTED_LAYER_DECISION(
            this, "  Display item does not cover the visible rect\n");
      }
    }
    if (uniformColor) {
      if (isFirstVisibleItem) {
        // This color is all we have
        mSolidColor = *uniformColor;
        mIsSolidColorInVisibleRegion = true;
      } else if (mIsSolidColorInVisibleRegion &&
                 mVisibleRegion.IsEqual(nsIntRegion(aVisibleRect)) &&
                 clipMatches) {
        // we can just blend the colors together
        mSolidColor = NS_ComposeColors(mSolidColor, *uniformColor);
      } else {
        FLB_LOG_PAINTED_LAYER_DECISION(
            this, "  Layer not a solid color: Can't blend colors togethers\n");
        mIsSolidColorInVisibleRegion = false;
      }
    } else {
      FLB_LOG_PAINTED_LAYER_DECISION(this,
                                     "  Layer is not a solid color: Display "
                                     "item is not uniform over the visible "
                                     "bound\n");
      mIsSolidColorInVisibleRegion = false;
    }

    mVisibleRegion.Or(mVisibleRegion, aVisibleRect);
    mVisibleRegion.SimplifyOutward(4);
  }

  if (!opaquePixels.IsEmpty()) {
    for (auto iter = opaquePixels.RectIter(); !iter.Done(); iter.Next()) {
      // We don't use SimplifyInward here since it's not defined exactly
      // what it will discard. For our purposes the most important case
      // is a large opaque background at the bottom of z-order (e.g.,
      // a canvas background), so we need to make sure that the first rect
      // we see doesn't get discarded.
      nsIntRegion tmp;
      tmp.Or(mOpaqueRegion, iter.Get());
      // Opaque display items in chrome documents whose window is partially
      // transparent are always added to the opaque region. This helps ensure
      // that we get as much subpixel-AA as possible in the chrome.
      if (tmp.GetNumRects() <= 4 || aItem->Frame()->PresContext()->IsChrome()) {
        mOpaqueRegion = std::move(tmp);
      }
    }
  }
}

nsRegion PaintedLayerData::CombinedTouchActionRegion() {
  nsRegion result;
  result.Or(mHorizontalPanRegion, mVerticalPanRegion);
  result.OrWith(mNoActionRegion);
  return result;
}

void PaintedLayerData::AccumulateHitTestItem(ContainerState* aState,
                                             nsDisplayItem* aItem,
                                             const DisplayItemClip& aClip,
                                             TransformClipNode* aTransform) {
  MOZ_ASSERT(aItem->HasHitTestInfo());
  auto* item = static_cast<nsDisplayHitTestInfoItem*>(aItem);

  const HitTestInfo& info = item->GetHitTestInfo();

  nsRect area = info.mArea;
  const CompositorHitTestInfo& flags = info.mFlags;

  FLB_LOG_PAINTED_LAYER_DECISION(
      this,
      "Accumulating hit test info %p against pld=%p, "
      "area: [%d, %d, %d, %d], flags: 0x%x]\n",
      item, this, area.x, area.y, area.width, area.height, flags.serialize());

  area = aClip.ApplyNonRoundedIntersection(area);

  if (aTransform) {
    area = aTransform->TransformRect(area, aState->mAppUnitsPerDevPixel);
  }

  if (area.IsEmpty()) {
    FLB_LOG_PAINTED_LAYER_DECISION(
        this, "Discarded empty hit test info %p for pld=%p\n", item, this);
    return;
  }

  bool hasRoundedCorners = aClip.GetRoundedRectCount() > 0;

  // use the NS_FRAME_SIMPLE_EVENT_REGIONS to avoid calling the slightly
  // expensive HasNonZeroCorner function if we know from a previous run that
  // the frame has zero corners.
  nsIFrame* frame = item->Frame();
  bool simpleRegions = frame->HasAnyStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
  if (!simpleRegions) {
    if (nsLayoutUtils::HasNonZeroCorner(frame->StyleBorder()->mBorderRadius)) {
      hasRoundedCorners = true;
    } else {
      frame->AddStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
    }
  }

  if (hasRoundedCorners || (frame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
    mMaybeHitRegion.OrWith(area);
  } else {
    mHitRegion.OrWith(area);
  }

  if (flags.contains(CompositorHitTestFlags::eDispatchToContent)) {
    mDispatchToContentHitRegion.OrWith(area);

    if (flags.contains(CompositorHitTestFlags::eRequiresTargetConfirmation)) {
      mDTCRequiresTargetConfirmation = true;
    }
  }

  const auto touchFlags = flags & CompositorHitTestTouchActionMask;
  if (!touchFlags.isEmpty()) {
    // If there are multiple touch-action areas, there are multiple elements
    // with touch-action properties. We don't know what the relationship is
    // between those elements in terms of DOM ancestry, and so we don't know how
    // to combine the regions properly. Instead, we just add all the areas to
    // the dispatch-to-content region, so that the APZ knows to check with the
    // main thread. See bug 1286957.
    if (mCollapsedTouchActions) {
      mDispatchToContentHitRegion.OrWith(area);
    } else if (touchFlags == CompositorHitTestTouchActionMask) {
      // everything was disabled, so touch-action:none
      mNoActionRegion.OrWith(area);
    } else {
      // The event regions code does not store enough information to actually
      // represent all the different states. Prior to the introduction of
      // CompositorHitTestInfo here in bug 1389149, the following two cases
      // were effectively getting collapsed:
      //   (1) touch-action: auto
      //   (2) touch-action: manipulation
      // In both of these cases, none of {mNoActionRegion, mHorizontalPanRegion,
      // mVerticalPanRegion} were modified, and so the fact that case (2) should
      // have prevented double-tap-zooming was getting lost.
      // With CompositorHitTestInfo we can now represent that case correctly,
      // but only if we use CompositorHitTestInfo all the way to the compositor
      // (i.e. in the WebRender-enabled case). In the non-WebRender case where
      // we still use the event regions, we must collapse these two cases back
      // together. Or add another region to the event regions to fix this
      // properly.
      if (touchFlags !=
          CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled) {
        if (!flags.contains(CompositorHitTestFlags::eTouchActionPanXDisabled)) {
          // pan-x is allowed
          mHorizontalPanRegion.OrWith(area);
        }
        if (!flags.contains(CompositorHitTestFlags::eTouchActionPanYDisabled)) {
          // pan-y is allowed
          mVerticalPanRegion.OrWith(area);
        }
      } else {
        // the touch-action: manipulation case described above. To preserve the
        // existing behaviour, don't touch either mHorizontalPanRegion or
        // mVerticalPanRegion
      }
    }
  }

  if (!mCollapsedTouchActions) {
    // If there are multiple touch-action areas, there are multiple elements
    // with touch-action properties. We don't know what the relationship is
    // between those elements in terms of DOM ancestry, and so we don't know how
    // to combine the regions properly. Instead, we just add all the areas to
    // the dispatch-to-content region, so that the APZ knows to check with the
    // main thread. See bug 1286957.
    const int alreadyHadRegions = mNoActionRegion.GetNumRects() +
                                  mHorizontalPanRegion.GetNumRects() +
                                  mVerticalPanRegion.GetNumRects();

    if (alreadyHadRegions > 1) {
      mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion());
      mNoActionRegion.SetEmpty();
      mHorizontalPanRegion.SetEmpty();
      mVerticalPanRegion.SetEmpty();
      mCollapsedTouchActions = true;
    }
  }

  // Avoid quadratic performance as a result of the region growing to include
  // and arbitrarily large number of rects, which can happen on some pages.
  mMaybeHitRegion.SimplifyOutward(8);
  mDispatchToContentHitRegion.SimplifyOutward(8);

  // Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion
  // for quick access in FindPaintedLayerFor().
  mScaledHitRegionBounds = aState->ScaleToOutsidePixels(mHitRegion.GetBounds());
  mScaledMaybeHitRegionBounds =
      aState->ScaleToOutsidePixels(mMaybeHitRegion.GetBounds());
}

void ContainerState::NewPaintedLayerData(
    PaintedLayerData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot,
    const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
    const ActiveScrolledRoot* aScrollMetadataASR, const nsPoint& aTopLeft,
    const nsIFrame* aReferenceFrame, const bool aBackfaceHidden) {
  aData->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
  aData->mASR = aASR;
  aData->mClipChain = aClipChain;
  aData->mAnimatedGeometryRootOffset = aTopLeft;
  aData->mReferenceFrame = aReferenceFrame;
  aData->mBackfaceHidden = aBackfaceHidden;

  aData->mNewChildLayersIndex = mNewChildLayers.Length();
  NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
  newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
  newLayerEntry->mASR = aASR;
  newLayerEntry->mScrollMetadataASR = aScrollMetadataASR;
  newLayerEntry->mClipChain = aClipChain;
  // newLayerEntry->mOpaqueRegion is filled in later from
  // paintedLayerData->mOpaqueRegion, if necessary.

  // Allocate another entry for this layer's optimization to
  // ColorLayer/ImageLayer
  mNewChildLayers.AppendElement();
}

#ifdef MOZ_DUMP_PAINTING
static void DumpPaintedImage(nsDisplayItem* aItem, SourceSurface* aSurface) {
  nsCString string(aItem->Name());
  string.Append('-');
  string.AppendInt((uint64_t)aItem);
  fprintf_stderr(gfxUtils::sDumpPaintFile, "<script>array[\"%s\"]=\"",
                 string.BeginReading());
  gfxUtils::DumpAsDataURI(aSurface, gfxUtils::sDumpPaintFile);
  fprintf_stderr(gfxUtils::sDumpPaintFile, "\";</script>\n");
}
#endif

static void PaintInactiveLayer(nsDisplayListBuilder* aBuilder,
                               LayerManager* aManager, nsDisplayItem* aItem,
                               gfxContext* aContext, gfxContext* aCtx) {
  // This item has an inactive layer. Render it to a PaintedLayer
  // using a temporary BasicLayerManager.
  BasicLayerManager* basic = static_cast<BasicLayerManager*>(aManager);
  RefPtr<gfxContext> context = aContext;
#ifdef MOZ_DUMP_PAINTING
  int32_t appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem);
  nsIntRect itemVisibleRect =
      aItem->GetPaintRect().ToOutsidePixels(appUnitsPerDevPixel);

  RefPtr<DrawTarget> tempDT;
  if (gfxEnv::DumpPaint()) {
    tempDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
        itemVisibleRect.Size(), SurfaceFormat::B8G8R8A8);
    if (tempDT) {
      context = gfxContext::CreateOrNull(tempDT);
      if (!context) {
        // Leave this as crash, it's in the debugging code, we want to know
        gfxDevCrash(LogReason::InvalidContext)
            << "PaintInactive context problem " << gfx::hexa(tempDT);
        return;
      }
      context->SetMatrix(
          Matrix::Translation(-itemVisibleRect.x, -itemVisibleRect.y));
    }
  }
#endif
  basic->BeginTransaction();
  basic->SetTarget(context);

  if (aItem->GetType() == DisplayItemType::TYPE_MASK) {
    static_cast<nsDisplayMasksAndClipPaths*>(aItem)->PaintAsLayer(aBuilder,
                                                                  aCtx, basic);
    if (basic->InTransaction()) {
      basic->AbortTransaction();
    }
  } else if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
    static_cast<nsDisplayFilters*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic);
    if (basic->InTransaction()) {
      basic->AbortTransaction();
    }
  } else {
    basic->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder);
  }
  FrameLayerBuilder* builder = static_cast<FrameLayerBuilder*>(
      basic->GetUserData(&gLayerManagerLayerBuilder));
  if (builder) {
    builder->DidEndTransaction();
  }

  basic->SetTarget(nullptr);

#ifdef MOZ_DUMP_PAINTING
  if (gfxEnv::DumpPaint() && tempDT) {
    RefPtr<SourceSurface> surface = tempDT->Snapshot();
    DumpPaintedImage(aItem, surface);

    DrawTarget* drawTarget = aContext->GetDrawTarget();
    Rect rect(itemVisibleRect.x, itemVisibleRect.y, itemVisibleRect.width,
              itemVisibleRect.height);
    drawTarget->DrawSurface(surface, rect, Rect(Point(0, 0), rect.Size()));

    aItem->SetPainted();
  }
#endif
}

nsRect ContainerState::GetDisplayPortForAnimatedGeometryRoot(
    AnimatedGeometryRoot* aAnimatedGeometryRoot) {
  if (mLastDisplayPortAGR == aAnimatedGeometryRoot) {
    return mLastDisplayPortRect;
  }

  mLastDisplayPortAGR = aAnimatedGeometryRoot;

  nsIScrollableFrame* sf =
      nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
  if (sf == nullptr ||
      nsLayoutUtils::UsesAsyncScrolling(*aAnimatedGeometryRoot)) {
    mLastDisplayPortRect = nsRect();
    return mLastDisplayPortRect;
  }

  bool usingDisplayport = nsLayoutUtils::GetDisplayPort(
      (*aAnimatedGeometryRoot)->GetContent(), &mLastDisplayPortRect,
      RelativeTo::ScrollFrame);
  if (!usingDisplayport) {
    // No async scrolling, so all that matters is that the layer contents
    // cover the scrollport.
    mLastDisplayPortRect = sf->GetScrollPortRect();
  }
  nsIFrame* scrollFrame = do_QueryFrame(sf);
  mLastDisplayPortRect +=
      scrollFrame->GetOffsetToCrossDoc(mContainerReferenceFrame);
  return mLastDisplayPortRect;
}

nsIntRegion ContainerState::ComputeOpaqueRect(
    nsDisplayItem* aItem, AnimatedGeometryRoot* aAnimatedGeometryRoot,
    const ActiveScrolledRoot* aASR, const DisplayItemClip& aClip,
    nsDisplayList* aList, bool* aHideAllLayersBelow,
    bool* aOpaqueForAnimatedGeometryRootParent) {
  bool snapOpaque;
  nsRegion opaque = aItem->GetOpaqueRegion(mBuilder, &snapOpaque);
  if (opaque.IsEmpty()) {
    return nsIntRegion();
  }

  nsIntRegion opaquePixels;
  nsRegion opaqueClipped;
  for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) {
    opaqueClipped.Or(opaqueClipped,
                     aClip.ApproximateIntersectInward(iter.Get()));
  }
  if (aAnimatedGeometryRoot == mContainerAnimatedGeometryRoot &&
      aASR == mContainerASR && opaqueClipped.Contains(mContainerBounds)) {
    *aHideAllLayersBelow = true;
    aList->SetIsOpaque();
  }
  // Add opaque areas to the "exclude glass" region. Only do this when our
  // container layer is going to be the rootmost layer, otherwise transforms
  // etc will mess us up (and opaque contributions from other containers are
  // not needed).
  if (!nsLayoutUtils::GetCrossDocParentFrame(mContainerFrame)) {
    mBuilder->AddWindowOpaqueRegion(opaqueClipped);
  }
  opaquePixels = ScaleRegionToInsidePixels(opaqueClipped, snapOpaque);

  if (IsInInactiveLayer()) {
    return opaquePixels;
  }

  const nsRect& displayport =
      GetDisplayPortForAnimatedGeometryRoot(aAnimatedGeometryRoot);
  if (!displayport.IsEmpty() &&
      opaquePixels.Contains(ScaleRegionToNearestPixels(displayport))) {
    *aOpaqueForAnimatedGeometryRootParent = true;
  }
  return opaquePixels;
}

Maybe<size_t> ContainerState::SetupMaskLayerForScrolledClip(
    Layer* aLayer, const DisplayItemClip& aClip) {
  if (aClip.GetRoundedRectCount() > 0) {
    Maybe<size_t> maskLayerIndex = Some(aLayer->GetAncestorMaskLayerCount());
    if (RefPtr<Layer> maskLayer =
            CreateMaskLayer(aLayer, aClip, maskLayerIndex)) {
      aLayer->AddAncestorMaskLayer(maskLayer);
      return maskLayerIndex;
    }
    // Fall through to |return Nothing()|.
  }
  return Nothing();
}

static const ActiveScrolledRoot* GetASRForPerspective(
    const ActiveScrolledRoot* aASR, nsIFrame* aPerspectiveFrame) {
  for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
    nsIFrame* scrolledFrame = asr->mScrollableFrame->GetScrolledFrame();
    if (nsLayoutUtils::IsAncestorFrameCrossDoc(scrolledFrame,
                                               aPerspectiveFrame)) {
      return asr;
    }
  }
  return nullptr;
}

static CSSMaskLayerUserData* GetCSSMaskLayerUserData(Layer* aMaskLayer) {
  if (!aMaskLayer) {
    return nullptr;
  }

  return static_cast<CSSMaskLayerUserData*>(
      aMaskLayer->GetUserData(&gCSSMaskLayerUserData));
}

static void SetCSSMaskLayerUserData(Layer* aMaskLayer) {
  MOZ_ASSERT(aMaskLayer);

  aMaskLayer->SetUserData(&gCSSMaskLayerUserData, new CSSMaskLayerUserData());
}

void ContainerState::SetupMaskLayerForCSSMask(
    Layer* aLayer, nsDisplayMasksAndClipPaths* aMaskItem) {
  RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor(
      MaskLayerKey(aLayer, Nothing()), GetCSSMaskLayerUserData,
      SetCSSMaskLayerUserData);
  CSSMaskLayerUserData* oldUserData = GetCSSMaskLayerUserData(maskLayer.get());
  MOZ_ASSERT(oldUserData);

  bool snap;
  nsRect bounds = aMaskItem->GetBounds(mBuilder, &snap);
  nsIntRect itemRect = ScaleToOutsidePixels(bounds, snap);

  // Setup mask layer offset.
  // We do not repaint mask for mask position change, so update base transform
  // each time is required.
  Matrix4x4 matrix;
  matrix.PreTranslate(itemRect.x, itemRect.y, 0);
  matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
  maskLayer->SetBaseTransform(matrix);

  nsPoint maskLayerOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft();

  CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect,
                                   maskLayerOffset);
  nsRect dirtyRect;
  if (!aMaskItem->IsInvalid(dirtyRect) && *oldUserData == newUserData) {
    aLayer->SetMaskLayer(maskLayer);
    return;
  }

  int32_t maxSize = mManager->GetMaxTextureSize();
  IntSize surfaceSize(std::min(itemRect.width, maxSize),
                      std::min(itemRect.height, maxSize));

  if (surfaceSize.IsEmpty()) {
    // Return early if we know that the size of this mask surface is empty.
    return;
  }

  MaskImageData imageData(surfaceSize, mManager);
  RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
  if (!dt || !dt->IsValid()) {
    NS_WARNING("Could not create DrawTarget for mask layer.");
    return;
  }

  RefPtr<gfxContext> maskCtx = gfxContext::CreateOrNull(dt);
  maskCtx->SetMatrix(Matrix::Translation(-itemRect.TopLeft()));
  maskCtx->Multiply(
      gfxMatrix::Scaling(mParameters.mXScale, mParameters.mYScale));

  bool isPaintFinished = aMaskItem->PaintMask(mBuilder, maskCtx);

  RefPtr<ImageContainer> imgContainer =
      imageData.CreateImageAndImageContainer();
  if (!imgContainer) {
    return;
  }
  maskLayer->SetContainer(imgContainer);

  if (isPaintFinished) {
    *oldUserData = std::move(newUserData);
  }
  aLayer->SetMaskLayer(maskLayer);
}

static bool IsScrollThumbLayer(nsDisplayItem* aItem) {
  return aItem->GetType() == DisplayItemType::TYPE_OWN_LAYER &&
         static_cast<nsDisplayOwnLayer*>(aItem)->IsScrollThumbLayer();
}

template <typename ClearFn, typename SelectFn>
static void ProcessDisplayItemMarker(DisplayItemEntryType aMarker,
                                     ClearFn ClearLayerSelectionIfNeeded,
                                     SelectFn SelectLayerIfNeeded) {
  switch (aMarker) {
    case DisplayItemEntryType::PUSH_TRANSFORM:
    case DisplayItemEntryType::PUSH_OPACITY:
      SelectLayerIfNeeded();
      break;
    case DisplayItemEntryType::POP_TRANSFORM:
    case DisplayItemEntryType::POP_OPACITY:
      ClearLayerSelectionIfNeeded();
      break;
    default:
      break;
  }
}
/*
 * 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 PaintedLayers where an item
 * has moved from one PaintedLayer to another. Also,
 * aState->mInvalidPaintedContent is invalidated in every PaintedLayer.
 * We set the clip rect for items that generated their own layer, and
 * create a mask layer to do any rounded rect clipping.
 * (PaintedLayers don't need a clip rect on the layer, we clip the items
 * individually when we draw them.)
 * We set the visible rect for all layers, although the actual setting
 * of visible rects for some PaintedLayers is deferred until the calling
 * of ContainerState::Finish.
 */
void ContainerState::ProcessDisplayItems(nsDisplayList* aList) {
  AUTO_PROFILER_LABEL("ContainerState::ProcessDisplayItems", GRAPHICS);

  nsPoint topLeft(0, 0);

  int32_t maxLayers = gfxPrefs::MaxActiveLayers();
  int layerCount = 0;

  if (!mManager->IsWidgetLayerManager()) {
    mPaintedLayerDataTree.InitializeForInactiveLayer(
        mContainerAnimatedGeometryRoot);
  }

  AnimatedGeometryRoot* lastAnimatedGeometryRoot = nullptr;
  nsPoint lastTopLeft;

  // Tracks the PaintedLayerData that the item will be accumulated in, if it is
  // non-null.
  PaintedLayerData* selectedLayer = nullptr;
  AutoTArray<size_t, 2> opacityIndices;

  // AGR and ASR for the container item that was flattened.
  AnimatedGeometryRoot* containerAGR = nullptr;
  const ActiveScrolledRoot* containerASR = nullptr;
  nsIFrame* containerReferenceFrame = nullptr;
  RefPtr<TransformClipNode> transformNode = nullptr;

  const auto InTransform = [&]() { return transformNode; };

  const auto InOpacity = [&]() {
    return selectedLayer && opacityIndices.Length() > 0;
  };

  FLBDisplayItemIterator iter(mBuilder, aList, this);
  while (iter.HasNext()) {
    DisplayItemEntry e = iter.GetNextEntry();
    DisplayItemEntryType marker = e.mType;
    nsDisplayItem* item = e.mItem;
    MOZ_ASSERT(item);
    DisplayItemType itemType = item->GetType();

    if (itemType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
      // Override the marker for nsDisplayCompositorHitTestInfo items.
      marker = DisplayItemEntryType::HIT_TEST_INFO;
    }

    const bool inEffect = InTransform() || InOpacity();

    NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item),
                 "items in a container layer should all have the same app "
                 "units per dev pixel");

    if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) {
      aList->SetNeedsTransparentSurface();
    }

    if (mParameters.mForEventsAndPluginsOnly &&
        (marker != DisplayItemEntryType::HIT_TEST_INFO &&
         itemType != DisplayItemType::TYPE_PLUGIN)) {
      // Only process hit test info items or plugin items.
      continue;
    }

    LayerState layerState = LAYER_NONE;
    if (marker == DisplayItemEntryType::ITEM) {
      layerState = item->GetLayerState(mBuilder, mManager, mParameters);

      if (layerState == LAYER_INACTIVE && nsDisplayItem::ForceActiveLayers()) {
        layerState = LAYER_ACTIVE;
      }
    }

    AnimatedGeometryRoot* itemAGR = nullptr;
    const ActiveScrolledRoot* itemASR = nullptr;
    const DisplayItemClipChain* layerClipChain = nullptr;
    const DisplayItemClipChain* itemClipChain = nullptr;
    const DisplayItemClip* itemClipPtr = nullptr;

    bool snap = false;
    nsRect itemContent;

    if (marker == DisplayItemEntryType::HIT_TEST_INFO) {
      const auto& hitTestInfo =
          static_cast<nsDisplayHitTestInfoItem*>(item)->GetHitTestInfo();

      // Override the layer selection hints for items that have hit test
      // information. This is needed because container items may have different
      // clipping, AGR, or ASR than the child items in them.
      itemAGR = hitTestInfo.mAGR;
      itemASR = hitTestInfo.mASR;
      itemClipChain = hitTestInfo.mClipChain;
      itemClipPtr = hitTestInfo.mClip;
      itemContent = hitTestInfo.mArea;
    } else {
      itemAGR = item->GetAnimatedGeometryRoot();
      itemASR = item->GetActiveScrolledRoot();
      itemClipChain = item->GetClipChain();
      itemClipPtr = &item->GetClip();
      itemContent = item->GetBounds(mBuilder, &snap);
    }

    if (mManager->IsWidgetLayerManager() && !inEffect) {
      if (itemClipChain && itemClipChain->mASR == itemASR &&
          itemType != DisplayItemType::TYPE_STICKY_POSITION) {
        layerClipChain = itemClipChain->mParent;
      } else {
        layerClipChain = itemClipChain;
      }
    } else {
      // Inside a flattened effect or inactive layer, use container AGR and ASR.
      itemAGR = inEffect ? containerAGR : mContainerAnimatedGeometryRoot;
      itemASR = inEffect ? containerASR : mContainerASR;

      if (marker == DisplayItemEntryType::HIT_TEST_INFO) {
        // Items with hit test info are processed twice, once with HIT_TEST_INFO
        // marker and then with ITEM marker.
        // With HIT_TEST_INFO markers, fuse the clip chain of hit test struct,
        // and with ITEM markers, fuse the clip chain of the actual item.
        itemClipChain = mBuilder->FuseClipChainUpTo(itemClipChain, itemASR);
      } else if (!IsEffectEndMarker(marker)) {
        // No need to fuse clip chain for effect end markers, since it was
        // already done for effect start markers.
        item->FuseClipChainUpTo(mBuilder, itemASR);
        itemClipChain = item->GetClipChain();
      }

      itemClipPtr = itemClipChain ? &itemClipChain->mClip : nullptr;
    }

    const DisplayItemClip& itemClip =
        itemClipPtr ? *itemClipPtr : DisplayItemClip::NoClip();

    if (inEffect && marker == DisplayItemEntryType::HIT_TEST_INFO) {
      // Fast-path for hit test items inside flattened inactive layers.
      MOZ_ASSERT(selectedLayer);
      selectedLayer->AccumulateHitTestItem(this, item, itemClip, transformNode);
      continue;
    }

    if (inEffect && marker == DisplayItemEntryType::ITEM) {
      // Fast-path for items inside flattened inactive layers. This works
      // because the layer state of the item cannot be active, otherwise the
      // parent item would not have been flattened.
      MOZ_ASSERT(selectedLayer);
      selectedLayer->Accumulate(this, item, nsIntRect(), nsRect(), itemClip,
                                layerState, aList, marker, opacityIndices,
                                transformNode);
      continue;
    }

    // Items outside of flattened effects and non-item markers inside flattened
    // effects are processed here.
    MOZ_ASSERT(!inEffect || (marker != DisplayItemEntryType::ITEM));

    if (itemAGR == lastAnimatedGeometryRoot) {
      topLeft = lastTopLeft;
    } else {
      lastTopLeft = topLeft =
          (*itemAGR)->GetOffsetToCrossDoc(mContainerReferenceFrame);
      lastAnimatedGeometryRoot = itemAGR;
    }

    const ActiveScrolledRoot* scrollMetadataASR =
        layerClipChain
            ? ActiveScrolledRoot::PickDescendant(itemASR, layerClipChain->mASR)
            : itemASR;

    const bool prerenderedTransform =
        itemType == DisplayItemType::TYPE_TRANSFORM &&
        static_cast<nsDisplayTransform*>(item)->MayBeAnimated(mBuilder);

    nsIntRect itemDrawRect = ScaleToOutsidePixels(itemContent, snap);
    ParentLayerIntRect clipRect;
    if (itemClip.HasClip()) {
      const nsRect& itemClipRect = itemClip.GetClipRect();
      itemContent.IntersectRect(itemContent, itemClipRect);
      clipRect = ViewAs<ParentLayerPixel>(ScaleToNearestPixels(itemClipRect));

      if (!prerenderedTransform && !IsScrollThumbLayer(item)) {
        itemDrawRect.IntersectRect(itemDrawRect, clipRect.ToUnknownRect());
      }

      clipRect.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
    }

    if (marker == DisplayItemEntryType::POP_TRANSFORM) {
      MOZ_ASSERT(transformNode);
      transformNode = transformNode->Parent();
    }

    if (transformNode) {
      // If we are within transform, transform itemContent and itemDrawRect.
      MOZ_ASSERT(transformNode);

      itemContent =
          transformNode->TransformRect(itemContent, mAppUnitsPerDevPixel);

      itemDrawRect = transformNode->TransformRect(itemDrawRect);
    }

#ifdef DEBUG
    nsRect bounds = itemContent;

    if (marker == DisplayItemEntryType::HIT_TEST_INFO || inEffect) {
      bounds.SetEmpty();
    }

    if (!bounds.IsEmpty() && itemASR != mContainerASR) {
      if (Maybe<nsRect> clip =
              item->GetClipWithRespectToASR(mBuilder, mContainerASR)) {
        bounds = clip.ref();
      }
    }

    ((nsRect&)mAccumulatedChildBounds)
        .UnionRect(mAccumulatedChildBounds, bounds);
#endif

    nsIntRect itemVisibleRect = itemDrawRect;

    // We intersect the building rect with the clipped item bounds to get a
    // tighter visible rect.
    if (!prerenderedTransform) {
      nsRect itemBuildingRect = item->GetBuildingRect();

      if (transformNode) {
        itemBuildingRect = transformNode->TransformRect(itemBuildingRect,
                                                        mAppUnitsPerDevPixel);
      }

      itemVisibleRect = itemVisibleRect.Intersect(
          ScaleToOutsidePixels(itemBuildingRect, false));
    }

    const bool forceInactive = maxLayers != -1 && layerCount >= maxLayers;

    // Assign the item to a layer
    bool treatInactiveItemAsActive =
        (layerState == LAYER_INACTIVE &&
         mLayerBuilder->GetContainingPaintedLayerData());
    if (layerState == LAYER_ACTIVE_FORCE || treatInactiveItemAsActive ||
        (!forceInactive &&
         (layerState == LAYER_ACTIVE_EMPTY || layerState == LAYER_ACTIVE))) {
      layerCount++;

      // Currently we do not support flattening effects within nested inactive
      // layer trees.
      MOZ_ASSERT(selectedLayer == nullptr);
      MOZ_ASSERT(marker == DisplayItemEntryType::ITEM);

      // LAYER_ACTIVE_EMPTY means the layer is created just for its metadata.
      // We should never see an empty layer with any visible content!
      NS_ASSERTION(
          layerState != LAYER_ACTIVE_EMPTY || itemVisibleRect.IsEmpty(),
          "State is LAYER_ACTIVE_EMPTY but visible rect is not.");

      // As long as the new layer isn't going to be a PaintedLayer,
      // InvalidateForLayerChange doesn't need the new layer pointer.
      // We also need to check the old data now, because BuildLayer
      // can overwrite it.
      DisplayItemData* oldData = mLayerBuilder->GetOldLayerForFrame(
          item->Frame(), item->GetPerFrameKey());
      InvalidateForLayerChange(item, nullptr, oldData);

      // If the item would have its own layer but is invisible, just hide it.
      // Note that items without their own layers can't be skipped this
      // way, since their PaintedLayer may decide it wants to draw them
      // into its buffer even if they're currently covered.
      if (itemVisibleRect.IsEmpty() &&
          !item->ShouldBuildLayerEvenIfInvisible(mBuilder)) {
        continue;
      }

      // 3D-transformed layers don't necessarily draw in the order in which
      // they're added to their parent container layer.
      bool mayDrawOutOfOrder =
          itemType == DisplayItemType::TYPE_TRANSFORM &&
          (item->Frame()->Combines3DTransformWithAncestors() ||
           item->Frame()->Extend3DContext());

      // Let mPaintedLayerDataTree know about this item, so that
      // FindPaintedLayerFor and FindOpaqueBackgroundColor are aware of this
      // item, even though it's not in any PaintedLayerDataStack.
      // Ideally we'd only need the "else" case here and have
      // mPaintedLayerDataTree figure out the right clip from the animated
      // geometry root that we give it, but it can't easily figure about
      // overflow:hidden clips on ancestors just by looking at the frame.
      // So we'll do a little hand holding and pass the clip instead of the
      // visible rect for the two important cases.
      nscolor uniformColor = NS_RGBA(0, 0, 0, 0);
      nscolor* uniformColorPtr =
          (mayDrawOutOfOrder || IsInInactiveLayer()) ? nullptr : &uniformColor;
      nsIntRect clipRectUntyped;
      nsIntRect* clipPtr = nullptr;
      if (itemClip.HasClip()) {
        clipRectUntyped = clipRect.ToUnknownRect();
        clipPtr = &clipRectUntyped;
      }

      bool hasScrolledClip =
          layerClipChain && layerClipChain->mClip.HasClip() &&
          (!ActiveScrolledRoot::IsAncestor(layerClipChain->mASR, itemASR) ||
           itemType == DisplayItemType::TYPE_STICKY_POSITION);

      if (hasScrolledClip) {
        // If the clip is scrolled, reserve just the area of the clip for
        // layerization, so that elements outside the clip can still merge
        // into the same layer.
        const ActiveScrolledRoot* clipASR = layerClipChain->mASR;
        AnimatedGeometryRoot* clipAGR =
            mBuilder->AnimatedGeometryRootForASR(clipASR);
        nsIntRect scrolledClipRect =
            ScaleToNearestPixels(layerClipChain->mClip.GetClipRect()) +
            mParameters.mOffset;
        mPaintedLayerDataTree.AddingOwnLayer(clipAGR, &scrolledClipRect,
                                             uniformColorPtr);
      } else if (item->ShouldFixToViewport(mBuilder) && itemClip.HasClip() &&
                 item->AnimatedGeometryRootForScrollMetadata() != itemAGR &&
                 !nsLayoutUtils::UsesAsyncScrolling(item->Frame())) {
        // This is basically the same as the case above, but for the non-APZ
        // case. At the moment, when APZ is off, there is only the root ASR
        // (because scroll frames without display ports don't create ASRs) and
        // the whole clip chain is always just one fused clip.
        // Bug 1336516 aims to change that and to remove this workaround.
        AnimatedGeometryRoot* clipAGR =
            item->AnimatedGeometryRootForScrollMetadata();
        nsIntRect scrolledClipRect =
            ScaleToNearestPixels(itemClip.GetClipRect()) + mParameters.mOffset;
        mPaintedLayerDataTree.AddingOwnLayer(clipAGR, &scrolledClipRect,
                                             uniformColorPtr);
      } else if (IsScrollThumbLayer(item) && mManager->IsWidgetLayerManager()) {
        // For scrollbar thumbs, the clip we care about is the clip added by the
        // slider frame.
        mPaintedLayerDataTree.AddingOwnLayer(itemAGR->mParentAGR, clipPtr,
                                             uniformColorPtr);
      } else if (prerenderedTransform && mManager->IsWidgetLayerManager()) {
        if (itemAGR->mParentAGR) {
          mPaintedLayerDataTree.AddingOwnLayer(itemAGR->mParentAGR, clipPtr,
                                               uniformColorPtr);
        } else {
          mPaintedLayerDataTree.AddingOwnLayer(itemAGR, nullptr,
                                               uniformColorPtr);
        }
      } else {
        // Using itemVisibleRect here isn't perfect. itemVisibleRect can be
        // larger or smaller than the potential bounds of item's contents in
        // itemAGR: It's too large if there's a clipped display
        // port somewhere among item's contents (see bug 1147673), and it can
        // be too small if the contents can move, because it only looks at the
        // contents' current bounds and doesn't anticipate any animations.
        // Time will tell whether this is good enough, or whether we need to do
        // something more sophisticated here.
        mPaintedLayerDataTree.AddingOwnLayer(itemAGR, &itemVisibleRect,
                                             uniformColorPtr);
      }

      ContainerLayerParameters params = mParameters;
      params.mBackgroundColor = uniformColor;
      params.mLayerCreationHint = GetLayerCreationHint(itemAGR);
      params.mScrollMetadataASR =
          ActiveScrolledRoot::IsAncestor(scrollMetadataASR,
                                         mContainerScrollMetadataASR)
              ? mContainerScrollMetadataASR
              : scrollMetadataASR;
      params.mCompositorASR =
          params.mScrollMetadataASR != mContainerScrollMetadataASR
              ? params.mScrollMetadataASR
              : mContainerCompositorASR;
      if (itemType == DisplayItemType::TYPE_FIXED_POSITION) {
        params.mCompositorASR = itemASR;
      }

      if (itemType == DisplayItemType::TYPE_PERSPECTIVE) {
        // Perspective items have a single child item, an nsDisplayTransform.
        // If the perspective item is scrolled, but the perspective-inducing
        // frame is outside the scroll frame (indicated by item->Frame()
        // being outside that scroll frame), we have to take special care to
        // make APZ scrolling work properly. APZ needs us to put the scroll
        // frame's FrameMetrics on our child transform ContainerLayer instead.
        // It's worth investigating whether this ASR adjustment can be done at
        // display item creation time.
        scrollMetadataASR = GetASRForPerspective(
            scrollMetadataASR,
            item->Frame()->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME));
        params.mScrollMetadataASR = scrollMetadataASR;
        itemASR = scrollMetadataASR;
      }

      // Just use its layer.
      // Set layerContentsVisibleRect.width/height to -1 to indicate we
      // currently don't know. If BuildContainerLayerFor gets called by
      // item->BuildLayer, this will be set to a proper rect.
      nsIntRect layerContentsVisibleRect(0, 0, -1, -1);
      params.mLayerContentsVisibleRect = &layerContentsVisibleRect;

      // If this display item wants to build inactive layers but we are treating
      // it as active because we are already inside an inactive layer tree,
      // we need to make sure that the display item's clip is reflected in
      // FrameLayerBuilder::mInactiveLayerClip (which is normally set in
      // AddPaintedDisplayItem() when entering an inactive layer tree).
      // We intersect the display item's clip into any existing inactive layer
      // clip.
      const DisplayItemClip* originalInactiveClip = nullptr;
      DisplayItemClip combinedInactiveClip;
      if (treatInactiveItemAsActive) {
        originalInactiveClip = mLayerBuilder->GetInactiveLayerClip();
        if (originalInactiveClip) {
          combinedInactiveClip = *originalInactiveClip;
        }
        DisplayItemClip nestedClip = item->GetClip();
        if (nestedClip.HasClip()) {
          nsRect nestedClipRect = nestedClip.NonRoundedIntersection();

          // Transform the nested clip to be relative to the same reference
          // frame as the existing mInactiveLayerClip, so that we can intersect
          // them below.
          nestedClipRect = nsLayoutUtils::TransformFrameRectToAncestor(
              item->ReferenceFrame(), nestedClipRect,
              mLayerBuilder->GetContainingPaintedLayerData()->mReferenceFrame);

          nestedClip.SetTo(nestedClipRect);
          combinedInactiveClip.IntersectWith(nestedClip);
          mLayerBuilder->SetInactiveLayerClip(&combinedInactiveClip);
        }
      }

      RefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager, params);

      // If above we combined a nested clip into mInactiveLayerClip, restore
      // the original inactive layer clip here.
      if (treatInactiveItemAsActive) {
        mLayerBuilder->SetInactiveLayerClip(originalInactiveClip);
      }

      if (!ownLayer) {
        continue;
      }

      NS_ASSERTION(!ownLayer->AsPaintedLayer(),
                   "Should never have created a dedicated Painted layer!");

      if (item->BackfaceIsHidden()) {
        ownLayer->SetContentFlags(ownLayer->GetContentFlags() |
                                  Layer::CONTENT_BACKFACE_HIDDEN);
      } else {
        ownLayer->SetContentFlags(ownLayer->GetContentFlags() &
                                  ~Layer::CONTENT_BACKFACE_HIDDEN);
      }

      nsRect invalid;
      if (item->IsInvalid(invalid)) {
        ownLayer->SetInvalidRectToVisibleRegion();
      }

      // If it's not a ContainerLayer, we need to apply the scale transform
      // ourselves.
      if (!ownLayer->AsContainerLayer()) {
        ownLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale);
      }

      // Update that layer's clip and visible rects.
      NS_ASSERTION(ownLayer->Manager() == mManager, "Wrong manager");
      NS_ASSERTION(!ownLayer->HasUserData(&gLayerManagerUserData),
                   "We shouldn't have a FrameLayerBuilder-managed layer here!");
      NS_ASSERTION(itemClip.HasClip() || itemClip.GetRoundedRectCount() == 0,
                   "If we have rounded rects, we must have a clip rect");

      // It has its own layer. Update that layer's clip and visible rects.
      ownLayer->SetClipRect(Nothing());
      ownLayer->SetScrolledClip(Nothing());
      ownLayer->SetAncestorMaskLayers({});
      if (itemClip.HasClip()) {
        ownLayer->SetClipRect(Some(clipRect));

        // rounded rectangle clipping using mask layers
        // (must be done after visible rect is set on layer)
        if (itemClip.GetRoundedRectCount() > 0) {
          SetupMaskLayer(ownLayer, itemClip);
        }
      }

      if (hasScrolledClip) {
        const DisplayItemClip& scrolledClip = layerClipChain->mClip;
        LayerClip scrolledLayerClip;
        scrolledLayerClip.SetClipRect(ViewAs<ParentLayerPixel>(
            ScaleToNearestPixels(scrolledClip.GetClipRect()) +
            mParameters.mOffset));
        if (scrolledClip.GetRoundedRectCount() > 0) {
          scrolledLayerClip.SetMaskLayerIndex(
              SetupMaskLayerForScrolledClip(ownLayer.get(), scrolledClip));
        }
        ownLayer->SetScrolledClip(Some(scrolledLayerClip));
      }

      if (item->GetType() == DisplayItemType::TYPE_MASK) {
        MOZ_ASSERT(itemClip.GetRoundedRectCount() == 0);

        nsDisplayMasksAndClipPaths* maskItem =
            static_cast<nsDisplayMasksAndClipPaths*>(item);
        SetupMaskLayerForCSSMask(ownLayer, maskItem);

        if (iter.PeekNext() && iter.PeekNext()->GetType() ==
                                   DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
          // Since we do build a layer for mask, there is no need for this
          // scroll info layer anymore.
          iter.GetNext();
        }
      }

      // Convert the visible rect to a region and give the item
      // a chance to try restrict it further.
      nsIntRegion itemVisibleRegion = itemVisibleRect;
      nsRegion tightBounds = item->GetTightBounds(mBuilder, &snap);
      if (!tightBounds.IsEmpty()) {
        itemVisibleRegion.AndWith(ScaleToOutsidePixels(tightBounds, snap));
      }

      ContainerLayer* oldContainer = ownLayer->GetParent();
      if (oldContainer && oldContainer != mContainerLayer) {
        oldContainer->RemoveChild(ownLayer);
      }
      NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0,
                   "Layer already in list???");

      NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
      newLayerEntry->mLayer = ownLayer;
      newLayerEntry->mAnimatedGeometryRoot = itemAGR;
      newLayerEntry->mASR = itemASR;
      newLayerEntry->mScrollMetadataASR = scrollMetadataASR;
      newLayerEntry->mClipChain = layerClipChain;
      newLayerEntry->mLayerState = layerState;
      if (itemType == DisplayItemType::TYPE_FIXED_POSITION) {
        newLayerEntry->mIsFixedToRootScrollFrame =
            item->Frame()->StyleDisplay()->mPosition ==
                NS_STYLE_POSITION_FIXED &&
            nsLayoutUtils::IsReallyFixedPos(item->Frame());
      }

      // Don't attempt to flatten compnent alpha layers that are within
      // a forced active layer, or an active transform;
      if (itemType == DisplayItemType::TYPE_TRANSFORM ||
          layerState == LAYER_ACTIVE_FORCE) {
        newLayerEntry->mPropagateComponentAlphaFlattening = false;
      }

      float contentXScale = 1.0f;
      float contentYScale = 1.0f;
      if (ContainerLayer* ownContainer = ownLayer->AsContainerLayer()) {
        contentXScale = 1 / ownContainer->GetPreXScale();
        contentYScale = 1 / ownContainer->GetPreYScale();
      }
      // nsDisplayTransform::BuildLayer must set layerContentsVisibleRect.
      // We rely on this to ensure 3D transforms compute a reasonable
      // layer visible region.
      NS_ASSERTION(itemType != DisplayItemType::TYPE_TRANSFORM ||
                       layerContentsVisibleRect.width >= 0,
                   "Transform items must set layerContentsVisibleRect!");
      if (mLayerBuilder->IsBuildingRetainedLayers()) {
        newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect;
        if (itemType == DisplayItemType::TYPE_PERSPECTIVE ||
            (itemType == DisplayItemType::TYPE_TRANSFORM &&
             (item->Frame()->Extend3DContext() ||
              item->Frame()->Combines3DTransformWithAncestors() ||
              item->Frame()->HasPerspective()))) {
          // Give untransformed visible region as outer visible region
          // to avoid failure caused by singular transforms.
          newLayerEntry->mUntransformedVisibleRegion = true;
          newLayerEntry->mVisibleRegion =
              item->GetBuildingRectForChildren().ScaleToOutsidePixels(
                  contentXScale, contentYScale, mAppUnitsPerDevPixel);
        } else {
          newLayerEntry->mVisibleRegion = itemVisibleRegion;
        }
        newLayerEntry->mOpaqueRegion = ComputeOpaqueRect(
            item, itemAGR, itemASR, itemClip, aList,
            &newLayerEntry->mHideAllLayersBelow,
            &newLayerEntry->mOpaqueForAnimatedGeometryRootParent);
      } else {
        bool useChildrenVisible = itemType == DisplayItemType::TYPE_TRANSFORM &&
                                  (item->Frame()->IsPreserve3DLeaf() ||
                                   item->Frame()->HasPerspective());
        const nsIntRegion& visible =
            useChildrenVisible
                ? item->GetBuildingRectForChildren().ScaleToOutsidePixels(
                      contentXScale, contentYScale, mAppUnitsPerDevPixel)
                : itemVisibleRegion;

        SetOuterVisibleRegionForLayer(ownLayer, visible,
                                      layerContentsVisibleRect.width >= 0
                                          ? &layerContentsVisibleRect
                                          : nullptr,
                                      useChildrenVisible);
      }
      if (itemType == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
        nsDisplayScrollInfoLayer* scrollItem =
            static_cast<nsDisplayScrollInfoLayer*>(item);
        newLayerEntry->mOpaqueForAnimatedGeometryRootParent = false;
        newLayerEntry->mBaseScrollMetadata =
            scrollItem->ComputeScrollMetadata(ownLayer->Manager(), mParameters);
      } else if ((itemType == DisplayItemType::TYPE_SUBDOCUMENT ||
                  itemType == DisplayItemType::TYPE_ZOOM ||
                  itemType == DisplayItemType::TYPE_RESOLUTION) &&
                 gfxPrefs::LayoutUseContainersForRootFrames()) {
        newLayerEntry->mBaseScrollMetadata =
            static_cast<nsDisplaySubDocument*>(item)->ComputeScrollMetadata(
                ownLayer->Manager(), mParameters);
      }

      /**
       * No need to allocate geometry for items that aren't
       * part of a PaintedLayer.
       */
      if (ownLayer->Manager() == mLayerBuilder->GetRetainingLayerManager()) {
        oldData = mLayerBuilder->GetOldLayerForFrame(item->Frame(),
                                                     item->GetPerFrameKey());
        mLayerBuilder->StoreDataForFrame(item, ownLayer, layerState, oldData);
      }
    } else {
      const bool backfaceHidden = item->In3DContextAndBackfaceIsHidden();

      // When container item hit test info is processed, we need to use the same
      // reference frame as the container children.
      const nsIFrame* referenceFrame = item == mContainerItem
                                           ? mContainerReferenceFrame
                                           : item->ReferenceFrame();

      MOZ_ASSERT(item != mContainerItem ||
                 marker == DisplayItemEntryType::HIT_TEST_INFO);

      PaintedLayerData* paintedLayerData = selectedLayer;

      if (!paintedLayerData) {
        paintedLayerData = mPaintedLayerDataTree.FindPaintedLayerFor(
            itemAGR, itemASR, layerClipChain, itemVisibleRect, backfaceHidden,
            [&](PaintedLayerData* aData) {
              NewPaintedLayerData(aData, itemAGR, itemASR, layerClipChain,
                                  scrollMetadataASR, topLeft, referenceFrame,
                                  backfaceHidden);
            });
      }
      MOZ_ASSERT(paintedLayerData);

      if (marker == DisplayItemEntryType::HIT_TEST_INFO) {
        MOZ_ASSERT(!transformNode);
        paintedLayerData->AccumulateHitTestItem(this, item, itemClip, nullptr);
      } else {
        paintedLayerData->Accumulate(this, item, itemVisibleRect, itemContent,
                                     itemClip, layerState, aList, marker,
                                     opacityIndices, transformNode);

        if (!paintedLayerData->mLayer) {
          // Try to recycle the old layer of this display item.
          RefPtr<PaintedLayer> layer = AttemptToRecyclePaintedLayer(
              itemAGR, item, topLeft,
              inEffect ? containerReferenceFrame : referenceFrame);
          if (layer) {
            paintedLayerData->mLayer = layer;

            PaintedDisplayItemLayerUserData* userData =
                GetPaintedDisplayItemLayerUserData(layer);
            paintedLayerData->mAssignedDisplayItems.reserve(
                userData->mLastItemCount);

            NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
                         "Layer already in list???");
            mNewChildLayers[paintedLayerData->mNewChildLayersIndex].mLayer =
                layer.forget();
          }
        }
      }

      const auto ClearLayerSelectionIfNeeded = [&]() {
        if (!InOpacity() && !InTransform()) {
          selectedLayer = nullptr;
          containerAGR = nullptr;
          containerASR = nullptr;
          containerReferenceFrame = nullptr;
        }
      };

      const auto SelectLayerIfNeeded = [&]() {
        if (!selectedLayer) {
          selectedLayer = paintedLayerData;
          containerAGR = itemAGR;
          containerASR = itemASR;
          containerReferenceFrame = const_cast<nsIFrame*>(referenceFrame);
        }
      };

      if (marker == DisplayItemEntryType::PUSH_TRANSFORM) {
        nsDisplayTransform* transform = static_cast<nsDisplayTransform*>(item);

        const Matrix4x4Flagged& matrix = transform->GetTransformForRendering();

        Maybe<gfx::IntRect> clip;
        if (itemClip.HasClip()) {
          const nsRect nonRoundedClip = itemClip.NonRoundedIntersection();
          clip.emplace(nonRoundedClip.ToNearestPixels(mAppUnitsPerDevPixel));
        }

        transformNode = new TransformClipNode(transformNode, matrix, clip);
      }

      ProcessDisplayItemMarker(marker, ClearLayerSelectionIfNeeded,
                               SelectLayerIfNeeded);
    }

    nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
    if (childItems && childItems->NeedsTransparentSurface()) {
      aList->SetNeedsTransparentSurface();
    }
  }

  MOZ_ASSERT(selectedLayer == nullptr);
}

void ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem,
                                              PaintedLayer* aNewLayer,
                                              DisplayItemData* aData) {
  NS_ASSERTION(aItem->GetPerFrameKey(),
               "Display items that render using Thebes must have a key");
  Layer* oldLayer = aData ? aData->mLayer.get() : nullptr;
  if (aNewLayer != oldLayer && oldLayer) {
    // The item has changed layers.
    // Invalidate the old bounds in the old layer and new bounds in the new
    // layer.
    PaintedLayer* t = oldLayer->AsPaintedLayer();
    if (t && aData->mGeometry) {
      // Note that whenever the layer's scale changes, we invalidate the whole
      // thing, so it doesn't matter whether we are using the old scale at last
      // paint or a new scale here
#ifdef MOZ_DUMP_PAINTING
      if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
        printf_stderr("Display item type %s(%p) changed layers %p to %p!\n",
                      aItem->Name(), aItem->Frame(), t, aNewLayer);
      }
#endif
      InvalidatePreTransformRect(
          t, aData->mGeometry->ComputeInvalidationRegion(), aData->mClip,
          mLayerBuilder->GetLastPaintOffset(t), aData->mTransform);
    }
    // Clear the old geometry so that invalidation thinks the item has been
    // added this paint.
    aData->mGeometry = nullptr;
  }
}

static nsRect GetInvalidationRect(nsDisplayItemGeometry* aGeometry,
                                  const DisplayItemClip& aClip,
                                  TransformClipNode* aTransform,
                                  const int32_t aA2D) {
  const nsRect& rect = aGeometry->ComputeInvalidationRegion();
  const nsRect clipped = aClip.ApplyNonRoundedIntersection(rect);

  if (aTransform) {
    return aTransform->TransformRect(clipped, aA2D);
  }

  return clipped;
}

void FrameLayerBuilder::ComputeGeometryChangeForItem(DisplayItemData* aData) {
  nsDisplayItem* item = aData->mItem;
  PaintedLayer* paintedLayer = aData->mLayer->AsPaintedLayer();
  // If aData->mOptLayer is presence, means this item has been optimized to the
  // separate layer. Thus, skip geometry change calculation.
  if (aData->mOptLayer || !item || !paintedLayer) {
    aData->EndUpdate();
    return;
  }

  // If we're a reused display item, then we can't be invalid, so no need to
  // do an in-depth comparison. If we haven't previously stored geometry
  // for this item (if it was an active layer), then we can't skip this
  // yet.
  nsAutoPtr<nsDisplayItemGeometry> geometry;
  if (aData->mReusedItem && aData->mGeometry) {
    aData->EndUpdate();
    return;
  }

  PaintedDisplayItemLayerUserData* layerData =
      static_cast<PaintedDisplayItemLayerUserData*>(
          aData->mLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
  nsPoint shift = layerData->mAnimatedGeometryRootOrigin -
                  layerData->mLastAnimatedGeometryRootOrigin;

  const DisplayItemClip& clip = item->GetClip();
  const int32_t appUnitsPerDevPixel = layerData->mAppUnitsPerDevPixel;

  // If the frame is marked as invalidated, and didn't specify a rect to
  // invalidate then we want to invalidate both the old and new bounds,
  // otherwise we only want to invalidate the changed areas. If we do get an
  // invalid rect, then we want to add this on top of the change areas.
  nsRect invalid;
  nsIntRegion invalidPixels;

  if (!aData->mGeometry) {
    // This item is being added for the first time, invalidate its entire area.
    geometry = item->AllocateGeometry(mDisplayListBuilder);

    const nsRect bounds = GetInvalidationRect(geometry, clip, aData->mTransform,
                                              appUnitsPerDevPixel);

    invalidPixels = bounds.ScaleToOutsidePixels(
        layerData->mXScale, layerData->mYScale, appUnitsPerDevPixel);
#ifdef MOZ_DUMP_PAINTING
    if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
      printf_stderr("Display item type %s(%p) added to layer %p!\n",
                    item->Name(), item->Frame(), aData->mLayer.get());
    }
#endif
  } else if (aData->mIsInvalid ||
             (item->IsInvalid(invalid) && invalid.IsEmpty())) {
    // Layout marked item/frame as needing repainting (without an explicit
    // rect), invalidate the entire old and new areas.
    geometry = item->AllocateGeometry(mDisplayListBuilder);

    nsRect oldArea =
        GetInvalidationRect(aData->mGeometry, aData->mClip,
                            aData->mOldTransform, appUnitsPerDevPixel);
    oldArea.MoveBy(shift);

    nsRect newArea = GetInvalidationRect(geometry, clip, aData->mTransform,
                                         appUnitsPerDevPixel);

    nsRegion combined;
    combined.Or(oldArea, newArea);
    invalidPixels = combined.ScaleToOutsidePixels(
        layerData->mXScale, layerData->mYScale, appUnitsPerDevPixel);
#ifdef MOZ_DUMP_PAINTING
    if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
      printf_stderr(
          "Display item type %s(%p) (in layer %p) belongs to an "
          "invalidated frame!\n",
          item->Name(), item->Frame(), aData->mLayer.get());
    }
#endif
  } else {
    // Let the display item check for geometry changes and decide what needs to
    // be repainted.
    const nsRegion& changedFrameInvalidations =
        aData->GetChangedFrameInvalidations();

    if (aData->mTransform) {
      // If this display item is inside a flattened transform the offset is
      // already included in the root transform, so there is no need to shift.
      shift = nsPoint();
    }

    aData->mGeometry->MoveBy(shift);

    nsRegion combined;
    item->ComputeInvalidationRegion(mDisplayListBuilder, aData->mGeometry,
                                    &combined);

    // Only allocate a new geometry object if something actually changed,
    // otherwise the existing one should be fine. We always reallocate for
    // inactive layers, since these types don't implement
    // ComputeInvalidateRegion (and rely on the ComputeDifferences call in
    // AddPaintedDisplayItem instead).
    if (!combined.IsEmpty() || aData->mLayerState == LAYER_INACTIVE ||
        item->NeedsGeometryUpdates()) {
      geometry = item->AllocateGeometry(mDisplayListBuilder);
    }

    aData->mClip.AddOffsetAndComputeDifference(
        shift, aData->mGeometry->ComputeInvalidationRegion(), clip,
        geometry ? geometry->ComputeInvalidationRegion()
                 : aData->mGeometry->ComputeInvalidationRegion(),
        &combined);

    // Add in any rect that the frame specified
    combined.Or(combined, invalid);
    combined.Or(combined, changedFrameInvalidations);

    // Restrict invalidation to the clipped region
    nsRegion clipRegion;
    if (clip.ComputeRegionInClips(&aData->mClip, shift, &clipRegion)) {
      combined.And(combined, clipRegion);
    }

    invalidPixels = combined.ToOutsidePixels(appUnitsPerDevPixel);

    if (aData->mTransform) {
      invalidPixels = aData->mTransform->TransformRegion(invalidPixels);
    }

    invalidPixels.ScaleRoundOut(layerData->mXScale, layerData->mYScale);

#ifdef MOZ_DUMP_PAINTING
    if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
      if (!combined.IsEmpty()) {
        printf_stderr(
            "Display item type %s(%p) (in layer %p) changed geometry!\n",
            item->Name(), item->Frame(), aData->mLayer.get());
      }
    }
#endif
  }

  if (!invalidPixels.IsEmpty()) {
    InvalidatePostTransformRegion(paintedLayer, invalidPixels,
                                  layerData->mTranslation);
  }

  aData->EndUpdate(geometry);
}

void FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData,
                                              AssignedDisplayItem& aItem,
                                              ContainerState& aContainerState,
                                              Layer* aLayer) {
  PaintedLayer* layer = aLayerData->mLayer;
  PaintedDisplayItemLayerUserData* paintedData =
      static_cast<PaintedDisplayItemLayerUserData*>(
          layer->GetUserData(&gPaintedDisplayItemLayerUserData));
  RefPtr<BasicLayerManager> tempManager;
  nsIntRect intClip;
  if (aItem.mLayerState != LAYER_NONE) {
    if (aItem.mDisplayItemData) {
      tempManager = aItem.mDisplayItemData->mInactiveManager;

      // We need to grab these before updating the DisplayItemData because it
      // will overwrite them.
      nsRegion clip;
      if (aItem.mItem->GetClip().ComputeRegionInClips(
              &aItem.mDisplayItemData->GetClip(),
              aLayerData->mAnimatedGeometryRootOffset -
                  paintedData->mLastAnimatedGeometryRootOrigin,
              &clip)) {
        intClip = clip.GetBounds().ScaleToOutsidePixels(
            paintedData->mXScale, paintedData->mYScale,
            paintedData->mAppUnitsPerDevPixel);
      }
    }
    if (!tempManager) {
      tempManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
    }
  }

  if (layer->Manager() == mRetainingManager) {
    DisplayItemData* data = aItem.mDisplayItemData;
    if (data && !data->mUsed) {
      data->BeginUpdate(layer, aItem.mLayerState, aItem.mItem, aItem.mReused,
                        aItem.mMerged);
    } else {
      if (data && data->mUsed) {
        // If the DID has already been used (by a previously merged frame,
        // which is not merged this paint) we must create a new DID for the
        // item.
        aItem.mItem->SetDisplayItemData(nullptr, nullptr);
      }
      data = StoreDataForFrame(aItem.mItem, layer, aItem.mLayerState, nullptr);
    }
    data->mInactiveManager = tempManager;
    // We optimized this PaintedLayer into a ColorLayer/ImageLayer. Store the
    // optimized layer here.
    if (aLayer != layer) {
      data->mOptLayer = aLayer;
    }

    data->mOldTransform = data->mTransform;
    data->mTransform = aItem.mTransform;
  }

  if (tempManager) {
    FLB_LOG_PAINTED_LAYER_DECISION(
        aLayerData, "Creating nested FLB for item %p\n", aItem.mItem);
    FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
    layerBuilder->Init(mDisplayListBuilder, tempManager, aLayerData, true,
                       &aItem.mItem->GetClip());

    tempManager->BeginTransaction();
    if (mRetainingManager) {
      layerBuilder->DidBeginRetainedLayerTransaction(tempManager);
    }

    UniquePtr<LayerProperties> props(
        LayerProperties::CloneFrom(tempManager->GetRoot()));
    RefPtr<Layer> tmpLayer = aItem.mItem->BuildLayer(
        mDisplayListBuilder, tempManager, ContainerLayerParameters());
    // We have no easy way of detecting if this transaction will ever actually
    // get finished. For now, I've just silenced the warning with nested
    // transactions in BasicLayers.cpp
    if (!tmpLayer) {
      tempManager->EndTransaction(nullptr, nullptr);
      tempManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
      aItem.mItem = nullptr;
      return;
    }

    bool snap;
    nsRect visibleRect = aItem.mItem->GetBuildingRect().Intersect(
        aItem.mItem->GetBounds(mDisplayListBuilder, &snap));
    nsIntRegion rgn =
        visibleRect.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel);

    // Convert the visible rect to a region and give the item
    // a chance to try restrict it further.
    nsRegion tightBounds =
        aItem.mItem->GetTightBounds(mDisplayListBuilder, &snap);
    if (!tightBounds.IsEmpty()) {
      rgn.AndWith(
          tightBounds.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel));
    }
    SetOuterVisibleRegion(tmpLayer, &rgn);

    DisplayItemData* data = nullptr;
    // If BuildLayer didn't call BuildContainerLayerFor, then our new layer
    // won't have been stored in layerBuilder. Manually add it now.
    if (mRetainingManager) {
#ifdef DEBUG_DISPLAY_ITEM_DATA
      LayerManagerData* parentLmd = static_cast<LayerManagerData*>(
          layer->Manager()->GetUserData(&gLayerManagerUserData));
      LayerManagerData* lmd = static_cast<LayerManagerData*>(
          tempManager->GetUserData(&gLayerManagerUserData));
      lmd->mParent = parentLmd;
#endif
      data =
          layerBuilder->GetDisplayItemDataForManager(aItem.mItem, tempManager);
      data = layerBuilder->StoreDataForFrame(aItem.mItem, tmpLayer,
                                             LAYER_ACTIVE, data);
      data->mOldTransform = data->mTransform;
      data->mTransform = aItem.mTransform;
    }

    tempManager->SetRoot(tmpLayer);
    layerBuilder->WillEndTransaction();
    tempManager->AbortTransaction();

    if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) {
      fprintf_stderr(
          gfxUtils::sDumpPaintFile,
          "Basic layer tree for painting contents of display item %s(%p):\n",
          aItem.mItem->Name(), aItem.mItem->Frame());
      std::stringstream stream;
      tempManager->Dump(stream, "", gfxEnv::DumpPaintToFile());
      fprint_stderr(
          gfxUtils::sDumpPaintFile,
          stream);  // not a typo, fprint_stderr declared in LayersLogging.h
    }

    nsIntPoint offset =
        GetLastPaintOffset(layer) - GetTranslationForPaintedLayer(layer);
    props->MoveBy(-offset);
    // Effective transforms are needed by ComputeDifferences().
    tmpLayer->ComputeEffectiveTransforms(Matrix4x4());
    nsIntRegion invalid;
    if (!props->ComputeDifferences(tmpLayer, invalid, nullptr)) {
      nsRect visible = aItem.mItem->Frame()->GetVisualOverflowRect();
      invalid = visible.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel);
    }
    if (aItem.mLayerState == LAYER_SVG_EFFECTS) {
      invalid = nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(
          aItem.mItem->Frame(), aItem.mItem->ToReferenceFrame(), invalid);
    }
    if (!invalid.IsEmpty()) {
#ifdef MOZ_DUMP_PAINTING
      if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
        printf_stderr(
            "Inactive LayerManager(%p) for display item %s(%p) has "
            "an invalid region - invalidating layer %p\n",
            tempManager.get(), aItem.mItem->Name(), aItem.mItem->Frame(),
            layer);
      }
#endif

      if (data && data->mTransform) {
        invalid = data->mTransform->TransformRegion(invalid);
      }

      invalid.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale);

      InvalidatePostTransformRegion(layer, invalid,
                                    GetTranslationForPaintedLayer(layer));
    }
  }
  aItem.mInactiveLayerManager = tempManager;
}

DisplayItemData* FrameLayerBuilder::StoreDataForFrame(nsDisplayItem* aItem,
                                                      Layer* aLayer,
                                                      LayerState aState,
                                                      DisplayItemData* aData) {
  if (aData) {
    if (!aData->mUsed) {
      aData->BeginUpdate(aLayer, aState, false, aItem);
    }
    return aData;
  }

  LayerManagerData* lmd = static_cast<LayerManagerData*>(
      mRetainingManager->GetUserData(&gLayerManagerUserData));

  RefPtr<DisplayItemData> data = new (aItem->Frame()->PresContext())
      DisplayItemData(lmd, aItem->GetPerFrameKey(), aLayer);

  data->BeginUpdate(aLayer, aState, true, aItem);

  lmd->mDisplayItems.push_back(data);
  return data;
}

void FrameLayerBuilder::StoreDataForFrame(nsIFrame* aFrame,
                                          uint32_t aDisplayItemKey,
                                          Layer* aLayer, LayerState aState) {
  DisplayItemData* oldData = GetDisplayItemData(aFrame, aDisplayItemKey);
  if (oldData && oldData->mFrameList.Length() == 1) {
    oldData->BeginUpdate(aLayer, aState, false);
    return;
  }

  LayerManagerData* lmd = static_cast<LayerManagerData*>(
      mRetainingManager->GetUserData(&gLayerManagerUserData));

  RefPtr<DisplayItemData> data = new (aFrame->PresContext())
      DisplayItemData(lmd, aDisplayItemKey, aLayer, aFrame);

  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)
    : mItem(aItem),
      mLayerState(aLayerState),
      mDisplayItemData(aData),
      mContentRect(aContentRect),
      mTransform(aTransform),
      mType(aType),
      mReused(aItem->IsReused()),
      mMerged(aItem->HasMergedFrames()),
      mHasOpacity(aHasOpacity),
      mHasTransform(aTransform),
      mHasPaintRect(aItem->HasPaintRect()) {}

AssignedDisplayItem::~AssignedDisplayItem() {
  if (mInactiveLayerManager) {
    mInactiveLayerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
  }
}

nsIntPoint FrameLayerBuilder::GetLastPaintOffset(PaintedLayer* aLayer) {
  PaintedDisplayItemLayerUserData* layerData =
      GetPaintedDisplayItemLayerUserData(aLayer);
  MOZ_ASSERT(layerData);
  if (layerData->mHasExplicitLastPaintOffset) {
    return layerData->mLastPaintOffset;
  }
  return GetTranslationForPaintedLayer(aLayer);
}

bool FrameLayerBuilder::CheckInLayerTreeCompressionMode() {
  if (mInLayerTreeCompressionMode) {
    return true;
  }

  // If we wanted to be in layer tree compression mode, but weren't, then
  // scheduled a delayed repaint where we will be.
  mRootPresContext->PresShell()->GetRootFrame()->SchedulePaint(
      nsIFrame::PAINT_DELAYED_COMPRESS, false);

  return false;
}

void ContainerState::CollectOldLayers() {
  for (Layer* layer = mContainerLayer->GetFirstChild(); layer;
       layer = layer->GetNextSibling()) {
    NS_ASSERTION(!layer->HasUserData(&gMaskLayerUserData),
                 "Mask layers should not be part of the layer tree.");
    if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
      NS_ASSERTION(layer->AsPaintedLayer(), "Wrong layer type");
      mPaintedLayersAvailableForRecycling.PutEntry(
          static_cast<PaintedLayer*>(layer));
    }

    if (Layer* maskLayer = layer->GetMaskLayer()) {
      NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
                   "Could not recycle mask layer, unsupported layer type.");
      mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Nothing()),
                                   static_cast<ImageLayer*>(maskLayer));
    }
    for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) {
      Layer* maskLayer = layer->GetAncestorMaskLayerAt(i);

      NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
                   "Could not recycle mask layer, unsupported layer type.");
      mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Some(i)),
                                   static_cast<ImageLayer*>(maskLayer));
    }
  }
}

struct OpaqueRegionEntry {
  AnimatedGeometryRoot* mAnimatedGeometryRoot;
  const ActiveScrolledRoot* mASR;
  nsIntRegion mOpaqueRegion;
};

static OpaqueRegionEntry* FindOpaqueRegionEntry(
    nsTArray<OpaqueRegionEntry>& aEntries,
    AnimatedGeometryRoot* aAnimatedGeometryRoot,
    const ActiveScrolledRoot* aASR) {
  for (uint32_t i = 0; i < aEntries.Length(); ++i) {
    OpaqueRegionEntry* d = &aEntries[i];
    if (d->mAnimatedGeometryRoot == aAnimatedGeometryRoot && d->mASR == aASR) {
      return d;
    }
  }
  return nullptr;
}

static const ActiveScrolledRoot* FindDirectChildASR(
    const ActiveScrolledRoot* aParent, const ActiveScrolledRoot* aDescendant) {
  MOZ_ASSERT(aDescendant, "can't start at the root when looking for a child");
  MOZ_ASSERT(ActiveScrolledRoot::IsAncestor(aParent, aDescendant));
  const ActiveScrolledRoot* directChild = aDescendant;
  while (directChild->mParent != aParent) {
    directChild = directChild->mParent;
    MOZ_RELEASE_ASSERT(directChild, "this must not be null");
  }
  return directChild;
}

static void FixUpFixedPositionLayer(
    Layer* aLayer, const ActiveScrolledRoot* aTargetASR,
    const ActiveScrolledRoot* aLeafScrollMetadataASR,
    const ActiveScrolledRoot* aContainerScrollMetadataASR,
    const ActiveScrolledRoot* aContainerCompositorASR,
    bool aIsFixedToRootScrollFrame) {
  if (!aLayer->GetIsFixedPosition()) {
    return;
  }

  // Analyze ASRs to figure out if we need to fix up fixedness annotations on
  // the layer. Fixed annotations are required in multiple cases:
  //  - Sometimes we set scroll metadata on a layer for a scroll frame that we
  //    don't want the layer to be moved by. (We have to do this if there is a
  //    scrolled clip that is moved by that scroll frame.) So we set the fixed
  //    annotation so that the compositor knows that it should ignore that
  //    scroll metadata when determining the layer's position.
  //  - Sometimes there is a scroll meta data on aLayer's parent layer for a
  //    scroll frame that we don't want aLayer to be moved by. The most common
  //    way for this to happen is with containerful root scrolling, where the
  //    scroll metadata for the root scroll frame is on a container layer that
  //    wraps the whole document's contents.
  //  - Sometimes it's just needed for hit testing, i.e. figuring out what
  //    scroll frame should be scrolled by events over the layer.
  // A fixed layer needs to be annotated with the scroll ID of the scroll frame
  // that it is *fixed with respect to*, i.e. the outermost scroll frame which
  // does not move the layer. nsDisplayFixedPosition only ever annotates layers
  // with the scroll ID of the presshell's root scroll frame, which is
  // sometimes the wrong thing to do, so we correct it here. Specifically,
  // it's the wrong thing to do if the fixed frame's containing block is a
  // transformed frame - in that case, the fixed frame needs to scroll along
  // with the transformed frame instead of being fixed with respect to the rsf.
  // (It would be nice to compute the annotation only in one place and get it
  // right, instead of fixing it up after the fact like this, but this will
  // need to do for now.)
  // compositorASR is the ASR that the layer would move with on the compositor
  // if there were no fixed annotation on it.
  const ActiveScrolledRoot* compositorASR =
      aLeafScrollMetadataASR == aContainerScrollMetadataASR
          ? aContainerCompositorASR
          : aLeafScrollMetadataASR;

  // The goal of the annotation is to have the layer move with aTargetASR.
  if (compositorASR && aTargetASR != compositorASR) {
    // Mark this layer as fixed with respect to the child scroll frame of
    // aTargetASR.
    aLayer->SetFixedPositionData(
        FindDirectChildASR(aTargetASR, compositorASR)->GetViewId(),
        aLayer->GetFixedPositionAnchor(), aLayer->GetFixedPositionSides());
  } else {
    // Remove the fixed annotation from the layer, unless this layers is fixed
    // to the document's root scroll frame - in that case, the annotation is
    // needed for hit testing, because fixed layers in iframes should scroll
    // the iframe, even though their position is not affected by scrolling in
    // the iframe. (The APZ hit testing code has a special case for this.)
    // nsDisplayFixedPosition has annotated this layer with the document's
    // root scroll frame's scroll id.
    aLayer->SetIsFixedPosition(aIsFixedToRootScrollFrame);
  }
}

void ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) {
  if (!mBuilder->IsPaintingToWindow()) {
    // async scrolling not possible, and async scrolling info not computed
    // for this paint.
    return;
  }

  const ActiveScrolledRoot* startASR = aEntry->mScrollMetadataASR;
  const ActiveScrolledRoot* stopASR = mContainerScrollMetadataASR;
  if (!ActiveScrolledRoot::IsAncestor(stopASR, startASR)) {
    if (ActiveScrolledRoot::IsAncestor(startASR, stopASR)) {
      // startASR and stopASR are in the same branch of the ASR tree, but
      // startASR is closer to the root. Just start at stopASR so that the loop
      // below doesn't actually do anything.
      startASR = stopASR;
    } else {
      // startASR and stopASR are in different branches of the
      // ASR tree. Find a common ancestor and make that the stopASR.
      // This can happen when there's a scrollable frame inside a fixed layer
      // which has a scrolled clip. As far as scroll metadata is concerned,
      // the scroll frame's scroll metadata will be a child of the scroll ID
      // that scrolls the clip on the fixed layer. But as far as ASRs are
      // concerned, those two ASRs are siblings, parented to the ASR of the
      // fixed layer.
      do {
        stopASR = stopASR->mParent;
      } while (!ActiveScrolledRoot::IsAncestor(stopASR, startASR));
    }
  }

  FixUpFixedPositionLayer(aEntry->mLayer, aEntry->mASR, startASR,
                          mContainerScrollMetadataASR, mContainerCompositorASR,
                          aEntry->mIsFixedToRootScrollFrame);

  AutoTArray<ScrollMetadata, 2> metricsArray;
  if (aEntry->mBaseScrollMetadata) {
    metricsArray.AppendElement(*aEntry->mBaseScrollMetadata);

    // The base FrameMetrics was not computed by the nsIScrollableframe, so it
    // should not have a mask layer.
    MOZ_ASSERT(!aEntry->mBaseScrollMetadata->HasMaskLayer());
  }

  // Any extra mask layers we need to attach to ScrollMetadatas.
  // The list may already contain an entry added for the layer's scrolled clip
  // so add to it rather than overwriting it (we clear the list when recycling
  // a layer).
  nsTArray<RefPtr<Layer>> maskLayers(
      aEntry->mLayer->GetAllAncestorMaskLayers());

  // Iterate over the ASR chain and create the corresponding scroll metadatas.
  // This loop is slightly tricky because the scrollframe-to-clip relationship
  // is reversed between DisplayItemClipChain and ScrollMetadata:
  //  - DisplayItemClipChain associates the clip with the scroll frame that
  //    this clip is *moved by*, i.e. the clip is moving inside the scroll
  //    frame.
  //  - ScrollMetaData associates the scroll frame with the clip that's
  //    *just outside* the scroll frame, i.e. not moved by the scroll frame
  //    itself.
  // This discrepancy means that the leaf clip item of the clip chain is never
  // applied to any scroll meta data. Instead, it was applied earlier as the
  // layer's clip (or fused with the painted layer contents), or it was applied
  // as a ScrolledClip on the layer.
  const DisplayItemClipChain* clipChain = aEntry->mClipChain;

  for (const ActiveScrolledRoot* asr = startASR; asr != stopASR;
       asr = asr->mParent) {
    if (!asr) {
      MOZ_ASSERT_UNREACHABLE("Should have encountered stopASR on the way up.");
      break;
    }
    if (clipChain && clipChain->mASR == asr) {
      clipChain = clipChain->mParent;
    }

    nsIScrollableFrame* scrollFrame = asr->mScrollableFrame;
    const DisplayItemClip* clip = (clipChain && clipChain->mASR == asr->mParent)
                                      ? &clipChain->mClip
                                      : nullptr;

    scrollFrame->ClipLayerToDisplayPort(aEntry->mLayer, clip, mParameters);

    Maybe<ScrollMetadata> metadata;
    if (mCachedScrollMetadata.mASR == asr &&
        mCachedScrollMetadata.mClip == clip) {
      metadata = mCachedScrollMetadata.mMetadata;
    } else {
      metadata = scrollFrame->ComputeScrollMetadata(aEntry->mLayer->Manager(),
                                                    mContainerReferenceFrame,
                                                    Some(mParameters), clip);
      scrollFrame->NotifyApzTransaction();
      mCachedScrollMetadata.mASR = asr;
      mCachedScrollMetadata.mClip = clip;
      mCachedScrollMetadata.mMetadata = metadata;
    }

    if (!metadata) {
      continue;
    }

    if (clip && clip->HasClip() && clip->GetRoundedRectCount() > 0) {
      // The clip in between this scrollframe and its ancestor scrollframe
      // requires a mask layer. Since this mask layer should not move with
      // the APZC associated with this FrameMetrics, we attach the mask
      // layer as an additional, separate clip.
      Maybe<size_t> nextIndex = Some(maskLayers.Length());
      RefPtr<Layer> maskLayer =
          CreateMaskLayer(aEntry->mLayer, *clip, nextIndex);
      if (maskLayer) {
        MOZ_ASSERT(metadata->HasScrollClip());
        metadata->ScrollClip().SetMaskLayerIndex(nextIndex);
        maskLayers.AppendElement(maskLayer);
      }
    }

    metricsArray.AppendElement(*metadata);
  }

  // Watch out for FrameMetrics copies in profiles
  aEntry->mLayer->SetScrollMetadata(metricsArray);
  aEntry->mLayer->SetAncestorMaskLayers(maskLayers);
}

static inline Maybe<ParentLayerIntRect> GetStationaryClipInContainer(
    Layer* aLayer) {
  if (size_t metricsCount = aLayer->GetScrollMetadataCount()) {
    return aLayer->GetScrollMetadata(metricsCount - 1).GetClipRect();
  }
  return aLayer->GetClipRect();
}

void ContainerState::PostprocessRetainedLayers(
    nsIntRegion* aOpaqueRegionForContainer) {
  AutoTArray<OpaqueRegionEntry, 4> opaqueRegions;
  bool hideAll = false;
  int32_t opaqueRegionForContainer = -1;

  for (int32_t i = mNewChildLayers.Length() - 1; i >= 0; --i) {
    NewLayerEntry* e = &mNewChildLayers.ElementAt(i);
    if (!e->mLayer) {
      continue;
    }

    OpaqueRegionEntry* data =
        FindOpaqueRegionEntry(opaqueRegions, e->mAnimatedGeometryRoot, e->mASR);

    SetupScrollingMetadata(e);

    if (hideAll) {
      e->mVisibleRegion.SetEmpty();
    } else if (!e->mLayer->IsScrollbarContainer()) {
      Maybe<ParentLayerIntRect> clipRect =
          GetStationaryClipInContainer(e->mLayer);
      if (clipRect && opaqueRegionForContainer >= 0 &&
          opaqueRegions[opaqueRegionForContainer].mOpaqueRegion.Contains(
              clipRect->ToUnknownRect())) {
        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,
                                  e->mUntransformedVisibleRegion);

    if (!e->mOpaqueRegion.IsEmpty()) {
      AnimatedGeometryRoot* animatedGeometryRootToCover =
          e->mAnimatedGeometryRoot;
      const ActiveScrolledRoot* asrToCover = e->mASR;
      if (e->mOpaqueForAnimatedGeometryRootParent &&
          e->mAnimatedGeometryRoot->mParentAGR ==
              mContainerAnimatedGeometryRoot) {
        animatedGeometryRootToCover = mContainerAnimatedGeometryRoot;
        asrToCover = mContainerASR;
        data = FindOpaqueRegionEntry(opaqueRegions, animatedGeometryRootToCover,
                                     asrToCover);
      }

      if (!data) {
        if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot &&
            asrToCover == mContainerASR) {
          NS_ASSERTION(opaqueRegionForContainer == -1, "Already found it?");
          opaqueRegionForContainer = opaqueRegions.Length();
        }
        data = opaqueRegions.AppendElement();
        data->mAnimatedGeometryRoot = animatedGeometryRootToCover;
        data->mASR = asrToCover;
      }

      nsIntRegion clippedOpaque = e->mOpaqueRegion;
      Maybe<ParentLayerIntRect> clipRect = e->mLayer->GetCombinedClipRect();
      if (clipRect) {
        clippedOpaque.AndWith(clipRect->ToUnknownRect());
      }
      if (e->mLayer->GetScrolledClip()) {
        // The clip can move asynchronously, so we can't rely on opaque parts
        // staying visible.
        clippedOpaque.SetEmpty();
      } else if (e->mHideAllLayersBelow) {
        hideAll = true;
      }
      data->mOpaqueRegion.Or(data->mOpaqueRegion, clippedOpaque);
    }

    if (e->mLayer->GetType() == Layer::TYPE_READBACK) {
      // ReadbackLayers need to accurately read what's behind them. So,
      // we don't want to do any occlusion culling of layers behind them.
      // Theoretically we could just punch out the ReadbackLayer's rectangle
      // from all mOpaqueRegions, but that's probably not worth doing.
      opaqueRegions.Clear();
      opaqueRegionForContainer = -1;
    }
  }

  if (opaqueRegionForContainer >= 0) {
    aOpaqueRegionForContainer->Or(
        *aOpaqueRegionForContainer,
        opaqueRegions[opaqueRegionForContainer].mOpaqueRegion);
  }
}

void ContainerState::Finish(uint32_t* aTextContentFlags,
                            const nsIntRect& aContainerPixelBounds,
                            nsDisplayList* aChildItems) {
  mPaintedLayerDataTree.Finish();

  if (!mParameters.mForEventsAndPluginsOnly &&
      !gfxPrefs::LayoutUseContainersForRootFrames()) {
    // Bug 1336544 tracks re-enabling this assertion in the
    // gfxPrefs::LayoutUseContainersForRootFrames() case.
    NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds),
                 "Bounds computation mismatch");
  }

  if (mLayerBuilder->IsBuildingRetainedLayers()) {
    nsIntRegion 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
  // in the correct order.
  Layer* layer = nullptr;
  Layer* prevChild = nullptr;
  for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i, prevChild = layer) {
    if (!mNewChildLayers[i].mLayer) {
      continue;
    }

    layer = mNewChildLayers[i].mLayer;

    if (!layer->GetVisibleRegion().IsEmpty()) {
      textContentFlags |= layer->GetContentFlags() &
                          (Layer::CONTENT_COMPONENT_ALPHA |
                           Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT |
                           Layer::CONTENT_DISABLE_FLATTENING);
    }

    if (!layer->GetParent()) {
      // This is not currently a child of the container, so just add it
      // now.
      mContainerLayer->InsertAfter(layer, prevChild);
    } else {
      NS_ASSERTION(layer->GetParent() == mContainerLayer,
                   "Layer shouldn't be the child of some other container");
      if (layer->GetPrevSibling() != prevChild) {
        mContainerLayer->RepositionChild(layer, prevChild);
      }
    }
  }

  // Remove old layers that have become unused.
  if (!layer) {
    layer = mContainerLayer->GetFirstChild();
  } else {
    layer = layer->GetNextSibling();
  }
  while (layer) {
    Layer* layerToRemove = layer;
    layer = layer->GetNextSibling();
    mContainerLayer->RemoveChild(layerToRemove);
  }

  *aTextContentFlags = textContentFlags;
}

static void RestrictScaleToMaxLayerSize(Size& aScale,
                                        const nsRect& aVisibleRect,
                                        nsIFrame* aContainerFrame,
                                        Layer* aContainerLayer) {
  if (!aContainerLayer->Manager()->IsWidgetLayerManager()) {
    return;
  }

  nsIntRect pixelSize = aVisibleRect.ScaleToOutsidePixels(
      aScale.width, aScale.height,
      aContainerFrame->PresContext()->AppUnitsPerDevPixel());

  int32_t maxLayerSize = aContainerLayer->GetMaxLayerSize();

  if (pixelSize.width > maxLayerSize) {
    float scale = (float)pixelSize.width / maxLayerSize;
    scale = gfxUtils::ClampToScaleFactor(scale);
    aScale.width /= scale;
  }
  if (pixelSize.height > maxLayerSize) {
    float scale = (float)pixelSize.height / maxLayerSize;
    scale