/* -*- 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/.
 */

/*
 * structures that represent things to be painted (ordered in z-order),
 * used during painting and hit testing
 */

#include "nsDisplayList.h"

#include <stdint.h>
#include <algorithm>
#include <limits>

#include "gfxContext.h"
#include "gfxUtils.h"
#include "mozilla/DisplayPortUtils.h"
#include "mozilla/Likely.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/RemoteBrowser.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/ServiceWorkerRegistration.h"
#include "mozilla/dom/SVGElement.h"
#include "mozilla/dom/TouchEvent.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/PresShell.h"
#include "mozilla/ShapeUtils.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_print.h"
#include "mozilla/SVGIntegrationUtils.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/ViewportUtils.h"
#include "nsCSSRendering.h"
#include "nsCSSRenderingGradients.h"
#include "nsCaseTreatment.h"
#include "nsRefreshDriver.h"
#include "nsRegion.h"
#include "nsStyleStructInlines.h"
#include "nsStyleTransformMatrix.h"
#include "nsTransitionManager.h"
#include "gfxMatrix.h"
#include "nsLayoutUtils.h"
#include "nsIScrollableFrame.h"
#include "nsIFrameInlines.h"
#include "nsStyleConsts.h"
#include "BorderConsts.h"
#include "mozilla/MathAlgorithms.h"

#include "imgIContainer.h"
#include "nsImageFrame.h"
#include "nsSubDocumentFrame.h"
#include "nsViewManager.h"
#include "ImageContainer.h"
#include "nsCanvasFrame.h"
#include "nsSubDocumentFrame.h"
#include "StickyScrollContainer.h"
#include "mozilla/AnimationPerformanceWarning.h"
#include "mozilla/AnimationUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EffectSet.h"
#include "mozilla/HashTable.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/OperatorNewExtensions.h"
#include "mozilla/PendingAnimationTracker.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/StyleAnimationValue.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/SVGClipPathFrame.h"
#include "mozilla/SVGMaskFrame.h"
#include "mozilla/SVGObserverUtils.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/ViewportFrame.h"
#include "mozilla/gfx/gfxVars.h"
#include "ActiveLayerTracker.h"
#include "nsEscape.h"
#include "nsPrintfCString.h"
#include "UnitTransforms.h"
#include "LayerAnimationInfo.h"
#include "mozilla/EventStateManager.h"
#include "nsCaret.h"
#include "nsDOMTokenList.h"
#include "nsCSSProps.h"
#include "nsTableCellFrame.h"
#include "nsTableColFrame.h"
#include "nsTextFrame.h"
#include "nsTextPaintStyle.h"
#include "nsSliderFrame.h"
#include "nsFocusManager.h"
#include "TextDrawTarget.h"
#include "mozilla/layers/AnimationHelper.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/InputAPZContext.h"
#include "mozilla/layers/RenderRootStateManager.h"
#include "mozilla/layers/StackingContextHelper.h"
#include "mozilla/layers/TreeTraversal.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/WebRenderMessages.h"
#include "mozilla/layers/WebRenderScrollData.h"

namespace mozilla {

using namespace dom;
using namespace gfx;
using namespace layout;
using namespace layers;
using namespace image;

LazyLogModule sContentDisplayListLog("dl.content");
LazyLogModule sParentDisplayListLog("dl.parent");

LazyLogModule& GetLoggerByProcess() {
  return XRE_IsContentProcess() ? sContentDisplayListLog
                                : sParentDisplayListLog;
}

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
void AssertUniqueItem(nsDisplayItem* aItem) {
  for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) {
    if (i != aItem && !i->HasDeletedFrame() && i->Frame() == aItem->Frame() &&
        i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
      if (i->IsPreProcessedItem() || i->IsPreProcessed()) {
        continue;
      }
      MOZ_DIAGNOSTIC_ASSERT(false, "Duplicate display item!");
    }
  }
}
#endif

bool ShouldBuildItemForEvents(const DisplayItemType aType) {
  return aType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO ||
         (GetDisplayItemFlagsForType(aType) & TYPE_IS_CONTAINER);
}

static bool ItemTypeSupportsHitTesting(const DisplayItemType aType) {
  switch (aType) {
    case DisplayItemType::TYPE_BACKGROUND:
    case DisplayItemType::TYPE_BACKGROUND_COLOR:
    case DisplayItemType::TYPE_THEMED_BACKGROUND:
      return true;
    default:
      return false;
  }
}

void InitializeHitTestInfo(nsDisplayListBuilder* aBuilder,
                           nsPaintedDisplayItem* aItem,
                           const DisplayItemType aType) {
  if (ItemTypeSupportsHitTesting(aType)) {
    aItem->InitializeHitTestInfo(aBuilder);
  }
}

/* static */
already_AddRefed<ActiveScrolledRoot> ActiveScrolledRoot::CreateASRForFrame(
    const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame,
    bool aIsRetained) {
  nsIFrame* f = do_QueryFrame(aScrollableFrame);

  RefPtr<ActiveScrolledRoot> asr;
  if (aIsRetained) {
    asr = f->GetProperty(ActiveScrolledRootCache());
  }

  if (!asr) {
    asr = new ActiveScrolledRoot();

    if (aIsRetained) {
      RefPtr<ActiveScrolledRoot> ref = asr;
      f->SetProperty(ActiveScrolledRootCache(), ref.forget().take());
    }
  }
  asr->mParent = aParent;
  asr->mScrollableFrame = aScrollableFrame;
  asr->mViewId = Nothing();
  asr->mDepth = aParent ? aParent->mDepth + 1 : 1;
  asr->mRetained = aIsRetained;

  return asr.forget();
}

/* static */
bool ActiveScrolledRoot::IsAncestor(const ActiveScrolledRoot* aAncestor,
                                    const ActiveScrolledRoot* aDescendant) {
  if (!aAncestor) {
    // nullptr is the root
    return true;
  }
  if (Depth(aAncestor) > Depth(aDescendant)) {
    return false;
  }
  const ActiveScrolledRoot* asr = aDescendant;
  while (asr) {
    if (asr == aAncestor) {
      return true;
    }
    asr = asr->mParent;
  }
  return false;
}

/* static */
bool ActiveScrolledRoot::IsProperAncestor(
    const ActiveScrolledRoot* aAncestor,
    const ActiveScrolledRoot* aDescendant) {
  return aAncestor != aDescendant && IsAncestor(aAncestor, aDescendant);
}

/* static */
nsCString ActiveScrolledRoot::ToString(
    const ActiveScrolledRoot* aActiveScrolledRoot) {
  nsAutoCString str;
  for (const auto* asr = aActiveScrolledRoot; asr; asr = asr->mParent) {
    str.AppendPrintf("<0x%p>", asr->mScrollableFrame);
    if (asr->mParent) {
      str.AppendLiteral(", ");
    }
  }
  return std::move(str);
}

ScrollableLayerGuid::ViewID ActiveScrolledRoot::ComputeViewId() const {
  nsIContent* content = mScrollableFrame->GetScrolledFrame()->GetContent();
  return nsLayoutUtils::FindOrCreateIDFor(content);
}

ActiveScrolledRoot::~ActiveScrolledRoot() {
  if (mScrollableFrame && mRetained) {
    nsIFrame* f = do_QueryFrame(mScrollableFrame);
    f->RemoveProperty(ActiveScrolledRootCache());
  }
}

static uint64_t AddAnimationsForWebRender(
    nsDisplayItem* aItem, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder,
    const Maybe<LayoutDevicePoint>& aPosition = Nothing()) {
  auto* effects = EffectSet::GetForFrame(aItem->Frame(), aItem->GetType());
  if (!effects || effects->IsEmpty()) {
    // If there is no animation on the nsIFrame, that means
    //  1) we've never created any animations on this frame or
    //  2) the frame was reconstruced or
    //  3) all animations on the frame have finished
    // in such cases we don't need do anything here.
    //
    // Even if there is a WebRenderAnimationData for the display item type on
    // this frame, it's going to be discarded since it's not marked as being
    // used.
    return 0;
  }

  RefPtr<WebRenderAnimationData> animationData =
      aManager->CommandBuilder()
          .CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(aItem);
  AnimationInfo& animationInfo = animationData->GetAnimationInfo();
  animationInfo.AddAnimationsForDisplayItem(
      aItem->Frame(), aDisplayListBuilder, aItem, aItem->GetType(),
      aManager->LayerManager(), aPosition);
  animationInfo.StartPendingAnimations(
      aManager->LayerManager()->GetAnimationReadyTime());

  // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there
  // are no active animations.
  uint64_t animationsId = animationInfo.GetCompositorAnimationsId();
  if (!animationInfo.GetAnimations().IsEmpty()) {
    OpAddCompositorAnimations anim(
        CompositorAnimations(animationInfo.GetAnimations(), animationsId));
    aManager->WrBridge()->AddWebRenderParentCommand(anim);
    aManager->AddActiveCompositorAnimationId(animationsId);
  } else if (animationsId) {
    aManager->AddCompositorAnimationsIdForDiscard(animationsId);
    animationsId = 0;
  }

  return animationsId;
}

static bool GenerateAndPushTextMask(nsIFrame* aFrame, gfxContext* aContext,
                                    const nsRect& aFillRect,
                                    nsDisplayListBuilder* aBuilder) {
  if (aBuilder->IsForGenerateGlyphMask()) {
    return false;
  }

  SVGObserverUtils::GetAndObserveBackgroundClip(aFrame);

  // The main function of enabling background-clip:text property value.
  // When a nsDisplayBackgroundImage detects "text" bg-clip style, it will call
  // this function to
  // 1. Generate a mask by all descendant text frames
  // 2. Push the generated mask into aContext.

  gfxContext* sourceCtx = aContext;
  LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
      aFillRect, aFrame->PresContext()->AppUnitsPerDevPixel());

  // Create a mask surface.
  RefPtr<DrawTarget> sourceTarget = sourceCtx->GetDrawTarget();
  RefPtr<DrawTarget> maskDT = sourceTarget->CreateClippedDrawTarget(
      bounds.ToUnknownRect(), SurfaceFormat::A8);
  if (!maskDT || !maskDT->IsValid()) {
    return false;
  }
  gfxContext maskCtx(maskDT, /* aPreserveTransform */ true);
  maskCtx.Multiply(Matrix::Translation(bounds.TopLeft().ToUnknownPoint()));

  // Shade text shape into mask A8 surface.
  nsLayoutUtils::PaintFrame(
      &maskCtx, aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()),
      NS_RGB(255, 255, 255), nsDisplayListBuilderMode::GenerateGlyph);

  // Push the generated mask into aContext, so that the caller can pop and
  // blend with it.

  Matrix currentMatrix = sourceCtx->CurrentMatrix();
  Matrix invCurrentMatrix = currentMatrix;
  invCurrentMatrix.Invert();

  RefPtr<SourceSurface> maskSurface = maskDT->Snapshot();
  sourceCtx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 1.0,
                                   maskSurface, invCurrentMatrix);

  return true;
}

nsDisplayWrapper* nsDisplayWrapList::CreateShallowCopy(
    nsDisplayListBuilder* aBuilder) {
  const nsDisplayWrapList* wrappedItem = AsDisplayWrapList();
  MOZ_ASSERT(wrappedItem);

  // Create a new nsDisplayWrapList using a copy-constructor. This is done
  // to preserve the information about bounds.
  nsDisplayWrapper* wrapper =
      new (aBuilder) nsDisplayWrapper(aBuilder, *wrappedItem);
  wrapper->SetType(nsDisplayWrapper::ItemType());
  MOZ_ASSERT(wrapper);

  // Set the display list pointer of the new wrapper item to the display list
  // of the wrapped item.
  wrapper->mListPtr = wrappedItem->mListPtr;
  return wrapper;
}

nsDisplayWrapList* nsDisplayListBuilder::MergeItems(
    nsTArray<nsDisplayItem*>& aItems) {
  // For merging, we create a temporary item by cloning the last item of the
  // mergeable items list. This ensures that the temporary item will have the
  // correct frame and bounds.
  nsDisplayWrapList* last = aItems.PopLastElement()->AsDisplayWrapList();
  MOZ_ASSERT(last);
  nsDisplayWrapList* merged = last->Clone(this);
  MOZ_ASSERT(merged);
  AddTemporaryItem(merged);

  // Create nsDisplayWrappers that point to the internal display lists of the
  // items we are merging. These nsDisplayWrappers are added to the display list
  // of the temporary item.
  for (nsDisplayItem* item : aItems) {
    MOZ_ASSERT(item);
    MOZ_ASSERT(merged->CanMerge(item));
    merged->Merge(item);
    MOZ_ASSERT(item->AsDisplayWrapList());
    merged->GetChildren()->AppendToTop(
        static_cast<nsDisplayWrapList*>(item)->CreateShallowCopy(this));
  }

  merged->GetChildren()->AppendToTop(last->CreateShallowCopy(this));

  return merged;
}

void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter::
    SetCurrentActiveScrolledRoot(
        const ActiveScrolledRoot* aActiveScrolledRoot) {
  MOZ_ASSERT(!mUsed);

  // Set the builder's mCurrentActiveScrolledRoot.
  mBuilder->mCurrentActiveScrolledRoot = aActiveScrolledRoot;

  // We also need to adjust the builder's mCurrentContainerASR.
  // mCurrentContainerASR needs to be an ASR that all the container's
  // contents have finite bounds with respect to. If aActiveScrolledRoot
  // is an ancestor ASR of mCurrentContainerASR, that means we need to
  // set mCurrentContainerASR to aActiveScrolledRoot, because otherwise
  // the items that will be created with aActiveScrolledRoot wouldn't
  // have finite bounds with respect to mCurrentContainerASR. There's one
  // exception, in the case where there's a content clip on the builder
  // that is scrolled by a descendant ASR of aActiveScrolledRoot. This
  // content clip will clip all items that are created while this
  // AutoCurrentActiveScrolledRootSetter exists. This means that the items
  // created during our lifetime will have finite bounds with respect to
  // the content clip's ASR, even if the items' actual ASR is an ancestor
  // of that. And it also means that mCurrentContainerASR only needs to be
  // set to the content clip's ASR and not all the way to aActiveScrolledRoot.
  // This case is tested by fixed-pos-scrolled-clip-opacity-layerize.html
  // and fixed-pos-scrolled-clip-opacity-inside-layerize.html.

  // finiteBoundsASR is the leafmost ASR that all items created during
  // object's lifetime have finite bounds with respect to.
  const ActiveScrolledRoot* finiteBoundsASR =
      ActiveScrolledRoot::PickDescendant(mContentClipASR, aActiveScrolledRoot);

  // mCurrentContainerASR is adjusted so that it's still an ancestor of
  // finiteBoundsASR.
  mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickAncestor(
      mBuilder->mCurrentContainerASR, finiteBoundsASR);

  // If we are entering out-of-flow content inside a CSS filter, mark
  // scroll frames wrt. which the content is fixed as containing such content.
  if (mBuilder->mFilterASR && ActiveScrolledRoot::IsAncestor(
                                  aActiveScrolledRoot, mBuilder->mFilterASR)) {
    for (const ActiveScrolledRoot* asr = mBuilder->mFilterASR;
         asr && asr != aActiveScrolledRoot; asr = asr->mParent) {
      asr->mScrollableFrame->SetHasOutOfFlowContentInsideFilter();
    }
  }

  mUsed = true;
}

void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter::
    InsertScrollFrame(nsIScrollableFrame* aScrollableFrame) {
  MOZ_ASSERT(!mUsed);
  size_t descendantsEndIndex = mBuilder->mActiveScrolledRoots.Length();
  const ActiveScrolledRoot* parentASR = mBuilder->mCurrentActiveScrolledRoot;
  const ActiveScrolledRoot* asr =
      mBuilder->AllocateActiveScrolledRoot(parentASR, aScrollableFrame);
  mBuilder->mCurrentActiveScrolledRoot = asr;

  // All child ASRs of parentASR that were created while this
  // AutoCurrentActiveScrolledRootSetter object was on the stack belong to us
  // now. Reparent them to asr.
  for (size_t i = mDescendantsStartIndex; i < descendantsEndIndex; i++) {
    ActiveScrolledRoot* descendantASR = mBuilder->mActiveScrolledRoots[i];
    if (ActiveScrolledRoot::IsAncestor(parentASR, descendantASR)) {
      descendantASR->IncrementDepth();
      if (descendantASR->mParent == parentASR) {
        descendantASR->mParent = asr;
      }
    }
  }

  mUsed = true;
}

nsDisplayListBuilder::AutoContainerASRTracker::AutoContainerASRTracker(
    nsDisplayListBuilder* aBuilder)
    : mBuilder(aBuilder), mSavedContainerASR(aBuilder->mCurrentContainerASR) {
  mBuilder->mCurrentContainerASR = mBuilder->mCurrentActiveScrolledRoot;
}

nsPresContext* nsDisplayListBuilder::CurrentPresContext() {
  return CurrentPresShellState()->mPresShell->GetPresContext();
}

/* static */
nsRect nsDisplayListBuilder::OutOfFlowDisplayData::ComputeVisibleRectForFrame(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
    const nsRect& aVisibleRect, const nsRect& aDirtyRect,
    nsRect* aOutDirtyRect) {
  nsRect visible = aVisibleRect;
  nsRect dirtyRectRelativeToDirtyFrame = aDirtyRect;

  bool inPartialUpdate =
      aBuilder->IsRetainingDisplayList() && aBuilder->IsPartialUpdate();
  if (StaticPrefs::apz_allow_zooming() &&
      DisplayPortUtils::IsFixedPosFrameInDisplayPort(aFrame) &&
      aBuilder->IsPaintingToWindow() && !inPartialUpdate) {
    dirtyRectRelativeToDirtyFrame =
        nsRect(nsPoint(0, 0), aFrame->GetParent()->GetSize());

    // If there's a visual viewport size set, restrict the amount of the
    // fixed-position element we paint to the visual viewport. (In general
    // the fixed-position element can be as large as the layout viewport,
    // which at a high zoom level can cause us to paint too large of an
    // area.)
    PresShell* presShell = aFrame->PresShell();
    if (presShell->IsVisualViewportSizeSet()) {
      dirtyRectRelativeToDirtyFrame =
          nsRect(presShell->GetVisualViewportOffsetRelativeToLayoutViewport(),
                 presShell->GetVisualViewportSize());
      // But if we have a displayport, expand it to the displayport, so
      // that async-scrolling the visual viewport within the layout viewport
      // will not checkerboard.
      if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
        nsRect displayport;
        // Note that the displayport here is already in the right coordinate
        // space: it's relative to the scroll port (= layout viewport), but
        // covers the visual viewport with some margins around it, which is
        // exactly what we want.
        if (DisplayPortUtils::GetDisplayPort(
                rootScrollFrame->GetContent(), &displayport,
                DisplayPortOptions().With(ContentGeometryType::Fixed))) {
          dirtyRectRelativeToDirtyFrame = displayport;
        }
      }
    }
    visible = dirtyRectRelativeToDirtyFrame;
    if (StaticPrefs::apz_test_logging_enabled() &&
        presShell->GetDocument()->IsContentDocument()) {
      nsLayoutUtils::LogAdditionalTestData(
          aBuilder, "fixedPosDisplayport",
          ToString(CSSSize::FromAppUnits(visible)));
    }
  }

  *aOutDirtyRect = dirtyRectRelativeToDirtyFrame - aFrame->GetPosition();
  visible -= aFrame->GetPosition();

  nsRect overflowRect = aFrame->InkOverflowRect();

  if (aFrame->IsTransformed() && EffectCompositor::HasAnimationsForCompositor(
                                     aFrame, DisplayItemType::TYPE_TRANSFORM)) {
    /**
     * Add a fuzz factor to the overflow rectangle so that elements only
     * just out of view are pulled into the display list, so they can be
     * prerendered if necessary.
     */
    overflowRect.Inflate(nsPresContext::CSSPixelsToAppUnits(32));
  }

  visible.IntersectRect(visible, overflowRect);
  aOutDirtyRect->IntersectRect(*aOutDirtyRect, overflowRect);

  return visible;
}

nsDisplayListBuilder::Linkifier::Linkifier(nsDisplayListBuilder* aBuilder,
                                           nsIFrame* aFrame,
                                           nsDisplayList* aList)
    : mList(aList) {
  // Find the element that we need to check for link-ness, bailing out if
  // we can't find one.
  Element* elem = Element::FromNodeOrNull(aFrame->GetContent());
  if (!elem) {
    return;
  }

  // If the element has an id and/or name attribute, generate a destination
  // for possible internal linking.
  auto maybeGenerateDest = [&](const nsAtom* aAttr) {
    nsAutoString attrValue;
    elem->GetAttr(aAttr, attrValue);
    if (!attrValue.IsEmpty()) {
      NS_ConvertUTF16toUTF8 dest(attrValue);
      // Ensure that we only emit a given destination once, although there may
      // be multiple frames associated with a given element; we'll simply use
      // the first of them as the target of any links to it.
      // XXX(jfkthame) This prevents emitting duplicate destinations *on the
      // same page*, but does not prevent duplicates on subsequent pages, as
      // each new page is handled by a new temporary DisplayListBuilder. This
      // seems to be harmless in practice, though a bit wasteful of space. To
      // fix, we need to maintain the set of already-seen destinations globally
      // for the print job, rather than attached to the (per-page) builder.
      if (aBuilder->mDestinations.EnsureInserted(dest)) {
        auto* destination = MakeDisplayItem<nsDisplayDestination>(
            aBuilder, aFrame, dest.get(), aFrame->GetRect().TopLeft());
        mList->AppendToTop(destination);
      }
    }
  };

  if (StaticPrefs::print_save_as_pdf_internal_destinations_enabled()) {
    if (elem->HasID()) {
      maybeGenerateDest(nsGkAtoms::id);
    }
    if (elem->HasName()) {
      maybeGenerateDest(nsGkAtoms::name);
    }
  }

  // Links don't nest, so if the builder already has a destination, no need to
  // check for a link element here.
  if (!aBuilder->mLinkSpec.IsEmpty()) {
    return;
  }

  // Check if we have actually found a link.
  if (!elem->IsLink()) {
    return;
  }

  nsCOMPtr<nsIURI> uri = elem->GetHrefURI();
  if (!uri) {
    return;
  }

  // Is it a local (in-page) destination?
  bool hasRef, eqExRef;
  nsIURI* docURI;
  if (StaticPrefs::print_save_as_pdf_internal_destinations_enabled() &&
      NS_SUCCEEDED(uri->GetHasRef(&hasRef)) && hasRef &&
      (docURI = aFrame->PresContext()->Document()->GetDocumentURI()) &&
      NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &eqExRef)) && eqExRef) {
    if (NS_FAILED(uri->GetRef(aBuilder->mLinkSpec)) ||
        aBuilder->mLinkSpec.IsEmpty()) {
      return;
    }
    // The destination name is simply a string; we don't want URL-escaping
    // applied to it.
    NS_UnescapeURL(aBuilder->mLinkSpec);
    // Mark the link spec as being an internal destination
    aBuilder->mLinkSpec.Insert('#', 0);
  } else {
    if (NS_FAILED(uri->GetSpec(aBuilder->mLinkSpec)) ||
        aBuilder->mLinkSpec.IsEmpty()) {
      return;
    }
  }

  // Record that we need to reset the builder's state on destruction.
  mBuilderToReset = aBuilder;
}

void nsDisplayListBuilder::Linkifier::MaybeAppendLink(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
  // Note that we may generate a link here even if the constructor bailed out
  // without updating aBuilder->LinkSpec(), because it may have been set by
  // an ancestor that was associated with a link element.
  if (!aBuilder->mLinkSpec.IsEmpty()) {
    auto* link = MakeDisplayItem<nsDisplayLink>(
        aBuilder, aFrame, aBuilder->mLinkSpec.get(), aFrame->GetRect());
    mList->AppendToTop(link);
  }
}

uint32_t nsDisplayListBuilder::sPaintSequenceNumber(1);

nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
                                           nsDisplayListBuilderMode aMode,
                                           bool aBuildCaret,
                                           bool aRetainingDisplayList)
    : mReferenceFrame(aReferenceFrame),
      mIgnoreScrollFrame(nullptr),
      mCurrentActiveScrolledRoot(nullptr),
      mCurrentContainerASR(nullptr),
      mCurrentFrame(aReferenceFrame),
      mCurrentReferenceFrame(aReferenceFrame),
      mCaretFrame(nullptr),
      mScrollInfoItemsForHoisting(nullptr),
      mFirstClipChainToDestroy(nullptr),
      mTableBackgroundSet(nullptr),
      mCurrentScrollParentId(ScrollableLayerGuid::NULL_SCROLL_ID),
      mCurrentScrollbarTarget(ScrollableLayerGuid::NULL_SCROLL_ID),
      mFilterASR(nullptr),
      mDirtyRect(-1, -1, -1, -1),
      mBuildingExtraPagesForPageNum(0),
      mMode(aMode),
      mContainsBlendMode(false),
      mIsBuildingScrollbar(false),
      mCurrentScrollbarWillHaveLayer(false),
      mBuildCaret(aBuildCaret),
      mRetainingDisplayList(aRetainingDisplayList),
      mPartialUpdate(false),
      mIgnoreSuppression(false),
      mIncludeAllOutOfFlows(false),
      mDescendIntoSubdocuments(true),
      mSelectedFramesOnly(false),
      mAllowMergingAndFlattening(true),
      mInTransform(false),
      mInEventsOnly(false),
      mInFilter(false),
      mInPageSequence(false),
      mIsInChromePresContext(false),
      mSyncDecodeImages(false),
      mIsPaintingToWindow(false),
      mUseHighQualityScaling(false),
      mIsPaintingForWebRender(false),
      mIsCompositingCheap(false),
      mAncestorHasApzAwareEventHandler(false),
      mHaveScrollableDisplayPort(false),
      mWindowDraggingAllowed(false),
      mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)),
      mForceLayerForScrollParent(false),
      mContainsNonMinimalDisplayPort(false),
      mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)),
      mBuildingInvisibleItems(false),
      mIsBuilding(false),
      mInInvalidSubtree(false),
      mDisablePartialUpdates(false),
      mPartialBuildFailed(false),
      mIsInActiveDocShell(false),
      mBuildAsyncZoomContainer(false),
      mIsRelativeToLayoutViewport(false),
      mUseOverlayScrollbars(false),
      mAlwaysLayerizeScrollbars(false) {
  MOZ_COUNT_CTOR(nsDisplayListBuilder);

  mBuildCompositorHitTestInfo = mAsyncPanZoomEnabled && IsForPainting();

  ShouldRebuildDisplayListDueToPrefChange();

  mUseOverlayScrollbars =
      !!LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars);

  mAlwaysLayerizeScrollbars =
      StaticPrefs::layout_scrollbars_always_layerize_track();

  static_assert(
      static_cast<uint32_t>(DisplayItemType::TYPE_MAX) < (1 << TYPE_BITS),
      "Check TYPE_MAX should not overflow");

  mIsReusingStackingContextItems =
      mRetainingDisplayList && StaticPrefs::layout_display_list_retain_sc();
}

static PresShell* GetFocusedPresShell() {
  nsPIDOMWindowOuter* focusedWnd =
      nsFocusManager::GetFocusManager()->GetFocusedWindow();
  if (!focusedWnd) {
    return nullptr;
  }

  nsCOMPtr<nsIDocShell> focusedDocShell = focusedWnd->GetDocShell();
  if (!focusedDocShell) {
    return nullptr;
  }

  return focusedDocShell->GetPresShell();
}

void nsDisplayListBuilder::BeginFrame() {
  nsCSSRendering::BeginFrameTreesLocked();

  mIsPaintingToWindow = false;
  mUseHighQualityScaling = false;
  mIgnoreSuppression = false;
  mInTransform = false;
  mInFilter = false;
  mSyncDecodeImages = false;

  if (!mBuildCaret) {
    return;
  }

  RefPtr<PresShell> presShell = GetFocusedPresShell();
  if (presShell) {
    RefPtr<nsCaret> caret = presShell->GetCaret();
    mCaretFrame = caret->GetPaintGeometry(&mCaretRect);

    // The focused pres shell may not be in the document that we're
    // painting, or be in a popup. Check if the display root for
    // the caret matches the display root that we're painting, and
    // only use it if it matches.
    if (mCaretFrame &&
        nsLayoutUtils::GetDisplayRootFrame(mCaretFrame) !=
            nsLayoutUtils::GetDisplayRootFrame(mReferenceFrame)) {
      mCaretFrame = nullptr;
    }
  }
}

void nsDisplayListBuilder::AddEffectUpdate(dom::RemoteBrowser* aBrowser,
                                           const dom::EffectsInfo& aUpdate) {
  dom::EffectsInfo update = aUpdate;
  // For printing we create one display item for each page that an iframe
  // appears on, the proper visible rect is the union of all the visible rects
  // we get from each display item.
  nsPresContext* pc =
      mReferenceFrame ? mReferenceFrame->PresContext() : nullptr;
  if (pc && pc->Type() != nsPresContext::eContext_Galley) {
    Maybe<dom::EffectsInfo> existing = mEffectsUpdates.MaybeGet(aBrowser);
    if (existing) {
      // Only the visible rect should differ, the scales should match.
      MOZ_ASSERT(existing->mRasterScale == aUpdate.mRasterScale &&
                 existing->mTransformToAncestorScale ==
                     aUpdate.mTransformToAncestorScale);
      if (existing->mVisibleRect) {
        if (update.mVisibleRect) {
          update.mVisibleRect =
              Some(update.mVisibleRect->Union(*existing->mVisibleRect));
        } else {
          update.mVisibleRect = existing->mVisibleRect;
        }
      }
    }
  }
  mEffectsUpdates.InsertOrUpdate(aBrowser, update);
}

void nsDisplayListBuilder::EndFrame() {
  NS_ASSERTION(!mInInvalidSubtree,
               "Someone forgot to cleanup mInInvalidSubtree!");
  mCurrentContainerASR = nullptr;
  mActiveScrolledRoots.Clear();
  mEffectsUpdates.Clear();
  FreeClipChains();
  FreeTemporaryItems();
  nsCSSRendering::EndFrameTreesLocked();
  mCaretFrame = nullptr;
}

void nsDisplayListBuilder::MarkFrameForDisplay(nsIFrame* aFrame,
                                               const nsIFrame* aStopAtFrame) {
  mFramesMarkedForDisplay.AppendElement(aFrame);
  for (nsIFrame* f = aFrame; f;
       f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
    if (f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
      return;
    }
    f->AddStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
    if (f == aStopAtFrame) {
      // we've reached a frame that we know will be painted, so we can stop.
      break;
    }
  }
}

void nsDisplayListBuilder::AddFrameMarkedForDisplayIfVisible(nsIFrame* aFrame) {
  mFramesMarkedForDisplayIfVisible.AppendElement(aFrame);
}

static void MarkFrameForDisplayIfVisibleInternal(nsIFrame* aFrame,
                                                 const nsIFrame* aStopAtFrame) {
  for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) {
    if (f->ForceDescendIntoIfVisible()) {
      return;
    }
    f->SetForceDescendIntoIfVisible(true);

    // This condition must match the condition in
    // nsLayoutUtils::GetParentOrPlaceholderFor which is used by
    // nsLayoutUtils::GetDisplayListParent
    if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) {
      nsIFrame* parent = f->GetParent();
      if (parent && !parent->ForceDescendIntoIfVisible()) {
        // If the GetDisplayListParent call is going to walk to a placeholder,
        // in rare cases the placeholder might be contained in a different
        // continuation from the oof. So we have to make sure to mark the oofs
        // parent. In the common case this doesn't make us do any extra work,
        // just changes the order in which we visit the frames since walking
        // through placeholders will walk through the parent, and we stop when
        // we find a ForceDescendIntoIfVisible bit set.
        MarkFrameForDisplayIfVisibleInternal(parent, aStopAtFrame);
      }
    }

    if (f == aStopAtFrame) {
      // we've reached a frame that we know will be painted, so we can stop.
      break;
    }
  }
}

void nsDisplayListBuilder::MarkFrameForDisplayIfVisible(
    nsIFrame* aFrame, const nsIFrame* aStopAtFrame) {
  AddFrameMarkedForDisplayIfVisible(aFrame);

  MarkFrameForDisplayIfVisibleInternal(aFrame, aStopAtFrame);
}

void nsDisplayListBuilder::SetIsRelativeToLayoutViewport() {
  mIsRelativeToLayoutViewport = true;
  UpdateShouldBuildAsyncZoomContainer();
}

void nsDisplayListBuilder::UpdateShouldBuildAsyncZoomContainer() {
  const Document* document = mReferenceFrame->PresContext()->Document();
  mBuildAsyncZoomContainer = !mIsRelativeToLayoutViewport &&
                             !document->Fullscreen() &&
                             nsLayoutUtils::AllowZoomingForDocument(document);
}

// Certain prefs may cause display list items to be added or removed when they
// are toggled. In those cases, we need to fully rebuild the display list.
bool nsDisplayListBuilder::ShouldRebuildDisplayListDueToPrefChange() {
  // If we transition between wrapping the RCD-RSF contents into an async
  // zoom container vs. not, we need to rebuild the display list. This only
  // happens when the zooming or container scrolling prefs are toggled
  // (manually by the user, or during test setup).
  bool didBuildAsyncZoomContainer = mBuildAsyncZoomContainer;
  UpdateShouldBuildAsyncZoomContainer();

  bool hadOverlayScrollbarsLastTime = mUseOverlayScrollbars;
  mUseOverlayScrollbars =
      !!LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars);

  bool alwaysLayerizedScrollbarsLastTime = mAlwaysLayerizeScrollbars;
  mAlwaysLayerizeScrollbars =
      StaticPrefs::layout_scrollbars_always_layerize_track();

  if (didBuildAsyncZoomContainer != mBuildAsyncZoomContainer) {
    return true;
  }

  if (hadOverlayScrollbarsLastTime != mUseOverlayScrollbars) {
    return true;
  }

  if (alwaysLayerizedScrollbarsLastTime != mAlwaysLayerizeScrollbars) {
    return true;
  }

  return false;
}

void nsDisplayListBuilder::AddScrollFrameToNotify(
    nsIScrollableFrame* aScrollFrame) {
  mScrollFramesToNotify.insert(aScrollFrame);
}

void nsDisplayListBuilder::NotifyAndClearScrollFrames() {
  for (const auto& it : mScrollFramesToNotify) {
    it->NotifyApzTransaction();
  }
  mScrollFramesToNotify.clear();
}

bool nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay(
    nsIFrame* aDirtyFrame, nsIFrame* aFrame, const nsRect& aVisibleRect,
    const nsRect& aDirtyRect) {
  MOZ_ASSERT(aFrame->GetParent() == aDirtyFrame);
  nsRect dirty;
  nsRect visible = OutOfFlowDisplayData::ComputeVisibleRectForFrame(
      this, aFrame, aVisibleRect, aDirtyRect, &dirty);
  if (!aFrame->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) &&
      visible.IsEmpty()) {
    return false;
  }

  // Only MarkFrameForDisplay if we're dirty. If this is a nested out-of-flow
  // frame, then it will also mark any outer frames to ensure that building
  // reaches the dirty feame.
  if (!dirty.IsEmpty() || aFrame->ForceDescendIntoIfVisible()) {
    MarkFrameForDisplay(aFrame, aDirtyFrame);
  }

  return true;
}

static void UnmarkFrameForDisplay(nsIFrame* aFrame,
                                  const nsIFrame* aStopAtFrame) {
  for (nsIFrame* f = aFrame; f;
       f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
    if (!f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
      return;
    }
    f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
    if (f == aStopAtFrame) {
      // we've reached a frame that we know will be painted, so we can stop.
      break;
    }
  }
}

static void UnmarkFrameForDisplayIfVisible(nsIFrame* aFrame) {
  for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) {
    if (!f->ForceDescendIntoIfVisible()) {
      return;
    }
    f->SetForceDescendIntoIfVisible(false);

    // This condition must match the condition in
    // nsLayoutUtils::GetParentOrPlaceholderFor which is used by
    // nsLayoutUtils::GetDisplayListParent
    if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) {
      nsIFrame* parent = f->GetParent();
      if (parent && parent->ForceDescendIntoIfVisible()) {
        // If the GetDisplayListParent call is going to walk to a placeholder,
        // in rare cases the placeholder might be contained in a different
        // continuation from the oof. So we have to make sure to mark the oofs
        // parent. In the common case this doesn't make us do any extra work,
        // just changes the order in which we visit the frames since walking
        // through placeholders will walk through the parent, and we stop when
        // we find a ForceDescendIntoIfVisible bit set.
        UnmarkFrameForDisplayIfVisible(f);
      }
    }
  }
}

nsDisplayListBuilder::~nsDisplayListBuilder() {
  NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0,
               "All frames should have been unmarked");
  NS_ASSERTION(mFramesWithOOFData.Length() == 0,
               "All OOF data should have been removed");
  NS_ASSERTION(mPresShellStates.Length() == 0,
               "All presshells should have been exited");

  DisplayItemClipChain* c = mFirstClipChainToDestroy;
  while (c) {
    DisplayItemClipChain* next = c->mNextClipChainToDestroy;
    c->DisplayItemClipChain::~DisplayItemClipChain();
    c = next;
  }

  MOZ_COUNT_DTOR(nsDisplayListBuilder);
}

uint32_t nsDisplayListBuilder::GetBackgroundPaintFlags() {
  uint32_t flags = 0;
  if (mSyncDecodeImages) {
    flags |= nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES;
  }
  if (mIsPaintingToWindow) {
    flags |= nsCSSRendering::PAINTBG_TO_WINDOW;
  }
  if (mUseHighQualityScaling) {
    flags |= nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING;
  }
  return flags;
}

// TODO(emilio): Maybe unify BackgroundPaintFlags and IamgeRendererFlags.
uint32_t nsDisplayListBuilder::GetImageRendererFlags() const {
  uint32_t flags = 0;
  if (mSyncDecodeImages) {
    flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
  }
  if (mIsPaintingToWindow) {
    flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
  }
  if (mUseHighQualityScaling) {
    flags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING;
  }
  return flags;
}

uint32_t nsDisplayListBuilder::GetImageDecodeFlags() const {
  uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
  if (mSyncDecodeImages) {
    flags |= imgIContainer::FLAG_SYNC_DECODE;
  } else {
    flags |= imgIContainer::FLAG_SYNC_DECODE_IF_FAST;
  }
  if (mIsPaintingToWindow || mUseHighQualityScaling) {
    flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
  }
  return flags;
}

void nsDisplayListBuilder::SubtractFromVisibleRegion(nsRegion* aVisibleRegion,
                                                     const nsRegion& aRegion) {
  if (aRegion.IsEmpty()) {
    return;
  }

  nsRegion tmp;
  tmp.Sub(*aVisibleRegion, aRegion);
  // Don't let *aVisibleRegion get too complex, but don't let it fluff out
  // to its bounds either, which can be very bad (see bug 516740).
  // Do let aVisibleRegion get more complex if by doing so we reduce its
  // area by at least half.
  if (tmp.GetNumRects() <= 15 || tmp.Area() <= aVisibleRegion->Area() / 2) {
    *aVisibleRegion = tmp;
  }
}

nsCaret* nsDisplayListBuilder::GetCaret() {
  RefPtr<nsCaret> caret = CurrentPresShellState()->mPresShell->GetCaret();
  return caret;
}

void nsDisplayListBuilder::IncrementPresShellPaintCount(PresShell* aPresShell) {
  if (mIsPaintingToWindow) {
    aPresShell->IncrementPaintCount();
  }
}

void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame,
                                          bool aPointerEventsNoneDoc) {
  PresShellState* state = mPresShellStates.AppendElement();
  state->mPresShell = aReferenceFrame->PresShell();
  state->mFirstFrameMarkedForDisplay = mFramesMarkedForDisplay.Length();
  state->mFirstFrameWithOOFData = mFramesWithOOFData.Length();

  nsIScrollableFrame* sf = state->mPresShell->GetRootScrollFrameAsScrollable();
  if (sf && IsInSubdocument()) {
    // We are forcing a rebuild of nsDisplayCanvasBackgroundColor to make sure
    // that the canvas background color will be set correctly, and that only one
    // unscrollable item will be created.
    // This is done to avoid, for example, a case where only scrollbar frames
    // are invalidated - we would skip creating nsDisplayCanvasBackgroundColor
    // and possibly end up with an extra nsDisplaySolidColor item.
    // We skip this for the root document, since we don't want to use
    // MarkFrameForDisplayIfVisible before ComputeRebuildRegion. We'll
    // do it manually there.
    nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
    if (canvasFrame) {
      MarkFrameForDisplayIfVisible(canvasFrame, aReferenceFrame);
    }
  }

#ifdef DEBUG
  state->mAutoLayoutPhase.emplace(aReferenceFrame->PresContext(),
                                  nsLayoutPhase::DisplayListBuilding);
#endif

  state->mPresShell->UpdateCanvasBackground();

  bool buildCaret = mBuildCaret;
  if (mIgnoreSuppression || !state->mPresShell->IsPaintingSuppressed()) {
    state->mIsBackgroundOnly = false;
  } else {
    state->mIsBackgroundOnly = true;
    buildCaret = false;
  }

  bool pointerEventsNone = aPointerEventsNoneDoc;
  if (IsInSubdocument()) {
    pointerEventsNone |= mPresShellStates[mPresShellStates.Length() - 2]
                             .mInsidePointerEventsNoneDoc;
  }
  state->mInsidePointerEventsNoneDoc = pointerEventsNone;

  state->mPresShellIgnoreScrollFrame =
      state->mPresShell->IgnoringViewportScrolling()
          ? state->mPresShell->GetRootScrollFrame()
          : nullptr;

  nsPresContext* pc = aReferenceFrame->PresContext();
  mIsInChromePresContext = pc->IsChrome();
  nsIDocShell* docShell = pc->GetDocShell();

  if (docShell) {
    docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed);
  }

  state->mTouchEventPrefEnabledDoc = dom::TouchEvent::PrefEnabled(docShell);

  if (!buildCaret) {
    return;
  }

  // Caret frames add visual area to their frame, but we don't update the
  // overflow area. Use flags to make sure we build display items for that frame
  // instead.
  if (mCaretFrame && mCaretFrame->PresShell() == state->mPresShell) {
    MarkFrameForDisplay(mCaretFrame, aReferenceFrame);
  }
}

// A non-blank paint is a paint that does not just contain the canvas
// background.
static bool DisplayListIsNonBlank(nsDisplayList* aList) {
  for (nsDisplayItem* i : *aList) {
    switch (i->GetType()) {
      case DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO:
      case DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR:
      case DisplayItemType::TYPE_CANVAS_BACKGROUND_IMAGE:
        continue;
      case DisplayItemType::TYPE_SOLID_COLOR:
      case DisplayItemType::TYPE_BACKGROUND:
      case DisplayItemType::TYPE_BACKGROUND_COLOR:
        if (i->Frame()->IsCanvasFrame()) {
          continue;
        }
        return true;
      default:
        return true;
    }
  }
  return false;
}

// A contentful paint is a paint that does contains DOM content (text,
// images, non-blank canvases, SVG): "First Contentful Paint entry
// contains a DOMHighResTimeStamp reporting the time when the browser
// first rendered any text, image (including background images),
// non-white canvas or SVG. This excludes any content of iframes, but
// includes text with pending webfonts. This is the first time users
// could start consuming page content."
static bool DisplayListIsContentful(nsDisplayListBuilder* aBuilder,
                                    nsDisplayList* aList) {
  for (nsDisplayItem* i : *aList) {
    DisplayItemType type = i->GetType();
    nsDisplayList* children = i->GetChildren();

    switch (type) {
      case DisplayItemType::TYPE_SUBDOCUMENT:  // iframes are ignored
        break;
      // CANVASes check if they may have been modified (as a stand-in
      // actually tracking all modifications)
      default:
        if (i->IsContentful()) {
          bool dummy;
          nsRect bound = i->GetBounds(aBuilder, &dummy);
          if (!bound.IsEmpty()) {
            return true;
          }
        }
        if (children) {
          if (DisplayListIsContentful(aBuilder, children)) {
            return true;
          }
        }
        break;
    }
  }
  return false;
}

void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame,
                                          nsDisplayList* aPaintedContents) {
  NS_ASSERTION(
      CurrentPresShellState()->mPresShell == aReferenceFrame->PresShell(),
      "Presshell mismatch");

  if (mIsPaintingToWindow && aPaintedContents) {
    nsPresContext* pc = aReferenceFrame->PresContext();
    if (!pc->HadNonBlankPaint()) {
      if (!CurrentPresShellState()->mIsBackgroundOnly &&
          DisplayListIsNonBlank(aPaintedContents)) {
        pc->NotifyNonBlankPaint();
      }
    }
    nsRootPresContext* rootPresContext = pc->GetRootPresContext();
    if (!pc->HadFirstContentfulPaint() && rootPresContext) {
      if (!CurrentPresShellState()->mIsBackgroundOnly) {
        if (pc->HasEverBuiltInvisibleText() ||
            DisplayListIsContentful(this, aPaintedContents)) {
          pc->NotifyContentfulPaint();
        }
      }
    }
  }

  ResetMarkedFramesForDisplayList(aReferenceFrame);
  mPresShellStates.RemoveLastElement();

  if (!mPresShellStates.IsEmpty()) {
    nsPresContext* pc = CurrentPresContext();
    nsIDocShell* docShell = pc->GetDocShell();
    if (docShell) {
      docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed);
    }
    mIsInChromePresContext = pc->IsChrome();
  } else {
    for (uint32_t i = 0; i < mFramesMarkedForDisplayIfVisible.Length(); ++i) {
      UnmarkFrameForDisplayIfVisible(mFramesMarkedForDisplayIfVisible[i]);
    }
    mFramesMarkedForDisplayIfVisible.SetLength(0);
  }
}

void nsDisplayListBuilder::FreeClipChains() {
  // Iterate the clip chains from newest to oldest (forward
  // iteration), so that we destroy descendants first which
  // will drop the ref count on their ancestors.
  DisplayItemClipChain** indirect = &mFirstClipChainToDestroy;

  while (*indirect) {
    if (!(*indirect)->mRefCount) {
      DisplayItemClipChain* next = (*indirect)->mNextClipChainToDestroy;

      mClipDeduplicator.erase(*indirect);
      (*indirect)->DisplayItemClipChain::~DisplayItemClipChain();
      Destroy(DisplayListArenaObjectId::CLIPCHAIN, *indirect);

      *indirect = next;
    } else {
      indirect = &(*indirect)->mNextClipChainToDestroy;
    }
  }
}

void nsDisplayListBuilder::FreeTemporaryItems() {
  for (nsDisplayItem* i : mTemporaryItems) {
    // Temporary display items are not added to the frames.
    MOZ_ASSERT(i->Frame());
    i->RemoveFrame(i->Frame());
    i->Destroy(this);
  }

  mTemporaryItems.Clear();
}

void nsDisplayListBuilder::ResetMarkedFramesForDisplayList(
    const nsIFrame* aReferenceFrame) {
  // Unmark and pop off the frames marked for display in this pres shell.
  uint32_t firstFrameForShell =
      CurrentPresShellState()->mFirstFrameMarkedForDisplay;
  for (uint32_t i = firstFrameForShell; i < mFramesMarkedForDisplay.Length();
       ++i) {
    UnmarkFrameForDisplay(mFramesMarkedForDisplay[i], aReferenceFrame);
  }
  mFramesMarkedForDisplay.SetLength(firstFrameForShell);

  firstFrameForShell = CurrentPresShellState()->mFirstFrameWithOOFData;
  for (uint32_t i = firstFrameForShell; i < mFramesWithOOFData.Length(); ++i) {
    mFramesWithOOFData[i]->RemoveProperty(OutOfFlowDisplayDataProperty());
  }
  mFramesWithOOFData.SetLength(firstFrameForShell);
}

void nsDisplayListBuilder::ClearFixedBackgroundDisplayData() {
  CurrentPresShellState()->mFixedBackgroundDisplayData = Nothing();
}

void nsDisplayListBuilder::MarkFramesForDisplayList(
    nsIFrame* aDirtyFrame, const nsFrameList& aFrames) {
  nsRect visibleRect = GetVisibleRect();
  nsRect dirtyRect = GetDirtyRect();

  // If we are entering content that is fixed to the RCD-RSF, we are
  // crossing the async zoom container boundary, and need to convert from
  // visual to layout coordinates.
  if (ViewportFrame* viewportFrame = do_QueryFrame(aDirtyFrame)) {
    if (IsForEventDelivery() && ShouldBuildAsyncZoomContainer() &&
        viewportFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
      if (viewportFrame->PresShell()->GetRootScrollFrame()) {
#ifdef DEBUG
        for (nsIFrame* f : aFrames) {
          MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(f));
        }
#endif
        visibleRect = ViewportUtils::VisualToLayout(visibleRect,
                                                    viewportFrame->PresShell());
        dirtyRect = ViewportUtils::VisualToLayout(dirtyRect,
                                                  viewportFrame->PresShell());
      }
#ifdef DEBUG
      else {
        // This is an edge case that should only happen if we are in a
        // document with a XUL root element so that it does not have a root
        // scroll frame but it has fixed pos content and all of the frames in
        // aFrames are that fixed pos content.
        for (nsIFrame* f : aFrames) {
          MOZ_ASSERT(!ViewportUtils::IsZoomedContentRoot(f) &&
                     f->GetParent() == aDirtyFrame &&
                     f->StyleDisplay()->mPosition ==
                         StylePositionProperty::Fixed);
        }
        // There's no root scroll frame so there can't be any zooming or async
        // panning so we don't need to adjust the visible and dirty rects.
      }
#endif
    }
  }

  bool markedFrames = false;
  for (nsIFrame* e : aFrames) {
    // Skip the AccessibleCaret frame when building no caret.
    if (!IsBuildingCaret()) {
      nsIContent* content = e->GetContent();
      if (content && content->IsInNativeAnonymousSubtree() &&
          content->IsElement()) {
        const nsAttrValue* classes = content->AsElement()->GetClasses();
        if (classes &&
            classes->Contains(nsGkAtoms::mozAccessiblecaret, eCaseMatters)) {
          continue;
        }
      }
    }
    if (MarkOutOfFlowFrameForDisplay(aDirtyFrame, e, visibleRect, dirtyRect)) {
      markedFrames = true;
    }
  }

  if (markedFrames) {
    // mClipState.GetClipChainForContainingBlockDescendants can return pointers
    // to objects on the stack, so we need to clone the chain.
    const DisplayItemClipChain* clipChain =
        CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants());
    const DisplayItemClipChain* combinedClipChain =
        mClipState.GetCurrentCombinedClipChain(this);
    const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot;

    OutOfFlowDisplayData* data = new OutOfFlowDisplayData(
        clipChain, combinedClipChain, asr, this->mCurrentScrollParentId,
        visibleRect, dirtyRect);
    aDirtyFrame->SetProperty(
        nsDisplayListBuilder::OutOfFlowDisplayDataProperty(), data);
    mFramesWithOOFData.AppendElement(aDirtyFrame);
  }

  if (!aDirtyFrame->GetParent()) {
    // This is the viewport frame of aDirtyFrame's presshell.
    // Store the current display data so that it can be used for fixed
    // background images.
    NS_ASSERTION(
        CurrentPresShellState()->mPresShell == aDirtyFrame->PresShell(),
        "Presshell mismatch");
    MOZ_ASSERT(!CurrentPresShellState()->mFixedBackgroundDisplayData,
               "already traversed this presshell's root frame?");

    const DisplayItemClipChain* clipChain =
        CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants());
    const DisplayItemClipChain* combinedClipChain =
        mClipState.GetCurrentCombinedClipChain(this);
    const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot;
    CurrentPresShellState()->mFixedBackgroundDisplayData.emplace(
        clipChain, combinedClipChain, asr, this->mCurrentScrollParentId,
        GetVisibleRect(), GetDirtyRect());
  }
}

/**
 * Mark all preserve-3d children with
 * NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO to make sure
 * nsIFrame::BuildDisplayListForChild() would visit them.  Also compute
 * dirty rect for preserve-3d children.
 *
 * @param aDirtyFrame is the frame to mark children extending context.
 */
void nsDisplayListBuilder::MarkPreserve3DFramesForDisplayList(
    nsIFrame* aDirtyFrame) {
  for (const auto& childList : aDirtyFrame->ChildLists()) {
    for (nsIFrame* child : childList.mList) {
      if (child->Combines3DTransformWithAncestors()) {
        MarkFrameForDisplay(child, aDirtyFrame);
      }

      if (child->IsBlockWrapper()) {
        // Mark preserve-3d frames inside the block wrapper.
        MarkPreserve3DFramesForDisplayList(child);
      }
    }
  }
}

ActiveScrolledRoot* nsDisplayListBuilder::AllocateActiveScrolledRoot(
    const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame) {
  RefPtr<ActiveScrolledRoot> asr = ActiveScrolledRoot::CreateASRForFrame(
      aParent, aScrollableFrame, IsRetainingDisplayList());
  mActiveScrolledRoots.AppendElement(asr);
  return asr;
}

const DisplayItemClipChain* nsDisplayListBuilder::AllocateDisplayItemClipChain(
    const DisplayItemClip& aClip, const ActiveScrolledRoot* aASR,
    const DisplayItemClipChain* aParent) {
  MOZ_DIAGNOSTIC_ASSERT(!(aParent && aParent->mOnStack));
  void* p = Allocate(sizeof(DisplayItemClipChain),
                     DisplayListArenaObjectId::CLIPCHAIN);
  DisplayItemClipChain* c = new (KnownNotNull, p)
      DisplayItemClipChain(aClip, aASR, aParent, mFirstClipChainToDestroy);
#if defined(DEBUG) || defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
  c->mOnStack = false;
#endif
  auto result = mClipDeduplicator.insert(c);
  if (!result.second) {
    // An equivalent clip chain item was already created, so let's return that
    // instead. Destroy the one we just created.
    // Note that this can cause clip chains from different coordinate systems to
    // collapse into the same clip chain object, because clip chains do not keep
    // track of the reference frame that they were created in.
    c->DisplayItemClipChain::~DisplayItemClipChain();
    Destroy(DisplayListArenaObjectId::CLIPCHAIN, c);
    return *(result.first);
  }
  mFirstClipChainToDestroy = c;
  return c;
}

struct ClipChainItem {
  DisplayItemClip clip;
  const ActiveScrolledRoot* asr;
};

static const DisplayItemClipChain* FindCommonAncestorClipForIntersection(
    const DisplayItemClipChain* aOne, const DisplayItemClipChain* aTwo) {
  for (const ActiveScrolledRoot* asr =
           ActiveScrolledRoot::PickDescendant(aOne->mASR, aTwo->mASR);
       asr; asr = asr->mParent) {
    if (aOne == aTwo) {
      return aOne;
    }
    if (aOne->mASR == asr) {
      aOne = aOne->mParent;
    }
    if (aTwo->mASR == asr) {
      aTwo = aTwo->mParent;
    }
    if (!aOne) {
      return aTwo;
    }
    if (!aTwo) {
      return aOne;
    }
  }
  return nullptr;
}

const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection(
    const DisplayItemClipChain* aAncestor,
    const DisplayItemClipChain* aLeafClip1,
    const DisplayItemClipChain* aLeafClip2) {
  AutoTArray<ClipChainItem, 8> intersectedClips;

  const DisplayItemClipChain* clip1 = aLeafClip1;
  const DisplayItemClipChain* clip2 = aLeafClip2;

  const ActiveScrolledRoot* asr = ActiveScrolledRoot::PickDescendant(
      clip1 ? clip1->mASR : nullptr, clip2 ? clip2->mASR : nullptr);

  // Build up the intersection from the leaf to the root and put it into
  // intersectedClips. The loop below will convert intersectedClips into an
  // actual DisplayItemClipChain.
  // (We need to do this in two passes because we need the parent clip in order
  // to create the DisplayItemClipChain object, but the parent clip has not
  // been created at that point.)
  while (!aAncestor || asr != aAncestor->mASR) {
    if (clip1 && clip1->mASR == asr) {
      if (clip2 && clip2->mASR == asr) {
        DisplayItemClip intersection = clip1->mClip;
        intersection.IntersectWith(clip2->mClip);
        intersectedClips.AppendElement(ClipChainItem{intersection, asr});
        clip2 = clip2->mParent;
      } else {
        intersectedClips.AppendElement(ClipChainItem{clip1->mClip, asr});
      }
      clip1 = clip1->mParent;
    } else if (clip2 && clip2->mASR == asr) {
      intersectedClips.AppendElement(ClipChainItem{clip2->mClip, asr});
      clip2 = clip2->mParent;
    }
    if (!asr) {
      MOZ_ASSERT(!aAncestor, "We should have exited this loop earlier");
      break;
    }
    asr = asr->mParent;
  }

  // Convert intersectedClips into a DisplayItemClipChain.
  const DisplayItemClipChain* parentSC = aAncestor;
  for (auto& sc : Reversed(intersectedClips)) {
    parentSC = AllocateDisplayItemClipChain(sc.clip, sc.asr, parentSC);
  }
  return parentSC;
}

const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection(
    const DisplayItemClipChain* aLeafClip1,
    const DisplayItemClipChain* aLeafClip2) {
  // aLeafClip2 might be a reference to a clip on the stack. We need to make
  // sure that CreateClipChainIntersection will allocate the actual intersected
  // clip in the builder's arena, so for the aLeafClip1 == nullptr case,
  // we supply nullptr as the common ancestor so that
  // CreateClipChainIntersection clones the whole chain.
  const DisplayItemClipChain* ancestorClip =
      aLeafClip1 ? FindCommonAncestorClipForIntersection(aLeafClip1, aLeafClip2)
                 : nullptr;

  return CreateClipChainIntersection(ancestorClip, aLeafClip1, aLeafClip2);
}

const DisplayItemClipChain* nsDisplayListBuilder::CopyWholeChain(
    const DisplayItemClipChain* aClipChain) {
  return CreateClipChainIntersection(nullptr, aClipChain, nullptr);
}

const nsIFrame* nsDisplayListBuilder::FindReferenceFrameFor(
    const nsIFrame* aFrame, nsPoint* aOffset) const {
  auto MaybeApplyAdditionalOffset = [&]() {
    if (auto offset = AdditionalOffset()) {
      *aOffset += *offset;
    }
  };

  if (aFrame == mCurrentFrame) {
    if (aOffset) {
      *aOffset = mCurrentOffsetToReferenceFrame;
    }
    return mCurrentReferenceFrame;
  }

  for (const nsIFrame* f = aFrame; f;
       f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
    if (f == mReferenceFrame || f->IsTransformed()) {
      if (aOffset) {
        *aOffset = aFrame->GetOffsetToCrossDoc(f);
        MaybeApplyAdditionalOffset();
      }
      return f;
    }
  }

  if (aOffset) {
    *aOffset = aFrame->GetOffsetToCrossDoc(mReferenceFrame);
    MaybeApplyAdditionalOffset();
  }

  return mReferenceFrame;
}

// Sticky frames are active if their nearest scrollable frame is also active.
static bool IsStickyFrameActive(nsDisplayListBuilder* aBuilder,
                                nsIFrame* aFrame) {
  MOZ_ASSERT(aFrame->StyleDisplay()->mPosition ==
             StylePositionProperty::Sticky);

  StickyScrollContainer* stickyScrollContainer =
      StickyScrollContainer::GetStickyScrollContainerForFrame(aFrame);
  return stickyScrollContainer &&
         stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled();
}

bool nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame,
                                                  nsIFrame** aParent) {
  if (aFrame == mReferenceFrame) {
    return true;
  }

  if (!IsPaintingToWindow()) {
    if (aParent) {
      *aParent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
    }
    return false;
  }

  nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
  if (!parent) {
    return true;
  }
  *aParent = parent;

  if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Sticky &&
      IsStickyFrameActive(this, aFrame)) {
    return true;
  }

  if (aFrame->IsTransformed()) {
    if (EffectCompositor::HasAnimationsForCompositor(
            aFrame, DisplayItemType::TYPE_TRANSFORM)) {
      return true;
    }
  }

  LayoutFrameType parentType = parent->Type();
  if (parentType == LayoutFrameType::Scroll ||
      parentType == LayoutFrameType::ListControl) {
    nsIScrollableFrame* sf = do_QueryFrame(parent);
    if (sf->GetScrolledFrame() == aFrame) {
      MOZ_ASSERT(!aFrame->IsTransformed());
      return sf->IsMaybeAsynchronouslyScrolled();
    }
  }

  return false;
}

nsIFrame* nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor(
    nsIFrame* aFrame) {
  MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
      RootReferenceFrame(), aFrame));
  nsIFrame* cursor = aFrame;
  while (cursor != RootReferenceFrame()) {
    nsIFrame* next;
    if (IsAnimatedGeometryRoot(cursor, &next)) {
      return cursor;
    }
    cursor = next;
  }
  return cursor;
}

static nsRect ApplyAllClipNonRoundedIntersection(
    const DisplayItemClipChain* aClipChain, const nsRect& aRect) {
  nsRect result = aRect;
  while (aClipChain) {
    result = aClipChain->mClip.ApplyNonRoundedIntersection(result);
    aClipChain = aClipChain->mParent;
  }
  return result;
}

void nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame) {
  if (!mWindowDraggingAllowed || !IsForPainting()) {
    return;
  }

  const nsStyleUIReset* styleUI = aFrame->StyleUIReset();
  if (styleUI->mWindowDragging == StyleWindowDragging::Default) {
    // This frame has the default value and doesn't influence the window
    // dragging region.
    return;
  }

  LayoutDeviceToLayoutDeviceMatrix4x4 referenceFrameToRootReferenceFrame;

  // The const_cast is for nsLayoutUtils::GetTransformToAncestor.
  nsIFrame* referenceFrame =
      const_cast<nsIFrame*>(FindReferenceFrameFor(aFrame));

  if (IsInTransform()) {
    // Only support 2d rectilinear transforms. Transform support is needed for
    // the horizontal flip transform that's applied to the urlbar textbox in
    // RTL mode - it should be able to exclude itself from the draggable region.
    referenceFrameToRootReferenceFrame =
        ViewAs<LayoutDeviceToLayoutDeviceMatrix4x4>(
            nsLayoutUtils::GetTransformToAncestor(RelativeTo{referenceFrame},
                                                  RelativeTo{mReferenceFrame})
                .GetMatrix());
    Matrix referenceFrameToRootReferenceFrame2d;
    if (!referenceFrameToRootReferenceFrame.Is2D(
            &referenceFrameToRootReferenceFrame2d) ||
        !referenceFrameToRootReferenceFrame2d.IsRectilinear()) {
      return;
    }
  } else {
    MOZ_ASSERT(referenceFrame == mReferenceFrame,
               "referenceFrameToRootReferenceFrame needs to be adjusted");
  }

  // We do some basic visibility checking on the frame's border box here.
  // We intersect it both with the current dirty rect and with the current
  // clip. Either one is just a conservative approximation on its own, but
  // their intersection luckily works well enough for our purposes, so that
  // we don't have to do full-blown visibility computations.
  // The most important case we need to handle is the scrolled-off tab:
  // If the tab bar overflows, tab parts that are clipped by the scrollbox
  // should not be allowed to interfere with the window dragging region. Using
  // just the current DisplayItemClip is not enough to cover this case
  // completely because clips are reset while building stacking context
  // contents, so for example we'd fail to clip frames that have a clip path
  // applied to them. But the current dirty rect doesn't get reset in that
  // case, so we use it to make this case work.
  nsRect borderBox = aFrame->GetRectRelativeToSelf().Intersect(mVisibleRect);
  borderBox += ToReferenceFrame(aFrame);
  const DisplayItemClipChain* clip =
      ClipState().GetCurrentCombinedClipChain(this);
  borderBox = ApplyAllClipNonRoundedIntersection(clip, borderBox);
  if (borderBox.IsEmpty()) {
    return;
  }

  LayoutDeviceRect devPixelBorderBox = LayoutDevicePixel::FromAppUnits(
      borderBox, aFrame->PresContext()->AppUnitsPerDevPixel());

  LayoutDeviceRect transformedDevPixelBorderBox =
      TransformBy(referenceFrameToRootReferenceFrame, devPixelBorderBox);
  transformedDevPixelBorderBox.Round();
  LayoutDeviceIntRect transformedDevPixelBorderBoxInt;

  if (!transformedDevPixelBorderBox.ToIntRect(
          &transformedDevPixelBorderBoxInt)) {
    return;
  }

  LayoutDeviceIntRegion& region =
      styleUI->mWindowDragging == StyleWindowDragging::Drag
          ? mWindowDraggingRegion
          : mWindowNoDraggingRegion;

  if (!IsRetainingDisplayList()) {
    region.OrWith(transformedDevPixelBorderBoxInt);
    return;
  }

  gfx::IntRect rect(transformedDevPixelBorderBoxInt.ToUnknownRect());
  if (styleUI->mWindowDragging == StyleWindowDragging::Drag) {
    mRetainedWindowDraggingRegion.Add(aFrame, rect);
  } else {
    mRetainedWindowNoDraggingRegion.Add(aFrame, rect);
  }
}

LayoutDeviceIntRegion nsDisplayListBuilder::GetWindowDraggingRegion() const {
  LayoutDeviceIntRegion result;
  if (!IsRetainingDisplayList()) {
    result.Sub(mWindowDraggingRegion, mWindowNoDraggingRegion);
    return result;
  }

  LayoutDeviceIntRegion dragRegion =
      mRetainedWindowDraggingRegion.ToLayoutDeviceIntRegion();

  LayoutDeviceIntRegion noDragRegion =
      mRetainedWindowNoDraggingRegion.ToLayoutDeviceIntRegion();

  result.Sub(dragRegion, noDragRegion);
  return result;
}

void nsDisplayTransform::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
  nsPaintedDisplayItem::AddSizeOfExcludingThis(aSizes);
  aSizes.mLayoutRetainedDisplayListSize +=
      aSizes.mState.mMallocSizeOf(mTransformPreserves3D.get());
}

void nsDisplayListBuilder::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
  mPool.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::DisplayList);

  size_t n = 0;
  MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
  n += mDocumentWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf);
  n += mFrameWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf);
  n += mEffectsUpdates.ShallowSizeOfExcludingThis(mallocSizeOf);
  n += mRetainedWindowDraggingRegion.SizeOfExcludingThis(mallocSizeOf);
  n += mRetainedWindowNoDraggingRegion.SizeOfExcludingThis(mallocSizeOf);
  n += mRetainedWindowOpaqueRegion.SizeOfExcludingThis(mallocSizeOf);
  // XXX can't measure mClipDeduplicator since it uses std::unordered_set.

  aSizes.mLayoutRetainedDisplayListSize += n;
}

void RetainedDisplayList::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
  for (nsDisplayItem* item : *this) {
    item->AddSizeOfExcludingThis(aSizes);
    if (RetainedDisplayList* children = item->GetChildren()) {
      children->AddSizeOfExcludingThis(aSizes);
    }
  }

  size_t n = 0;

  n += mDAG.mDirectPredecessorList.ShallowSizeOfExcludingThis(
      aSizes.mState.mMallocSizeOf);
  n += mDAG.mNodesInfo.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
  n += mOldItems.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf);

  aSizes.mLayoutRetainedDisplayListSize += n;
}

size_t nsDisplayListBuilder::WeakFrameRegion::SizeOfExcludingThis(
    MallocSizeOf aMallocSizeOf) const {
  size_t n = 0;
  n += mFrames.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (const auto& frame : mFrames) {
    const UniquePtr<WeakFrame>& weakFrame = frame.mWeakFrame;
    n += aMallocSizeOf(weakFrame.get());
  }
  n += mRects.ShallowSizeOfExcludingThis(aMallocSizeOf);
  return n;
}

/**
 * Removes modified frames and rects from this WeakFrameRegion.
 */
void nsDisplayListBuilder::WeakFrameRegion::RemoveModifiedFramesAndRects() {
  MOZ_ASSERT(mFrames.Length() == mRects.Length());

  uint32_t i = 0;
  uint32_t length = mFrames.Length();

  while (i < length) {
    auto& wrapper = mFrames[i];

    if (!wrapper.mWeakFrame->IsAlive() ||
        AnyContentAncestorModified(wrapper.mWeakFrame->GetFrame())) {
      // To avoid multiple O(n) shifts in the array, move the last element of
      // the array to the current position and decrease the array length.
      mFrameSet.Remove(wrapper.mFrame);
      mFrames[i] = std::move(mFrames[length - 1]);
      mRects[i] = std::move(mRects[length - 1]);
      length--;
    } else {
      i++;
    }
  }

  mFrames.TruncateLength(length);
  mRects.TruncateLength(length);
}

void nsDisplayListBuilder::RemoveModifiedWindowRegions() {
  mRetainedWindowDraggingRegion.RemoveModifiedFramesAndRects();
  mRetainedWindowNoDraggingRegion.RemoveModifiedFramesAndRects();
  mRetainedWindowOpaqueRegion.RemoveModifiedFramesAndRects();
}

void nsDisplayListBuilder::ClearRetainedWindowRegions() {
  mRetainedWindowDraggingRegion.Clear();
  mRetainedWindowNoDraggingRegion.Clear();
  mRetainedWindowOpaqueRegion.Clear();
}

const uint32_t gWillChangeAreaMultiplier = 3;
static uint32_t GetLayerizationCost(const nsSize& aSize) {
  // There's significant overhead for each layer created from Gecko
  // (IPC+Shared Objects) and from the backend (like an OpenGL texture).
  // Therefore we set a minimum cost threshold of a 64x64 area.
  const int minBudgetCost = 64 * 64;

  const uint32_t budgetCost = std::max(
      minBudgetCost, nsPresContext::AppUnitsToIntCSSPixels(aSize.width) *
                         nsPresContext::AppUnitsToIntCSSPixels(aSize.height));

  return budgetCost;
}

bool nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame,
                                                 const nsSize& aSize) {
  MOZ_ASSERT(IsForPainting());

  if (aFrame->MayHaveWillChangeBudget()) {
    // The frame is already in the will-change budget.
    return true;
  }

  const nsPresContext* presContext = aFrame->PresContext();
  const nsRect area = presContext->GetVisibleArea();
  const uint32_t budgetLimit =
      nsPresContext::AppUnitsToIntCSSPixels(area.width) *
      nsPresContext::AppUnitsToIntCSSPixels(area.height);
  const uint32_t cost = GetLayerizationCost(aSize);

  DocumentWillChangeBudget& documentBudget =
      mDocumentWillChangeBudgets.LookupOrInsert(presContext);

  const bool onBudget =
      (documentBudget + cost) / gWillChangeAreaMultiplier < budgetLimit;

  if (onBudget) {
    documentBudget += cost;
    mFrameWillChangeBudgets.InsertOrUpdate(
        aFrame, FrameWillChangeBudget(presContext, cost));
    aFrame->SetMayHaveWillChangeBudget(true);
  }

  return onBudget;
}

bool nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame,
                                                const nsSize& aSize) {
  if (!IsForPainting()) {
    // If this nsDisplayListBuilder is not for painting, the layerization should
    // not matter. Do the simple thing and return false.
    return false;
  }

  const bool onBudget = AddToWillChangeBudget(aFrame, aSize);
  if (onBudget) {
    return true;
  }

  auto* pc = aFrame->PresContext();
  auto* doc = pc->Document();
  if (!doc->HasWarnedAbout(Document::eIgnoringWillChangeOverBudget)) {
    AutoTArray<nsString, 2> params;
    params.AppendElement()->AppendInt(gWillChangeAreaMultiplier);

    nsRect area = pc->GetVisibleArea();
    uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
                           nsPresContext::AppUnitsToIntCSSPixels(area.height);
    params.AppendElement()->AppendInt(budgetLimit);

    doc->WarnOnceAbout(Document::eIgnoringWillChangeOverBudget, false, params);
  }

  return false;
}

void nsDisplayListBuilder::ClearWillChangeBudgetStatus(nsIFrame* aFrame) {
  MOZ_ASSERT(IsForPainting());

  if (!aFrame->MayHaveWillChangeBudget()) {
    return;
  }

  aFrame->SetMayHaveWillChangeBudget(false);
  RemoveFromWillChangeBudgets(aFrame);
}

void nsDisplayListBuilder::RemoveFromWillChangeBudgets(const nsIFrame* aFrame) {
  if (auto entry = mFrameWillChangeBudgets.Lookup(aFrame)) {
    const FrameWillChangeBudget& frameBudget = entry.Data();

    auto documentBudget =
        mDocumentWillChangeBudgets.Lookup(frameBudget.mPresContext);

    if (documentBudget) {
      *documentBudget -= frameBudget.mUsage;
    }

    entry.Remove();
  }
}

void nsDisplayListBuilder::ClearWillChangeBudgets() {
  mFrameWillChangeBudgets.Clear();
  mDocumentWillChangeBudgets.Clear();
}

void nsDisplayListBuilder::EnterSVGEffectsContents(
    nsIFrame* aEffectsFrame, nsDisplayList* aHoistedItemsStorage) {
  MOZ_ASSERT(aHoistedItemsStorage);
  if (mSVGEffectsFrames.IsEmpty()) {
    MOZ_ASSERT(!mScrollInfoItemsForHoisting);
    mScrollInfoItemsForHoisting = aHoistedItemsStorage;
  }
  mSVGEffectsFrames.AppendElement(aEffectsFrame);
}

void nsDisplayListBuilder::ExitSVGEffectsContents() {
  MOZ_ASSERT(!mSVGEffectsFrames.IsEmpty());
  mSVGEffectsFrames.RemoveLastElement();
  MOZ_ASSERT(mScrollInfoItemsForHoisting);
  if (mSVGEffectsFrames.IsEmpty()) {
    mScrollInfoItemsForHoisting = nullptr;
  }
}

bool nsDisplayListBuilder::ShouldBuildScrollInfoItemsForHoisting() const {
  /*
   * Note: if changing the conditions under which scroll info layers
   * are created, make a corresponding change to
   * ScrollFrameWillBuildScrollInfoLayer() in nsSliderFrame.cpp.
   */
  for (nsIFrame* frame : mSVGEffectsFrames) {
    if (SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(frame)) {
      return true;
    }
  }
  return false;
}

void nsDisplayListBuilder::AppendNewScrollInfoItemForHoisting(
    nsDisplayScrollInfoLayer* aScrollInfoItem) {
  MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting());
  MOZ_ASSERT(mScrollInfoItemsForHoisting);
  mScrollInfoItemsForHoisting->AppendToTop(aScrollInfoItem);
}

void nsDisplayListBuilder::BuildCompositorHitTestInfoIfNeeded(
    nsIFrame* aFrame, nsDisplayList* aList) {
  MOZ_ASSERT(aFrame);
  MOZ_ASSERT(aList);

  if (!BuildCompositorHitTestInfo()) {
    return;
  }

  const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(this);
  if (info != CompositorHitTestInvisibleToHit) {
    aList->AppendNewToTop<nsDisplayCompositorHitTestInfo>(this, aFrame);
  }
}

void nsDisplayListBuilder::AddReusableDisplayItem(nsDisplayItem* aItem) {
  mReuseableItems.Insert(aItem);
}

void nsDisplayListBuilder::RemoveReusedDisplayItem(nsDisplayItem* aItem) {
  MOZ_ASSERT(aItem->IsReusedItem());
  mReuseableItems.Remove(aItem);
}

void nsDisplayListBuilder::ClearReuseableDisplayItems() {
  const size_t total = mReuseableItems.Count();

  size_t reused = 0;
  for (auto* item : mReuseableItems) {
    if (item->IsReusedItem()) {
      reused++;
      item->SetReusable();
    } else {
      item->Destroy(this);
    }
  }

  DL_LOGI("RDL - Reused %zu of %zu SC display items", reused, total);
  mReuseableItems.Clear();
}

void nsDisplayListBuilder::ReuseDisplayItem(nsDisplayItem* aItem) {
  const auto* previous = mCurrentContainerASR;
  const auto* asr = aItem->GetActiveScrolledRoot();
  mCurrentContainerASR =
      ActiveScrolledRoot::PickAncestor(asr, mCurrentContainerASR);

  if (previous != mCurrentContainerASR) {
    DL_LOGV("RDL - Changed mCurrentContainerASR from %p to %p", previous,
            mCurrentContainerASR);
  }

  aItem->SetReusedItem();
}

void nsDisplayListSet::CopyTo(const nsDisplayListSet& aDestination) const {
  for (size_t i = 0; i < mLists.size(); ++i) {
    auto* from = mLists[i];
    auto* to = aDestination.mLists[i];

    from->CopyTo(to);
  }
}

void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const {
  aDestination.BorderBackground()->AppendToTop(BorderBackground());
  aDestination.BlockBorderBackgrounds()->AppendToTop(BlockBorderBackgrounds());
  aDestination.Floats()->AppendToTop(Floats());
  aDestination.Content()->AppendToTop(Content());
  aDestination.PositionedDescendants()->AppendToTop(PositionedDescendants());
  aDestination.Outlines()->AppendToTop(Outlines());
}

nsRect nsDisplayList::GetClippedBounds(nsDisplayListBuilder* aBuilder) const {
  nsRect bounds;
  for (nsDisplayItem* i : *this) {
    bounds.UnionRect(bounds, i->GetClippedBounds(aBuilder));
  }
  return bounds;
}

nsRect nsDisplayList::GetClippedBoundsWithRespectToASR(
    nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR,
    nsRect* aBuildingRect) const {
  nsRect bounds;
  for (nsDisplayItem* i : *this) {
    nsRect r = i->GetClippedBounds(aBuilder);
    if (aASR != i->GetActiveScrolledRoot() && !r.IsEmpty()) {
      if (Maybe<nsRect> clip = i->GetClipWithRespectToASR(aBuilder, aASR)) {
        r = clip.ref();
      }
    }
    if (aBuildingRect) {
      aBuildingRect->UnionRect(*aBuildingRect, i->GetBuildingRect());
    }
    bounds.UnionRect(bounds, r);
  }
  return bounds;
}

nsRect nsDisplayList::GetBuildingRect() const {
  nsRect result;
  for (nsDisplayItem* i : *this) {
    result.UnionRect(result, i->GetBuildingRect());
  }
  return result;
}

static void TriggerPendingAnimations(Document& aDoc,
                                     const TimeStamp& aReadyTime) {
  MOZ_ASSERT(!aReadyTime.IsNull(),
             "Animation ready time is not set. Perhaps we're using a layer"
             " manager that doesn't update it");
  if (PendingAnimationTracker* tracker = aDoc.GetPendingAnimationTracker()) {
    PresShell* presShell = aDoc.GetPresShell();
    // If paint-suppression is in effect then we haven't finished painting
    // this document yet so we shouldn't start animations
    if (!presShell || !presShell->IsPaintingSuppressed()) {
      tracker->TriggerPendingAnimationsOnNextTick(aReadyTime);
    }
  }
  auto recurse = [&aReadyTime](Document& aDoc) {
    TriggerPendingAnimations(aDoc, aReadyTime);
    return CallState::Continue;
  };
  aDoc.EnumerateSubDocuments(recurse);
}

WindowRenderer* nsDisplayListBuilder::GetWidgetWindowRenderer(nsView** aView) {
  if (aView) {
    *aView = RootReferenceFrame()->GetView();
  }
  if (RootReferenceFrame() !=
      nsLayoutUtils::GetDisplayRootFrame(RootReferenceFrame())) {
    return nullptr;
  }
  nsIWidget* window = RootReferenceFrame()->GetNearestWidget();
  if (window) {
    return window->GetWindowRenderer();
  }
  return nullptr;
}

WebRenderLayerManager* nsDisplayListBuilder::GetWidgetLayerManager(
    nsView** aView) {
  WindowRenderer* renderer = GetWidgetWindowRenderer();
  if (renderer) {
    return renderer->AsWebRender();
  }
  return nullptr;
}

void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
                          int32_t aAppUnitsPerDevPixel) {
  FlattenedDisplayListIterator iter(aBuilder, this);
  while (iter.HasNext()) {
    nsPaintedDisplayItem* item = iter.GetNextItem()->AsPaintedDisplayItem();
    if (!item) {
      continue;
    }

    nsRect visible = item->GetClippedBounds(aBuilder);
    visible = visible.Intersect(item->GetPaintRect(aBuilder, aCtx));
    if (visible.IsEmpty()) {
      continue;
    }

    DisplayItemClip currentClip = item->GetClip();
    if (currentClip.HasClip()) {
      aCtx->Save();
      if (currentClip.IsRectClippedByRoundedCorner(visible)) {
        currentClip.ApplyTo(aCtx, aAppUnitsPerDevPixel);
      } else {
        currentClip.ApplyRectTo(aCtx, aAppUnitsPerDevPixel);
      }
    }
    aCtx->NewPath();

    item->Paint(aBuilder, aCtx);

    if (currentClip.HasClip()) {
      aCtx->Restore();
    }
  }
}

/**
 * We paint by executing a layer manager transaction, constructing a
 * single layer representing the display list, and then making it the
 * root of the layer manager, drawing into the PaintedLayers.
 */
void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
                              uint32_t aFlags,
                              Maybe<double> aDisplayListBuildTime) {
  AUTO_PROFILER_LABEL("nsDisplayList::PaintRoot", GRAPHICS);

  RefPtr<WebRenderLayerManager> layerManager;
  WindowRenderer* renderer = nullptr;
  bool widgetTransaction = false;
  bool doBeginTransaction = true;
  nsView* view = nullptr;
  if (aFlags & PAINT_USE_WIDGET_LAYERS) {
    renderer = aBuilder->GetWidgetWindowRenderer(&view);
    if (renderer) {
      // The fallback renderer doesn't retain any content, so it's
      // not meaningful to use it when drawing to an external context.
      if (aCtx && renderer->AsFallback()) {
        MOZ_ASSERT(!(aFlags & PAINT_EXISTING_TRANSACTION));
        renderer = nullptr;
      } else {
        layerManager = renderer->AsWebRender();
        doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION);
        widgetTransaction = true;
      }
    }
  }

  nsIFrame* frame = aBuilder->RootReferenceFrame();
  nsPresContext* presContext = frame->PresContext();
  PresShell* presShell = presContext->PresShell();
  Document* document = presShell->GetDocument();

  ScopeExit g([&]() {
#ifdef DEBUG
    MOZ_ASSERT(!layerManager || !layerManager->GetTarget());
#endif

    // For layers-free mode, we check the invalidation state bits in the
    // EndTransaction. So we clear the invalidation state bits after
    // EndTransaction.
    if (widgetTransaction ||
        // SVG-as-an-image docs don't paint as part of the retained layer tree,
        // but they still need the invalidation state bits cleared in order for
        // invalidation for CSS/SMIL animation to work properly.
        (document && document->IsBeingUsedAsImage())) {
      DL_LOGD("Clearing invalidation state bits");
      frame->ClearInvalidationStateBits();
    }
  });

  if (!renderer) {
    if (!aCtx) {
      NS_WARNING("Nowhere to paint into");
      return;
    }
    bool prevIsCompositingCheap = aBuilder->SetIsCompositingCheap(false);
    Paint(aBuilder, aCtx, presContext->AppUnitsPerDevPixel());

    aBuilder->SetIsCompositingCheap(prevIsCompositingCheap);
    return;
  }

  if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) {
    MOZ_ASSERT(layerManager);
    if (doBeginTransaction) {
      if (aCtx) {
        if (!layerManager->BeginTransactionWithTarget(aCtx, nsCString())) {
          return;
        }
      } else {
        if (!layerManager->BeginTransaction(nsCString())) {
          return;
        }
      }
    }

    bool prevIsCompositingCheap = aBuilder->SetIsCompositingCheap(true);
    layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());

    bool sent = false;
    if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) {
      MOZ_ASSERT(!aCtx);
      sent = layerManager->EndEmptyTransaction();
    }

    if (!sent) {
      auto* wrManager = static_cast<WebRenderLayerManager*>(layerManager.get());

      nsIDocShell* docShell = presContext->GetDocShell();
      WrFiltersHolder wrFilters;
      gfx::Matrix5x4* colorMatrix =
          nsDocShell::Cast(docShell)->GetColorMatrix();
      if (colorMatrix) {
        wrFilters.filters.AppendElement(
            wr::FilterOp::ColorMatrix(colorMatrix->components));
      }

      wrManager->EndTransactionWithoutLayer(this, aBuilder,
                                            std::move(wrFilters), nullptr,
                                            aDisplayListBuildTime.valueOr(0.0));
    }

    aBuilder->SetIsCompositingCheap(prevIsCompositingCheap);
    if (document && widgetTransaction) {
      TriggerPendingAnimations(*document,
                               layerManager->GetAnimationReadyTime());
    }

    if (presContext->RefreshDriver()->HasScheduleFlush()) {
      presContext->NotifyInvalidation(layerManager->GetLastTransactionId(),
                                      frame->GetRect());
    }

    return;
  }

  FallbackRenderer* fallback = renderer->AsFallback();
  MOZ_ASSERT(fallback);

  if (doBeginTransaction) {
    MOZ_ASSERT(!aCtx);
    if (!fallback->BeginTransaction()) {
      return;
    }
  }

  bool temp = aBuilder->SetIsCompositingCheap(renderer->IsCompositingCheap());
  fallback->EndTransactionWithList(aBuilder, this,
                                   presContext->AppUnitsPerDevPixel(),
                                   WindowRenderer::END_DEFAULT);

  aBuilder->SetIsCompositingCheap(temp);

  if (document && widgetTransaction) {
    TriggerPendingAnimations(*document, renderer->GetAnimationReadyTime());
  }
}

void nsDisplayList::DeleteAll(nsDisplayListBuilder* aBuilder) {
  for (auto* item : TakeItems()) {
    item->Destroy(aBuilder);
  }
}

static bool IsFrameReceivingPointerEvents(nsIFrame* aFrame) {
  return aFrame->Style()->PointerEvents() != StylePointerEvents::None;
}

// A list of frames, and their z depth. Used for sorting
// the results of hit testing.
struct FramesWithDepth {
  explicit FramesWithDepth(float aDepth) : mDepth(aDepth) {}

  bool operator<(const FramesWithDepth& aOther) const {
    if (!FuzzyEqual(mDepth, aOther.mDepth, 0.1f)) {
      // We want to sort so that the shallowest item (highest depth value) is
      // first
      return mDepth > aOther.mDepth;
    }
    return false;
  }
  bool operator==(const FramesWithDepth& aOther) const {
    return this == &aOther;
  }

  float mDepth;
  nsTArray<nsIFrame*> mFrames;
};

// Sort the frames by depth and then moves all the contained frames to the
// destination
static void FlushFramesArray(nsTArray<FramesWithDepth>& aSource,
                             nsTArray<nsIFrame*>* aDest) {
  if (aSource.IsEmpty()) {
    return;
  }
  aSource.StableSort();
  uint32_t length = aSource.Length();
  for (uint32_t i = 0; i < length; i++) {
    aDest->AppendElements(std::move(aSource[i].mFrames));
  }
  aSource.Clear();
}

void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                            nsDisplayItem::HitTestState* aState,
                            nsTArray<nsIFrame*>* aOutFrames) const {
  nsDisplayItem* item;

  if (aState->mInPreserves3D) {
    // Collect leaves of the current 3D rendering context.
    for (nsDisplayItem* item : *this) {
      auto itemType = item->GetType();
      if (itemType != DisplayItemType::TYPE_TRANSFORM ||
          !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
        item->HitTest(aBuilder, aRect, aState, aOutFrames);
      } else {
        // One of leaves in the current 3D rendering context.
        aState->mItemBuffer.AppendElement(item);
      }
    }
    return;
  }

  int32_t itemBufferStart = aState->mItemBuffer.Length();
  for (nsDisplayItem* item : *this) {
    aState->mItemBuffer.AppendElement(item);
  }

  AutoTArray<FramesWithDepth, 16> temp;
  for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart;
       --i) {
    // Pop element off the end of the buffer. We want to shorten the buffer
    // so that recursive calls to HitTest have more buffer space.
    item = aState->mItemBuffer[i];
    aState->mItemBuffer.SetLength(i);

    bool snap;
    nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect);
    auto itemType = item->GetType();
    bool same3DContext =
        (itemType == DisplayItemType::TYPE_TRANSFORM &&
         static_cast<nsDisplayTransform*>(item)->IsParticipating3DContext()) ||
        (itemType == DisplayItemType::TYPE_PERSPECTIVE &&
         item->Frame()->Extend3DContext());
    if (same3DContext &&
        (itemType != DisplayItemType::TYPE_TRANSFORM ||
         !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext())) {
      if (!item->GetClip().MayIntersect(aRect)) {
        continue;
      }
      AutoTArray<nsIFrame*, 1> neverUsed;
      // Start gathering leaves of the 3D rendering context, and
      // append leaves at the end of mItemBuffer.  Leaves are
      // processed at following iterations.
      aState->mInPreserves3D = true;
      item->HitTest(aBuilder, aRect, aState, &neverUsed);
      aState->mInPreserves3D = false;
      i = aState->mItemBuffer.Length();
      continue;
    }
    if (same3DContext || item->GetClip().MayIntersect(r)) {
      AutoTArray<nsIFrame*, 16> outFrames;
      item->HitTest(aBuilder, aRect, aState, &outFrames);

      // For 3d transforms with preserve-3d we add hit frames into the temp list
      // so we can sort them later, otherwise we add them directly to the output
      // list.
      nsTArray<nsIFrame*>* writeFrames = aOutFrames;
      if (item->GetType() == DisplayItemType::TYPE_TRANSFORM &&
          static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) {
        if (outFrames.Length()) {
          nsDisplayTransform* transform =
              static_cast<nsDisplayTransform*>(item);
          nsPoint point = aRect.TopLeft();
          // A 1x1 rect means a point, otherwise use the center of the rect
          if (aRect.width != 1 || aRect.height != 1) {
            point = aRect.Center();
          }
          temp.AppendElement(
              FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point)));
          writeFrames = &temp[temp.Length() - 1].mFrames;
        }
      } else {
        // We may have just finished a run of consecutive preserve-3d
        // transforms, so flush these into the destination array before
        // processing our frame list.
        FlushFramesArray(temp, aOutFrames);
      }

      for (uint32_t j = 0; j < outFrames.Length(); j++) {
        nsIFrame* f = outFrames.ElementAt(j);
        // Filter out some frames depending on the type of hittest
        // we are doing. For visibility tests, pass through all frames.
        // For pointer tests, only pass through frames that are styled
        // to receive pointer events.
        if (aBuilder->HitTestIsForVisibility() ||
            IsFrameReceivingPointerEvents(f)) {
          writeFrames->AppendElement(f);
        }
      }

      if (aBuilder->HitTestIsForVisibility()) {
        aState->mHitOccludingItem = [&] {
          if (aState->mHitOccludingItem) {
            // We already hit something before.
            return true;
          }
          if (aState->mCurrentOpacity == 1.0f &&
              item->GetOpaqueRegion(aBuilder, &snap).Contains(aRect)) {
            // An opaque item always occludes everything. Note that we need to
            // check wrapping opacity and such as well.
            return true;
          }
          float threshold = aBuilder->VisibilityThreshold();
          if (threshold == 1.0f) {
            return false;
          }
          float itemOpacity = [&] {
            switch (item->GetType()) {
              case DisplayItemType::TYPE_OPACITY:
                return static_cast<nsDisplayOpacity*>(item)->GetOpacity();
              case DisplayItemType::TYPE_BACKGROUND_COLOR:
                return static_cast<nsDisplayBackgroundColor*>(item)
                    ->GetOpacity();
              default:
                // Be conservative and assume it won't occlude other items.
                return 0.0f;
            }
          }();
          return itemOpacity * aState->mCurrentOpacity >= threshold;
        }();

        if (aState->mHitOccludingItem) {
          // We're exiting early, so pop the remaining items off the buffer.
          aState->mItemBuffer.TruncateLength(itemBufferStart);
          break;
        }
      }
    }
  }
  // Clear any remaining preserve-3d transforms.
  FlushFramesArray(temp, aOutFrames);
  NS_ASSERTION(aState->mItemBuffer.Length() == uint32_t(itemBufferStart),
               "How did we forget to pop some elements?");
}

static nsIContent* FindContentInDocument(nsDisplayItem* aItem, Document* aDoc) {
  nsIFrame* f = aItem->Frame();
  while (f) {
    nsPresContext* pc = f->PresContext();
    if (pc->Document() == aDoc) {
      return f->GetContent();
    }
    f = nsLayoutUtils::GetCrossDocParentFrameInProcess(
        pc->PresShell()->GetRootFrame());
  }
  return nullptr;
}

struct ZSortItem {
  nsDisplayItem* item;
  int32_t zIndex;

  explicit ZSortItem(nsDisplayItem* aItem)
      : item(aItem), zIndex(aItem->ZIndex()) {}

  operator nsDisplayItem*() { return item; }
};

struct ZOrderComparator {
  bool operator()(const ZSortItem& aLeft, const ZSortItem& aRight) const {
    // Note that we can't just take the difference of the two
    // z-indices here, because that might overflow a 32-bit int.
    return aLeft.zIndex < aRight.zIndex;
  }
};

void nsDisplayList::SortByZOrder() { Sort<ZSortItem>(ZOrderComparator()); }

struct ContentComparator {
  nsIContent* mCommonAncestor;

  explicit ContentComparator(nsIContent* aCommonAncestor)
      : mCommonAncestor(aCommonAncestor) {}

  bool operator()(nsDisplayItem* aLeft, nsDisplayItem* aRight) const {
    // It's possible that the nsIContent for aItem1 or aItem2 is in a
    // subdocument of commonAncestor, because display items for subdocuments
    // have been mixed into the same list. Ensure that we're looking at content
    // in commonAncestor's document.
    Document* commonAncestorDoc = mCommonAncestor->OwnerDoc();
    nsIContent* content1 = FindContentInDocument(aLeft, commonAncestorDoc);
    nsIContent* content2 = FindContentInDocument(aRight, commonAncestorDoc);
    if (!content1 || !content2) {
      NS_ERROR("Document trees are mixed up!");
      // Something weird going on
      return true;
    }
    return nsLayoutUtils::CompareTreePosition(content1, content2,
                                              mCommonAncestor) < 0;
  }
};

void nsDisplayList::SortByContentOrder(nsIContent* aCommonAncestor) {
  Sort<nsDisplayItem*>(ContentComparator(aCommonAncestor));
}

#if !defined(DEBUG) && !defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
static_assert(sizeof(nsDisplayItem) <= 176, "nsDisplayItem has grown");
#endif

nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
    : nsDisplayItem(aBuilder, aFrame, aBuilder->CurrentActiveScrolledRoot()) {}

nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                             const ActiveScrolledRoot* aActiveScrolledRoot)
    : mFrame(aFrame), mActiveScrolledRoot(aActiveScrolledRoot) {
  MOZ_COUNT_CTOR(nsDisplayItem);
  MOZ_ASSERT(mFrame);
  if (aBuilder->IsRetainingDisplayList()) {
    mFrame->AddDisplayItem(this);
  }

  aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame);
  NS_ASSERTION(
      aBuilder->GetVisibleRect().width >= 0 || !aBuilder->IsForPainting(),
      "visible rect not set");

  mClipChain = aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);

  // The visible rect is for mCurrentFrame, so we have to use
  // mCurrentOffsetToReferenceFrame
  nsRect visible = aBuilder->GetVisibleRect() +
                   aBuilder->GetCurrentFrameOffsetToReferenceFrame();
  SetBuildingRect(visible);

  const nsStyleDisplay* disp = mFrame->StyleDisplay();
  if (mFrame->BackfaceIsHidden(disp)) {
    mItemFlags += ItemFlag::BackfaceHidden;
  }
  if (mFrame->Combines3DTransformWithAncestors()) {
    mItemFlags += ItemFlag::Combines3DTransformWithAncestors;
  }
}

void nsDisplayItem::SetDeletedFrame() { mItemFlags += ItemFlag::DeletedFrame; }

bool nsDisplayItem::HasDeletedFrame() const {
  bool retval = mItemFlags.contains(ItemFlag::DeletedFrame) ||
                (GetType() == DisplayItemType::TYPE_REMOTE &&
                 !static_cast<const nsDisplayRemote*>(this)->GetFrameLoader());
  MOZ_ASSERT(retval || mFrame);
  return retval;
}

/* static */
bool nsDisplayItem::ForceActiveLayers() {
  return StaticPrefs::layers_force_active();
}

int32_t nsDisplayItem::ZIndex() const { return mFrame->ZIndex().valueOr(0); }

void nsDisplayItem::SetClipChain(const DisplayItemClipChain* aClipChain,
                                 bool aStore) {
  mClipChain = aClipChain;
}

Maybe<nsRect> nsDisplayItem::GetClipWithRespectToASR(
    nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const {
  if (const DisplayItemClip* clip =
          DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) {
    return Some(clip->GetClipRect());
  }
#ifdef DEBUG
  NS_ASSERTION(false, "item should have finite clip with respect to aASR");
#endif
  return Nothing();
}

const DisplayItemClip& nsDisplayItem::GetClip() const {
  const DisplayItemClip* clip =
      DisplayItemClipChain::ClipForASR(mClipChain, mActiveScrolledRoot);
  return clip ? *clip : DisplayItemClip::NoClip();
}

void nsDisplayItem::IntersectClip(nsDisplayListBuilder* aBuilder,
                                  const DisplayItemClipChain* aOther,
                                  bool aStore) {
  if (!aOther || mClipChain == aOther) {
    return;
  }

  // aOther might be a reference to a clip on the stack. We need to make sure
  // that CreateClipChainIntersection will allocate the actual intersected
  // clip in the builder's arena, so for the mClipChain == nullptr case,
  // we supply nullptr as the common ancestor so that
  // CreateClipChainIntersection clones the whole chain.
  const DisplayItemClipChain* ancestorClip =
      mClipChain ? FindCommonAncestorClipForIntersection(mClipChain, aOther)
                 : nullptr;

  SetClipChain(
      aBuilder->CreateClipChainIntersection(ancestorClip, mClipChain, aOther),
      aStore);
}

nsRect nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder) const {
  bool snap;
  nsRect r = GetBounds(aBuilder, &snap);
  return GetClip().ApplyNonRoundedIntersection(r);
}

nsDisplayContainer::nsDisplayContainer(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
    const ActiveScrolledRoot* aActiveScrolledRoot, nsDisplayList* aList)
    : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot),
      mChildren(aBuilder) {
  MOZ_COUNT_CTOR(nsDisplayContainer);
  mChildren.AppendToTop(aList);
  UpdateBounds(aBuilder);

  // Clear and store the clip chain set by nsDisplayItem constructor.
  nsDisplayItem::SetClipChain(nullptr, true);
}

nsRect nsDisplayItem::GetPaintRect(nsDisplayListBuilder* aBuilder,
                                   gfxContext* aCtx) {
  bool dummy;
  nsRect result = GetBounds(aBuilder, &dummy);
  if (aCtx) {
    result.IntersectRect(result,
                         nsLayoutUtils::RoundGfxRectToAppRect(
                             aCtx->GetClipExtents(),
                             mFrame->PresContext()->AppUnitsPerDevPixel()));
  }
  return result;
}

bool nsDisplayContainer::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
      GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources,
      false);
  return true;
}

nsRect nsDisplayContainer::GetBounds(nsDisplayListBuilder* aBuilder,
                                     bool* aSnap) const {
  *aSnap = false;
  return mBounds;
}

nsRect nsDisplayContainer::GetComponentAlphaBounds(
    nsDisplayListBuilder* aBuilder) const {
  return mChildren.GetComponentAlphaBounds(aBuilder);
}

static nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                nsDisplayList* aList,
                                const nsRect& aListBounds) {
  return aList->GetOpaqueRegion(aBuilder);
}

nsRegion nsDisplayContainer::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                             bool* aSnap) const {
  return ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(),
                                    GetBounds(aBuilder, aSnap));
}

Maybe<nsRect> nsDisplayContainer::GetClipWithRespectToASR(
    nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const {
  // Our children should have finite bounds with respect to |aASR|.
  if (aASR == mActiveScrolledRoot) {
    return Some(mBounds);
  }

  return Some(mChildren.GetClippedBoundsWithRespectToASR(aBuilder, aASR));
}

void nsDisplayContainer::HitTest(nsDisplayListBuilder* aBuilder,
                                 const nsRect& aRect, HitTestState* aState,
                                 nsTArray<nsIFrame*>* aOutFrames) {
  mChildren.HitTest(aBuilder, aRect, aState, aOutFrames);
}

void nsDisplayContainer::UpdateBounds(nsDisplayListBuilder* aBuilder) {
  // Container item bounds are expected to be clipped.
  mBounds =
      mChildren.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot);
}

nsRect nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder,
                                      bool* aSnap) const {
  *aSnap = true;
  return mBounds;
}

void nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder,
                                gfxContext* aCtx) {
  int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
  DrawTarget* drawTarget = aCtx->GetDrawTarget();
  Rect rect = NSRectToSnappedRect(GetPaintRect(aBuilder, aCtx),
                                  appUnitsPerDevPixel, *drawTarget);
  drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(mColor)));
}

void nsDisplaySolidColor::WriteDebugInfo(std::stringstream& aStream) {
  aStream << " (rgba " << (int)NS_GET_R(mColor) << "," << (int)NS_GET_G(mColor)
          << "," << (int)NS_GET_B(mColor) << "," << (int)NS_GET_A(mColor)
          << ")";
}

bool nsDisplaySolidColor::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
      mBounds, mFrame->PresContext()->AppUnitsPerDevPixel());
  wr::LayoutRect r = wr::ToLayoutRect(bounds);
  aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, mIsCheckerboardBackground,
                    wr::ToColorF(ToDeviceColor(mColor)));

  return true;
}

nsRect nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder,
                                            bool* aSnap) const {
  *aSnap = true;
  return mRegion.GetBounds();
}

void nsDisplaySolidColorRegion::Paint(nsDisplayListBuilder* aBuilder,
                                      gfxContext* aCtx) {
  int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
  DrawTarget* drawTarget = aCtx->GetDrawTarget();
  ColorPattern color(ToDeviceColor(mColor));
  for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
    Rect rect =
        NSRectToSnappedRect(iter.Get(), appUnitsPerDevPixel, *drawTarget);
    drawTarget->FillRect(rect, color);
  }
}

void nsDisplaySolidColorRegion::WriteDebugInfo(std::stringstream& aStream) {
  aStream << " (rgba " << int(mColor.r * 255) << "," << int(mColor.g * 255)
          << "," << int(mColor.b * 255) << "," << mColor.a << ")";
}

bool nsDisplaySolidColorRegion::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
    nsRect rect = iter.Get();
    LayoutDeviceRect layerRects = LayoutDeviceRect::FromAppUnits(
        rect, mFrame->PresContext()->AppUnitsPerDevPixel());
    wr::LayoutRect r = wr::ToLayoutRect(layerRects);
    aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false,
                      wr::ToColorF(ToDeviceColor(mColor)));
  }

  return true;
}

static void RegisterThemeGeometry(nsDisplayListBuilder* aBuilder,
                                  nsDisplayItem* aItem, nsIFrame* aFrame,
                                  nsITheme::ThemeGeometryType aType) {
  if (aBuilder->IsInChromeDocumentOrPopup()) {
    nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
    bool preservesAxisAlignedRectangles = false;
    nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(
        aFrame, aFrame->GetRectRelativeToSelf(), displayRoot,
        &preservesAxisAlignedRectangles);
    if (preservesAxisAlignedRectangles) {
      aBuilder->RegisterThemeGeometry(
          aType, aItem,
          LayoutDeviceIntRect::FromUnknownRect(borderBox.ToNearestPixels(
              aFrame->PresContext()->AppUnitsPerDevPixel())));
    }
  }
}

// Return the bounds of the viewport relative to |aFrame|'s reference frame.
// Returns Nothing() if transforming into |aFrame|'s coordinate space fails.
static Maybe<nsRect> GetViewportRectRelativeToReferenceFrame(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
  nsIFrame* rootFrame = aFrame->PresShell()->GetRootFrame();
  nsRect rootRect = rootFrame->GetRectRelativeToSelf();
  if (nsLayoutUtils::TransformRect(rootFrame, aFrame, rootRect) ==
      nsLayoutUtils::TRANSFORM_SUCCEEDED) {
    return Some(rootRect + aBuilder->ToReferenceFrame(aFrame));
  }
  return Nothing();
}

/* static */ nsDisplayBackgroundImage::InitData
nsDisplayBackgroundImage::GetInitData(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame, uint16_t aLayer,
                                      const nsRect& aBackgroundRect,
                                      const ComputedStyle* aBackgroundStyle) {
  nsPresContext* presContext = aFrame->PresContext();
  uint32_t flags = aBuilder->GetBackgroundPaintFlags();
  const nsStyleImageLayers::Layer& layer =
      aBackgroundStyle->StyleBackground()->mImage.mLayers[aLayer];

  bool isTransformedFixed;
  nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer(
      presContext, aFrame, flags, aBackgroundRect, aBackgroundRect, layer,
      &isTransformedFixed);

  // background-attachment:fixed is treated as background-attachment:scroll
  // if it's affected by a transform.
  // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17521.
  bool shouldTreatAsFixed =
      layer.mAttachment == StyleImageLayerAttachment::Fixed &&
      !isTransformedFixed;

  bool shouldFixToViewport = shouldTreatAsFixed && !layer.mImage.IsNone();
  bool isRasterImage = state.mImageRenderer.IsRasterImage();
  nsCOMPtr<imgIContainer> image;
  if (isRasterImage) {
    image = state.mImageRenderer.GetImage();
  }
  return InitData{aBuilder,        aBackgroundStyle, image,
                  aBackgroundRect, state.mFillArea,  state.mDestArea,
                  aLayer,          isRasterImage,    shouldFixToViewport};
}

nsDisplayBackgroundImage::nsDisplayBackgroundImage(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aInitData,
    nsIFrame* aFrameForBounds)
    : nsPaintedDisplayItem(aBuilder, aFrame),
      mBackgroundStyle(aInitData.backgroundStyle),
      mImage(aInitData.image),
      mDependentFrame(nullptr),
      mBackgroundRect(aInitData.backgroundRect),
      mFillRect(aInitData.fillArea),
      mDestRect(aInitData.destArea),
      mLayer(aInitData.layer),
      mIsRasterImage(aInitData.isRasterImage),
      mShouldFixToViewport(aInitData.shouldFixToViewport) {
  MOZ_COUNT_CTOR(nsDisplayBackgroundImage);
#ifdef DEBUG
  if (mBackgroundStyle && mBackgroundStyle != mFrame->Style()) {
    // If this changes, then you also need to adjust css::ImageLoader to
    // invalidate mFrame as needed.
    MOZ_ASSERT(mFrame->IsCanvasFrame() ||
               mFrame->IsFrameOfType(nsIFrame::eTablePart));
  }
#endif

  mBounds = GetBoundsInternal(aInitData.builder, aFrameForBounds);
  if (mShouldFixToViewport) {
    // Expand the item's visible rect to cover the entire bounds, limited to the
    // viewport rect. This is necessary because the background's clip can move
    // asynchronously.
    if (Maybe<nsRect> viewportRect = GetViewportRectRelativeToReferenceFrame(
            aInitData.builder, mFrame)) {
      SetBuildingRect(mBounds.Intersect(*viewportRect));
    }
  }
}

nsDisplayBackgroundImage::~nsDisplayBackgroundImage() {
  MOZ_COUNT_DTOR(nsDisplayBackgroundImage);
  if (mDependentFrame) {
    mDependentFrame->RemoveDisplayItem(this);
  }
}

static void SetBackgroundClipRegion(
    DisplayListClipState::AutoSaveRestore& aClipState, nsIFrame* aFrame,
    const nsStyleImageLayers::Layer& aLayer, const nsRect& aBackgroundRect,
    bool aWillPaintBorder) {
  nsCSSRendering::ImageLayerClipState clip;
  nsCSSRendering::GetImageLayerClip(
      aLayer, aFrame, *aFrame->StyleBorder(), aBackgroundRect, aBackgroundRect,
      aWillPaintBorder, aFrame->PresContext()->AppUnitsPerDevPixel(), &clip);

  if (clip.mHasAdditionalBGClipArea) {
    aClipState.ClipContentDescendants(
        clip.mAdditionalBGClipArea, clip.mBGClipArea,
        clip.mHasRoundedCorners ? clip.mRadii : nullptr);
  } else {
    aClipState.ClipContentDescendants(
        clip.mBGClipArea, clip.mHasRoundedCorners ? clip.mRadii : nullptr);
  }
}

/**
 * This is used for the find bar highlighter overlay. It's only accessible
 * through the AnonymousContent API, so it's not exposed to general web pages.
 */
static bool SpecialCutoutRegionCase(nsDisplayListBuilder* aBuilder,
                                    nsIFrame* aFrame,
                                    const nsRect& aBackgroundRect,
                                    nsDisplayList* aList, nscolor aColor) {
  nsIContent* content = aFrame->GetContent();
  if (!content) {
    return false;
  }

  void* cutoutRegion = content->GetProperty(nsGkAtoms::cutoutregion);
  if (!cutoutRegion) {
    return false;
  }

  if (NS_GET_A(aColor) == 0) {
    return true;
  }

  nsRegion region;
  region.Sub(aBackgroundRect, *static_cast<nsRegion*>(cutoutRegion));
  region.MoveBy(aBuilder->ToReferenceFrame(aFrame));
  aList->AppendNewToTop<nsDisplaySolidColorRegion>(aBuilder, aFrame, region,
                                                   aColor);

  return true;
}

enum class TableType : uint8_t {
  Table,
  TableCol,
  TableColGroup,
  TableRow,
  TableRowGroup,
  TableCell,

  MAX,
};

enum class TableTypeBits : uint8_t { Count = 3 };

static_assert(static_cast<uint8_t>(TableType::MAX) <
                  (1 << (static_cast<uint8_t>(TableTypeBits::Count) + 1)),
              "TableType cannot fit with TableTypeBits::Count");
TableType GetTableTypeFromFrame(nsIFrame* aFrame);

static uint16_t CalculateTablePerFrameKey(const uint16_t aIndex,
                                          const TableType aType) {
  const uint32_t key = (aIndex << static_cast<uint8_t>(TableTypeBits::Count)) |
                       static_cast<uint8_t>(aType);

  return static_cast<uint16_t>(key);
}

static nsDisplayBackgroundImage* CreateBackgroundImage(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
    const nsDisplayBackgroundImage::InitData& aBgData) {
  const auto index = aBgData.layer;

  if (aSecondaryFrame) {
    const auto tableType = GetTableTypeFromFrame(aFrame);
    const uint16_t tableItemIndex = CalculateTablePerFrameKey(index, tableType);

    return MakeDisplayItemWithIndex<nsDisplayTableBackgroundImage>(
        aBuilder, aSecondaryFrame, tableItemIndex, aBgData, aFrame);
  }

  return MakeDisplayItemWithIndex<nsDisplayBackgroundImage>(aBuilder, aFrame,
                                                            index, aBgData);
}

static nsDisplayThemedBackground* CreateThemedBackground(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
    const nsRect& aBgRect) {
  if (aSecondaryFrame) {
    const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame));
    return MakeDisplayItemWithIndex<nsDisplayTableThemedBackground>(
        aBuilder, aSecondaryFrame, index, aBgRect, aFrame);
  }

  return MakeDisplayItem<nsDisplayThemedBackground>(aBuilder, aFrame, aBgRect);
}

static nsDisplayBackgroundColor* CreateBackgroundColor(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
    nsRect& aBgRect, const ComputedStyle* aBgSC, nscolor aColor) {
  if (aSecondaryFrame) {
    const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame));
    return MakeDisplayItemWithIndex<nsDisplayTableBackgroundColor>(
        aBuilder, aSecondaryFrame, index, aBgRect, aBgSC, aColor, aFrame);
  }

  return MakeDisplayItem<nsDisplayBackgroundColor>(aBuilder, aFrame, aBgRect,
                                                   aBgSC, aColor);
}

static void DealWithWindowsAppearanceHacks(nsIFrame* aFrame,
                                           nsDisplayListBuilder* aBuilder) {
  const auto& disp = *aFrame->StyleDisplay();

  // We use default appearance rather than effective appearance because we want
  // to handle when titlebar buttons that have appearance: none.
  const auto defaultAppearance = disp.mDefaultAppearance;
  if (MOZ_LIKELY(defaultAppearance == StyleAppearance::None)) {
    return;
  }

  if (auto type = disp.GetWindowButtonType()) {
    if (auto* widget = aFrame->GetNearestWidget()) {
      auto rect = LayoutDevicePixel::FromAppUnitsToNearest(
          nsRect(aBuilder->ToReferenceFrame(aFrame), aFrame->GetSize()),
          aFrame->PresContext()->AppUnitsPerDevPixel());
      widget->SetWindowButtonRect(*type, rect);
    }
  }
}

/*static*/
AppendedBackgroundType nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
    const nsRect& aBackgroundRect, nsDisplayList* aList,
    bool aAllowWillPaintBorderOptimization, const nsRect& aBackgroundOriginRect,
    nsIFrame* aSecondaryReferenceFrame,
    Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>*
        aAutoBuildingDisplayList) {
  MOZ_ASSERT(!aFrame->IsCanvasFrame(),
             "We don't expect propagated canvas backgrounds here");
#ifdef DEBUG
  {
    nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aFrame);
    MOZ_ASSERT(
        !bgFrame || bgFrame == aFrame,
        "Should only suppress backgrounds, never propagate to another frame");
  }
#endif

  DealWithWindowsAppearanceHacks(aFrame, aBuilder);

  const bool isThemed = aFrame->IsThemed();

  const ComputedStyle* bgSC = aFrame->Style();
  const nsStyleBackground* bg = bgSC->StyleBackground();
  const bool needsBackgroundColor =
      aBuilder->IsForEventDelivery() ||
      (EffectCompositor::HasAnimationsForCompositor(
           aFrame, DisplayItemType::TYPE_BACKGROUND_COLOR) &&
       !isThemed);
  if (!needsBackgroundColor && !isThemed && bg->IsTransparent(bgSC)) {
    return AppendedBackgroundType::None;
  }

  bool drawBackgroundColor = false;
  bool drawBackgroundImage = false;
  nscolor color = NS_RGBA(0, 0, 0, 0);
  // Don't get background color / images if we propagated our background to the
  // canvas (that is, if FindBackgroundFrame is null). But don't early return
  // yet, since we might still need a background-color item for hit-testing.
  if (!isThemed && nsCSSRendering::FindBackgroundFrame(aFrame)) {
    color = nsCSSRendering::DetermineBackgroundColor(
        aFrame->PresContext(), bgSC, aFrame, drawBackgroundImage,
        drawBackgroundColor);
  }

  if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList,
                              color)) {
    return AppendedBackgroundType::None;
  }

  const nsStyleBorder& border = *aFrame->StyleBorder();
  const bool willPaintBorder =
      aAllowWillPaintBorderOptimization && !isThemed &&
      !aFrame->StyleEffects()->HasBoxShadowWithInset(true) &&
      border.HasBorder();

  auto EnsureBuildingDisplayList = [&] {
    if (!aAutoBuildingDisplayList || *aAutoBuildingDisplayList) {
      return;
    }
    nsPoint offset = aBuilder->GetCurrentFrame()->GetOffsetTo(aFrame);
    aAutoBuildingDisplayList->emplace(aBuilder, aFrame,
                                      aBuilder->GetVisibleRect() + offset,
                                      aBuilder->GetDirtyRect() + offset);
  };

  // An auxiliary list is necessary in case we have background blending; if that
  // is the case, background items need to be wrapped by a blend container to
  // isolate blending to the background
  nsDisplayList bgItemList(aBuilder);
  // Even if we don't actually have a background color to paint, we may still
  // need to create an item for hit testing and we still need to create an item
  // for background-color animations.
  if ((drawBackgroundColor && color != NS_RGBA(0, 0, 0, 0)) ||
      needsBackgroundColor) {
    EnsureBuildingDisplayList();
    Maybe<DisplayListClipState::AutoSaveRestore> clipState;
    nsRect bgColorRect = aBackgroundRect;
    if (!isThemed && !aBuilder->IsForEventDelivery()) {
      // Disable the will-paint-border optimization for background
      // colors with no border-radius. Enabling it for background colors
      // doesn't help much (there are no tiling issues) and clipping the
      // background breaks detection of the element's border-box being
      // opaque. For nonzero border-radius we still need it because we
      // want to inset the background if possible to avoid antialiasing
      // artifacts along the rounded corners.
      const bool useWillPaintBorderOptimization =
          willPaintBorder &&
          nsLayoutUtils::HasNonZeroCorner(border.mBorderRadius);

      nsCSSRendering::ImageLayerClipState clip;
      nsCSSRendering::GetImageLayerClip(
          bg->BottomLayer(), aFrame, border, aBackgroundRect, aBackgroundRect,
          useWillPaintBorderOptimization,
          aFrame->PresContext()->AppUnitsPerDevPixel(), &clip);

      bgColorRect = bgColorRect.Intersect(clip.mBGClipArea);
      if (clip.mHasAdditionalBGClipArea) {
        bgColorRect = bgColorRect.Intersect(clip.mAdditionalBGClipArea);
      }
      if (clip.mHasRoundedCorners) {
        clipState.emplace(aBuilder);
        clipState->ClipContentDescendants(clip.mBGClipArea, clip.mRadii);
      }
    }

    nsDisplayBackgroundColor* bgItem = CreateBackgroundColor(
        aBuilder, aFrame, aSecondaryReferenceFrame, bgColorRect, bgSC,
        drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0));

    if (bgItem) {
      bgItemList.AppendToTop(bgItem);
    }
  }

  if (isThemed) {
    nsDisplayThemedBackground* bgItem = CreateThemedBackground(
        aBuilder, aFrame, aSecondaryReferenceFrame, aBackgroundRect);

    if (bgItem) {
      bgItem->Init(aBuilder);
      bgItemList.AppendToTop(bgItem);
    }

    if (!bgItemList.IsEmpty()) {
      aList->AppendToTop(&bgItemList);
      return AppendedBackgroundType::ThemedBackground;
    }

    return AppendedBackgroundType::None;
  }

  if (!drawBackgroundImage) {
    if (!bgItemList.IsEmpty()) {
      aList->AppendToTop(&bgItemList);
      return AppendedBackgroundType::Background;
    }

    return AppendedBackgroundType::None;
  }

  const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();

  bool needBlendContainer = false;
  const nsRect& bgOriginRect =
      aBackgroundOriginRect.IsEmpty() ? aBackgroundRect : aBackgroundOriginRect;

  // Passing bg == nullptr in this macro will result in one iteration with
  // i = 0.
  NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, bg->mImage) {
    if (bg->mImage.mLayers[i].mImage.IsNone()) {
      continue;
    }

    EnsureBuildingDisplayList();

    if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) {
      needBlendContainer = true;
    }

    DisplayListClipState::AutoSaveRestore clipState(aBuilder);
    if (!aBuilder->IsForEventDelivery()) {
      const nsStyleImageLayers::Layer& layer = bg->mImage.mLayers[i];
      SetBackgroundClipRegion(clipState, aFrame, layer, aBackgroundRect,
                              willPaintBorder);
    }

    nsDisplayList thisItemList(aBuilder);
    nsDisplayBackgroundImage::InitData bgData =
        nsDisplayBackgroundImage::GetInitData(aBuilder, aFrame, i, bgOriginRect,
                                              bgSC);

    if (bgData.shouldFixToViewport) {
      auto* displayData = aBuilder->GetCurrentFixedBackgroundDisplayData();
      nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
          aBuilder, aFrame, aBuilder->GetVisibleRect(),
          aBuilder->GetDirtyRect());

      nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
          aBuilder);
      if (displayData) {
        asrSetter.SetCurrentActiveScrolledRoot(
            displayData->mContainingBlockActiveScrolledRoot);
        asrSetter.SetCurrentScrollParentId(displayData->mScrollParentId);
        if (nsLayoutUtils::UsesAsyncScrolling(aFrame)) {
          // Override the dirty rect on the builder to be the dirty rect of
          // the viewport.
          // displayData->mDirtyRect is relative to the presshell's viewport
          // frame (the root frame), and we need it to be relative to aFrame.
          nsIFrame* rootFrame =
              aBuilder->CurrentPresShellState()->mPresShell->GetRootFrame();
          // There cannot be any transforms between aFrame and rootFrame
          // because then bgData.shouldFixToViewport would have been false.
          nsRect visibleRect =
              displayData->mVisibleRect + aFrame->GetOffsetTo(rootFrame);
          aBuilder->SetVisibleRect(visibleRect);
          nsRect dirtyRect =
              displayData->mDirtyRect + aFrame->GetOffsetTo(rootFrame);
          aBuilder->SetDirtyRect(dirtyRect);
        }
      }

      nsDisplayBackgroundImage* bgItem = nullptr;
      {
        // The clip is captured by the nsDisplayFixedPosition, so clear the
        // clip for the nsDisplayBackgroundImage inside.
        DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder);
        bgImageClip.Clear();
        bgItem = CreateBackgroundImage(aBuilder, aFrame,
                                       aSecondaryReferenceFrame, bgData);
      }
      if (bgItem) {
        thisItemList.AppendToTop(
            nsDisplayFixedPosition::CreateForFixedBackground(
                aBuilder, aFrame, aSecondaryReferenceFrame, bgItem, i, asr));
      }
    } else {  // bgData.shouldFixToViewport == false
      nsDisplayBackgroundImage* bgItem = CreateBackgroundImage(
          aBuilder, aFrame, aSecondaryReferenceFrame, bgData);
      if (bgItem) {
        thisItemList.AppendToTop(bgItem);
      }
    }

    if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) {
      // asr is scrolled. Even if we wrap a fixed background layer, that's
      // fine, because the item will have a scrolled clip that limits the
      // item with respect to asr.
      if (aSecondaryReferenceFrame) {
        const auto tableType = GetTableTypeFromFrame(aFrame);
        const uint16_t index = CalculateTablePerFrameKey(i + 1, tableType);

        thisItemList.AppendNewToTopWithIndex<nsDisplayTableBlendMode>(
            aBuilder, aSecondaryReferenceFrame, index, &thisItemList,
            bg->mImage.mLayers[i].mBlendMode, asr, aFrame, true);
      } else {
        thisItemList.AppendNewToTopWithIndex<nsDisplayBlendMode>(
            aBuilder, aFrame, i + 1, &thisItemList,
            bg->mImage.mLayers[i].mBlendMode, asr, true);
      }
    }
    bgItemList.AppendToTop(&thisItemList);
  }

  if (needBlendContainer) {
    bgItemList.AppendToTop(
        nsDisplayBlendContainer::CreateForBackgroundBlendMode(
            aBuilder, aFrame, aSecondaryReferenceFrame, &bgItemList, asr));
  }

  if (!bgItemList.IsEmpty()) {
    aList->AppendToTop(&bgItemList);
    return AppendedBackgroundType::Background;
  }

  return AppendedBackgroundType::None;
}

// Check that the rounded border of aFrame, added to aToReferenceFrame,
// intersects aRect.  Assumes that the unrounded border has already
// been checked for intersection.
static bool RoundedBorderIntersectsRect(nsIFrame* aFrame,
                                        const nsPoint& aFrameToReferenceFrame,
                                        const nsRect& aTestRect) {
  if (!nsRect(aFrameToReferenceFrame, aFrame->GetSize())
           .Intersects(aTestRect)) {
    return false;
  }

  nscoord radii[8];
  return !aFrame->GetBorderRadii(radii) ||
         nsLayoutUtils::RoundedRectIntersectsRect(
             nsRect(aFrameToReferenceFrame, aFrame->GetSize()), radii,
             aTestRect);
}

// Returns TRUE if aContainedRect is guaranteed to be contained in
// the rounded rect defined by aRoundedRect and aRadii. Complex cases are
// handled conservatively by returning FALSE in some situations where
// a more thorough analysis could return TRUE.
//
// See also RoundedRectIntersectsRect.
static bool RoundedRectContainsRect(const nsRect& aRoundedRect,
                                    const nscoord aRadii[8],
                                    const nsRect& aContainedRect) {
  nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(aRoundedRect, aRadii,
                                                         aContainedRect);
  return rgn.Contains(aContainedRect);
}

bool nsDisplayBackgroundImage::CanApplyOpacity(
    WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const {
  return CanBuildWebRenderDisplayItems(aManager, aBuilder);
}

bool nsDisplayBackgroundImage::CanBuildWebRenderDisplayItems(
    WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const {
  return mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip !=
             StyleGeometryBox::Text &&
         nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
             aManager, *StyleFrame()->PresContext(), StyleFrame(),
             mBackgroundStyle->StyleBackground(), mLayer,
             aBuilder->GetBackgroundPaintFlags());
}

bool nsDisplayBackgroundImage::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  if (!CanBuildWebRenderDisplayItems(aManager->LayerManager(),
                                     aDisplayListBuilder)) {
    return false;
  }

  uint32_t paintFlags = aDisplayListBuilder->GetBackgroundPaintFlags();
  bool dummy;
  nsCSSRendering::PaintBGParams params =
      nsCSSRendering::PaintBGParams::ForSingleLayer(
          *StyleFrame()->PresContext(), GetBounds(aDisplayListBuilder, &dummy),
          mBackgroundRect, StyleFrame(), paintFlags, mLayer,
          CompositionOp::OP_OVER, aBuilder.GetInheritedOpacity());
  params.bgClipRect = &mBounds;
  ImgDrawResult result =
      nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
          params, aBuilder, aResources, aSc, aManager, this);
  if (result == ImgDrawResult::NOT_SUPPORTED) {
    return false;
  }
  return true;
}

void nsDisplayBackgroundImage::HitTest(nsDisplayListBuilder* aBuilder,
                                       const nsRect& aRect,
                                       HitTestState* aState,
                                       nsTArray<nsIFrame*>* aOutFrames) {
  if (RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
    aOutFrames->AppendElement(mFrame);
  }
}

static nsRect GetInsideClipRect(const nsDisplayItem* aItem,
                                StyleGeometryBox aClip, const nsRect& aRect,
                                const nsRect& aBackgroundRect) {
  if (aRect.IsEmpty()) {
    return {};
  }

  nsIFrame* frame = aItem->Frame();

  nsRect clipRect = aBackgroundRect;
  if (frame->IsCanvasFrame()) {
    nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame);
    clipRect = canvasFrame->CanvasArea() + aItem->ToReferenceFrame();
  } else if (aClip == StyleGeometryBox::PaddingBox ||
             aClip == StyleGeometryBox::ContentBox) {
    nsMargin border = frame->GetUsedBorder();
    if (aClip == StyleGeometryBox::ContentBox) {
      border += frame->GetUsedPadding();
    }
    border.ApplySkipSides(frame->GetSkipSides());
    clipRect.Deflate(border);
  }

  return clipRect.Intersect(aRect);
}

nsRegion nsDisplayBackgroundImage::GetOpaqueRegion(
    nsDisplayListBuilder* aBuilder, bool* aSnap) const {
  nsRegion result;
  *aSnap = false;

  if (!mBackgroundStyle) {
    return result;
  }

  *aSnap = true;

  // For StyleBoxDecorationBreak::Slice, don't try to optimize here, since
  // this could easily lead to O(N^2) behavior inside InlineBackgroundData,
  // which expects frames to be sent to it in content order, not reverse
  // content order which we'll produce here.
  // Of course, if there's only one frame in the flow, it doesn't matter.
  if (mFrame->StyleBorder()->mBoxDecorationBreak ==
          StyleBoxDecorationBreak::Clone ||
      (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
    const nsStyleImageLayers::Layer& layer =
        mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
    if (layer.mImage.IsOpaque() && layer.mBlendMode == StyleBlend::Normal &&
        layer.mRepeat.mXRepeat != StyleImageLayerRepeat::Space &&
        layer.mRepeat.mYRepeat != StyleImageLayerRepeat::Space &&
        layer.mClip != StyleGeometryBox::Text) {
      result = GetInsideClipRect(this, layer.mClip, mBounds, mBackgroundRect);
    }
  }

  return result;
}

Maybe<nscolor> nsDisplayBackgroundImage::IsUniform(
    nsDisplayListBuilder* aBuilder) const {
  if (!mBackgroundStyle) {
    return Some(NS_RGBA(0, 0, 0, 0));
  }
  return Nothing();
}

nsRect nsDisplayBackgroundImage::GetPositioningArea() const {
  if (!mBackgroundStyle) {
    return nsRect();
  }
  nsIFrame* attachedToFrame;
  bool transformedFixed;
  return nsCSSRendering::ComputeImageLayerPositioningArea(
             mFrame->PresContext(), mFrame, mBackgroundRect,
             mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer],
             &attachedToFrame, &transformedFixed) +
         ToReferenceFrame();
}

bool nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange()
    const {
  if (!mBackgroundStyle) {
    return false;
  }

  nscoord radii[8];
  if (mFrame->GetBorderRadii(radii)) {
    // A change in the size of the positioning area might change the position
    // of the rounded corners.
    return true;
  }

  const nsStyleImageLayers::Layer& layer =
      mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
  return layer.RenderingMightDependOnPositioningAreaSizeChange();
}

void nsDisplayBackgroundImage::Paint(nsDisplayListBuilder* aBuilder,
                                     gfxContext* aCtx) {
  PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), &mBounds);
}

void nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder,
                                             gfxContext* aCtx,
                                             const nsRect& aBounds,
                                             nsRect* aClipRect) {
  gfxContext* ctx = aCtx;
  StyleGeometryBox clip =
      mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip;

  if (clip == StyleGeometryBox::Text) {
    if (!GenerateAndPushTextMask(StyleFrame(), aCtx, mBackgroundRect,
                                 aBuilder)) {
      return;
    }
  }

  nsCSSRendering::PaintBGParams params =
      nsCSSRendering::PaintBGParams::ForSingleLayer(
          *StyleFrame()->PresContext(), aBounds, mBackgroundRect, StyleFrame(),
          aBuilder->GetBackgroundPaintFlags(), mLayer, CompositionOp::OP_OVER,
          1.0f);
  params.bgClipRect = aClipRect;
  Unused << nsCSSRendering::PaintStyleImageLayer(params, *aCtx);

  if (clip == StyleGeometryBox::Text) {
    ctx->PopGroupAndBlend();
  }
}

void nsDisplayBackgroundImage::ComputeInvalidationRegion(
    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
    nsRegion* aInvalidRegion) const {
  if (!mBackgroundStyle) {
    return;
  }

  const auto* geometry =
      static_cast<const nsDisplayBackgroundGeometry*>(aGeometry);

  bool snap;
  nsRect bounds = GetBounds(aBuilder, &snap);
  nsRect positioningArea = GetPositioningArea();
  if (positioningArea.TopLeft() != geometry->mPositioningArea.TopLeft() ||
      (positioningArea.Size() != geometry->mPositioningArea.Size() &&
       RenderingMightDependOnPositioningAreaSizeChange())) {
    // Positioning area changed in a way that could cause everything to change,
    // so invalidate everything (both old and new painting areas).
    aInvalidRegion->Or(bounds, geometry->mBounds);
    return;
  }
  if (!mDestRect.IsEqualInterior(geometry->mDestRect)) {
    // Dest area changed in a way that could cause everything to change,
    // so invalidate everything (both old and new painting areas).
    aInvalidRegion->Or(bounds, geometry->mBounds);
    return;
  }
  if (!bounds.IsEqualInterior(geometry->mBounds)) {
    // Positioning area is unchanged, so invalidate just the change in the
    // painting area.
    aInvalidRegion->Xor(bounds, geometry->mBounds);
  }
}

nsRect nsDisplayBackgroundImage::GetBounds(nsDisplayListBuilder* aBuilder,
                                           bool* aSnap) const {
  *aSnap = true;
  return mBounds;
}

nsRect nsDisplayBackgroundImage::GetBoundsInternal(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrameForBounds) {
  // This allows nsDisplayTableBackgroundImage to change the frame used for
  // bounds calculation.
  nsIFrame* frame = aFrameForBounds ? aFrameForBounds : mFrame;

  nsPresContext* presContext = frame->PresContext();

  if (!mBackgroundStyle) {
    return nsRect();
  }

  nsRect clipRect = mBackgroundRect;
  if (frame->IsCanvasFrame()) {
    nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame);
    clipRect = canvasFrame->CanvasArea() + ToReferenceFrame();
  }
  const nsStyleImageLayers::Layer& layer =
      mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
  return nsCSSRendering::GetBackgroundLayerRect(
      presContext, frame, mBackgroundRect, clipRect, layer,
      aBuilder->GetBackgroundPaintFlags());
}

nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aData,
    nsIFrame* aCellFrame)
    : nsDisplayBackgroundImage(aBuilder, aFrame, aData, aCellFrame),
      mStyleFrame(aCellFrame) {
  if (aBuilder->IsRetainingDisplayList()) {
    mStyleFrame->AddDisplayItem(this);
  }
}

nsDisplayTableBackgroundImage::~nsDisplayTableBackgroundImage() {
  if (mStyleFrame) {
    mStyleFrame->RemoveDisplayItem(this);
  }
}

bool nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect) const {
  bool result = mStyleFrame ? mStyleFrame->IsInvalid(aRect) : false;
  aRect += ToReferenceFrame();
  return result;
}

nsDisplayThemedBackground::nsDisplayThemedBackground(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
    const nsRect& aBackgroundRect)
    : nsPaintedDisplayItem(aBuilder, aFrame), mBackgroundRect(aBackgroundRect) {
  MOZ_COUNT_CTOR(nsDisplayThemedBackground);
}

void nsDisplayThemedBackground::Init(nsDisplayListBuilder* aBuilder) {
  const nsStyleDisplay* disp = StyleFrame()->StyleDisplay();
  mAppearance = disp->EffectiveAppearance();
  StyleFrame()->IsThemed(disp, &mThemeTransparency);

  // Perform necessary RegisterThemeGeometry
  nsITheme* theme = StyleFrame()->PresContext()->Theme();
  nsITheme::ThemeGeometryType type =
      theme->ThemeGeometryTypeForWidget(StyleFrame(), mAppearance);
  if (type != nsITheme::eThemeGeometryTypeUnknown) {
    RegisterThemeGeometry(aBuilder, this, StyleFrame(), type);
  }

  mBounds = GetBoundsInternal();
}

void nsDisplayThemedBackground::WriteDebugInfo(std::stringstream& aStream) {
  aStream << " (themed, appearance:" << (int)mAppearance << ")";
}

void nsDisplayThemedBackground::HitTest(nsDisplayListBuilder* aBuilder,
                                        const nsRect& aRect,
                                        HitTestState* aState,
                                        nsTArray<nsIFrame*>* aOutFrames) {
  // Assume that any point in our background rect is a hit.
  if (mBackgroundRect.Intersects(aRect)) {
    aOutFrames->AppendElement(mFrame);
  }
}

nsRegion nsDisplayThemedBackground::GetOpaqueRegion(
    nsDisplayListBuilder* aBuilder, bool* aSnap) const {
  nsRegion result;
  *aSnap = false;

  if (mThemeTransparency == nsITheme::eOpaque) {
    *aSnap = true;
    result = mBackgroundRect;
  }
  return result;
}

Maybe<nscolor> nsDisplayThemedBackground::IsUniform(
    nsDisplayListBuilder* aBuilder) const {
  return Nothing();
}

nsRect nsDisplayThemedBackground::GetPositioningArea() const {
  return mBackgroundRect;
}

void nsDisplayThemedBackground::Paint(nsDisplayListBuilder* aBuilder,
                                      gfxContext* aCtx) {
  PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), nullptr);
}

void nsDisplayThemedBackground::PaintInternal(nsDisplayListBuilder* aBuilder,
                                              gfxContext* aCtx,
                                              const nsRect& aBounds,
                                              nsRect* aClipRect) {
  // XXXzw this ignores aClipRect.
  nsPresContext* presContext = StyleFrame()->PresContext();
  nsITheme* theme = presContext->Theme();
  nsRect drawing(mBackgroundRect);
  theme->GetWidgetOverflow(presContext->DeviceContext(), StyleFrame(),
                           mAppearance, &drawing);
  drawing.IntersectRect(drawing, aBounds);
  theme->DrawWidgetBackground(aCtx, StyleFrame(), mAppearance, mBackgroundRect,
                              drawing);
}

bool nsDisplayThemedBackground::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  nsITheme* theme = StyleFrame()->PresContext()->Theme();
  return theme->CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc,
                                                 aManager, StyleFrame(),
                                                 mAppearance, mBackgroundRect);
}

bool nsDisplayThemedBackground::IsWindowActive() const {
  DocumentState docState = mFrame->GetContent()->OwnerDoc()->GetDocumentState();
  return !docState.HasState(DocumentState::WINDOW_INACTIVE);
}

void nsDisplayThemedBackground::ComputeInvalidationRegion(
    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
    nsRegion* aInvalidRegion) const {
  const auto* geometry =
      static_cast<const nsDisplayThemedBackgroundGeometry*>(aGeometry);

  bool snap;
  nsRect bounds = GetBounds(aBuilder, &snap);
  nsRect positioningArea = GetPositioningArea();
  if (!positioningArea.IsEqualInterior(geometry->mPositioningArea)) {
    // Invalidate everything (both old and new painting areas).
    aInvalidRegion->Or(bounds, geometry->mBounds);
    return;
  }
  if (!bounds.IsEqualInterior(geometry->mBounds)) {
    // Positioning area is unchanged, so invalidate just the change in the
    // painting area.
    aInvalidRegion->Xor(bounds, geometry->mBounds);
  }
  nsITheme* theme = StyleFrame()->PresContext()->Theme();
  if (theme->WidgetAppearanceDependsOnWindowFocus(mAppearance) &&
      IsWindowActive() != geometry->mWindowIsActive) {
    aInvalidRegion->Or(*aInvalidRegion, bounds);
  }
}

nsRect nsDisplayThemedBackground::GetBounds(nsDisplayListBuilder* aBuilder,
                                            bool* aSnap) const {
  *aSnap = true;
  return mBounds;
}

nsRect nsDisplayThemedBackground::GetBoundsInternal() {
  nsPresContext* presContext = mFrame->PresContext();

  nsRect r = mBackgroundRect - ToReferenceFrame();
  presContext->Theme()->GetWidgetOverflow(
      presContext->DeviceContext(), mFrame,
      mFrame->StyleDisplay()->EffectiveAppearance(), &r);
  return r + ToReferenceFrame();
}

#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF)
void nsDisplayReflowCount::Paint(nsDisplayListBuilder* aBuilder,
                                 gfxContext* aCtx) {
  mFrame->PresShell()->PaintCount(mFrameName, aCtx, mFrame->PresContext(),
                                  mFrame, ToReferenceFrame(), mColor);
}
#endif

bool nsDisplayBackgroundColor::CanApplyOpacity(
    WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const {
  // Don't apply opacity if the background color is animated since the color is
  // going to be changed on the compositor.
  return !EffectCompositor::HasAnimationsForCompositor(
      mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR);
}

bool nsDisplayBackgroundColor::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  gfx::sRGBColor color = mColor;
  color.a *= aBuilder.GetInheritedOpacity();

  if (color == sRGBColor() &&
      !EffectCompositor::HasAnimationsForCompositor(
          mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) {
    return true;
  }

  if (HasBackgroundClipText()) {
    return false;
  }

  uint64_t animationsId = 0;
  // We don't support background-color animations on table elements yet.
  if (GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) {
    animationsId =
        AddAnimationsForWebRender(this, aManager, aDisplayListBuilder);
  }

  LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
      mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel());
  wr::LayoutRect r = wr::ToLayoutRect(bounds);

  if (animationsId) {
    wr::WrAnimationProperty prop{
        wr::WrAnimationType::BackgroundColor,
        animationsId,
    };
    aBuilder.PushRectWithAnimation(r, r, !BackfaceIsHidden(),
                                   wr::ToColorF(ToDeviceColor(color)), &prop);
  } else {
    aBuilder.StartGroup(this);
    aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false,
                      wr::ToColorF(ToDeviceColor(color)));
    aBuilder.FinishGroup();
  }

  return true;
}

void nsDisplayBackgroundColor::PaintWithClip(nsDisplayListBuilder* aBuilder,
                                             gfxContext* aCtx,
                                             const DisplayItemClip& aClip) {
  MOZ_ASSERT(!HasBackgroundClipText());

  if (mColor == sRGBColor()) {
    return;
  }

  nsRect fillRect = mBackgroundRect;
  if (aClip.HasClip()) {
    fillRect.IntersectRect(fillRect, aClip.GetClipRect());
  }

  DrawTarget* dt = aCtx->GetDrawTarget();
  int32_t A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
  Rect bounds = ToRect(nsLayoutUtils::RectToGfxRect(fillRect, A2D));
  MaybeSnapToDevicePixels(bounds, *dt);
  ColorPattern fill(ToDeviceColor(mColor));

  if (aClip.GetRoundedRectCount()) {
    MOZ_ASSERT(aClip.GetRoundedRectCount() == 1);

    AutoTArray<DisplayItemClip::RoundedRect, 1> roundedRect;
    aClip.AppendRoundedRects(&roundedRect);

    bool pushedClip = false;
    if (!fillRect.Contains(roundedRect[0].mRect)) {
      dt->PushClipRect(bounds);
      pushedClip = true;
    }

    RectCornerRadii pixelRadii;
    nsCSSRendering::ComputePixelRadii(roundedRect[0].mRadii, A2D, &pixelRadii);
    dt->FillRoundedRect(
        RoundedRect(NSRectToSnappedRect(roundedRect[0].mRect, A2D, *dt),
                    pixelRadii),
        fill);
    if (pushedClip) {
      dt->PopClip();
    }
  } else {
    dt->FillRect(bounds, fill);
  }
}

void nsDisplayBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
                                     gfxContext* aCtx) {
  if (mColor == sRGBColor()) {
    return;
  }

#if 0
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148418#c21 for why this
  // results in a precision induced rounding issue that makes the rect one
  // pixel shorter in rare cases. Disabled in favor of the old code for now.
  // Note that the pref layout.css.devPixelsPerPx needs to be set to 1 to
  // reproduce the bug.
  //
  // TODO:
  // This new path does not include support for background-clip:text; need to
  // be fixed if/when we switch to this new code path.

  DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();

  Rect rect = NSRectToSnappedRect(mBackgroundRect,
                                  mFrame->PresContext()->AppUnitsPerDevPixel(),
                                  aDrawTarget);
  ColorPattern color(ToDeviceColor(mColor));
  aDrawTarget.FillRect(rect, color);
#else
  gfxContext* ctx = aCtx;
  gfxRect bounds = nsLayoutUtils::RectToGfxRect(
      mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel());

  if (HasBackgroundClipText()) {
    if (!GenerateAndPushTextMask(mFrame, aCtx, mBackgroundRect, aBuilder)) {
      return;
    }

    ctx->SetColor(mColor);
    ctx->NewPath();
    ctx->SnappedRectangle(bounds);
    ctx->Fill();
    ctx->PopGroupAndBlend();
    return;
  }

  ctx->SetColor(mColor);
  ctx->NewPath();
  ctx->SnappedRectangle(bounds);
  ctx->Fill();
#endif
}

nsRegion nsDisplayBackgroundColor::GetOpaqueRegion(
    nsDisplayListBuilder* aBuilder, bool* aSnap) const {
  *aSnap = false;

  if (mColor.a != 1 ||
      // Even if the current alpha channel is 1, we treat this item as if it's
      // non-opaque if there is a background-color animation since the animation
      // might change the alpha channel.
      EffectCompositor::HasAnimationsForCompositor(
          mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) {
    return nsRegion();
  }

  if (!mHasStyle || HasBackgroundClipText()) {
    return nsRegion();
  }

  *aSnap = true;
  return GetInsideClipRect(this, mBottomLayerClip, mBackgroundRect,
                           mBackgroundRect);
}

Maybe<nscolor> nsDisplayBackgroundColor::IsUniform(
    nsDisplayListBuilder* aBuilder) const {
  return Some(mColor.ToABGR());
}

void nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder,
                                       const nsRect& aRect,
                                       HitTestState* aState,
                                       nsTArray<nsIFrame*>* aOutFrames) {
  if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
    // aRect doesn't intersect our border-radius curve.
    return;
  }

  aOutFrames->AppendElement(mFrame);
}

void nsDisplayBackgroundColor::WriteDebugInfo(std::stringstream& aStream) {
  aStream << " (rgba " << mColor.r << "," << mColor.g << "," << mColor.b << ","
          << mColor.a << ")";
  aStream << " backgroundRect" << mBackgroundRect;
}

nsRect nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder,
                                   bool* aSnap) const {
  *aSnap = false;
  return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
}

nsRect nsDisplayOutline::GetInnerRect() const {
  if (nsRect* savedOutlineInnerRect =
          mFrame->GetProperty(nsIFrame::OutlineInnerRectProperty())) {
    return *savedOutlineInnerRect;
  }
  return mFrame->GetRectRelativeToSelf();
}

void nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
  // TODO join outlines together
  MOZ_ASSERT(mFrame->StyleOutline()->ShouldPaintOutline(),
             "Should have not created a nsDisplayOutline!");

  nsRect rect = GetInnerRect() + ToReferenceFrame();
  nsPresContext* pc = mFrame->PresContext();
  if (IsThemedOutline()) {
    rect.Inflate(mFrame->StyleOutline()->EffectiveOffsetFor(rect));
    pc->Theme()->DrawWidgetBackground(aCtx, mFrame,
                                      StyleAppearance::FocusOutline, rect,
                                      GetPaintRect(aBuilder, aCtx));
    return;
  }

  nsCSSRendering::PaintNonThemedOutline(
      pc, *aCtx, mFrame, GetPaintRect(aBuilder, aCtx), rect, mFrame->Style());
}

bool nsDisplayOutline::IsThemedOutline() const {
#ifdef DEBUG
  nsPresContext* pc = mFrame->PresContext();
  MOZ_ASSERT(
      pc->Theme()->ThemeSupportsWidget(pc, mFrame,
                                       StyleAppearance::FocusOutline),
      "All of our supported platforms have support for themed focus-outlines");
#endif
  return mFrame->StyleOutline()->mOutlineStyle.IsAuto();
}

bool nsDisplayOutline::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  nsPresContext* pc = mFrame->PresContext();
  nsRect rect = GetInnerRect() + ToReferenceFrame();
  if (IsThemedOutline()) {
    rect.Inflate(mFrame->StyleOutline()->EffectiveOffsetFor(rect));
    return pc->Theme()->CreateWebRenderCommandsForWidget(
        aBuilder, aResources, aSc, aManager, mFrame,
        StyleAppearance::FocusOutline, rect);
  }

  bool dummy;
  Maybe<nsCSSBorderRenderer> borderRenderer =
      nsCSSRendering::CreateBorderRendererForNonThemedOutline(
          pc, /* aDrawTarget = */ nullptr, mFrame,
          GetBounds(aDisplayListBuilder, &dummy), rect, mFrame->Style());

  if (!borderRenderer) {
    // No border renderer means "there is no outline".
    // Paint nothing and return success.
    return true;
  }

  borderRenderer->CreateWebRenderCommands(this, aBuilder, aResources, aSc);
  return true;
}

bool nsDisplayOutline::HasRadius() const {
  const auto& radius = mFrame->StyleBorder()->mBorderRadius;
  return !nsLayoutUtils::HasNonZeroCorner(radius);
}

bool nsDisplayOutline::IsInvisibleInRect(const nsRect& aRect) const {
  const nsStyleOutline* outline = mFrame->StyleOutline();
  nsRect borderBox(ToReferenceFrame(), mFrame->GetSize());
  if (borderBox.Contains(aRect) && !HasRadius() &&
      outline->mOutlineOffset.ToCSSPixels() >= 0.0f) {
    // aRect is entirely inside the border-rect, and the outline isn't rendered
    // inside the border-rect, so the outline is not visible.
    return true;
  }
  return false;
}

void nsDisplayEventReceiver::HitTest(nsDisplayListBuilder* aBuilder,
                                     const nsRect& aRect, HitTestState* aState,
                                     nsTArray<nsIFrame*>* aOutFrames) {
  if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) {
    // aRect doesn't intersect our border-radius curve.
    return;
  }

  aOutFrames->AppendElement(mFrame);
}

bool nsDisplayCompositorHitTestInfo::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  return true;
}

int32_t nsDisplayCompositorHitTestInfo::ZIndex() const {
  return mOverrideZIndex ? *mOverrideZIndex : nsDisplayItem::ZIndex();
}

void nsDisplayCompositorHitTestInfo::SetOverrideZIndex(int32_t aZIndex) {
  mOverrideZIndex = Some(aZIndex);
}

nsDisplayCaret::nsDisplayCaret(nsDisplayListBuilder* aBuilder,
                               nsIFrame* aCaretFrame)
    : nsPaintedDisplayItem(aBuilder, aCaretFrame),
      mCaret(aBuilder->GetCaret()),
      mBounds(aBuilder->GetCaretRect() + ToReferenceFrame()) {
  MOZ_COUNT_CTOR(nsDisplayCaret);
  // The presence of a caret doesn't change the overflow rect
  // of the owning frame, so the normal building rect might not
  // include the caret at all. We use MarkFrameForDisplay to ensure
  // we build this item, and here we override the building rect
  // to cover the pixels we're going to draw.
  SetBuildingRect(mBounds);
}

#ifdef NS_BUILD_REFCNT_LOGGING
nsDisplayCaret::~nsDisplayCaret() { MOZ_COUNT_DTOR(nsDisplayCaret); }
#endif

nsRect nsDisplayCaret::GetBounds(nsDisplayListBuilder* aBuilder,
                                 bool* aSnap) const {
  *aSnap = true;
  // The caret returns a rect in the coordinates of mFrame.
  return mBounds;
}

void nsDisplayCaret::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
  // Note: Because we exist, we know that the caret is visible, so we don't
  // need to check for the caret's visibility.
  mCaret->PaintCaret(*aCtx->GetDrawTarget(), mFrame, ToReferenceFrame());
}

bool nsDisplayCaret::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  using namespace layers;
  nsRect caretRect;
  nsRect hookRect;
  nscolor caretColor;
  nsIFrame* frame =
      mCaret->GetPaintGeometry(&caretRect, &hookRect, &caretColor);
  MOZ_ASSERT(frame == mFrame, "We're referring different frame");
  if (!frame) {
    return true;
  }

  int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
  gfx::DeviceColor color = ToDeviceColor(caretColor);
  LayoutDeviceRect devCaretRect = LayoutDeviceRect::FromAppUnits(
      caretRect + ToReferenceFrame(), appUnitsPerDevPixel);
  LayoutDeviceRect devHookRect = LayoutDeviceRect::FromAppUnits(
      hookRect + ToReferenceFrame(), appUnitsPerDevPixel);

  wr::LayoutRect caret = wr::ToLayoutRect(devCaretRect);
  wr::LayoutRect hook = wr::ToLayoutRect(devHookRect);

  // Note, WR will pixel snap anything that is layout aligned.
  aBuilder.PushRect(caret, caret, !BackfaceIsHidden(), false, false,
                    wr::ToColorF(color));

  if (!devHookRect.IsEmpty()) {
    aBuilder.PushRect(hook, hook, !BackfaceIsHidden(), false, false,
                      wr::ToColorF(color));
  }
  return true;
}

nsDisplayBorder::nsDisplayBorder(nsDisplayListBuilder* aBuilder,
                                 nsIFrame* aFrame)
    : nsPaintedDisplayItem(aBuilder, aFrame) {
  MOZ_COUNT_CTOR(nsDisplayBorder);

  mBounds = CalculateBounds<nsRect>(*mFrame->StyleBorder());
}

bool nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect) const {
  nsRect paddingRect = GetPaddingRect();
  const nsStyleBorder* styleBorder;
  if (paddingRect.Contains(aRect) &&
      !(styleBorder = mFrame->StyleBorder())->IsBorderImageSizeAvailable() &&
      !nsLayoutUtils::HasNonZeroCorner(styleBorder->mBorderRadius)) {
    // aRect is entirely inside the content rect, and no part
    // of the border is rendered inside the content rect, so we are not
    // visible
    // Skip this if there's a border-image (which draws a background
    // too) or if there is a border-radius (which makes the border draw
    // further in).
    return true;
  }

  return false;
}

nsDisplayItemGeometry* nsDisplayBorder::AllocateGeometry(
    nsDisplayListBuilder* aBuilder) {
  return new nsDisplayBorderGeometry(this, aBuilder);
}

void nsDisplayBorder::ComputeInvalidationRegion(
    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
    nsRegion* aInvalidRegion) const {
  const auto* geometry = static_cast<const nsDisplayBorderGeometry*>(aGeometry);
  bool snap;

  if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap))) {
    // We can probably get away with only invalidating the difference
    // between the border and padding rects, but the XUL ui at least
    // is apparently painting a background with this?
    aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
  }
}

bool nsDisplayBorder::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  nsRect rect = nsRect(ToReferenceFrame(), mFrame->GetSize());

  ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder(
      this, mFrame, rect, aBuilder, aResources, aSc, aManager,
      aDisplayListBuilder);

  if (drawResult == ImgDrawResult::NOT_SUPPORTED) {
    return false;
  }
  return true;
};

void nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
  nsPoint offset = ToReferenceFrame();

  PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
                               ? PaintBorderFlags::SyncDecodeImages
                               : PaintBorderFlags();

  Unused << nsCSSRendering::PaintBorder(
      mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(aBuilder, aCtx),
      nsRect(offset, mFrame->GetSize()), mFrame->Style(), flags,
      mFrame->GetSkipSides());
}

nsRect nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder,
                                  bool* aSnap) const {
  *aSnap = true;
  return mBounds;
}

void nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder,
                                    gfxContext* aCtx) {
  nsPoint offset = ToReferenceFrame();
  nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset;
  nsPresContext* presContext = mFrame->PresContext();

  AUTO_PROFILER_LABEL("nsDisplayBoxShadowOuter::Paint", GRAPHICS);

  nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame, borderRect,
                                      GetPaintRect(aBuilder, aCtx), 1.0f);
}

nsRect nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder,
                                          bool* aSnap) const {
  *aSnap = false;
  return mBounds;
}

nsRect nsDisplayBoxShadowOuter::GetBoundsInternal() {
  return nsLayoutUtils::GetBoxShadowRectForFrame(mFrame, mFrame->GetSize()) +
         ToReferenceFrame();
}

bool nsDisplayBoxShadowOuter::IsInvisibleInRect(const nsRect& aRect) const {
  nsPoint origin = ToReferenceFrame();
  nsRect frameRect(origin, mFrame->GetSize());
  if (!frameRect.Contains(aRect)) {
    return false;
  }

  // the visible region is entirely inside the border-rect, and box shadows
  // never render within the border-rect (unless there's a border radius).
  nscoord twipsRadii[8];
  bool hasBorderRadii = mFrame->GetBorderRadii(twipsRadii);
  if (!hasBorderRadii) {
    return true;
  }

  return RoundedRectContainsRect(frameRect, twipsRadii, aRect);
}

bool nsDisplayBoxShadowOuter::CanBuildWebRenderDisplayItems() const {
  auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan();
  if (shadows.IsEmpty()) {
    return false;
  }

  bool hasBorderRadius;
  // We don't support native themed things yet like box shadows around
  // input buttons.
  //
  // TODO(emilio): The non-native theme could provide the right rect+radius
  // instead relatively painlessly, if we find this causes performance issues or
  // what not.
  return !nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);
}

bool nsDisplayBoxShadowOuter::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  if (!CanBuildWebRenderDisplayItems()) {
    return false;
  }

  int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
  nsPoint offset = ToReferenceFrame();
  nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset;
  bool snap;
  nsRect bounds = GetBounds(aDisplayListBuilder, &snap);

  bool hasBorderRadius;
  bool nativeTheme =
      nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);

  // Don't need the full size of the shadow rect like we do in
  // nsCSSRendering since WR takes care of calculations for blur
  // and spread radius.
  nsRect frameRect =
      nsCSSRendering::GetShadowRect(borderRect, nativeTheme, mFrame);

  RectCornerRadii borderRadii;
  if (hasBorderRadius) {
    hasBorderRadius = nsCSSRendering::GetBorderRadii(frameRect, borderRect,
                                                     mFrame, borderRadii);
  }

  // Everything here is in app units, change to device units.
  LayoutDeviceRect clipRect =
      LayoutDeviceRect::FromAppUnits(bounds, appUnitsPerDevPixel);
  auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan();
  MOZ_ASSERT(!shadows.IsEmpty());

  for (const auto& shadow : Reversed(shadows)) {
    if (shadow.inset) {
      continue;
    }

    float blurRadius =
        float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel);
    gfx::sRGBColor shadowColor = nsCSSRendering::GetShadowColor(
        shadow.base, mFrame, aBuilder.GetInheritedOpacity());

    // We don't move the shadow rect here since WR does it for us
    // Now translate everything to device pixels.
    const nsRect& shadowRect = frameRect;
    LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits(
        nsPoint(shadow.base.horizontal.ToAppUnits(),
                shadow.base.vertical.ToAppUnits()),
        appUnitsPerDevPixel);

    LayoutDeviceRect deviceBox =
        LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel);
    wr::LayoutRect deviceBoxRect = wr::ToLayoutRect(deviceBox);
    wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect);

    LayoutDeviceSize zeroSize;
    wr::BorderRadius borderRadius =
        wr::ToBorderRadius(zeroSize, zeroSize, zeroSize, zeroSize);
    if (hasBorderRadius) {
      borderRadius = wr::ToBorderRadius(
          LayoutDeviceSize::FromUnknownSize(borderRadii.TopLeft()),
          LayoutDeviceSize::FromUnknownSize(borderRadii.TopRight()),
          LayoutDeviceSize::FromUnknownSize(borderRadii.BottomLeft()),
          LayoutDeviceSize::FromUnknownSize(borderRadii.BottomRight()));
    }

    float spreadRadius =
        float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel);

    aBuilder.PushBoxShadow(deviceBoxRect, deviceClipRect, !BackfaceIsHidden(),
                           deviceBoxRect, wr::ToLayoutVector2D(shadowOffset),
                           wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius,
                           spreadRadius, borderRadius,
                           wr::BoxShadowClipMode::Outset);
  }

  return true;
}

void nsDisplayBoxShadowOuter::ComputeInvalidationRegion(
    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
    nsRegion* aInvalidRegion) const {
  const auto* geometry =
      static_cast<const nsDisplayItemGenericGeometry*>(aGeometry);
  bool snap;
  if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
      !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
    nsRegion oldShadow, newShadow;
    nscoord dontCare[8];
    bool hasBorderRadius = mFrame->GetBorderRadii(dontCare);
    if (hasBorderRadius) {
      // If we have rounded corners then we need to invalidate the frame area
      // too since we paint into it.
      oldShadow = geometry->mBounds;
      newShadow = GetBounds(aBuilder, &snap);
    } else {
      oldShadow.Sub(geometry->mBounds, geometry->mBorderRect);
      newShadow.Sub(GetBounds(aBuilder, &snap), GetBorderRect());
    }
    aInvalidRegion->Or(oldShadow, newShadow);
  }
}

void nsDisplayBoxShadowInner::Paint(nsDisplayListBuilder* aBuilder,
                                    gfxContext* aCtx) {
  nsPoint offset = ToReferenceFrame();
  nsRect borderRect = nsRect(offset, mFrame->GetSize());
  nsPresContext* presContext = mFrame->PresContext();

  AUTO_PROFILER_LABEL("nsDisplayBoxShadowInner::Paint", GRAPHICS);

  nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect);
}

bool nsDisplayBoxShadowInner::CanCreateWebRenderCommands(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
    const nsPoint& aReferenceOffset) {
  auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
  if (shadows.IsEmpty()) {
    // Means we don't have to paint anything
    return true;
  }

  bool hasBorderRadius;
  bool nativeTheme =
      nsCSSRendering::HasBoxShadowNativeTheme(aFrame, hasBorderRadius);

  // We don't support native themed things yet like box shadows around
  // input buttons.
  return !nativeTheme;
}

/* static */
void nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc,
    nsRect& aVisibleRect, nsIFrame* aFrame, const nsRect& aBorderRect) {
  if (!nsCSSRendering::ShouldPaintBoxShadowInner(aFrame)) {
    return;
  }

  int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();

  auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();

  LayoutDeviceRect clipRect =
      LayoutDeviceRect::FromAppUnits(aVisibleRect, appUnitsPerDevPixel);

  for (const auto& shadow : Reversed(shadows)) {
    if (!shadow.inset) {
      continue;
    }

    nsRect shadowRect =
        nsCSSRendering::GetBoxShadowInnerPaddingRect(aFrame, aBorderRect);
    RectCornerRadii innerRadii;
    nsCSSRendering::GetShadowInnerRadii(aFrame, aBorderRect, innerRadii);

    // Now translate everything to device pixels.
    LayoutDeviceRect deviceBoxRect =
        LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel);
    wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect);
    sRGBColor shadowColor =
        nsCSSRendering::GetShadowColor(shadow.base, aFrame, 1.0);

    LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits(
        nsPoint(shadow.base.horizontal.ToAppUnits(),
                shadow.base.vertical.ToAppUnits()),
        appUnitsPerDevPixel);

    float blurRadius =
        float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel);

    wr::BorderRadius borderRadius = wr::ToBorderRadius(
        LayoutDeviceSize::FromUnknownSize(innerRadii.TopLeft()),
        LayoutDeviceSize::FromUnknownSize(innerRadii.TopRight()),
        LayoutDeviceSize::FromUnknownSize(innerRadii.BottomLeft()),
        LayoutDeviceSize::FromUnknownSize(innerRadii.BottomRight()));
    // NOTE: Any spread radius > 0 will render nothing. WR Bug.
    float spreadRadius =
        float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel);

    aBuilder.PushBoxShadow(
        wr::ToLayoutRect(deviceBoxRect), deviceClipRect,
        !aFrame->BackfaceIsHidden(), wr::ToLayoutRect(deviceBoxRect),
        wr::ToLayoutVector2D(shadowOffset),
        wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius, spreadRadius,
        borderRadius, wr::BoxShadowClipMode::Inset);
  }
}

bool nsDisplayBoxShadowInner::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  if (!CanCreateWebRenderCommands(aDisplayListBuilder, mFrame,
                                  ToReferenceFrame())) {
    return false;
  }

  bool snap;
  nsRect visible = GetBounds(aDisplayListBuilder, &snap);
  nsPoint offset = ToReferenceFrame();
  nsRect borderRect = nsRect(offset, mFrame->GetSize());
  nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
      aBuilder, aSc, visible, mFrame, borderRect);

  return true;
}

nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
                                     nsIFrame* aFrame, nsDisplayList* aList)
    : nsDisplayWrapList(aBuilder, aFrame, aList,
                        aBuilder->CurrentActiveScrolledRoot(), false) {}

nsDisplayWrapList::nsDisplayWrapList(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain)
    : nsPaintedDisplayItem(aBuilder, aFrame, aActiveScrolledRoot),
      mList(aBuilder),
      mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()),
      mOverrideZIndex(0),
      mHasZIndexOverride(false),
      mClearingClipChain(aClearClipChain) {
  MOZ_COUNT_CTOR(nsDisplayWrapList);

  mBaseBuildingRect = GetBuildingRect();

  mListPtr = &mList;
  mListPtr->AppendToTop(aList);
  mOriginalClipChain = mClipChain;
  nsDisplayWrapList::UpdateBounds(aBuilder);
}

nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
                                     nsIFrame* aFrame, nsDisplayItem* aItem)
    : nsPaintedDisplayItem(aBuilder, aFrame,
                           aBuilder->CurrentActiveScrolledRoot()),
      mList(aBuilder),
      mOverrideZIndex(0),
      mHasZIndexOverride(false) {
  MOZ_COUNT_CTOR(nsDisplayWrapList);

  mBaseBuildingRect = GetBuildingRect();

  mListPtr = &mList;
  mListPtr->AppendToTop(aItem);
  mOriginalClipChain = mClipChain;
  nsDisplayWrapList::UpdateBounds(aBuilder);

  if (!aFrame || !aFrame->IsTransformed()) {
    return;
  }

  // See the previous nsDisplayWrapList constructor
  if (aItem->Frame() == aFrame) {
    mToReferenceFrame = aItem->ToReferenceFrame();
  }

  nsRect visible = aBuilder->GetVisibleRect() +
                   aBuilder->GetCurrentFrameOffsetToReferenceFrame();

  SetBuildingRect(visible);
}

nsDisplayWrapList::~nsDisplayWrapList() { MOZ_COUNT_DTOR(nsDisplayWrapList); }

void nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder,
                                const nsRect& aRect, HitTestState* aState,
                                nsTArray<nsIFrame*>* aOutFrames) {
  mListPtr->HitTest(aBuilder, aRect, aState, aOutFrames);
}

nsRect nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder,
                                    bool* aSnap) const {
  *aSnap = false;
  return mBounds;
}

nsRegion nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                            bool* aSnap) const {
  *aSnap = false;
  bool snap;
  return ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(),
                                    GetBounds(aBuilder, &snap));
}

Maybe<nscolor> nsDisplayWrapList::IsUniform(
    nsDisplayListBuilder* aBuilder) const {
  // We could try to do something but let's conservatively just return Nothing.
  return Nothing();
}

void nsDisplayWrapper::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
  NS_ERROR("nsDisplayWrapper should have been flattened away for painting");
}

nsRect nsDisplayWrapList::GetComponentAlphaBounds(
    nsDisplayListBuilder* aBuilder) const {
  return mListPtr->GetComponentAlphaBounds(aBuilder);
}

bool nsDisplayWrapList::CreateWebRenderCommandsNewClipListOption(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder, bool aNewClipList) {
  aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
      GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources,
      aNewClipList);
  return true;
}

static nsresult WrapDisplayList(nsDisplayListBuilder* aBuilder,
                                nsIFrame* aFrame, nsDisplayList* aList,
                                nsDisplayItemWrapper* aWrapper) {
  if (!aList->GetTop()) {
    return NS_OK;
  }
  nsDisplayItem* item = aWrapper->WrapList(aBuilder, aFrame, aList);
  if (!item) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  // aList was emptied
  aList->AppendToTop(item);
  return NS_OK;
}

static nsresult WrapEachDisplayItem(nsDisplayListBuilder* aBuilder,
                                    nsDisplayList* aList,
                                    nsDisplayItemWrapper* aWrapper) {
  for (nsDisplayItem* item : aList->TakeItems()) {
    item = aWrapper->WrapItem(aBuilder, item);
    if (!item) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
    aList->AppendToTop(item);
  }
  // aList was emptied
  return NS_OK;
}

nsresult nsDisplayItemWrapper::WrapLists(nsDisplayListBuilder* aBuilder,
                                         nsIFrame* aFrame,
                                         const nsDisplayListSet& aIn,
                                         const nsDisplayListSet& aOut) {
  nsresult rv = WrapListsInPlace(aBuilder, aFrame, aIn);
  NS_ENSURE_SUCCESS(rv, rv);

  if (&aOut == &aIn) {
    return NS_OK;
  }
  aOut.BorderBackground()->AppendToTop(aIn.BorderBackground());
  aOut.BlockBorderBackgrounds()->AppendToTop(aIn.BlockBorderBackgrounds());
  aOut.Floats()->AppendToTop(aIn.Floats());
  aOut.Content()->AppendToTop(aIn.Content());
  aOut.PositionedDescendants()->AppendToTop(aIn.PositionedDescendants());
  aOut.Outlines()->AppendToTop(aIn.Outlines());
  return NS_OK;
}

nsresult nsDisplayItemWrapper::WrapListsInPlace(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
    const nsDisplayListSet& aLists) {
  nsresult rv;
  if (WrapBorderBackground()) {
    // Our border-backgrounds are in-flow
    rv = WrapDisplayList(aBuilder, aFrame, aLists.BorderBackground(), this);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  // Our block border-backgrounds are in-flow
  rv = WrapDisplayList(aBuilder, aFrame, aLists.BlockBorderBackgrounds(), this);
  NS_ENSURE_SUCCESS(rv, rv);
  // The floats are not in flow
  rv = WrapEachDisplayItem(aBuilder, aLists.Floats(), this);
  NS_ENSURE_SUCCESS(rv, rv);
  // Our child content is in flow
  rv = WrapDisplayList(aBuilder, aFrame, aLists.Content(), this);
  NS_ENSURE_SUCCESS(rv, rv);
  // The positioned descendants may not be in-flow
  rv = WrapEachDisplayItem(aBuilder, aLists.PositionedDescendants(), this);
  NS_ENSURE_SUCCESS(rv, rv);
  // The outlines may not be in-flow
  return WrapEachDisplayItem(aBuilder, aLists.Outlines(), this);
}

nsDisplayOpacity::nsDisplayOpacity(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot, bool aForEventsOnly,
    bool aNeedsActiveLayer, bool aWrapsBackdropFilter)
    : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
      mOpacity(aFrame->StyleEffects()->mOpacity),
      mForEventsOnly(aForEventsOnly),
      mNeedsActiveLayer(aNeedsActiveLayer),
      mChildOpacityState(ChildOpacityState::Unknown),
      mWrapsBackdropFilter(aWrapsBackdropFilter) {
  MOZ_COUNT_CTOR(nsDisplayOpacity);
}

void nsDisplayOpacity::HitTest(nsDisplayListBuilder* aBuilder,
                               const nsRect& aRect,
                               nsDisplayItem::HitTestState* aState,
                               nsTArray<nsIFrame*>* aOutFrames) {
  AutoRestore<float> opacity(aState->mCurrentOpacity);
  aState->mCurrentOpacity *= mOpacity;

  // TODO(emilio): special-casing zero is a bit arbitrary... Maybe we should
  // only consider fully opaque items? Or make this configurable somehow?
  if (aBuilder->HitTestIsForVisibility() && mOpacity == 0.0f) {
    return;
  }
  nsDisplayWrapList::HitTest(aBuilder, aRect, aState, aOutFrames);
}

nsRegion nsDisplayOpacity::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                           bool* aSnap) const {
  *aSnap = false;
  // The only time where mOpacity == 1.0 should be when we have will-change.
  // We could report this as opaque then but when the will-change value starts
  // animating the element would become non opaque and could cause repaints.
  return nsRegion();
}

void nsDisplayOpacity::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
  if (GetOpacity() == 0.0f) {
    return;
  }

  if (GetOpacity() == 1.0f) {
    GetChildren()->Paint(aBuilder, aCtx,
                         mFrame->PresContext()->AppUnitsPerDevPixel());
    return;
  }

  // TODO: Compute a bounds rect to pass to PushLayer for a smaller
  // allocation.
  aCtx->GetDrawTarget()->PushLayer(false, GetOpacity(), nullptr, gfx::Matrix());
  GetChildren()->Paint(aBuilder, aCtx,
                       mFrame->PresContext()->AppUnitsPerDevPixel());
  aCtx->GetDrawTarget()->PopLayer();
}

/* static */
bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder,
                                        nsIFrame* aFrame) {
  return EffectCompositor::HasAnimationsForCompositor(
             aFrame, DisplayItemType::TYPE_OPACITY) ||
         (ActiveLayerTracker::IsStyleAnimated(
             aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties()));
}

bool nsDisplayOpacity::CanApplyOpacity(WebRenderLayerManager* aManager,
                                       nsDisplayListBuilder* aBuilder) const {
  return !EffectCompositor::HasAnimationsForCompositor(
      mFrame, DisplayItemType::TYPE_OPACITY);
}

// Only try folding our opacity down if we have at most |kOpacityMaxChildCount|
// children that don't overlap and can all apply the opacity to themselves.
static const size_t kOpacityMaxChildCount = 3;

// |kOpacityMaxListSize| defines an early exit condition for opacity items that
// are likely have more child items than |kOpacityMaxChildCount|.
static const size_t kOpacityMaxListSize = kOpacityMaxChildCount * 2;

/**
 * Recursively iterates through |aList| and collects at most
 * |kOpacityMaxChildCount| display item pointers to items that return true for
 * CanApplyOpacity(). The item pointers are added to |aArray|.
 *
 * LayerEventRegions and WrapList items are ignored.
 *
 * We need to do this recursively, because the child display items might contain
 * nested nsDisplayWrapLists.
 *
 * Returns false if there are more than |kOpacityMaxChildCount| items, or if an
 * item that returns false for CanApplyOpacity() is encountered.
 * Otherwise returns true.
 */
static bool CollectItemsWithOpacity(WebRenderLayerManager* aManager,
                                    nsDisplayListBuilder* aBuilder,
                                    nsDisplayList* aList,
                                    nsTArray<nsPaintedDisplayItem*>& aArray) {
  if (aList->Length() > kOpacityMaxListSize) {
    // Exit early, since |aList| will likely contain more than
    // |kOpacityMaxChildCount| items.
    return false;
  }

  for (nsDisplayItem* i : *aList) {
    const DisplayItemType type = i->GetType();

    if (type == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
      continue;
    }

    // Descend only into wraplists.
    if (type == DisplayItemType::TYPE_WRAP_LIST ||
        type == DisplayItemType::TYPE_CONTAINER) {
      // The current display item has children, process them first.
      if (!CollectItemsWithOpacity(aManager, aBuilder, i->GetChildren(),
                                   aArray)) {
        return false;
      }

      continue;
    }

    if (aArray.Length() == kOpacityMaxChildCount) {
      return false;
    }

    auto* item = i->AsPaintedDisplayItem();
    if (!item || !item->CanApplyOpacity(aManager, aBuilder)) {
      return false;
    }

    aArray.AppendElement(item);
  }

  return true;
}

bool nsDisplayOpacity::CanApplyToChildren(WebRenderLayerManager* aManager,
                                          nsDisplayListBuilder* aBuilder) {
  if (mChildOpacityState == ChildOpacityState::Deferred) {
    return false;
  }

  // Iterate through the child display list and copy at most
  // |kOpacityMaxChildCount| child display item pointers to a temporary list.
  AutoTArray<nsPaintedDisplayItem*, kOpacityMaxChildCount> items;
  if (!CollectItemsWithOpacity(aManager, aBuilder, &mList, items)) {
    mChildOpacityState = ChildOpacityState::Deferred;
    return false;
  }

  struct {
    nsPaintedDisplayItem* item{};
    nsRect bounds;
  } children[kOpacityMaxChildCount];

  bool snap;
  size_t childCount = 0;
  for (nsPaintedDisplayItem* item : items) {
    children[childCount].item = item;
    children[childCount].bounds = item->GetBounds(aBuilder, &snap);
    childCount++;
  }

  for (size_t i = 0; i < childCount; i++) {
    for (size_t j = i + 1; j < childCount; j++) {
      if (children[i].bounds.Intersects(children[j].bounds)) {
        mChildOpacityState = ChildOpacityState::Deferred;
        return false;
      }
    }
  }

  mChildOpacityState = ChildOpacityState::Applied;
  return true;
}

/**
 * Returns true if this nsDisplayOpacity contains only a filter or a mask item
 * that has the same frame as the opacity item, and that supports painting with
 * opacity. In this case the opacity item can be optimized away.
 */
bool nsDisplayOpacity::ApplyToMask() {
  if (mList.Length() != 1) {
    return false;
  }

  nsDisplayItem* item = mList.GetBottom();
  if (item->Frame() != mFrame) {
    // The effect item needs to have the same frame as the opacity item.
    return false;
  }

  const DisplayItemType type = item->GetType();
  if (type == DisplayItemType::TYPE_MASK) {
    return true;
  }

  return false;
}

bool nsDisplayOpacity::CanApplyOpacityToChildren(
    WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder,
    float aInheritedOpacity) {
  if (mFrame->GetPrevContinuation() || mFrame->GetNextContinuation() ||
      mFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
    // If we've been split, then we might need to merge, so
    // don't flatten us away.
    return false;
  }

  if (mNeedsActiveLayer || mOpacity == 0.0) {
    // If our opacity is zero then we'll discard all descendant display items
    // except for layer event regions, so there's no point in doing this
    // optimization (and if we do do it, then invalidations of those descendants
    // might trigger repainting).
    return false;
  }

  if (mList.IsEmpty()) {
    return false;
  }

  // We can only flatten opacity items into a mask if we haven't
  // already flattened an earlier ancestor, since the SVG code pulls the opacity
  // from style directly, and won't know about the outer opacity value.
  if (aInheritedOpacity == 1.0f && ApplyToMask()) {
    MOZ_ASSERT(SVGIntegrationUtils::UsingEffectsForFrame(mFrame));
    mChildOpacityState = ChildOpacityState::Applied;
    return true;
  }

  // Return true if we successfully applied opacity to child items.
  return CanApplyToChildren(aManager, aBuilder);
}

void nsDisplayOpacity::ComputeInvalidationRegion(
    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
    nsRegion* aInvalidRegion) const {
  const auto* geometry =
      static_cast<const nsDisplayOpacityGeometry*>(aGeometry);

  bool snap;
  if (mOpacity != geometry->mOpacity) {
    aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
  }
}

void nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream) {
  aStream << " (opacity " << mOpacity << ", mChildOpacityState: ";
  switch (mChildOpacityState) {
    case ChildOpacityState::Unknown:
      aStream << "Unknown";
      break;
    case ChildOpacityState::Applied:
      aStream << "Applied";
      break;
    case ChildOpacityState::Deferred:
      aStream << "Deferred";
      break;
    default:
      break;
  }

  aStream << ")";
}

bool nsDisplayOpacity::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied);
  float oldOpacity = aBuilder.GetInheritedOpacity();
  const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain();
  aBuilder.SetInheritedOpacity(1.0f);
  aBuilder.SetInheritedClipChain(nullptr);
  float opacity = mOpacity * oldOpacity;
  float* opacityForSC = &opacity;

  uint64_t animationsId =
      AddAnimationsForWebRender(this, aManager, aDisplayListBuilder);
  wr::WrAnimationProperty prop{
      wr::WrAnimationType::Opacity,
      animationsId,
  };

  wr::StackingContextParams params;
  params.animation = animationsId ? &prop : nullptr;
  params.opacity = opacityForSC;
  params.clip =
      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
  if (mWrapsBackdropFilter) {
    params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER;
  }
  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                           params);

  aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
      &mList, this, aDisplayListBuilder, sc, aBuilder, aResources);
  aBuilder.SetInheritedOpacity(oldOpacity);
  aBuilder.SetInheritedClipChain(oldClipChain);
  return true;
}

nsDisplayBlendMode::nsDisplayBlendMode(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    StyleBlend aBlendMode, const ActiveScrolledRoot* aActiveScrolledRoot,
    const bool aIsForBackground)
    : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
      mBlendMode(aBlendMode),
      mIsForBackground(aIsForBackground) {
  MOZ_COUNT_CTOR(nsDisplayBlendMode);
}

nsRegion nsDisplayBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                             bool* aSnap) const {
  *aSnap = false;
  // We are never considered opaque
  return nsRegion();
}

bool nsDisplayBlendMode::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  wr::StackingContextParams params;
  params.mix_blend_mode =
      wr::ToMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode));
  params.clip =
      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                           params);

  return nsDisplayWrapList::CreateWebRenderCommands(
      aBuilder, aResources, sc, aManager, aDisplayListBuilder);
}

void nsDisplayBlendMode::Paint(nsDisplayListBuilder* aBuilder,
                               gfxContext* aCtx) {
  // This should be switched to use PushLayerWithBlend, once it's
  // been implemented for all DrawTarget backends.
  DrawTarget* dt = aCtx->GetDrawTarget();
  int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
  Rect rect = NSRectToRect(GetPaintRect(aBuilder, aCtx), appUnitsPerDevPixel);
  rect.RoundOut();

  // Create a temporary DrawTarget that is clipped to the area that
  // we're going to draw to. This will include the same transform as
  // is currently on |dt|.
  RefPtr<DrawTarget> temp =
      dt->CreateClippedDrawTarget(rect, SurfaceFormat::B8G8R8A8);
  if (!temp) {
    return;
  }

  gfxContext ctx(temp, /* aPreserveTransform */ true);

  GetChildren()->Paint(aBuilder, &ctx,
                       mFrame->PresContext()->AppUnitsPerDevPixel());

  // Draw the temporary DT to the real destination, applying the blend mode, but
  // no transform.
  temp->Flush();
  RefPtr<SourceSurface> surface = temp->Snapshot();
  gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
  dt->SetTransform(Matrix());
  dt->DrawSurface(
      surface, Rect(surface->GetRect()), Rect(surface->GetRect()),
      DrawSurfaceOptions(),
      DrawOptions(1.0f, nsCSSRendering::GetGFXBlendMode(mBlendMode)));
}

gfx::CompositionOp nsDisplayBlendMode::BlendMode() {
  return nsCSSRendering::GetGFXBlendMode(mBlendMode);
}

bool nsDisplayBlendMode::CanMerge(const nsDisplayItem* aItem) const {
  // Items for the same content element should be merged into a single
  // compositing group.
  if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) ||
      !HasSameContent(aItem)) {
    return false;
  }

  const auto* item = static_cast<const nsDisplayBlendMode*>(aItem);
  if (mIsForBackground || item->mIsForBackground) {
    // Don't merge background-blend-mode items
    return false;
  }

  return true;
}

/* static */
nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForMixBlendMode(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot) {
  return MakeDisplayItem<nsDisplayBlendContainer>(aBuilder, aFrame, aList,
                                                  aActiveScrolledRoot, false);
}

/* static */
nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForBackgroundBlendMode(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
    nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot) {
  if (aSecondaryFrame) {
    auto type = GetTableTypeFromFrame(aFrame);
    auto index = static_cast<uint16_t>(type);

    return MakeDisplayItemWithIndex<nsDisplayTableBlendContainer>(
        aBuilder, aSecondaryFrame, index, aList, aActiveScrolledRoot, true,
        aFrame);
  }

  return MakeDisplayItemWithIndex<nsDisplayBlendContainer>(
      aBuilder, aFrame, 1, aList, aActiveScrolledRoot, true);
}

nsDisplayBlendContainer::nsDisplayBlendContainer(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot, bool aIsForBackground)
    : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
      mIsForBackground(aIsForBackground) {
  MOZ_COUNT_CTOR(nsDisplayBlendContainer);
}

void nsDisplayBlendContainer::Paint(nsDisplayListBuilder* aBuilder,
                                    gfxContext* aCtx) {
  aCtx->GetDrawTarget()->PushLayer(false, 1.0, nullptr, gfx::Matrix());
  GetChildren()->Paint(aBuilder, aCtx,
                       mFrame->PresContext()->AppUnitsPerDevPixel());
  aCtx->GetDrawTarget()->PopLayer();
}

bool nsDisplayBlendContainer::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  wr::StackingContextParams params;
  params.flags |= wr::StackingContextFlags::IS_BLEND_CONTAINER;
  params.clip =
      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                           params);

  return nsDisplayWrapList::CreateWebRenderCommands(
      aBuilder, aResources, sc, aManager, aDisplayListBuilder);
}

nsDisplayOwnLayer::nsDisplayOwnLayer(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot,
    nsDisplayOwnLayerFlags aFlags, const ScrollbarData& aScrollbarData,
    bool aForceActive, bool aClearClipChain)
    : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot,
                        aClearClipChain),
      mFlags(aFlags),
      mScrollbarData(aScrollbarData),
      mForceActive(aForceActive),
      mWrAnimationId(0) {
  MOZ_COUNT_CTOR(nsDisplayOwnLayer);
}

bool nsDisplayOwnLayer::IsScrollThumbLayer() const {
  return mScrollbarData.mScrollbarLayerType == ScrollbarLayerType::Thumb;
}

bool nsDisplayOwnLayer::IsScrollbarContainer() const {
  return mScrollbarData.mScrollbarLayerType == ScrollbarLayerType::Container;
}

bool nsDisplayOwnLayer::IsRootScrollbarContainer() const {
  return IsScrollbarContainer() && IsScrollbarLayerForRoot();
}

bool nsDisplayOwnLayer::IsScrollbarLayerForRoot() const {
  return mFrame->PresContext()->IsRootContentDocumentCrossProcess() &&
         mScrollbarData.mTargetViewId ==
             nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext());
}

bool nsDisplayOwnLayer::IsZoomingLayer() const {
  return GetType() == DisplayItemType::TYPE_ASYNC_ZOOM;
}

bool nsDisplayOwnLayer::IsFixedPositionLayer() const {
  return GetType() == DisplayItemType::TYPE_FIXED_POSITION;
}

bool nsDisplayOwnLayer::IsStickyPositionLayer() const {
  return GetType() == DisplayItemType::TYPE_STICKY_POSITION;
}

bool nsDisplayOwnLayer::HasDynamicToolbar() const {
  if (!mFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
    return false;
  }
  return mFrame->PresContext()->HasDynamicToolbar() ||
         // For tests on Android, this pref is set to simulate the dynamic
         // toolbar
         StaticPrefs::apz_fixed_margin_override_enabled();
}

bool nsDisplayOwnLayer::ShouldFixedAndStickyContentGetAnimationIds() const {
#if defined(MOZ_WIDGET_ANDROID)
  return mFrame->PresContext()->IsRootContentDocumentCrossProcess();
#else
  return false;
#endif
}

bool nsDisplayOwnLayer::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  Maybe<wr::WrAnimationProperty> prop;
  bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() &&
                   (IsScrollThumbLayer() || IsZoomingLayer() ||
                    (IsFixedPositionLayer() &&
                     ShouldFixedAndStickyContentGetAnimationIds()) ||
                    (IsStickyPositionLayer() &&
                     ShouldFixedAndStickyContentGetAnimationIds()) ||
                    (IsRootScrollbarContainer() && HasDynamicToolbar()));

  if (needsProp) {
    // APZ is enabled and this is a scroll thumb or zooming layer, so we need
    // to create and set an animation id. That way APZ can adjust the position/
    // zoom of this content asynchronously as needed.
    RefPtr<WebRenderAPZAnimationData> animationData =
        aManager->CommandBuilder()
            .CreateOrRecycleWebRenderUserData<WebRenderAPZAnimationData>(this);
    mWrAnimationId = animationData->GetAnimationId();

    prop.emplace();
    prop->id = mWrAnimationId;
    prop->key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(),
                               wr::SpatialKeyKind::APZ);
    prop->effect_type = wr::WrAnimationType::Transform;
  }

  wr::StackingContextParams params;
  params.animation = prop.ptrOr(nullptr);
  params.clip =
      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
  if (IsScrollbarContainer() && IsRootScrollbarContainer()) {
    params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_CONTAINER;
  }
  if (IsZoomingLayer() ||
      ((IsFixedPositionLayer() &&
        ShouldFixedAndStickyContentGetAnimationIds()) ||
       (IsStickyPositionLayer() &&
        ShouldFixedAndStickyContentGetAnimationIds()) ||
       (IsRootScrollbarContainer() && HasDynamicToolbar()))) {
    params.is_2d_scale_translation = true;
    params.should_snap = true;
  }

  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                           params);

  nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
                                             aDisplayListBuilder);
  return true;
}

bool nsDisplayOwnLayer::UpdateScrollData(WebRenderScrollData* aData,
                                         WebRenderLayerScrollData* aLayerData) {
  bool isRelevantToApz =
      (IsScrollThumbLayer() || IsScrollbarContainer() || IsZoomingLayer() ||
       (IsFixedPositionLayer() &&
        ShouldFixedAndStickyContentGetAnimationIds()) ||
       (IsStickyPositionLayer() &&
        ShouldFixedAndStickyContentGetAnimationIds()));

  if (!isRelevantToApz) {
    return false;
  }

  if (!aLayerData) {
    return true;
  }

  if (IsZoomingLayer()) {
    aLayerData->SetZoomAnimationId(mWrAnimationId);
    return true;
  }

  if (IsFixedPositionLayer() && ShouldFixedAndStickyContentGetAnimationIds()) {
    aLayerData->SetFixedPositionAnimationId(mWrAnimationId);
    return true;
  }

  if (IsStickyPositionLayer() && ShouldFixedAndStickyContentGetAnimationIds()) {
    aLayerData->SetStickyPositionAnimationId(mWrAnimationId);
    return true;
  }

  MOZ_ASSERT(IsScrollbarContainer() || IsScrollThumbLayer());

  aLayerData->SetScrollbarData(mScrollbarData);

  if (IsRootScrollbarContainer() && HasDynamicToolbar()) {
    aLayerData->SetScrollbarAnimationId(mWrAnimationId);
    return true;
  }

  if (IsScrollThumbLayer()) {
    aLayerData->SetScrollbarAnimationId(mWrAnimationId);
    LayoutDeviceRect bounds = LayoutDeviceIntRect::FromAppUnits(
        mBounds, mFrame->PresContext()->AppUnitsPerDevPixel());
    // Subframe scrollbars are subject to the pinch-zoom scale,
    // but root scrollbars are not because they are outside of the
    // region that is zoomed.
    const float resolution =
        IsScrollbarLayerForRoot()
            ? 1.0f
            : mFrame->PresContext()->PresShell()->GetCumulativeResolution();
    LayerIntRect layerBounds =
        RoundedOut(bounds * LayoutDeviceToLayerScale(resolution));
    aLayerData->SetVisibleRegion(LayerIntRegion(layerBounds));
  }
  return true;
}

void nsDisplayOwnLayer::WriteDebugInfo(std::stringstream& aStream) {
  aStream << nsPrintfCString(" (flags 0x%x) (scrolltarget %" PRIu64 ")",
                             (int)mFlags, mScrollbarData.mTargetViewId)
                 .get();
}

nsDisplaySubDocument::nsDisplaySubDocument(nsDisplayListBuilder* aBuilder,
                                           nsIFrame* aFrame,
                                           nsSubDocumentFrame* aSubDocFrame,
                                           nsDisplayList* aList,
                                           nsDisplayOwnLayerFlags aFlags)
    : nsDisplayOwnLayer(aBuilder, aFrame, aList,
                        aBuilder->CurrentActiveScrolledRoot(), aFlags),
      mScrollParentId(aBuilder->GetCurrentScrollParentId()),
      mShouldFlatten(false),
      mSubDocFrame(aSubDocFrame) {
  MOZ_COUNT_CTOR(nsDisplaySubDocument);

  if (mSubDocFrame && mSubDocFrame != mFrame) {
    mSubDocFrame->AddDisplayItem(this);
  }
}

nsDisplaySubDocument::~nsDisplaySubDocument() {
  MOZ_COUNT_DTOR(nsDisplaySubDocument);
  if (mSubDocFrame) {
    mSubDocFrame->RemoveDisplayItem(this);
  }
}

nsIFrame* nsDisplaySubDocument::FrameForInvalidation() const {
  return mSubDocFrame ? mSubDocFrame : mFrame;
}

void nsDisplaySubDocument::RemoveFrame(nsIFrame* aFrame) {
  if (aFrame == mSubDocFrame) {
    mSubDocFrame = nullptr;
    SetDeletedFrame();
  }
  nsDisplayOwnLayer::RemoveFrame(aFrame);
}

static bool UseDisplayPortForViewport(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame) {
  return aBuilder->IsPaintingToWindow() &&
         DisplayPortUtils::ViewportHasDisplayPort(aFrame->PresContext());
}

nsRect nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder,
                                       bool* aSnap) const {
  bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);

  if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) &&
      usingDisplayPort) {
    *aSnap = false;
    return mFrame->GetRect() + aBuilder->ToReferenceFrame(mFrame);
  }

  return nsDisplayOwnLayer::GetBounds(aBuilder, aSnap);
}

nsRegion nsDisplaySubDocument::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                               bool* aSnap) const {
  bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);

  if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) &&
      usingDisplayPort) {
    *aSnap = false;
    return nsRegion();
  }

  return nsDisplayOwnLayer::GetOpaqueRegion(aBuilder, aSnap);
}

/* static */
nsDisplayFixedPosition* nsDisplayFixedPosition::CreateForFixedBackground(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
    nsDisplayBackgroundImage* aImage, const uint16_t aIndex,
    const ActiveScrolledRoot* aScrollTargetASR) {
  nsDisplayList temp(aBuilder);
  temp.AppendToTop(aImage);

  if (aSecondaryFrame) {
    auto tableType = GetTableTypeFromFrame(aFrame);
    const uint16_t index = CalculateTablePerFrameKey(aIndex + 1, tableType);
    return MakeDisplayItemWithIndex<nsDisplayTableFixedPosition>(
        aBuilder, aSecondaryFrame, index, &temp, aFrame, aScrollTargetASR);
  }

  return MakeDisplayItemWithIndex<nsDisplayFixedPosition>(
      aBuilder, aFrame, aIndex + 1, &temp, aScrollTargetASR);
}

nsDisplayFixedPosition::nsDisplayFixedPosition(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot,
    const ActiveScrolledRoot* aScrollTargetASR)
    : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot),
      mScrollTargetASR(aScrollTargetASR),
      mIsFixedBackground(false) {
  MOZ_COUNT_CTOR(nsDisplayFixedPosition);
}

nsDisplayFixedPosition::nsDisplayFixedPosition(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aScrollTargetASR)
    : nsDisplayOwnLayer(aBuilder, aFrame, aList,
                        aBuilder->CurrentActiveScrolledRoot()),
      // For fixed backgrounds, this is the ASR for the nearest scroll frame.
      mScrollTargetASR(aScrollTargetASR),
      mIsFixedBackground(true) {
  MOZ_COUNT_CTOR(nsDisplayFixedPosition);
}

ScrollableLayerGuid::ViewID nsDisplayFixedPosition::GetScrollTargetId() {
  if (mScrollTargetASR &&
      (mIsFixedBackground || !nsLayoutUtils::IsReallyFixedPos(mFrame))) {
    return mScrollTargetASR->GetViewId();
  }
  return nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext());
}

bool nsDisplayFixedPosition::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  SideBits sides = SideBits::eNone;
  if (!mIsFixedBackground) {
    sides = nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame);
  }

  // We install this RAII scrolltarget tracker so that any
  // nsDisplayCompositorHitTestInfo items inside this fixed-pos item (and that
  // share the same ASR as this item) use the correct scroll target. That way
  // attempts to scroll on those items will scroll the root scroll frame.
  wr::DisplayListBuilder::FixedPosScrollTargetTracker tracker(
      aBuilder, GetActiveScrolledRoot(), GetScrollTargetId(), sides);
  return nsDisplayOwnLayer::CreateWebRenderCommands(
      aBuilder, aResources, aSc, aManager, aDisplayListBuilder);
}

bool nsDisplayFixedPosition::UpdateScrollData(
    WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
  if (aLayerData) {
    if (!mIsFixedBackground) {
      aLayerData->SetFixedPositionSides(
          nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame));
    }
    aLayerData->SetFixedPositionScrollContainerId(GetScrollTargetId());
  }
  nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData);
  return true;
}

void nsDisplayFixedPosition::WriteDebugInfo(std::stringstream& aStream) {
  aStream << nsPrintfCString(
                 " (containerASR %s) (scrolltarget %" PRIu64 ")",
                 ActiveScrolledRoot::ToString(mScrollTargetASR).get(),
                 GetScrollTargetId())
                 .get();
}

TableType GetTableTypeFromFrame(nsIFrame* aFrame) {
  if (aFrame->IsTableFrame()) {
    return TableType::Table;
  }

  if (aFrame->IsTableColFrame()) {
    return TableType::TableCol;
  }

  if (aFrame->IsTableColGroupFrame()) {
    return TableType::TableColGroup;
  }

  if (aFrame->IsTableRowFrame()) {
    return TableType::TableRow;
  }

  if (aFrame->IsTableRowGroupFrame()) {
    return TableType::TableRowGroup;
  }

  if (aFrame->IsTableCellFrame()) {
    return TableType::TableCell;
  }

  MOZ_ASSERT_UNREACHABLE("Invalid frame.");
  return TableType::Table;
}

nsDisplayTableFixedPosition::nsDisplayTableFixedPosition(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    nsIFrame* aAncestorFrame, const ActiveScrolledRoot* aScrollTargetASR)
    : nsDisplayFixedPosition(aBuilder, aFrame, aList, aScrollTargetASR),
      mAncestorFrame(aAncestorFrame) {
  if (aBuilder->IsRetainingDisplayList()) {
    mAncestorFrame->AddDisplayItem(this);
  }
}

nsDisplayStickyPosition::nsDisplayStickyPosition(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot,
    const ActiveScrolledRoot* aContainerASR, bool aClippedToDisplayPort)
    : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot),
      mContainerASR(aContainerASR),
      mClippedToDisplayPort(aClippedToDisplayPort),
      mShouldFlatten(false) {
  MOZ_COUNT_CTOR(nsDisplayStickyPosition);
}

// Returns the smallest distance from "0" to the range [min, max] where
// min <= max. Despite the name, the return value is actually a 1-D vector,
// and so may be negative if max < 0.
static nscoord DistanceToRange(nscoord min, nscoord max) {
  MOZ_ASSERT(min <= max);
  if (max < 0) {
    return max;
  }
  if (min > 0) {
    return min;
  }
  MOZ_ASSERT(min <= 0 && max >= 0);
  return 0;
}

// Returns the magnitude of the part of the range [min, max] that is greater
// than zero. The return value is always non-negative.
static nscoord PositivePart(nscoord min, nscoord max) {
  MOZ_ASSERT(min <= max);
  if (min >= 0) {
    return max - min;
  }
  if (max > 0) {
    return max;
  }
  return 0;
}

// Returns the magnitude of the part of the range [min, max] that is less
// than zero. The return value is always non-negative.
static nscoord NegativePart(nscoord min, nscoord max) {
  MOZ_ASSERT(min <= max);
  if (max <= 0) {
    return max - min;
  }
  if (min < 0) {
    return 0 - min;
  }
  return 0;
}

StickyScrollContainer* nsDisplayStickyPosition::GetStickyScrollContainer() {
  StickyScrollContainer* stickyScrollContainer =
      StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame);
  if (stickyScrollContainer) {
    // If there's no ASR for the scrollframe that this sticky item is attached
    // to, then don't create a WR sticky item for it either. Trying to do so
    // will end in sadness because WR will interpret some coordinates as
    // relative to the nearest enclosing scrollframe, which will correspond
    // to the nearest ancestor ASR on the gecko side. That ASR will not be the
    // same as the scrollframe this sticky item is actually supposed to be
    // attached to, thus the sadness.
    // Not sending WR the sticky item is ok, because the enclosing scrollframe
    // will never be asynchronously scrolled. Instead we will always position
    // the sticky items correctly on the gecko side and WR will never need to
    // adjust their position itself.
    MOZ_ASSERT(
        stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled());
    if (!stickyScrollContainer->ScrollFrame()
             ->IsMaybeAsynchronouslyScrolled()) {
      stickyScrollContainer = nullptr;
    }
  }
  return stickyScrollContainer;
}

bool nsDisplayStickyPosition::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer();

  Maybe<wr::SpaceAndClipChainHelper> saccHelper;

  if (stickyScrollContainer) {
    float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();

    bool snap;
    nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap);

    Maybe<float> topMargin;
    Maybe<float> rightMargin;
    Maybe<float> bottomMargin;
    Maybe<float> leftMargin;
    wr::StickyOffsetBounds vBounds = {0.0, 0.0};
    wr::StickyOffsetBounds hBounds = {0.0, 0.0};
    nsPoint appliedOffset;

    nsRectAbsolute outer;
    nsRectAbsolute inner;
    stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner);

    nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame());
    nsPoint offset =
        scrollFrame->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame();

    // Adjust the scrollPort coordinates to be relative to the reference frame,
    // so that it is in the same space as everything else.
    nsRect scrollPort =
        stickyScrollContainer->ScrollFrame()->GetScrollPortRect();
    scrollPort += offset;

    // The following computations make more sense upon understanding the
    // semantics of "inner" and "outer", which is explained in the comment on
    // SetStickyPositionData in Layers.h.

    if (outer.YMost() != inner.YMost()) {
      // Question: How far will itemBounds.y be from the top of the scrollport
      // when we have scrolled from the current scroll position of "0" to
      // reach the range [inner.YMost(), outer.YMost()] where the item gets
      // stuck?
      // Answer: the current distance is "itemBounds.y - scrollPort.y". That
      // needs to be adjusted by the distance to the range, less any other
      // sticky ranges that fall between 0 and the range. If the distance is
      // negative (i.e. inner.YMost() <= outer.YMost() < 0) then we would be
      // scrolling upwards (decreasing scroll offset) to reach that range,
      // which would increase itemBounds.y and make it farther away from the
      // top of the scrollport. So in that case the adjustment is -distance.
      // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then
      // we would be scrolling downwards, itemBounds.y would decrease, and we
      // again need to adjust by -distance. If we are already in the range
      // then no adjustment is needed and distance is 0 so again using
      // -distance works. If the distance is positive, and the item has both
      // top and bottom sticky ranges, then the bottom sticky range may fall
      // (entirely[1] or partly[2]) between the current scroll position.
      // [1]: 0 <= outer.Y() <= inner.Y() < inner.YMost() <= outer.YMost()
      // [2]: outer.Y() < 0 <= inner.Y() < inner.YMost() <= outer.YMost()
      // In these cases, the item doesn't actually move for that part of the
      // distance, so we need to subtract out that bit, which can be computed
      // as the positive portion of the range [outer.Y(), inner.Y()].
      nscoord distance = DistanceToRange(inner.YMost(), outer.YMost());
      if (distance > 0) {
        distance -= PositivePart(outer.Y(), inner.Y());
      }
      topMargin = Some(NSAppUnitsToFloatPixels(
          itemBounds.y - scrollPort.y - distance, auPerDevPixel));
      // Question: What is the maximum positive ("downward") offset that WR
      // will have to apply to this item in order to prevent the item from
      // visually moving?
      // Answer: Since the item is "sticky" in the range [inner.YMost(),
      // outer.YMost()], the maximum offset will be the size of the range, which
      // is outer.YMost() - inner.YMost().
      vBounds.max =
          NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel);
      // Question: how much of an offset has layout already applied to the item?
      // Answer: if we are
      // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or
      // (b) past the sticky range (inner.YMost() < outer.YMost() < 0)
      // then layout has already applied some offset to the position of the
      // item. The amount of the adjustment is |0 - inner.YMost()| in case (a)
      // and |outer.YMost() - inner.YMost()| in case (b).
      if (inner.YMost() < 0) {
        appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost();
        MOZ_ASSERT(appliedOffset.y > 0);
      }
    }
    if (outer.Y() != inner.Y()) {
      // Similar logic as in the previous section, but this time we care about
      // the distance from itemBounds.YMost() to scrollPort.YMost().
      nscoord distance = DistanceToRange(outer.Y(), inner.Y());
      if (distance < 0) {
        distance += NegativePart(inner.YMost(), outer.YMost());
      }
      bottomMargin = Some(NSAppUnitsToFloatPixels(
          scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel));
      // And here WR will be moving the item upwards rather than downwards so
      // again things are inverted from the previous block.
      vBounds.min =
          NSAppUnitsToFloatPixels(outer.Y() - inner.Y(), auPerDevPixel);
      // We can't have appliedOffset be both positive and negative, and the top
      // adjustment takes priority. So here we only update appliedOffset.y if
      // it wasn't set by the top-sticky case above.
      if (appliedOffset.y == 0 && inner.Y() > 0) {
        appliedOffset.y = std::max(0, outer.Y()) - inner.Y();
        MOZ_ASSERT(appliedOffset.y < 0);
      }
    }
    // Same as above, but for the x-axis
    if (outer.XMost() != inner.XMost()) {
      nscoord distance = DistanceToRange(inner.XMost(), outer.XMost());
      if (distance > 0) {
        distance -= PositivePart(outer.X(), inner.X());
      }
      leftMargin = Some(NSAppUnitsToFloatPixels(
          itemBounds.x - scrollPort.x - distance, auPerDevPixel));
      hBounds.max =
          NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel);
      if (inner.XMost() < 0) {
        appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost();
        MOZ_ASSERT(appliedOffset.x > 0);
      }
    }
    if (outer.X() != inner.X()) {
      nscoord distance = DistanceToRange(outer.X(), inner.X());
      if (distance < 0) {
        distance += NegativePart(inner.XMost(), outer.XMost());
      }
      rightMargin = Some(NSAppUnitsToFloatPixels(
          scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel));
      hBounds.min =
          NSAppUnitsToFloatPixels(outer.X() - inner.X(), auPerDevPixel);
      if (appliedOffset.x == 0 && inner.X() > 0) {
        appliedOffset.x = std::max(0, outer.X()) - inner.X();
        MOZ_ASSERT(appliedOffset.x < 0);
      }
    }

    LayoutDeviceRect bounds =
        LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel);
    wr::LayoutVector2D applied = {
        NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel),
        NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)};
    wr::WrSpatialId spatialId = aBuilder.DefineStickyFrame(
        wr::ToLayoutRect(bounds), topMargin.ptrOr(nullptr),
        rightMargin.ptrOr(nullptr), bottomMargin.ptrOr(nullptr),
        leftMargin.ptrOr(nullptr), vBounds, hBounds, applied,
        wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(),
                       wr::SpatialKeyKind::Sticky));

    saccHelper.emplace(aBuilder, spatialId);
    aManager->CommandBuilder().PushOverrideForASR(mContainerASR, spatialId);
  }

  {
    wr::StackingContextParams params;
    params.clip =
        wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
    StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this,
                             aBuilder, params);
    nsDisplayOwnLayer::CreateWebRenderCommands(aBuilder, aResources, sc,
                                               aManager, aDisplayListBuilder);
  }

  if (stickyScrollContainer) {
    aManager->CommandBuilder().PopOverrideForASR(mContainerASR);
  }

  return true;
}

void nsDisplayStickyPosition::CalculateLayerScrollRanges(
    StickyScrollContainer* aStickyScrollContainer, float aAppUnitsPerDevPixel,
    float aScaleX, float aScaleY, LayerRectAbsolute& aStickyOuter,
    LayerRectAbsolute& aStickyInner) {
  nsRectAbsolute outer;
  nsRectAbsolute inner;
  aStickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner);
  aStickyOuter.SetBox(
      NSAppUnitsToFloatPixels(outer.X(), aAppUnitsPerDevPixel) * aScaleX,
      NSAppUnitsToFloatPixels(outer.Y(), aAppUnitsPerDevPixel) * aScaleY,
      NSAppUnitsToFloatPixels(outer.XMost(), aAppUnitsPerDevPixel) * aScaleX,
      NSAppUnitsToFloatPixels(outer.YMost(), aAppUnitsPerDevPixel) * aScaleY);
  aStickyInner.SetBox(
      NSAppUnitsToFloatPixels(inner.X(), aAppUnitsPerDevPixel) * aScaleX,
      NSAppUnitsToFloatPixels(inner.Y(), aAppUnitsPerDevPixel) * aScaleY,
      NSAppUnitsToFloatPixels(inner.XMost(), aAppUnitsPerDevPixel) * aScaleX,
      NSAppUnitsToFloatPixels(inner.YMost(), aAppUnitsPerDevPixel) * aScaleY);
}

bool nsDisplayStickyPosition::UpdateScrollData(
    WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
  bool hasDynamicToolbar = HasDynamicToolbar();
  if (aLayerData && hasDynamicToolbar) {
    StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer();
    if (stickyScrollContainer) {
      float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
      float cumulativeResolution =
          mFrame->PresShell()->GetCumulativeResolution();
      LayerRectAbsolute stickyOuter;
      LayerRectAbsolute stickyInner;
      CalculateLayerScrollRanges(stickyScrollContainer, auPerDevPixel,
                                 cumulativeResolution, cumulativeResolution,
                                 stickyOuter, stickyInner);
      aLayerData->SetStickyScrollRangeOuter(stickyOuter);
      aLayerData->SetStickyScrollRangeInner(stickyInner);

      SideBits sides =
          nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame);
      aLayerData->SetFixedPositionSides(sides);

      ScrollableLayerGuid::ViewID scrollId =
          nsLayoutUtils::FindOrCreateIDFor(stickyScrollContainer->ScrollFrame()
                                               ->GetScrolledFrame()
                                               ->GetContent());
      aLayerData->SetStickyPositionScrollContainerId(scrollId);
    }
  }
  // Return true if either there is a dynamic toolbar affecting this sticky
  // item or the OwnLayer base implementation returns true for some other
  // reason.
  bool ret = hasDynamicToolbar;
  ret |= nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData);
  return ret;
}

nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer(
    nsDisplayListBuilder* aBuilder, nsIFrame* aScrolledFrame,
    nsIFrame* aScrollFrame, const CompositorHitTestInfo& aHitInfo,
    const nsRect& aHitArea)
    : nsDisplayWrapList(aBuilder, aScrollFrame),
      mScrollFrame(aScrollFrame),
      mScrolledFrame(aScrolledFrame),
      mScrollParentId(aBuilder->GetCurrentScrollParentId()),
      mHitInfo(aHitInfo),
      mHitArea(aHitArea) {
#ifdef NS_BUILD_REFCNT_LOGGING
  MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer);
#endif
}

UniquePtr<ScrollMetadata> nsDisplayScrollInfoLayer::ComputeScrollMetadata(
    nsDisplayListBuilder* aBuilder, WebRenderLayerManager* aLayerManager) {
  ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata(
      mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(), Frame(),
      ToReferenceFrame(), aLayerManager, mScrollParentId,
      mScrollFrame->GetSize(), false);
  metadata.GetMetrics().SetIsScrollInfoLayer(true);
  nsIScrollableFrame* scrollableFrame = mScrollFrame->GetScrollTargetFrame();
  if (scrollableFrame) {
    aBuilder->AddScrollFrameToNotify(scrollableFrame);
  }

  return UniquePtr<ScrollMetadata>(new ScrollMetadata(metadata));
}

bool nsDisplayScrollInfoLayer::UpdateScrollData(
    WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
  if (aLayerData) {
    UniquePtr<ScrollMetadata> metadata =
        ComputeScrollMetadata(aData->GetBuilder(), aData->GetManager());
    MOZ_ASSERT(aData);
    MOZ_ASSERT(metadata);
    aLayerData->AppendScrollMetadata(*aData, *metadata);
  }
  return true;
}

bool nsDisplayScrollInfoLayer::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  ScrollableLayerGuid::ViewID scrollId =
      nsLayoutUtils::FindOrCreateIDFor(mScrollFrame->GetContent());

  const LayoutDeviceRect devRect = LayoutDeviceRect::FromAppUnits(
      mHitArea, mScrollFrame->PresContext()->AppUnitsPerDevPixel());

  const wr::LayoutRect rect = wr::ToLayoutRect(devRect);

  aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden(), scrollId, mHitInfo,
                       SideBits::eNone);

  return true;
}

void nsDisplayScrollInfoLayer::WriteDebugInfo(std::stringstream& aStream) {
  aStream << " (scrollframe " << mScrollFrame << " scrolledFrame "
          << mScrolledFrame << ")";
}

nsDisplayZoom::nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                             nsSubDocumentFrame* aSubDocFrame,
                             nsDisplayList* aList, int32_t aAPD,
                             int32_t aParentAPD, nsDisplayOwnLayerFlags aFlags)
    : nsDisplaySubDocument(aBuilder, aFrame, aSubDocFrame, aList, aFlags),
      mAPD(aAPD),
      mParentAPD(aParentAPD) {
  MOZ_COUNT_CTOR(nsDisplayZoom);
}

nsRect nsDisplayZoom::GetBounds(nsDisplayListBuilder* aBuilder,
                                bool* aSnap) const {
  nsRect bounds = nsDisplaySubDocument::GetBounds(aBuilder, aSnap);
  *aSnap = false;
  return bounds.ScaleToOtherAppUnitsRoundOut(mAPD, mParentAPD);
}

void nsDisplayZoom::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                            HitTestState* aState,
                            nsTArray<nsIFrame*>* aOutFrames) {
  nsRect rect;
  // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1
  // rect as well instead of possibly rounding the width or height to zero.
  if (aRect.width == 1 && aRect.height == 1) {
    rect.MoveTo(aRect.TopLeft().ScaleToOtherAppUnits(mParentAPD, mAPD));
    rect.width = rect.height = 1;
  } else {
    rect = aRect.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD);
  }
  mList.HitTest(aBuilder, rect, aState, aOutFrames);
}

nsDisplayAsyncZoom::nsDisplayAsyncZoom(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot, FrameMetrics::ViewID aViewID)
    : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot),
      mViewID(aViewID) {
  MOZ_COUNT_CTOR(nsDisplayAsyncZoom);
}

#ifdef NS_BUILD_REFCNT_LOGGING
nsDisplayAsyncZoom::~nsDisplayAsyncZoom() {
  MOZ_COUNT_DTOR(nsDisplayAsyncZoom);
}
#endif

void nsDisplayAsyncZoom::HitTest(nsDisplayListBuilder* aBuilder,
                                 const nsRect& aRect, HitTestState* aState,
                                 nsTArray<nsIFrame*>* aOutFrames) {
#ifdef DEBUG
  nsIScrollableFrame* scrollFrame = do_QueryFrame(mFrame);
  MOZ_ASSERT(scrollFrame && ViewportUtils::IsZoomedContentRoot(
                                scrollFrame->GetScrolledFrame()));
#endif
  nsRect rect = ViewportUtils::VisualToLayout(aRect, mFrame->PresShell());
  mList.HitTest(aBuilder, rect, aState, aOutFrames);
}

bool nsDisplayAsyncZoom::UpdateScrollData(
    WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
  bool ret = nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData);
  MOZ_ASSERT(ret);
  if (aLayerData) {
    aLayerData->SetAsyncZoomContainerId(mViewID);
  }
  return ret;
}

///////////////////////////////////////////////////
// nsDisplayTransform Implementation
//

#ifndef DEBUG
static_assert(sizeof(nsDisplayTransform) <= 512,
              "nsDisplayTransform has grown");
#endif

nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
                                       nsIFrame* aFrame, nsDisplayList* aList,
                                       const nsRect& aChildrenBuildingRect)
    : nsPaintedDisplayItem(aBuilder, aFrame),
      mChildren(aBuilder),
      mTransform(Some(Matrix4x4())),
      mChildrenBuildingRect(aChildrenBuildingRect),
      mPrerenderDecision(PrerenderDecision::No),
      mIsTransformSeparator(true),
      mHasTransformGetter(false),
      mHasAssociatedPerspective(false) {
  MOZ_COUNT_CTOR(nsDisplayTransform);
  MOZ_ASSERT(aFrame, "Must have a frame!");
  Init(aBuilder, aList);
}

nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
                                       nsIFrame* aFrame, nsDisplayList* aList,
                                       const nsRect& aChildrenBuildingRect,
                                       PrerenderDecision aPrerenderDecision)
    : nsPaintedDisplayItem(aBuilder, aFrame),
      mChildren(aBuilder),
      mChildrenBuildingRect(aChildrenBuildingRect),
      mPrerenderDecision(aPrerenderDecision),
      mIsTransformSeparator(false),
      mHasTransformGetter(false),
      mHasAssociatedPerspective(false) {
  MOZ_COUNT_CTOR(nsDisplayTransform);
  MOZ_ASSERT(aFrame, "Must have a frame!");
  SetReferenceFrameToAncestor(aBuilder);
  Init(aBuilder, aList);
}

nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder,
                                       nsIFrame* aFrame, nsDisplayList* aList,
                                       const nsRect& aChildrenBuildingRect,
                                       decltype(WithTransformGetter))
    : nsPaintedDisplayItem(aBuilder, aFrame),
      mChildren(aBuilder),
      mChildrenBuildingRect(aChildrenBuildingRect),
      mPrerenderDecision(PrerenderDecision::No),
      mIsTransformSeparator(false),
      mHasTransformGetter(true),
      mHasAssociatedPerspective(false) {
  MOZ_COUNT_CTOR(nsDisplayTransform);
  MOZ_ASSERT(aFrame, "Must have a frame!");
  MOZ_ASSERT(aFrame->GetTransformGetter());
  Init(aBuilder, aList);
}

void nsDisplayTransform::SetReferenceFrameToAncestor(
    nsDisplayListBuilder* aBuilder) {
  if (mFrame == aBuilder->RootReferenceFrame()) {
    return;
  }
  // We manually recompute mToReferenceFrame without going through the
  // builder, since this won't apply the 'additional offset'. Our
  // children will already be painting with that applied, and we don't
  // want to include it a second time in our transform. We don't recompute
  // our visible/building rects, since those should still include the additional
  // offset.
  // TODO: Are there are things computed using our ToReferenceFrame that should
  // have the additional offset applied? Should we instead just manually remove
  // the offset from our transform instead of this more general value?
  // Can we instead apply the additional offset to us and not our children, like
  // we do for all other offsets (and how reference frames are supposed to
  // work)?
  nsIFrame* outerFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(mFrame);
  const nsIFrame* referenceFrame = aBuilder->FindReferenceFrameFor(outerFrame);
  mToReferenceFrame = mFrame->GetOffsetToCrossDoc(referenceFrame);
}

void nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder,
                              nsDisplayList* aChildren) {
  mChildren.AppendToTop(aChildren);
  UpdateBounds(aBuilder);
}

bool nsDisplayTransform::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
  return false;
}

/* Returns the delta specified by the transform-origin property.
 * This is a positive delta, meaning that it indicates the direction to move
 * to get from (0, 0) of the frame to the transform origin.  This function is
 * called off the main thread.
 */
/* static */
Point3D nsDisplayTransform::GetDeltaToTransformOrigin(
    const nsIFrame* aFrame, TransformReferenceBox& aRefBox,
    float aAppUnitsPerPixel) {
  MOZ_ASSERT(aFrame, "Can't get delta for a null frame!");
  MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() ||
                 aFrame->Combines3DTransformWithAncestors(),
             "Shouldn't get a delta for an untransformed frame!");

  if (!aFrame->IsTransformed()) {
    return Point3D();
  }

  /* For both of the coordinates, if the value of transform is a
   * percentage, it's relative to the size of the frame.  Otherwise, if it's
   * a distance, it's already computed for us!
   */
  const nsStyleDisplay* display = aFrame->StyleDisplay();

  const StyleTransformOrigin& transformOrigin = display->mTransformOrigin;
  CSSPoint origin = nsStyleTransformMatrix::Convert2DPosition(
      transformOrigin.horizontal, transformOrigin.vertical, aRefBox);

  // Note:
  // 1. SVG frames have a reference box that can be (and typically is) offset
  //    from the TopLeft() of the frame. We need to account for that here.
  // 2. If we are using transform-box:content-box in CSS layout, we have the
  //    offset from TopLeft() of the frame as well.
  origin.x += CSSPixel::FromAppUnits(aRefBox.X());
  origin.y += CSSPixel::FromAppUnits(aRefBox.Y());

  float scale = AppUnitsPerCSSPixel() / float(aAppUnitsPerPixel);
  float z = transformOrigin.depth._0;
  return Point3D(origin.x * scale, origin.y * scale, z * scale);
}

/* static */
bool nsDisplayTransform::ComputePerspectiveMatrix(const nsIFrame* aFrame,
                                                  float aAppUnitsPerPixel,
                                                  Matrix4x4& aOutMatrix) {
  MOZ_ASSERT(aFrame, "Can't get delta for a null frame!");
  MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() ||
                 aFrame->Combines3DTransformWithAncestors(),
             "Shouldn't get a delta for an untransformed frame!");
  MOZ_ASSERT(aOutMatrix.IsIdentity(), "Must have a blank output matrix");

  if (!aFrame->IsTransformed()) {
    return false;
  }

  // TODO: Is it possible that the perspectiveFrame's bounds haven't been set
  // correctly yet (similar to the aBoundsOverride case for
  // GetResultingTransformMatrix)?
  nsIFrame* perspectiveFrame =
      aFrame->GetClosestFlattenedTreeAncestorPrimaryFrame();
  if (!perspectiveFrame) {
    return false;
  }

  /* Grab the values for perspective and perspective-origin (if present) */
  const nsStyleDisplay* perspectiveDisplay = perspectiveFrame->StyleDisplay();
  if (perspectiveDisplay->mChildPerspective.IsNone()) {
    return false;
  }

  MOZ_ASSERT(perspectiveDisplay->mChildPerspective.IsLength());
  float perspective =
      perspectiveDisplay->mChildPerspective.AsLength().ToCSSPixels();
  perspective = std::max(1.0f, perspective);
  if (perspective < std::numeric_limits<Float>::epsilon()) {
    return true;
  }

  TransformReferenceBox refBox(perspectiveFrame);

  Point perspectiveOrigin = nsStyleTransformMatrix::Convert2DPosition(
      perspectiveDisplay->mPerspectiveOrigin.horizontal,
      perspectiveDisplay->mPerspectiveOrigin.vertical, refBox,
      aAppUnitsPerPixel);

  /* GetOffsetTo computes the offset required to move from 0,0 in
   * perspectiveFrame to 0,0 in aFrame. Although we actually want the inverse of
   * this, it's faster to compute this way.
   */
  nsPoint frameToPerspectiveOffset = -aFrame->GetOffsetTo(perspectiveFrame);
  Point frameToPerspectiveGfxOffset(
      NSAppUnitsToFloatPixels(frameToPerspectiveOffset.x, aAppUnitsPerPixel),
      NSAppUnitsToFloatPixels(frameToPerspectiveOffset.y, aAppUnitsPerPixel));

  /* Move the perspective origin to be relative to aFrame, instead of relative
   * to the containing block which is how it was specified in the style system.
   */
  perspectiveOrigin += frameToPerspectiveGfxOffset;

  aOutMatrix._34 =
      -1.0 / NSAppUnitsToFloatPixels(CSSPixel::ToAppUnits(perspective),
                                     aAppUnitsPerPixel);

  aOutMatrix.ChangeBasis(Point3D(perspectiveOrigin.x, perspectiveOrigin.y, 0));
  return true;
}

nsDisplayTransform::FrameTransformProperties::FrameTransformProperties(
    const nsIFrame* aFrame, TransformReferenceBox& aRefBox,
    float aAppUnitsPerPixel)
    : mFrame(aFrame),
      mTranslate(aFrame->StyleDisplay()->mTranslate),
      mRotate(aFrame->StyleDisplay()->mRotate),
      mScale(aFrame->StyleDisplay()->mScale),
      mTransform(aFrame->StyleDisplay()->mTransform),
      mMotion(MotionPathUtils::ResolveMotionPath(aFrame, aRefBox)),
      mToTransformOrigin(
          GetDeltaToTransformOrigin(aFrame, aRefBox, aAppUnitsPerPixel)) {}

/* Wraps up the transform matrix in a change-of-basis matrix pair that
 * translates from local coordinate space to transform coordinate space, then
 * hands it back.
 */
Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix(
    const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox,
    float aAppUnitsPerPixel) {
  return GetResultingTransformMatrixInternal(aProperties, aRefBox, nsPoint(),
                                             aAppUnitsPerPixel, 0);
}

Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix(
    const nsIFrame* aFrame, const nsPoint& aOrigin, float aAppUnitsPerPixel,
    uint32_t aFlags) {
  TransformReferenceBox refBox(aFrame);
  FrameTransformProperties props(aFrame, refBox, aAppUnitsPerPixel);
  return GetResultingTransformMatrixInternal(props, refBox, aOrigin,
                                             aAppUnitsPerPixel, aFlags);
}

Matrix4x4 nsDisplayTransform::GetResultingTransformMatrixInternal(
    const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox,
    const nsPoint& aOrigin, float aAppUnitsPerPixel, uint32_t aFlags) {
  const nsIFrame* frame = aProperties.mFrame;
  NS_ASSERTION(frame || !(aFlags & INCLUDE_PERSPECTIVE),
               "Must have a frame to compute perspective!");

  // Get the underlying transform matrix:

  /* Get the matrix, then change its basis to factor in the origin. */
  Matrix4x4 result;
  // Call IsSVGTransformed() regardless of the value of
  // aProperties.HasTransform(), since we still need any
  // potential parentsChildrenOnlyTransform.
  Matrix svgTransform, parentsChildrenOnlyTransform;
  const bool hasSVGTransforms =
      frame && frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) &&
      frame->IsSVGTransformed(&svgTransform, &parentsChildrenOnlyTransform);
  bool shouldRound = nsLayoutUtils::ShouldSnapToGrid(frame);

  /* Transformed frames always have a transform, or are preserving 3d (and might
   * still have perspective!) */
  if (aProperties.HasTransform()) {
    result = nsStyleTransformMatrix::ReadTransforms(
        aProperties.mTranslate, aProperties.mRotate, aProperties.mScale,
        aProperties.mMotion.ptrOr(nullptr), aProperties.mTransform, aRefBox,
        aAppUnitsPerPixel);
  } else if (hasSVGTransforms) {
    // Correct the translation components for zoom:
    float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel;
    svgTransform._31 *= pixelsPerCSSPx;
    svgTransform._32 *= pixelsPerCSSPx;
    result = Matrix4x4::From2D(svgTransform);
  }

  // Apply any translation due to 'transform-origin' and/or 'transform-box':
  result.ChangeBasis(aProperties.mToTransformOrigin);

  // See the comment for SVGContainerFrame::HasChildrenOnlyTransform for
  // an explanation of what children-only transforms are.
  const bool parentHasChildrenOnlyTransform =
      hasSVGTransforms && !parentsChildrenOnlyTransform.IsIdentity();

  if (parentHasChildrenOnlyTransform) {
    float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel;
    parentsChildrenOnlyTransform._31 *= pixelsPerCSSPx;
    parentsChildrenOnlyTransform._32 *= pixelsPerCSSPx;

    Point3D frameOffset(
        NSAppUnitsToFloatPixels(-frame->GetPosition().x, aAppUnitsPerPixel),
        NSAppUnitsToFloatPixels(-frame->GetPosition().y, aAppUnitsPerPixel), 0);
    Matrix4x4 parentsChildrenOnlyTransform3D =
        Matrix4x4::From2D(parentsChildrenOnlyTransform)
            .ChangeBasis(frameOffset);

    result *= parentsChildrenOnlyTransform3D;
  }

  Matrix4x4 perspectiveMatrix;
  bool hasPerspective = aFlags & INCLUDE_PERSPECTIVE;
  if (hasPerspective) {
    if (ComputePerspectiveMatrix(frame, aAppUnitsPerPixel, perspectiveMatrix)) {
      result *= perspectiveMatrix;
    }
  }

  if ((aFlags & INCLUDE_PRESERVE3D_ANCESTORS) && frame &&
      frame->Combines3DTransformWithAncestors()) {
    // Include the transform set on our parent
    nsIFrame* parentFrame =
        frame->GetClosestFlattenedTreeAncestorPrimaryFrame();
    NS_ASSERTION(parentFrame && parentFrame->IsTransformed() &&
                     parentFrame->Extend3DContext(),
                 "Preserve3D mismatch!");
    TransformReferenceBox refBox(parentFrame);
    FrameTransformProperties props(parentFrame, refBox, aAppUnitsPerPixel);

    uint32_t flags =
        aFlags & (INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE);

    // If this frame isn't transformed (but we exist for backface-visibility),
    // then we're not a reference frame so no offset to origin will be added.
    // Otherwise we need to manually translate into our parent's coordinate
    // space.
    if (frame->IsTransformed()) {
      nsLayoutUtils::PostTranslate(result, frame->GetPosition(),
                                   aAppUnitsPerPixel, shouldRound);
    }
    Matrix4x4 parent = GetResultingTransformMatrixInternal(
        props, refBox, nsPoint(0, 0), aAppUnitsPerPixel, flags);
    result = result * parent;
  }

  if (aFlags & OFFSET_BY_ORIGIN) {
    nsLayoutUtils::PostTranslate(result, aOrigin, aAppUnitsPerPixel,
                                 shouldRound);
  }

  return result;
}

bool nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) {
  static constexpr nsCSSPropertyIDSet opacitySet =
      nsCSSPropertyIDSet::OpacityProperties();
  if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, opacitySet)) {
    return true;
  }

  EffectCompositor::SetPerformanceWarning(
      mFrame, opacitySet,
      AnimationPerformanceWarning(
          AnimationPerformanceWarning::Type::OpacityFrameInactive));

  return false;
}

bool nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) {
  return mPrerenderDecision != PrerenderDecision::No;
}

bool nsDisplayBackgroundColor::CanUseAsyncAnimations(
    nsDisplayListBuilder* aBuilder) {
  return StaticPrefs::gfx_omta_background_color();
}

static bool IsInStickyPositionedSubtree(const nsIFrame* aFrame) {
  for (const nsIFrame* frame = aFrame; frame;
       frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) {
    if (frame->IsStickyPositioned()) {
      return true;
    }
  }
  return false;
}

static bool ShouldUsePartialPrerender(const nsIFrame* aFrame) {
  return StaticPrefs::layout_animation_prerender_partial() &&
         // Bug 1642547: Support partial prerender for position:sticky elements.
         !IsInStickyPositionedSubtree(aFrame);
}

/* static */
auto nsDisplayTransform::ShouldPrerenderTransformedContent(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect)
    -> PrerenderInfo {
  PrerenderInfo result;
  // If we are in a preserve-3d tree, and we've disallowed async animations, we
  // return No prerender decision directly.
  if ((aFrame->Extend3DContext() ||
       aFrame->Combines3DTransformWithAncestors()) &&
      !aBuilder->GetPreserves3DAllowAsyncAnimation()) {
    return result;
  }

  // Elements whose transform has been modified recently, or which
  // have a compositor-animated transform, can be prerendered. An element
  // might have only just had its transform animated in which case
  // the ActiveLayerManager may not have been notified yet.
  static constexpr nsCSSPropertyIDSet transformSet =
      nsCSSPropertyIDSet::TransformLikeProperties();
  if (!ActiveLayerTracker::IsTransformMaybeAnimated(aFrame) &&
      !EffectCompositor::HasAnimationsForCompositor(
          aFrame, DisplayItemType::TYPE_TRANSFORM)) {
    EffectCompositor::SetPerformanceWarning(
        aFrame, transformSet,
        AnimationPerformanceWarning(
            AnimationPerformanceWarning::Type::TransformFrameInactive));

    // This case happens when we're sure that the frame is not animated and its
    // preserve-3d ancestors are not, either. So we don't need to pre-render.
    // However, this decision shouldn't affect the decisions for other frames in
    // the preserve-3d context. We need this flag to determine whether we should
    // block async animations on other frames in the current preserve-3d tree.
    result.mHasAnimations = false;
    return result;
  }

  // We should not allow prerender if any ancestor container element has
  // mask/clip-path effects.
  //
  // With prerender and async transform animation, we do not need to restyle an
  // animated element to respect position changes, since that transform is done
  // by layer animation. As a result, the container element is not aware of
  // position change of that containing element and loses the chance to update
  // the content of mask/clip-path.
  //
  // Why do we need to update a mask? This is relative to how we generate a
  // mask layer in ContainerState::SetupMaskLayerForCSSMask. While creating a
  // mask layer, to reduce memory usage, we did not choose the size of the
  // masked element as mask size. Instead, we read the union of bounds of all
  // children display items by nsDisplayWrapList::GetBounds, which is smaller
  // than or equal to the masked element's boundary, and use it as the position
  // size of the mask layer. That union bounds is actually affected by the
  // geometry of the animated element. To keep the content of mask up to date,
  // forbidding of prerender is required.
  for (nsIFrame* container =
           nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
       container;
       container = nsLayoutUtils::GetCrossDocParentFrameInProcess(container)) {
    const nsStyleSVGReset* svgReset = container->StyleSVGReset();
    if (svgReset->HasMask() || svgReset->HasClipPath()) {
      return result;
    }
  }

  // If the incoming dirty rect already contains the entire overflow area,
  // we are already rendering the entire content.
  nsRect overflow = aFrame->InkOverflowRectRelativeToSelf();
  // UntransformRect will not touch the output rect (`&untranformedDirtyRect`)
  // in cases of non-invertible transforms, so we set `untransformedRect` to
  // `aDirtyRect` as an initial value for such cases.
  nsRect untransformedDirtyRect = *aDirtyRect;
  UntransformRect(*aDirtyRect, overflow, aFrame, &untransformedDirtyRect);
  if (untransformedDirtyRect.Contains(overflow)) {
    *aDirtyRect = untransformedDirtyRect;
    result.mDecision = PrerenderDecision::Full;
    return result;
  }

  float viewportRatio =
      StaticPrefs::layout_animation_prerender_viewport_ratio_limit();
  uint32_t absoluteLimitX =
      StaticPrefs::layout_animation_prerender_absolute_limit_x();
  uint32_t absoluteLimitY =
      StaticPrefs::layout_animation_prerender_absolute_limit_y();
  nsSize refSize = aBuilder->RootReferenceFrame()->GetSize();

  float resolution = aFrame->PresShell()->GetCumulativeResolution();
  if (resolution < 1.0f) {
    refSize.SizeTo(
        NSCoordSaturatingNonnegativeMultiply(refSize.width, 1.0f / resolution),
        NSCoordSaturatingNonnegativeMultiply(refSize.height,
                                             1.0f / resolution));
  }

  // Only prerender if the transformed frame's size is <= a multiple of the
  // reference frame size (~viewport), and less than an absolute limit.
  // Both the ratio and the absolute limit are configurable.
  nscoord maxLength = std::max(nscoord(refSize.width * viewportRatio),
                               nscoord(refSize.height * viewportRatio));
  nsSize relativeLimit(maxLength, maxLength);
  nsSize absoluteLimit(
      aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitX),
      aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitY));
  nsSize maxSize = Min(relativeLimit, absoluteLimit);

  const auto transform = nsLayoutUtils::GetTransformToAncestor(
      RelativeTo{aFrame},
      RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)});
  const gfxRect transformedBounds = transform.TransformAndClipBounds(
      gfxRect(overflow.x, overflow.y, overflow.width, overflow.height),
      gfxRect::MaxIntRect());
  const nsSize frameSize =
      nsSize(transformedBounds.width, transformedBounds.height);

  uint64_t maxLimitArea = uint64_t(maxSize.width) * maxSize.height;
  uint64_t frameArea = uint64_t(frameSize.width) * frameSize.height;
  if (frameArea <= maxLimitArea && frameSize <= absoluteLimit) {
    *aDirtyRect = overflow;
    result.mDecision = PrerenderDecision::Full;
    return result;
  }

  if (ShouldUsePartialPrerender(aFrame)) {
    *aDirtyRect = nsLayoutUtils::ComputePartialPrerenderArea(
        aFrame, untransformedDirtyRect, overflow, maxSize);
    result.mDecision = PrerenderDecision::Partial;
    return result;
  }

  if (frameArea > maxLimitArea) {
    uint64_t appUnitsPerPixel = AppUnitsPerCSSPixel();
    EffectCompositor::SetPerformanceWarning(
        aFrame, transformSet,
        AnimationPerformanceWarning(
            AnimationPerformanceWarning::Type::ContentTooLargeArea,
            {
                int(frameArea / (appUnitsPerPixel * appUnitsPerPixel)),
                int(maxLimitArea / (appUnitsPerPixel * appUnitsPerPixel)),
            }));
  } else {
    EffectCompositor::SetPerformanceWarning(
        aFrame, transformSet,
        AnimationPerformanceWarning(
            AnimationPerformanceWarning::Type::ContentTooLarge,
            {
                nsPresContext::AppUnitsToIntCSSPixels(frameSize.width),
                nsPresContext::AppUnitsToIntCSSPixels(frameSize.height),
                nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.width),
                nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.height),
                nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.width),
                nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.height),
            }));
  }

  return result;
}

/* If the matrix is singular, or a hidden backface is shown, the frame won't be
 * visible or hit. */
static bool IsFrameVisible(nsIFrame* aFrame, const Matrix4x4& aMatrix) {
  if (aMatrix.IsSingular()) {
    return false;
  }
  if (aFrame->BackfaceIsHidden() && aMatrix.IsBackfaceVisible()) {
    return false;
  }
  return true;
}

const Matrix4x4Flagged& nsDisplayTransform::GetTransform() const {
  if (mTransform) {
    return *mTransform;
  }

  float scale = mFrame->PresContext()->AppUnitsPerDevPixel();

  if (mHasTransformGetter) {
    mTransform.emplace((mFrame->GetTransformGetter())(mFrame, scale));
    Point3D newOrigin =
        Point3D(NSAppUnitsToFloatPixels(mToReferenceFrame.x, scale),
                NSAppUnitsToFloatPixels(mToReferenceFrame.y, scale), 0.0f);
    mTransform->ChangeBasis(newOrigin.x, newOrigin.y, newOrigin.z);
  } else if (!mIsTransformSeparator) {
    DebugOnly<bool> isReference = mFrame->IsTransformed() ||
                                  mFrame->Combines3DTransformWithAncestors() ||
                                  mFrame->Extend3DContext();
    MOZ_ASSERT(isReference);
    mTransform.emplace(
        GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale,
                                    INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN));
  } else {
    // Use identity matrix
    mTransform.emplace();
  }

  return *mTransform;
}

const Matrix4x4Flagged& nsDisplayTransform::GetInverseTransform() const {
  if (mInverseTransform) {
    return *mInverseTransform;
  }

  MOZ_ASSERT(!GetTransform().IsSingular());

  mInverseTransform.emplace(GetTransform().Inverse());

  return *mInverseTransform;
}

Matrix4x4 nsDisplayTransform::GetTransformForRendering(
    LayoutDevicePoint* aOutOrigin) const {
  if (!mFrame->HasPerspective() || mHasTransformGetter ||
      mIsTransformSeparator) {
    if (!mHasTransformGetter && !mIsTransformSeparator && aOutOrigin) {
      // If aOutOrigin is provided, put the offset to origin into it, because
      // we need to keep it separate for webrender. The combination of
      // *aOutOrigin and the returned matrix here should always be equivalent
      // to what GetTransform() would have returned.
      float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
      *aOutOrigin = LayoutDevicePoint::FromAppUnits(ToReferenceFrame(), scale);

      // The rounding behavior should also be the same as GetTransform().
      if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) {
        aOutOrigin->Round();
      }
      return GetResultingTransformMatrix(mFrame, nsPoint(0, 0), scale,
                                         INCLUDE_PERSPECTIVE);
    }
    return GetTransform().GetMatrix();
  }
  MOZ_ASSERT(!mHasTransformGetter);

  float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
  // Don't include perspective transform, or the offset to origin, since
  // nsDisplayPerspective will handle both of those.
  return GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, 0);
}

const Matrix4x4& nsDisplayTransform::GetAccumulatedPreserved3DTransform(
    nsDisplayListBuilder* aBuilder) {
  MOZ_ASSERT(!mFrame->Extend3DContext() || IsLeafOf3DContext());

  if (!IsLeafOf3DContext()) {
    return GetTransform().GetMatrix();
  }

  if (!mTransformPreserves3D) {
    const nsIFrame* establisher;  // Establisher of the 3D rendering context.
    for (establisher = mFrame;
         establisher && establisher->Combines3DTransformWithAncestors();
         establisher =
             establisher->GetClosestFlattenedTreeAncestorPrimaryFrame()) {
    }
    const nsIFrame* establisherReference = aBuilder->FindReferenceFrameFor(
        nsLayoutUtils::GetCrossDocParentFrameInProcess(establisher));

    nsPoint offset = establisher->GetOffsetToCrossDoc(establisherReference);
    float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
    uint32_t flags =
        INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN;
    mTransformPreserves3D = MakeUnique<Matrix4x4>(
        GetResultingTransformMatrix(mFrame, offset, scale, flags));
  }

  return *mTransformPreserves3D;
}

bool nsDisplayTransform::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  // We want to make sure we don't pollute the transform property in the WR
  // stacking context by including the position of this frame (relative to the
  // parent reference frame). We need to keep those separate; the position of
  // this frame goes into the stacking context bounds while the transform goes
  // into the transform.
  LayoutDevicePoint position;
  Matrix4x4 newTransformMatrix = GetTransformForRendering(&position);

  gfx::Matrix4x4* transformForSC = &newTransformMatrix;
  if (newTransformMatrix.IsIdentity()) {
    // If the transform is an identity transform, strip it out so that WR
    // doesn't turn this stacking context into a reference frame, as it
    // affects positioning. Bug 1345577 tracks a better fix.
    transformForSC = nullptr;

    // In ChooseScaleAndSetTransform, we round the offset from the reference
    // frame used to adjust the transform, if there is no transform, or it
    // is just a translation. We need to do the same here.
    if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) {
      position.Round();
    }
  }

  auto key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(),
                            wr::SpatialKeyKind::Transform);

  // We don't send animations for transform separator display items.
  uint64_t animationsId =
      mIsTransformSeparator
          ? 0
          : AddAnimationsForWebRender(
                this, aManager, aDisplayListBuilder,
                IsPartialPrerender() ? Some(position) : Nothing());
  wr::WrAnimationProperty prop{wr::WrAnimationType::Transform, animationsId,
                               key};

  nsDisplayTransform* deferredTransformItem = nullptr;
  if (!mFrame->ChildrenHavePerspective()) {
    // If it has perspective, we create a new scroll data via the
    // UpdateScrollData call because that scenario is more complex. Otherwise
    // we can just stash the transform on the StackingContextHelper and
    // apply it to any scroll data that are created inside this
    // nsDisplayTransform.
    deferredTransformItem = this;
  }

  // Determine if we're possibly animated (= would need an active layer in FLB).
  bool animated = !mIsTransformSeparator &&
                  ActiveLayerTracker::IsTransformMaybeAnimated(Frame());

  wr::StackingContextParams params;
  params.mBoundTransform = &newTransformMatrix;
  params.animation = animationsId ? &prop : nullptr;

  wr::WrTransformInfo transform_info;
  if (transformForSC) {
    transform_info.transform = wr::ToLayoutTransform(newTransformMatrix);
    transform_info.key = key;
    params.mTransformPtr = &transform_info;
  } else {
    params.mTransformPtr = nullptr;
  }

  params.prim_flags = !BackfaceIsHidden()
                          ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE
                          : wr::PrimitiveFlags{0};
  params.paired_with_perspective = mHasAssociatedPerspective;
  params.mDeferredTransformItem = deferredTransformItem;
  params.mAnimated = animated;
  // Determine if we would have to rasterize any items in local raster space
  // (i.e. disable subpixel AA). We don't always need to rasterize locally even
  // if the stacking context is possibly animated (at the cost of potentially
  // some false negatives with respect to will-change handling), so we pass in
  // this determination separately to accurately match with when FLB would
  // normally disable subpixel AA.
  params.mRasterizeLocally = animated && Frame()->HasAnimationOfTransform();
  params.SetPreserve3D(mFrame->Extend3DContext() && !mIsTransformSeparator);
  params.clip =
      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());

  LayoutDeviceSize boundsSize = LayoutDeviceSize::FromAppUnits(
      mChildBounds.Size(), mFrame->PresContext()->AppUnitsPerDevPixel());

  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                           params, LayoutDeviceRect(position, boundsSize));

  aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
      GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources);
  return true;
}

bool nsDisplayTransform::UpdateScrollData(
    WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) {
  if (!mFrame->ChildrenHavePerspective()) {
    // This case is handled in CreateWebRenderCommands by stashing the transform
    // on the stacking context.
    return false;
  }
  if (aLayerData) {
    aLayerData->SetTransform(GetTransform().GetMatrix());
    aLayerData->SetTransformIsPerspective(true);
  }
  return true;
}

bool nsDisplayTransform::ShouldSkipTransform(
    nsDisplayListBuilder* aBuilder) const {
  return (aBuilder->RootReferenceFrame() == mFrame) &&
         aBuilder->IsForGenerateGlyphMask();
}

void nsDisplayTransform::Collect3DTransformLeaves(
    nsDisplayListBuilder* aBuilder, nsTArray<nsDisplayTransform*>& aLeaves) {
  if (!IsParticipating3DContext() || IsLeafOf3DContext()) {
    aLeaves.AppendElement(this);
    return;
  }

  FlattenedDisplayListIterator iter(aBuilder, &mChildren);
  while (iter.HasNext()) {
    nsDisplayItem* item = iter.GetNextItem();
    if (item->GetType() == DisplayItemType::TYPE_PERSPECTIVE) {
      auto* perspective = static_cast<nsDisplayPerspective*>(item);
      if (!perspective->GetChildren()->GetTop()) {
        continue;
      }
      item = perspective->GetChildren()->GetTop();
    }
    if (item->GetType() != DisplayItemType::TYPE_TRANSFORM) {
      gfxCriticalError() << "Invalid child item within 3D transform of type: "
                         << item->Name();
      continue;
    }
    static_cast<nsDisplayTransform*>(item)->Collect3DTransformLeaves(aBuilder,
                                                                     aLeaves);
  }
}

static RefPtr<gfx::Path> BuildPathFromPolygon(const RefPtr<DrawTarget>& aDT,
                                              const gfx::Polygon& aPolygon) {
  MOZ_ASSERT(!aPolygon.IsEmpty());

  RefPtr<PathBuilder> pathBuilder = aDT->CreatePathBuilder();
  const nsTArray<Point4D>& points = aPolygon.GetPoints();

  pathBuilder->MoveTo(points[0].As2DPoint());

  for (size_t i = 1; i < points.Length(); ++i) {
    pathBuilder->LineTo(points[i].As2DPoint());
  }

  pathBuilder->Close();
  return pathBuilder->Finish();
}

void nsDisplayTransform::CollectSorted3DTransformLeaves(
    nsDisplayListBuilder* aBuilder, nsTArray<TransformPolygon>& aLeaves) {
  std::list<TransformPolygon> inputLayers;

  nsTArray<nsDisplayTransform*> leaves;
  Collect3DTransformLeaves(aBuilder, leaves);
  for (nsDisplayTransform* item : leaves) {
    auto bounds = LayoutDeviceRect::FromAppUnits(
        item->mChildBounds, item->mFrame->PresContext()->AppUnitsPerDevPixel());
    Matrix4x4 transform = item->GetAccumulatedPreserved3DTransform(aBuilder);

    if (!IsFrameVisible(item->mFrame, transform)) {
      continue;
    }
    gfx::Polygon polygon =
        gfx::Polygon::FromRect(gfx::Rect(bounds.ToUnknownRect()));

    polygon.TransformToScreenSpace(transform);

    if (polygon.GetPoints().Length() >= 3) {
      inputLayers.push_back(TransformPolygon(item, std::move(polygon)));
    }
  }

  if (inputLayers.empty()) {
    return;
  }

  BSPTree<nsDisplayTransform> tree(inputLayers);
  nsTArray<TransformPolygon> orderedLayers(tree.GetDrawOrder());

  for (TransformPolygon& polygon : orderedLayers) {
    Matrix4x4 inverse =
        polygon.data->GetAccumulatedPreserved3DTransform(aBuilder).Inverse();

    MOZ_ASSERT(polygon.geometry);
    polygon.geometry->TransformToLayerSpace(inverse);
  }

  aLeaves = std::move(orderedLayers);
}

void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder,
                               gfxContext* aCtx) {
  Paint(aBuilder, aCtx, Nothing());
}

void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
                               const Maybe<gfx::Polygon>& aPolygon) {
  if (IsParticipating3DContext() && !IsLeafOf3DContext()) {
    MOZ_ASSERT(!aPolygon);
    nsTArray<TransformPolygon> leaves;
    CollectSorted3DTransformLeaves(aBuilder, leaves);
    for (TransformPolygon& item : leaves) {
      item.data->Paint(aBuilder, aCtx, item.geometry);
    }
    return;
  }

  gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
  Matrix4x4 trans = ShouldSkipTransform(aBuilder)
                        ? Matrix4x4()
                        : GetAccumulatedPreserved3DTransform(aBuilder);
  if (!IsFrameVisible(mFrame, trans)) {
    return;
  }

  Matrix trans2d;
  if (trans.CanDraw2D(&trans2d)) {
    aCtx->Multiply(ThebesMatrix(trans2d));

    if (aPolygon) {
      RefPtr<gfx::Path> path =
          BuildPathFromPolygon(aCtx->GetDrawTarget(), *aPolygon);
      aCtx->GetDrawTarget()->PushClip(path);
    }

    GetChildren()->Paint(aBuilder, aCtx,
                         mFrame->PresContext()->AppUnitsPerDevPixel());

    if (aPolygon) {
      aCtx->GetDrawTarget()->PopClip();
    }
    return;
  }

  // TODO: Implement 3d transform handling, including plane splitting and
  // sorting. See BasicCompositor.
  auto pixelBounds = LayoutDeviceRect::FromAppUnitsToOutside(
      mChildBounds, mFrame->PresContext()->AppUnitsPerDevPixel());
  RefPtr<DrawTarget> untransformedDT =
      gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
          IntSize(pixelBounds.Width(), pixelBounds.Height()),
          SurfaceFormat::B8G8R8A8, true);
  if (!untransformedDT || !untransformedDT->IsValid()) {
    return;
  }
  untransformedDT->SetTransform(
      Matrix::Translation(-Point(pixelBounds.X(), pixelBounds.Y())));

  gfxContext groupTarget(untransformedDT, /* aPreserveTransform */ true);

  if (aPolygon) {
    RefPtr<gfx::Path> path =
        BuildPathFromPolygon(aCtx->GetDrawTarget(), *aPolygon);
    aCtx->GetDrawTarget()->PushClip(path);
  }

  GetChildren()->Paint(aBuilder, &groupTarget,
                       mFrame->PresContext()->AppUnitsPerDevPixel());

  if (aPolygon) {
    aCtx->GetDrawTarget()->PopClip();
  }

  RefPtr<SourceSurface> untransformedSurf = untransformedDT->Snapshot();

  trans.PreTranslate(pixelBounds.X(), pixelBounds.Y(), 0);
  aCtx->GetDrawTarget()->Draw3DTransformedSurface(untransformedSurf, trans);
}

bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder) const {
  // If EffectCompositor::HasAnimationsForCompositor() is true then we can
  // completely bypass the main thread for this animation, so it is always
  // worthwhile.
  // For ActiveLayerTracker::IsTransformAnimated() cases the main thread is
  // already involved so there is less to be gained.
  // Therefore we check that the *post-transform* bounds of this item are
  // big enough to justify an active layer.
  return EffectCompositor::HasAnimationsForCompositor(
             mFrame, DisplayItemType::TYPE_TRANSFORM) ||
         (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame));
}

nsRect nsDisplayTransform::TransformUntransformedBounds(
    nsDisplayListBuilder* aBuilder, const Matrix4x4Flagged& aMatrix) const {
  bool snap;
  const nsRect untransformedBounds = GetUntransformedBounds(aBuilder, &snap);
  // GetTransform always operates in dev pixels.
  const float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
  return nsLayoutUtils::MatrixTransformRect(untransformedBounds, aMatrix,
                                            factor);
}

/**
 * Returns the bounds for this transform. The bounds are calculated during
 * display list building and merging, see |nsDisplayTransform::UpdateBounds()|.
 */
nsRect nsDisplayTransform::GetBounds(nsDisplayListBuilder* aBuilder,
                                     bool* aSnap) const {
  *aSnap = false;
  return mBounds;
}

void nsDisplayTransform::ComputeBounds(nsDisplayListBuilder* aBuilder) {
  MOZ_ASSERT(mFrame->Extend3DContext() || IsLeafOf3DContext());

  /* Some transforms can get empty bounds in 2D, but might get transformed again
   * and get non-empty bounds. A simple example of this would be a 180 degree
   * rotation getting applied twice.
   * We should not depend on transforming bounds level by level.
   *
   * This function collects the bounds of this transform and stores it in
   * nsDisplayListBuilder. If this is not a leaf of a 3D context, we recurse
   * down and include the bounds of the child transforms.
   * The bounds are transformed with the accumulated transformation matrix up to
   * the 3D context root coordinate space.
   */
  nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder);
  accTransform.Accumulate(GetTransform().GetMatrix());

  // Do not dive into another 3D context.
  if (!IsLeafOf3DContext()) {
    for (nsDisplayItem* i : *GetChildren()) {
      i->DoUpdateBoundsPreserves3D(aBuilder);
    }
  }

  /* The child transforms that extend 3D context further will have empty bounds,
   * so the untransformed bounds here is the bounds of all the non-preserve-3d
   * content under this transform.
   */
  const nsRect rect = TransformUntransformedBounds(
      aBuilder, accTransform.GetCurrentTransform());
  aBuilder->AccumulateRect(rect);
}

void nsDisplayTransform::DoUpdateBoundsPreserves3D(
    nsDisplayListBuilder* aBuilder) {
  MOZ_ASSERT(mFrame->Combines3DTransformWithAncestors() ||
             IsTransformSeparator());
  // Updating is not going through to child 3D context.
  ComputeBounds(aBuilder);
}

void nsDisplayTransform::UpdateBounds(nsDisplayListBuilder* aBuilder) {
  UpdateUntransformedBounds(aBuilder);

  if (IsTransformSeparator()) {
    MOZ_ASSERT(GetTransform().IsIdentity());
    mBounds = mChildBounds;
    return;
  }

  if (mFrame->Extend3DContext()) {
    if (!Combines3DTransformWithAncestors()) {
      // The transform establishes a 3D context. |UpdateBoundsFor3D()| will
      // collect the bounds from the child transforms.
      UpdateBoundsFor3D(aBuilder);
    } else {
      // With nested 3D transforms, the 2D bounds might not be useful.
      mBounds = nsRect();
    }

    return;
  }

  MOZ_ASSERT(!mFrame->Extend3DContext());

  // We would like to avoid calculating 2D bounds here for nested 3D transforms,
  // but mix-blend-mode relies on having bounds set. See bug 1556956.

  // A stand-alone transform.
  mBounds = TransformUntransformedBounds(aBuilder, GetTransform());
}

void nsDisplayTransform::UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder) {
  MOZ_ASSERT(mFrame->Extend3DContext() &&
             !mFrame->Combines3DTransformWithAncestors() &&
             !IsTransformSeparator());

  // Always start updating from an establisher of a 3D rendering context.
  nsDisplayListBuilder::AutoAccumulateRect accRect(aBuilder);
  nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder);
  accTransform.StartRoot();
  ComputeBounds(aBuilder);
  mBounds = aBuilder->GetAccumulatedRect();
}

void nsDisplayTransform::UpdateUntransformedBounds(
    nsDisplayListBuilder* aBuilder) {
  mChildBounds = GetChildren()->GetClippedBoundsWithRespectToASR(
      aBuilder, mActiveScrolledRoot);
}

#ifdef DEBUG_HIT
#  include <time.h>
#endif

/* HitTest does some fun stuff with matrix transforms to obtain the answer. */
void nsDisplayTransform::HitTest(nsDisplayListBuilder* aBuilder,
                                 const nsRect& aRect, HitTestState* aState,
                                 nsTArray<nsIFrame*>* aOutFrames) {
  if (aState->mInPreserves3D) {
    GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames);
    return;
  }

  /* Here's how this works:
   * 1. Get the matrix.  If it's singular, abort (clearly we didn't hit
   *    anything).
   * 2. Invert the matrix.
   * 3. Use it to transform the rect into the correct space.
   * 4. Pass that rect down through to the list's version of HitTest.
   */
  // GetTransform always operates in dev pixels.
  float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
  Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder);

  if (!IsFrameVisible(mFrame, matrix)) {
    return;
  }

  const bool oldHitOccludingItem = aState->mHitOccludingItem;

  /* We want to go from transformed-space to regular space.
   * Thus we have to invert the matrix, which normally does
   * the reverse operation (e.g. regular->transformed)
   */

  /* Now, apply the transform and pass it down the channel. */
  matrix.Invert();
  nsRect resultingRect;
  // Magic width/height indicating we're hit testing a point, not a rect
  const bool testingPoint = aRect.width == 1 && aRect.height == 1;
  if (testingPoint) {
    Point4D point =
        matrix.ProjectPoint(Point(NSAppUnitsToFloatPixels(aRect.x, factor),
                                  NSAppUnitsToFloatPixels(aRect.y, factor)));
    if (!point.HasPositiveWCoord()) {
      return;
    }

    Point point2d = point.As2DPoint();

    resultingRect =
        nsRect(NSFloatPixelsToAppUnits(float(point2d.x), factor),
               NSFloatPixelsToAppUnits(float(point2d.y), factor), 1, 1);

  } else {
    Rect originalRect(NSAppUnitsToFloatPixels(aRect.x, factor),
                      NSAppUnitsToFloatPixels(aRect.y, factor),
                      NSAppUnitsToFloatPixels(aRect.width, factor),
                      NSAppUnitsToFloatPixels(aRect.height, factor));

    Rect childGfxBounds(NSAppUnitsToFloatPixels(mChildBounds.x, factor),
                        NSAppUnitsToFloatPixels(mChildBounds.y, factor),
                        NSAppUnitsToFloatPixels(mChildBounds.width, factor),
                        NSAppUnitsToFloatPixels(mChildBounds.height, factor));

    Rect rect = matrix.ProjectRectBounds(originalRect, childGfxBounds);

    resultingRect =
        nsRect(NSFloatPixelsToAppUnits(float(rect.X()), factor),
               NSFloatPixelsToAppUnits(float(rect.Y()), factor),
               NSFloatPixelsToAppUnits(float(rect.Width()), factor),
               NSFloatPixelsToAppUnits(float(rect.Height()), factor));
  }

  if (resultingRect.IsEmpty()) {
    return;
  }

#ifdef DEBUG_HIT
  printf("Frame: %p\n", dynamic_cast<void*>(mFrame));
  printf("  Untransformed point: (%f, %f)\n", resultingRect.X(),
         resultingRect.Y());
  uint32_t originalFrameCount = aOutFrames.Length();
#endif

  GetChildren()->HitTest(aBuilder, resultingRect, aState, aOutFrames);

  if (aState->mHitOccludingItem && !testingPoint &&
      !mChildBounds.Contains(aRect)) {
    MOZ_ASSERT(aBuilder->HitTestIsForVisibility());
    // We're hit-testing a rect that's bigger than our child bounds, but
    // resultingRect is clipped by our bounds (in ProjectRectBounds above), so
    // we can't stop hit-testing altogether.
    //
    // FIXME(emilio): I think this means that theoretically we might include
    // some frames fully behind other transformed-but-opaque frames? Then again
    // that's our pre-existing behavior for other untransformed content that
    // doesn't fill the whole rect. To be fully correct I think we'd need proper
    // "known occluded region" tracking, but that might be overkill for our
    // purposes here.
    aState->mHitOccludingItem = oldHitOccludingItem;
  }

#ifdef DEBUG_HIT
  if (originalFrameCount != aOutFrames.Length())
    printf("  Hit! Time: %f, first frame: %p\n", static_cast<double>(clock()),
           dynamic_cast<void*>(aOutFrames.ElementAt(0)));
  printf("=== end of hit test ===\n");
#endif
}

float nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder,
                                             const nsPoint& aPoint) {
  // GetTransform always operates in dev pixels.
  float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
  Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder);

  NS_ASSERTION(IsFrameVisible(mFrame, matrix),
               "We can't have hit a frame that isn't visible!");

  Matrix4x4 inverse = matrix;
  inverse.Invert();
  Point4D point =
      inverse.ProjectPoint(Point(NSAppUnitsToFloatPixels(aPoint.x, factor),
                                 NSAppUnitsToFloatPixels(aPoint.y, factor)));

  Point point2d = point.As2DPoint();

  Point3D transformed = matrix.TransformPoint(Point3D(point2d.x, point2d.y, 0));
  return transformed.z;
}

/* The transform is opaque iff the transform consists solely of scales and
 * translations and if the underlying content is opaque.  Thus if the transform
 * is of the form
 *
 * |a c e|
 * |b d f|
 * |0 0 1|
 *
 * We need b and c to be zero.
 *
 * We also need to check whether the underlying opaque content completely fills
 * our visible rect. We use UntransformRect which expands to the axis-aligned
 * bounding rect, but that's OK since if
 * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it
 * certainly contains the actual (non-axis-aligned) untransformed rect.
 */
nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                             bool* aSnap) const {
  *aSnap = false;

  nsRect untransformedVisible;
  if (!UntransformBuildingRect(aBuilder, &untransformedVisible)) {
    return nsRegion();
  }

  const Matrix4x4Flagged& matrix = GetTransform();
  Matrix matrix2d;
  if (!matrix.Is2D(&matrix2d) || !matrix2d.PreservesAxisAlignedRectangles()) {
    return nsRegion();
  }

  nsRegion result;

  bool tmpSnap;
  const nsRect bounds = GetUntransformedBounds(aBuilder, &tmpSnap);
  const nsRegion opaque =
      ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(), bounds);

  if (opaque.Contains(untransformedVisible)) {
    result = GetBuildingRect().Intersect(GetBounds(aBuilder, &tmpSnap));
  }
  return result;
}

nsRect nsDisplayTransform::GetComponentAlphaBounds(
    nsDisplayListBuilder* aBuilder) const {
  if (GetChildren()->GetComponentAlphaBounds(aBuilder).IsEmpty()) {
    return nsRect();
  }

  bool snap;
  return GetBounds(aBuilder, &snap);
}

/* TransformRect takes in as parameters a rectangle (in app space) and returns
 * the smallest rectangle (in app space) containing the transformed image of
 * that rectangle.  That is, it takes the four corners of the rectangle,
 * transforms them according to the matrix associated with the specified frame,
 * then returns the smallest rectangle containing the four transformed points.
 *
 * @param aUntransformedBounds The rectangle (in app units) to transform.
 * @param aFrame The frame whose transformation should be applied.
 * @param aOrigin The delta from the frame origin to the coordinate space origin
 * @return The smallest rectangle containing the image of the transformed
 *         rectangle.
 */
nsRect nsDisplayTransform::TransformRect(const nsRect& aUntransformedBounds,
                                         const nsIFrame* aFrame,
                                         TransformReferenceBox& aRefBox) {
  MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!");

  float factor = aFrame->PresContext()->AppUnitsPerDevPixel();

  FrameTransformProperties props(aFrame, aRefBox, factor);
  return nsLayoutUtils::MatrixTransformRect(
      aUntransformedBounds,
      GetResultingTransformMatrixInternal(props, aRefBox, nsPoint(), factor,
                                          kTransformRectFlags),
      factor);
}

bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds,
                                         const nsRect& aChildBounds,
                                         const nsIFrame* aFrame,
                                         nsRect* aOutRect) {
  MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!");

  float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
  Matrix4x4 transform = GetResultingTransformMatrix(aFrame, nsPoint(), factor,
                                                    kTransformRectFlags);
  return UntransformRect(aTransformedBounds, aChildBounds, transform, factor,
                         aOutRect);
}

bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds,
                                         const nsRect& aChildBounds,
                                         const Matrix4x4& aMatrix,
                                         float aAppUnitsPerPixel,
                                         nsRect* aOutRect) {
  if (aMatrix.IsSingular()) {
    return false;
  }

  RectDouble result(
      NSAppUnitsToFloatPixels(aTransformedBounds.x, aAppUnitsPerPixel),
      NSAppUnitsToFloatPixels(aTransformedBounds.y, aAppUnitsPerPixel),
      NSAppUnitsToFloatPixels(aTransformedBounds.width, aAppUnitsPerPixel),
      NSAppUnitsToFloatPixels(aTransformedBounds.height, aAppUnitsPerPixel));

  RectDouble childGfxBounds(
      NSAppUnitsToFloatPixels(aChildBounds.x, aAppUnitsPerPixel),
      NSAppUnitsToFloatPixels(aChildBounds.y, aAppUnitsPerPixel),
      NSAppUnitsToFloatPixels(aChildBounds.width, aAppUnitsPerPixel),
      NSAppUnitsToFloatPixels(aChildBounds.height, aAppUnitsPerPixel));

  result = aMatrix.Inverse().ProjectRectBounds(result, childGfxBounds);
  *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result),
                                                   aAppUnitsPerPixel);
  return true;
}

bool nsDisplayTransform::UntransformRect(nsDisplayListBuilder* aBuilder,
                                         const nsRect& aRect,
                                         nsRect* aOutRect) const {
  if (GetTransform().IsSingular()) {
    return false;
  }

  // GetTransform always operates in dev pixels.
  float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
  RectDouble result(NSAppUnitsToFloatPixels(aRect.x, factor),
                    NSAppUnitsToFloatPixels(aRect.y, factor),
                    NSAppUnitsToFloatPixels(aRect.width, factor),
                    NSAppUnitsToFloatPixels(aRect.height, factor));

  bool snap;
  nsRect childBounds = GetUntransformedBounds(aBuilder, &snap);
  RectDouble childGfxBounds(
      NSAppUnitsToFloatPixels(childBounds.x, factor),
      NSAppUnitsToFloatPixels(childBounds.y, factor),
      NSAppUnitsToFloatPixels(childBounds.width, factor),
      NSAppUnitsToFloatPixels(childBounds.height, factor));

  /* We want to untransform the matrix, so invert the transformation first! */
  result = GetInverseTransform().ProjectRectBounds(result, childGfxBounds);

  *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor);

  return true;
}

void nsDisplayTransform::WriteDebugInfo(std::stringstream& aStream) {
  aStream << GetTransform().GetMatrix();
  if (IsTransformSeparator()) {
    aStream << " transform-separator";
  }
  if (IsLeafOf3DContext()) {
    aStream << " 3d-context-leaf";
  }
  if (mFrame->Extend3DContext()) {
    aStream << " extends-3d-context";
  }
  if (mFrame->Combines3DTransformWithAncestors()) {
    aStream << " combines-3d-with-ancestors";
  }

  aStream << " prerender(";
  switch (mPrerenderDecision) {
    case PrerenderDecision::No:
      aStream << "no";
      break;
    case PrerenderDecision::Partial:
      aStream << "partial";
      break;
    case PrerenderDecision::Full:
      aStream << "full";
      break;
  }
  aStream << ")";
  aStream << " childrenBuildingRect" << mChildrenBuildingRect;
}

nsDisplayPerspective::nsDisplayPerspective(nsDisplayListBuilder* aBuilder,
                                           nsIFrame* aFrame,
                                           nsDisplayList* aList)
    : nsPaintedDisplayItem(aBuilder, aFrame), mList(aBuilder) {
  mList.AppendToTop(aList);
  MOZ_ASSERT(mList.Length() == 1);
  MOZ_ASSERT(mList.GetTop()->GetType() == DisplayItemType::TYPE_TRANSFORM);
}

void nsDisplayPerspective::Paint(nsDisplayListBuilder* aBuilder,
                                 gfxContext* aCtx) {
  // Just directly recurse into children, since we'll include the persepctive
  // value in any nsDisplayTransform children.
  GetChildren()->Paint(aBuilder, aCtx,
                       mFrame->PresContext()->AppUnitsPerDevPixel());
}

nsRegion nsDisplayPerspective::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                               bool* aSnap) const {
  if (!GetChildren()->GetTop()) {
    *aSnap = false;
    return nsRegion();
  }

  return GetChildren()->GetTop()->GetOpaqueRegion(aBuilder, aSnap);
}

bool nsDisplayPerspective::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
  Matrix4x4 perspectiveMatrix;
  DebugOnly<bool> hasPerspective = nsDisplayTransform::ComputePerspectiveMatrix(
      mFrame, appUnitsPerPixel, perspectiveMatrix);
  MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?");

  /*
   * ClipListToRange can remove our child after we were created.
   */
  if (!GetChildren()->GetTop()) {
    return false;
  }

  /*
   * The resulting matrix is still in the coordinate space of the transformed
   * frame. Append a translation to the reference frame coordinates.
   */
  nsDisplayTransform* transform =
      static_cast<nsDisplayTransform*>(GetChildren()->GetTop());

  Point3D newOrigin =
      Point3D(NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x,
                                      appUnitsPerPixel),
              NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y,
                                      appUnitsPerPixel),
              0.0f);
  Point3D roundedOrigin(NS_round(newOrigin.x), NS_round(newOrigin.y), 0);

  perspectiveMatrix.PostTranslate(roundedOrigin);

  nsIFrame* perspectiveFrame =
      mFrame->GetClosestFlattenedTreeAncestorPrimaryFrame();

  // Passing true here is always correct, since perspective always combines
  // transforms with the descendants. However that'd make WR do a lot of work
  // that it doesn't really need to do if there aren't other transforms forming
  // part of the 3D context.
  //
  // WR knows how to treat perspective in that case, so the only thing we need
  // to do is to ensure we pass true when we're involved in a 3d context in any
  // other way via the transform-style property on either the transformed frame
  // or the perspective frame in order to not confuse WR's preserve-3d code in
  // very awful ways.
  bool preserve3D =
      mFrame->Extend3DContext() || perspectiveFrame->Extend3DContext();

  wr::StackingContextParams params;

  wr::WrTransformInfo transform_info;
  transform_info.transform = wr::ToLayoutTransform(perspectiveMatrix);
  transform_info.key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(),
                                      wr::SpatialKeyKind::Perspective);
  params.mTransformPtr = &transform_info;

  params.reference_frame_kind = wr::WrReferenceFrameKind::Perspective;
  params.prim_flags = !BackfaceIsHidden()
                          ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE
                          : wr::PrimitiveFlags{0};
  params.SetPreserve3D(preserve3D);
  params.clip =
      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());

  Maybe<uint64_t> scrollingRelativeTo;
  for (const auto* asr = GetActiveScrolledRoot(); asr; asr = asr->mParent) {
    // In OOP documents, the root scrollable frame of the in-process root
    // document is always active, so using IsAncestorFrameCrossDocInProcess
    // should be fine here.
    if (nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
            asr->mScrollableFrame->GetScrolledFrame(), perspectiveFrame)) {
      scrollingRelativeTo.emplace(asr->GetViewId());
      break;
    }
  }

  // We put the perspective reference frame wrapping the transformed frame,
  // even though there may be arbitrarily nested scroll frames in between.
  //
  // We need to know how many ancestor scroll-frames are we nested in, in order
  // for the async scrolling code in WebRender to calculate the right
  // transformation for the perspective contents.
  params.scrolling_relative_to = scrollingRelativeTo.ptrOr(nullptr);

  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                           params);

  aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
      GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources);

  return true;
}

nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder,
                             nsTextFrame* aFrame)
    : nsPaintedDisplayItem(aBuilder, aFrame),
      mVisIStartEdge(0),
      mVisIEndEdge(0) {
  MOZ_COUNT_CTOR(nsDisplayText);
  mBounds = mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
  // Bug 748228
  mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
  mVisibleRect = aBuilder->GetVisibleRect() +
                 aBuilder->GetCurrentFrameOffsetToReferenceFrame();
}

bool nsDisplayText::CanApplyOpacity(WebRenderLayerManager* aManager,
                                    nsDisplayListBuilder* aBuilder) const {
  auto* f = static_cast<nsTextFrame*>(mFrame);

  if (f->IsSelected()) {
    return false;
  }

  const nsStyleText* textStyle = f->StyleText();
  if (textStyle->HasTextShadow()) {
    return false;
  }

  nsTextFrame::TextDecorations decorations;
  f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
                        decorations);
  return !decorations.HasDecorationLines();
}

void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
  AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
  // We don't pass mVisibleRect here, since this can be called from within
  // the WebRender fallback painting path, and we don't want to issue
  // recorded commands that are dependent on the visible/building rect.
  RenderToContext(aCtx, aBuilder, GetPaintRect(aBuilder, aCtx));
}

bool nsDisplayText::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  auto* f = static_cast<nsTextFrame*>(mFrame);
  auto appUnitsPerDevPixel = f->PresContext()->AppUnitsPerDevPixel();

  nsRect bounds = f->WebRenderBounds() + ToReferenceFrame();
  // Bug 748228
  bounds.Inflate(appUnitsPerDevPixel);

  if (bounds.IsEmpty()) {
    return true;
  }

  // For large font sizes, punt to a blob image, to avoid the blurry rendering
  // that results from WR clamping the glyph size used for rasterization.
  //
  // (See FONT_SIZE_LIMIT in webrender/src/glyph_rasterizer/mod.rs.)
  //
  // This is not strictly accurate, as final used font sizes might not be the
  // same as claimed by the fontGroup's style.size (eg: due to font-size-adjust
  // altering the used size of the font actually used).
  // It also fails to consider how transforms might affect the device-font-size
  // that webrender uses (and clamps).
  // But it should be near enough for practical purposes; the limitations just
  // mean we might sometimes end up with webrender still applying some bitmap
  // scaling, or bail out when we didn't really need to.
  constexpr float kWebRenderFontSizeLimit = 320.0;
  f->EnsureTextRun(nsTextFrame::eInflated);
  gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated);
  if (textRun &&
      textRun->GetFontGroup()->GetStyle()->size > kWebRenderFontSizeLimit) {
    return false;
  }

  gfx::Point deviceOffset =
      LayoutDevicePoint::FromAppUnits(bounds.TopLeft(), appUnitsPerDevPixel)
          .ToUnknownPoint();

  // Clipping the bounds to the PaintRect (factoring in what's covered by parent
  // frames) lets us early reject a bunch of things.
  nsRect visible = mVisibleRect;

  // Add the "source rect" area from which the given shadows could intersect
  // with mVisibleRect, and which therefore needs to included in the paint
  // operation, to the `visible` rect that we will use to limit the bounds of
  // what we send to the renderer.
  auto addShadowSourceToVisible = [&](Span<const StyleSimpleShadow> aShadows) {
    for (const auto& shadow : aShadows) {
      nsRect sourceRect = mVisibleRect;
      // Negate the offsets, because we're looking for the "source" rect that
      // could cast a shadow into the visible rect, rather than a "target" area
      // onto which the visible rect would cast a shadow.
      sourceRect.MoveBy(-shadow.horizontal.ToAppUnits(),
                        -shadow.vertical.ToAppUnits());
      // Inflate to account for the shadow blur.
      sourceRect.Inflate(nsContextBoxBlur::GetBlurRadiusMargin(
          shadow.blur.ToAppUnits(), appUnitsPerDevPixel));
      visible.OrWith(sourceRect);
    }
  };

  // Shadows can translate things back into view, so we enlarge the notional
  // "visible" rect to ensure we don't skip painting relevant parts that might
  // cast a shadow within the visible area.
  addShadowSourceToVisible(f->StyleText()->mTextShadow.AsSpan());

  // Similarly for shadows that may be cast by ::selection.
  if (f->IsSelected()) {
    nsTextPaintStyle textPaint(f);
    Span<const StyleSimpleShadow> shadows;
    f->GetSelectionTextShadow(SelectionType::eNormal, textPaint, &shadows);
    addShadowSourceToVisible(shadows);
  }

  // Inflate a little extra to allow for potential antialiasing "blur".
  visible.Inflate(3 * appUnitsPerDevPixel);
  bounds = bounds.Intersect(visible);

  gfxContext* textDrawer = aBuilder.GetTextContext(aResources, aSc, aManager,
                                                   this, bounds, deviceOffset);

  aBuilder.StartGroup(this);

  RenderToContext(textDrawer, aDisplayListBuilder, mVisibleRect,
                  aBuilder.GetInheritedOpacity(), true);
  const bool result = textDrawer->GetTextDrawer()->Finish();

  if (result) {
    aBuilder.FinishGroup();
  } else {
    aBuilder.CancelGroup(true);
  }

  return result;
}

void nsDisplayText::RenderToContext(gfxContext* aCtx,
                                    nsDisplayListBuilder* aBuilder,
                                    const nsRect& aVisibleRect, float aOpacity,
                                    bool aIsRecording) {
  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);

  // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
  // antialiased pixels beyond the measured text extents.
  // This is temporary until we do this in the actual calculation of text
  // extents.
  auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
  LayoutDeviceRect extraVisible =
      LayoutDeviceRect::FromAppUnits(aVisibleRect, A2D);
  extraVisible.Inflate(1);

  gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width,
                       extraVisible.height);
  pixelVisible.Inflate(2);
  pixelVisible.RoundOut();

  gfxClipAutoSaveRestore autoSaveClip(aCtx);
  if (!aBuilder->IsForGenerateGlyphMask() && !aIsRecording) {
    autoSaveClip.Clip(pixelVisible);
  }

  NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
  NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");

  gfxContextMatrixAutoSaveRestore matrixSR;

  nsPoint framePt = ToReferenceFrame();
  if (f->Style()->IsTextCombined()) {
    float scaleFactor = nsTextFrame::GetTextCombineScaleFactor(f);
    if (scaleFactor != 1.0f) {
      if (auto* textDrawer = aCtx->GetTextDrawer()) {
        // WebRender doesn't support scaling text like this yet
        textDrawer->FoundUnsupportedFeature();
        return;
      }
      matrixSR.SetContext(aCtx);
      // Setup matrix to compress text for text-combine-upright if
      // necessary. This is done here because we want selection be
      // compressed at the same time as text.
      gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D);
      gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated);
      if (textRun && textRun->IsRightToLeft()) {
        pt.x += gfxFloat(f->GetSize().width) / A2D;
      }
      gfxMatrix mat = aCtx->CurrentMatrixDouble()
                          .PreTranslate(pt)
                          .PreScale(scaleFactor, 1.0)
                          .PreTranslate(-pt);
      aCtx->SetMatrixDouble(mat);
    }
  }
  nsTextFrame::PaintTextParams params(aCtx);
  params.framePt = gfx::Point(framePt.x, framePt.y);
  params.dirtyRect = extraVisible;

  if (aBuilder->IsForGenerateGlyphMask()) {
    params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
  } else {
    params.state = nsTextFrame::PaintTextParams::PaintText;
  }

  f->PaintText(params, mVisIStartEdge, mVisIEndEdge, ToReferenceFrame(),
               f->IsSelected(), aOpacity);
}

// This could go to nsDisplayListInvalidation.h, but
// |nsTextFrame::TextDecorations| requires including of nsTextFrame.h which
// would produce circular dependencies.
class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry {
 public:
  nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder)
      : nsDisplayItemGenericGeometry(aItem, aBuilder),
        mVisIStartEdge(aItem->VisIStartEdge()),
        mVisIEndEdge(aItem->VisIEndEdge()) {
    nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
    f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
                          mDecorations);
  }

  /**
   * We store the computed text decorations here since they are
   * computed using style data from parent frames. Any changes to these
   * styles will only invalidate the parent frame and not this frame.
   */
  nsTextFrame::TextDecorations mDecorations;
  nscoord mVisIStartEdge;
  nscoord mVisIEndEdge;
};

nsDisplayItemGeometry* nsDisplayText::AllocateGeometry(
    nsDisplayListBuilder* aBuilder) {
  return new nsDisplayTextGeometry(this, aBuilder);
}

void nsDisplayText::ComputeInvalidationRegion(
    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
    nsRegion* aInvalidRegion) const {
  const nsDisplayTextGeometry* geometry =
      static_cast<const nsDisplayTextGeometry*>(aGeometry);
  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);

  nsTextFrame::TextDecorations decorations;
  f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
                        decorations);

  bool snap;
  const nsRect& newRect = geometry->mBounds;
  nsRect oldRect = GetBounds(aBuilder, &snap);
  if (decorations != geometry->mDecorations ||
      mVisIStartEdge != geometry->mVisIStartEdge ||
      mVisIEndEdge != geometry->mVisIEndEdge ||
      !oldRect.IsEqualInterior(newRect) ||
      !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
    aInvalidRegion->Or(oldRect, newRect);
  }
}

void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) {
#ifdef DEBUG
  aStream << " (\"";

  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
  nsCString buf;
  f->ToCString(buf);

  aStream << buf.get() << "\")";
#endif
}

nsDisplayEffectsBase::nsDisplayEffectsBase(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain)
    : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot,
                        aClearClipChain) {
  MOZ_COUNT_CTOR(nsDisplayEffectsBase);
}

nsDisplayEffectsBase::nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder,
                                           nsIFrame* aFrame,
                                           nsDisplayList* aList)
    : nsDisplayWrapList(aBuilder, aFrame, aList) {
  MOZ_COUNT_CTOR(nsDisplayEffectsBase);
}

nsRegion nsDisplayEffectsBase::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                               bool* aSnap) const {
  *aSnap = false;
  return nsRegion();
}

void nsDisplayEffectsBase::HitTest(nsDisplayListBuilder* aBuilder,
                                   const nsRect& aRect, HitTestState* aState,
                                   nsTArray<nsIFrame*>* aOutFrames) {
  nsPoint rectCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2);
  if (SVGIntegrationUtils::HitTestFrameForEffects(
          mFrame, rectCenter - ToReferenceFrame())) {
    mList.HitTest(aBuilder, aRect, aState, aOutFrames);
  }
}

gfxRect nsDisplayEffectsBase::BBoxInUserSpace() const {
  return SVGUtils::GetBBox(mFrame);
}

gfxPoint nsDisplayEffectsBase::UserSpaceOffset() const {
  return SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mFrame);
}

void nsDisplayEffectsBase::ComputeInvalidationRegion(
    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
    nsRegion* aInvalidRegion) const {
  const auto* geometry =
      static_cast<const nsDisplaySVGEffectGeometry*>(aGeometry);
  bool snap;
  nsRect bounds = GetBounds(aBuilder, &snap);
  if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() ||
      geometry->mUserSpaceOffset != UserSpaceOffset() ||
      !geometry->mBBox.IsEqualInterior(BBoxInUserSpace())) {
    // Filter and mask output can depend on the location of the frame's user
    // space and on the frame's BBox. We need to invalidate if either of these
    // change relative to the reference frame.
    // Invalidations from our inactive layer manager are not enough to catch
    // some of these cases because filters can produce output even if there's
    // nothing in the filter input.
    aInvalidRegion->Or(bounds, geometry->mBounds);
  }
}

bool nsDisplayEffectsBase::ValidateSVGFrame() {
  if (mFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
    ISVGDisplayableFrame* svgFrame = do_QueryFrame(mFrame);
    if (!svgFrame) {
      return false;
    }
    if (auto* svgElement = SVGElement::FromNode(mFrame->GetContent())) {
      // The SVG spec says only to draw filters if the element
      // has valid dimensions.
      return svgElement->HasValidDimensions();
    }
    return false;
  }

  return true;
}

using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;

static void ComputeMaskGeometry(PaintFramesParams& aParams) {
  // Properties are added lazily and may have been removed by a restyle, so
  // make sure all applicable ones are set again.
  nsIFrame* firstFrame =
      nsLayoutUtils::FirstContinuationOrIBSplitSibling(aParams.frame);

  const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();

  nsTArray<SVGMaskFrame*> maskFrames;
  // XXX check return value?
  SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);

  if (maskFrames.Length() == 0) {
    return;
  }

  gfxContext& ctx = aParams.ctx;
  nsIFrame* frame = aParams.frame;

  nsPoint offsetToUserSpace =
      nsLayoutUtils::ComputeOffsetToUserSpace(aParams.builder, aParams.frame);

  auto cssToDevScale = frame->PresContext()->CSSToDevPixelScale();
  int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();

  gfxPoint devPixelOffsetToUserSpace =
      nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, appUnitsPerDevPixel);

  gfxContextMatrixAutoSaveRestore matSR(&ctx);
  ctx.SetMatrixDouble(
      ctx.CurrentMatrixDouble().PreTranslate(devPixelOffsetToUserSpace));

  // Convert boaderArea and dirtyRect to user space.
  nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace;
  nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace;

  // Union all mask layer rectangles in user space.
  LayoutDeviceRect maskInUserSpace;
  for (size_t i = 0; i < maskFrames.Length(); i++) {
    SVGMaskFrame* maskFrame = maskFrames[i];
    LayoutDeviceRect currentMaskSurfaceRect;

    if (maskFrame) {
      auto rect = maskFrame->GetMaskArea(aParams.frame);
      currentMaskSurfaceRect =
          CSSRect::FromUnknownRect(ToRect(rect)) * cssToDevScale;
    } else {
      nsCSSRendering::ImageLayerClipState clipState;
      nsCSSRendering::GetImageLayerClip(
          svgReset->mMask.mLayers[i], frame, *frame->StyleBorder(),
          userSpaceBorderArea, userSpaceDirtyRect,
          /* aWillPaintBorder = */ false, appUnitsPerDevPixel, &clipState);
      currentMaskSurfaceRect = LayoutDeviceRect::FromUnknownRect(
          ToRect(clipState.mDirtyRectInDevPx));
    }

    maskInUserSpace = maskInUserSpace.Union(currentMaskSurfaceRect);
  }

  if (!maskInUserSpace.IsEmpty()) {
    aParams.maskRect = Some(maskInUserSpace);
  } else {
    aParams.maskRect = Nothing();
  }
}

nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths(
    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
    const ActiveScrolledRoot* aActiveScrolledRoot, bool aWrapsBackdropFilter)
    : nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
      mWrapsBackdropFilter(aWrapsBackdropFilter) {
  MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);

  nsPresContext* presContext = mFrame->PresContext();
  uint32_t flags =
      aBuilder->GetBackgroundPaintFlags() | nsCSSRendering::PAINTBG_MASK_IMAGE;
  const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
  NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) {
    const auto& layer = svgReset->mMask.mLayers[i];
    if (!layer.mImage.IsResolved()) {
      continue;
    }
    const nsRect& borderArea = mFrame->GetRectRelativeToSelf();
    // NOTE(emilio): We only care about the dest rect so we don't bother
    // computing a clip.
    bool isTransformedFixed = false;
    nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer(
        presContext, aFrame, flags, borderArea, borderArea, layer,
        &isTransformedFixed);
    mDestRects.AppendElement(state.mDestArea);
  }
}

static bool CanMergeDisplayMaskFrame(nsIFrame* aFrame) {
  // Do not merge items for box-decoration-break:clone elements,
  // since each box should have its own mask in that case.
  if (aFrame->StyleBorder()->mBoxDecorationBreak ==
      StyleBoxDecorationBreak::Clone) {
    return false;
  }

  // Do not merge if either frame has a mask. Continuation frames should apply
  // the mask independently (just like nsDisplayBackgroundImage).
  if (aFrame->StyleSVGReset()->HasMask()) {
    return false;
  }

  return true;
}

bool nsDisplayMasksAndClipPaths::CanMerge(const nsDisplayItem* aItem) const {
  // Items for the same content element should be merged into a single
  // compositing group.
  if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) ||
      !HasSameContent(aItem)) {
    return false;
  }

  return CanMergeDisplayMaskFrame(mFrame) &&
         CanMergeDisplayMaskFrame(aItem->Frame());
}

bool nsDisplayMasksAndClipPaths::IsValidMask() {
  if (!ValidateSVGFrame()) {
    return false;
  }

  nsIFrame* firstFrame =
      nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);

  return !(SVGObserverUtils::GetAndObserveClipPath(firstFrame, nullptr) ==
               SVGObserverUtils::eHasRefsSomeInvalid ||
           SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) ==
               SVGObserverUtils::eHasRefsSomeInvalid);
}

bool nsDisplayMasksAndClipPaths::PaintMask(nsDisplayListBuilder* aBuilder,
                                           gfxContext* aMaskContext,
                                           bool aHandleOpacity,
                                           bool* aMaskPainted) {
  MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8);

  imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
  nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
  PaintFramesParams params(*aMaskContext, mFrame, mBounds, borderArea, aBuilder,
                           aHandleOpacity, imgParams);
  ComputeMaskGeometry(params);
  bool maskIsComplete = false;
  bool painted = SVGIntegrationUtils::PaintMask(params, maskIsComplete);
  if (aMaskPainted) {
    *aMaskPainted = painted;
  }

  return maskIsComplete &&
         (imgParams.result == ImgDrawResult::SUCCESS ||
          imgParams.result == ImgDrawResult::SUCCESS_NOT_COMPLETE ||
          imgParams.result == ImgDrawResult::WRONG_SIZE);
}

void nsDisplayMasksAndClipPaths::ComputeInvalidationRegion(
    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
    nsRegion* aInvalidRegion) const {
  nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry,
                                                  aInvalidRegion);

  const auto* geometry =
      static_cast<const nsDisplayMasksAndClipPathsGeometry*>(aGeometry);
  bool snap;
  nsRect bounds = GetBounds(aBuilder, &snap);

  if (mDestRects.Length() != geometry->mDestRects.Length()) {
    aInvalidRegion->Or(bounds, geometry->mBounds);
  } else {
    for (size_t i = 0; i < mDestRects.Length(); i++) {
      if (!mDestRects[i].IsEqualInterior(geometry->mDestRects[i])) {
        aInvalidRegion->Or(bounds, geometry->mBounds);
        break;
      }
    }
  }
}

void nsDisplayMasksAndClipPaths::PaintWithContentsPaintCallback(
    nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
    const std::function<void()>& aPaintChildren) {
  // Clip the drawing target by mVisibleRect, which contains the visible
  // region of the target frame and its out-of-flow and inflow descendants.
  Rect bounds = NSRectToRect(GetPaintRect(aBuilder, aCtx),
                             mFrame->PresContext()->AppUnitsPerDevPixel());
  bounds.RoundOut();
  gfxClipAutoSaveRestore autoSaveClip(aCtx);
  autoSaveClip.Clip(bounds);

  imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
  nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
  PaintFramesParams params(*aCtx, mFrame, GetPaintRect(aBuilder, aCtx),
                           borderArea, aBuilder, false, imgParams);

  ComputeMaskGeometry(params);

  SVGIntegrationUtils::PaintMaskAndClipPath(params, aPaintChildren);
}

void nsDisplayMasksAndClipPaths::Paint(nsDisplayListBuilder* aBuilder,
                                       gfxContext* aCtx) {
  if (!IsValidMask()) {
    return;
  }
  PaintWithContentsPaintCallback(aBuilder, aCtx, [&] {
    GetChildren()->Paint(aBuilder, aCtx,
                         mFrame->PresContext()->AppUnitsPerDevPixel());
  });
}

static Maybe<wr::WrClipChainId> CreateSimpleClipRegion(
    const nsDisplayMasksAndClipPaths& aDisplayItem,
    wr::DisplayListBuilder& aBuilder) {
  nsIFrame* frame = aDisplayItem.Frame();
  const auto* style = frame->StyleSVGReset();
  MOZ_ASSERT(style->HasClipPath() || style->HasMask());
  if (!SVGIntegrationUtils::UsingSimpleClipPathForFrame(frame)) {
    return Nothing();
  }

  const auto& clipPath = style->mClipPath;
  const auto& shape = *clipPath.AsShape()._0;

  auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
  const nsRect refBox =
      nsLayoutUtils::ComputeGeometryBox(frame, clipPath.AsShape()._1);

  wr::WrClipId clipId{};

  switch (shape.tag) {
    case StyleBasicShape::Tag::Rect: {
      const nsRect rect =
          ShapeUtils::ComputeInsetRect(shape.AsRect().rect, refBox) +
          aDisplayItem.ToReferenceFrame();

      nscoord radii[8] = {0};
      if (ShapeUtils::ComputeRectRadii(shape.AsRect().round, refBox, rect,
                                       radii)) {
        clipId = aBuilder.DefineRoundedRectClip(
            Nothing(),
            wr::ToComplexClipRegion(rect, radii, appUnitsPerDevPixel));
      } else {
        clipId = aBuilder.DefineRectClip(
            Nothing(), wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
                           rect, appUnitsPerDevPixel)));
      }

      break;
    }
    case StyleBasicShape::Tag::Ellipse:
    case StyleBasicShape::Tag::Circle: {
      nsPoint center = ShapeUtils::ComputeCircleOrEllipseCenter(shape, refBox);

      nsSize radii;
      if (shape.IsEllipse()) {
        radii = ShapeUtils::ComputeEllipseRadii(shape, center, refBox);
      } else {
        nscoord radius = ShapeUtils::ComputeCircleRadius(shape, center, refBox);
        radii = {radius, radius};
      }

      nsRect ellipseRect(aDisplayItem.ToReferenceFrame() + center -
                             nsPoint(radii.width, radii.height),
                         radii * 2);

      nscoord ellipseRadii[8];
      for (const auto corner : AllPhysicalHalfCorners()) {
        ellipseRadii[corner] =
            HalfCornerIsX(corner) ? radii.width : radii.height;
      }

      clipId = aBuilder.DefineRoundedRectClip(
          Nothing(), wr::ToComplexClipRegion(ellipseRect, ellipseRadii,
                                             appUnitsPerDevPixel));

      break;
    }
    default:
      // Please don't add more exceptions, try to find a way to define the clip
      // without using a mask image.
      //
      // And if you _really really_ need to add an exception, add it to
      // SVGIntegrationUtils::UsingSimpleClipPathForFrame
      MOZ_ASSERT_UNREACHABLE("Unhandled shape id?");
      return Nothing();
  }

  wr::WrClipChainId clipChainId = aBuilder.DefineClipChain({clipId}, true);

  return Some(clipChainId);
}

static void FillPolygonDataForDisplayItem(
    const nsDisplayMasksAndClipPaths& aDisplayItem,
    nsTArray<wr::LayoutPoint>& aPoints, wr::FillRule& aFillRule) {
  nsIFrame* frame = aDisplayItem.Frame();
  const auto* style = frame->StyleSVGReset();
  bool isPolygon = style->HasClipPath() && style->mClipPath.IsShape() &&
                   style->mClipPath.AsShape()._0->IsPolygon();
  if (!isPolygon) {
    return;
  }

  const auto& clipPath = style->mClipPath;
  const auto& shape = *clipPath.AsShape()._0;
  const nsRect refBox =
      nsLayoutUtils::ComputeGeometryBox(frame, clipPath.AsShape()._1);

  // We only fill polygon data for polygons that are below a complexity
  // limit.
  nsTArray<nsPoint> vertices =
      ShapeUtils::ComputePolygonVertices(shape, refBox);
  if (vertices.Length() > wr::POLYGON_CLIP_VERTEX_MAX) {
    return;
  }

  auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();

  for (size_t i = 0; i < vertices.Length(); ++i) {
    wr::LayoutPoint point = wr::ToLayoutPoint(
        LayoutDevicePoint::FromAppUnits(vertices[i], appUnitsPerDevPixel));
    aPoints.AppendElement(point);
  }

  aFillRule = (shape.AsPolygon().fill == StyleFillRule::Nonzero)
                  ? wr::FillRule::Nonzero
                  : wr::FillRule::Evenodd;
}

static Maybe<wr::WrClipChainId> CreateWRClipPathAndMasks(
    nsDisplayMasksAndClipPaths* aDisplayItem, const LayoutDeviceRect& aBounds,
    wr::IpcResourceUpdateQueue& aResources, wr::DisplayListBuilder& aBuilder,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  if (auto clip = CreateSimpleClipRegion(*aDisplayItem, aBuilder)) {
    return clip;
  }

  Maybe<wr::ImageMask> mask = aManager->CommandBuilder().BuildWrMaskImage(
      aDisplayItem, aBuilder, aResources, aSc, aDisplayListBuilder, aBounds);
  if (!mask) {
    return Nothing();
  }

  // We couldn't create a simple clip region, but before we create an image
  // mask clip, see if we can get a polygon clip to add to it.
  nsTArray<wr::LayoutPoint> points;
  wr::FillRule fillRule = wr::FillRule::Nonzero;
  FillPolygonDataForDisplayItem(*aDisplayItem, points, fillRule);

  wr::WrClipId clipId =
      aBuilder.DefineImageMaskClip(mask.ref(), points, fillRule);

  wr::WrClipChainId clipChainId = aBuilder.DefineClipChain({clipId}, true);

  return Some(clipChainId);
}

bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  bool snap;
  auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
  nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap);
  LayoutDeviceRect bounds =
      LayoutDeviceRect::FromAppUnits(displayBounds, appUnitsPerDevPixel);

  Maybe<wr::WrClipChainId> clip = CreateWRClipPathAndMasks(
      this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder);

  float oldOpacity = aBuilder.GetInheritedOpacity();

  Maybe<StackingContextHelper> layer;
  const StackingContextHelper* sc = &aSc;
  if (clip) {
    // Create a new stacking context to attach the mask to, ensuring the mask is
    // applied to the aggregate, and not the individual elements.

    // The stacking context shouldn't have any offset.
    bounds.MoveTo(0, 0);

    Maybe<float> opacity =
        (SVGIntegrationUtils::UsingSimpleClipPathForFrame(mFrame) &&
         aBuilder.GetInheritedOpacity() != 1.0f)
            ? Some(aBuilder.GetInheritedOpacity())
            : Nothing();

    wr::StackingContextParams params;
    params.clip = wr::WrStackingContextClip::ClipChain(clip->id);
    params.opacity = opacity.ptrOr(nullptr);
    if (mWrapsBackdropFilter) {
      params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER;
    }
    layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params,
                  bounds);
    sc = layer.ptr();
  }

  aBuilder.SetInheritedOpacity(1.0f);
  const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain();
  aBuilder.SetInheritedClipChain(nullptr);
  CreateWebRenderCommandsNewClipListOption(aBuilder, aResources, *sc, aManager,
                                           aDisplayListBuilder, layer.isSome());
  aBuilder.SetInheritedOpacity(oldOpacity);
  aBuilder.SetInheritedClipChain(oldClipChain);

  return true;
}

Maybe<nsRect> nsDisplayMasksAndClipPaths::GetClipWithRespectToASR(
    nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const {
  if (const DisplayItemClip* clip =
          DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) {
    return Some(clip->GetClipRect());
  }
  // This item does not have a clip with respect to |aASR|. However, we
  // might still have finite bounds with respect to |aASR|. Check our
  // children.
  nsDisplayList* childList = GetSameCoordinateSystemChildren();
  if (childList) {
    return Some(childList->GetClippedBoundsWithRespectToASR(aBuilder, aASR));
  }
#ifdef DEBUG
  NS_ASSERTION(false, "item should have finite clip with respect to aASR");
#endif
  return Nothing();
}

#ifdef MOZ_DUMP_PAINTING
void nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo) {
  nsIFrame* firstFrame =
      nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
  bool first = true;
  aTo += " effects=(";
  SVGClipPathFrame* clipPathFrame;
  // XXX Check return value?
  SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
  if (clipPathFrame) {
    if (!first) {
      aTo += ", ";
    }
    aTo += nsPrintfCString(
        "clip(%s)", clipPathFrame->IsTrivial() ? "trivial" : "non-trivial");
    first = false;
  } else if (mFrame->StyleSVGReset()->HasClipPath()) {
    if (!first) {
      aTo += ", ";
    }
    aTo += "clip(basic-shape)";
    first = false;
  }

  nsTArray<SVGMaskFrame*> masks;
  // XXX check return value?
  SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks);
  if (!masks.IsEmpty() && masks[0]) {
    if (!first) {
      aTo += ", ";
    }
    aTo += "mask";
  }
  aTo += ")";
}
#endif

bool nsDisplayBackdropFilters::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  WrFiltersHolder wrFilters;
  const ComputedStyle& style = mStyle ? *mStyle : *mFrame->Style();
  auto filterChain = style.StyleEffects()->mBackdropFilters.AsSpan();
  bool initialized = true;
  if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame,
                                                      wrFilters) &&
      !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
                                                  wrFilters, initialized)) {
    // TODO: If painting backdrop-filters on the content side is implemented,
    // consider returning false to fall back to that.
    wrFilters = {};
  }

  if (!initialized) {
    wrFilters = {};
  }

  nsCSSRendering::ImageLayerClipState clip;
  nsCSSRendering::GetImageLayerClip(
      style.StyleBackground()->BottomLayer(), mFrame, *style.StyleBorder(),
      mBackdropRect, mBackdropRect, false,
      mFrame->PresContext()->AppUnitsPerDevPixel(), &clip);

  LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
      mBackdropRect, mFrame->PresContext()->AppUnitsPerDevPixel());

  wr::ComplexClipRegion region =
      wr::ToComplexClipRegion(clip.mBGClipArea, clip.mRadii,
                              mFrame->PresContext()->AppUnitsPerDevPixel());

  aBuilder.PushBackdropFilter(wr::ToLayoutRect(bounds), region,
                              wrFilters.filters, wrFilters.filter_datas,
                              !BackfaceIsHidden());

  wr::StackingContextParams params;
  params.clip =
      wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                           params);

  nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
                                             aDisplayListBuilder);
  return true;
}

void nsDisplayBackdropFilters::Paint(nsDisplayListBuilder* aBuilder,
                                     gfxContext* aCtx) {
  // TODO: Implement backdrop filters
  GetChildren()->Paint(aBuilder, aCtx,
                       mFrame->PresContext()->AppUnitsPerDevPixel());
}

nsRect nsDisplayBackdropFilters::GetBounds(nsDisplayListBuilder* aBuilder,
                                           bool* aSnap) const {
  nsRect childBounds = nsDisplayWrapList::GetBounds(aBuilder, aSnap);

  *aSnap = false;

  return mBackdropRect.Union(childBounds);
}

/* static */
nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder,
                                   nsIFrame* aFrame, nsDisplayList* aList,
                                   nsIFrame* aStyleFrame,
                                   bool aWrapsBackdropFilter)
    : nsDisplayEffectsBase(aBuilder, aFrame, aList),
      mStyle(aFrame == aStyleFrame ? nullptr : aStyleFrame->Style()),
      mEffectsBounds(aFrame->InkOverflowRectRelativeToSelf()),
      mWrapsBackdropFilter(aWrapsBackdropFilter) {
  MOZ_COUNT_CTOR(nsDisplayFilters);
  mVisibleRect = aBuilder->GetVisibleRect() +
                 aBuilder->GetCurrentFrameOffsetToReferenceFrame();
}

void nsDisplayFilters::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
  PaintWithContentsPaintCallback(aBuilder, aCtx, [&](gfxContext* aContext) {
    GetChildren()->Paint(aBuilder, aContext,
                         mFrame->PresContext()->AppUnitsPerDevPixel());
  });
}

void nsDisplayFilters::PaintWithContentsPaintCallback(
    nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
    const std::function<void(gfxContext* aContext)>& aPaintChildren) {
  imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
  nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
  PaintFramesParams params(*aCtx, mFrame, mVisibleRect, borderArea, aBuilder,
                           false, imgParams);

  gfxPoint userSpaceToFrameSpaceOffset =
      SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx(mFrame, params);

  auto filterChain = mStyle ? mStyle->StyleEffects()->mFilters.AsSpan()
                            : mFrame->StyleEffects()->mFilters.AsSpan();
  SVGIntegrationUtils::PaintFilter(
      params, filterChain,
      [&](gfxContext& aContext, imgDrawingParams&, const gfxMatrix*,
          const nsIntRect*) {
        gfxContextMatrixAutoSaveRestore autoSR(&aContext);
        aContext.SetMatrixDouble(aContext.CurrentMatrixDouble().PreTranslate(
            -userSpaceToFrameSpaceOffset));
        aPaintChildren(&aContext);
      });
}

bool nsDisplayFilters::CanCreateWebRenderCommands() const {
  return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(mFrame);
}

bool nsDisplayFilters::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  WrFiltersHolder wrFilters;
  const ComputedStyle& style = mStyle ? *mStyle : *mFrame->Style();
  auto filterChain = style.StyleEffects()->mFilters.AsSpan();
  bool initialized = true;
  if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame,
                                                      wrFilters) &&
      !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
                                                  wrFilters, initialized)) {
    if (mStyle) {
      // TODO(bug 1769223): Support fallback filters in the root code-path,
      // perhaps. For now treat it the same way as invalid filters.
      wrFilters = {};
    } else {
      // Draw using fallback.
      return false;
    }
  }

  if (!initialized) {
    // https://drafts.fxtf.org/filter-effects/#typedef-filter-url:
    //
    //   If the filter references a non-existent object or the referenced object
    //   is not a filter element, then the whole filter chain is ignored. No
    //   filter is applied to the object.
    //
    // Note that other engines have a weird discrepancy between SVG and HTML
    // content here, but the spec is clear.
    wrFilters = {};
  }

  uint64_t clipChainId;
  if (wrFilters.post_filters_clip) {
    auto devPxRect = LayoutDeviceRect::FromAppUnits(
        wrFilters.post_filters_clip.value() + ToReferenceFrame(),
        mFrame->PresContext()->AppUnitsPerDevPixel());
    auto clipId =
        aBuilder.DefineRectClip(Nothing(), wr::ToLayoutRect(devPxRect));
    clipChainId = aBuilder.DefineClipChain({clipId}, true).id;
  } else {
    clipChainId = aBuilder.CurrentClipChainId();
  }
  wr::WrStackingContextClip clip =
      wr::WrStackingContextClip::ClipChain(clipChainId);

  float opacity = aBuilder.GetInheritedOpacity();
  aBuilder.SetInheritedOpacity(1.0f);
  const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain();
  aBuilder.SetInheritedClipChain(nullptr);
  wr::StackingContextParams params;
  params.mFilters = std::move(wrFilters.filters);
  params.mFilterDatas = std::move(wrFilters.filter_datas);
  params.opacity = opacity != 1.0f ? &opacity : nullptr;
  params.clip = clip;
  if (mWrapsBackdropFilter) {
    params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER;
  }
  StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                           params);

  nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc,
                                                aManager, aDisplayListBuilder);
  aBuilder.SetInheritedOpacity(opacity);
  aBuilder.SetInheritedClipChain(oldClipChain);

  return true;
}

#ifdef MOZ_DUMP_PAINTING
void nsDisplayFilters::PrintEffects(nsACString& aTo) {
  nsIFrame* firstFrame =
      nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
  bool first = true;
  aTo += " effects=(";
  // We may exist for a mix of CSS filter functions and/or references to SVG
  // filters.  If we have invalid references to SVG filters then we paint
  // nothing, but otherwise we will apply one or more filters.
  if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) !=
      SVGObserverUtils::eHasRefsSomeInvalid) {
    if (!first) {
      aTo += ", ";
    }
    aTo += "filter";
  }
  aTo += ")";
}
#endif

nsDisplaySVGWrapper::nsDisplaySVGWrapper(nsDisplayListBuilder* aBuilder,
                                         nsIFrame* aFrame, nsDisplayList* aList)
    : nsDisplayWrapList(aBuilder, aFrame, aList) {
  MOZ_COUNT_CTOR(nsDisplaySVGWrapper);
}

bool nsDisplaySVGWrapper::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
  return !aBuilder->GetWidgetLayerManager();
}

bool nsDisplaySVGWrapper::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  return CreateWebRenderCommandsNewClipListOption(
      aBuilder, aResources, aSc, aManager, aDisplayListBuilder, false);
}

nsDisplayForeignObject::nsDisplayForeignObject(nsDisplayListBuilder* aBuilder,
                                               nsIFrame* aFrame,
                                               nsDisplayList* aList)
    : nsDisplayWrapList(aBuilder, aFrame, aList) {
  MOZ_COUNT_CTOR(nsDisplayForeignObject);
}

#ifdef NS_BUILD_REFCNT_LOGGING
nsDisplayForeignObject::~nsDisplayForeignObject() {
  MOZ_COUNT_DTOR(nsDisplayForeignObject);
}
#endif

bool nsDisplayForeignObject::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
  return !aBuilder->GetWidgetLayerManager();
}

bool nsDisplayForeignObject::CreateWebRenderCommands(
    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  AutoRestore<bool> restoreDoGrouping(aManager->CommandBuilder().mDoGrouping);
  aManager->CommandBuilder().mDoGrouping = false;
  return CreateWebRenderCommandsNewClipListOption(
      aBuilder, aResources, aSc, aManager, aDisplayListBuilder, false);
}

void nsDisplayLink::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
  auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel();
  aCtx->GetDrawTarget()->Link(
      mLinkSpec.get(), NSRectToRect(GetPaintRect(aBuilder, aCtx), appPerDev));
}

void nsDisplayDestination::Paint(nsDisplayListBuilder* aBuilder,
                                 gfxContext* aCtx) {
  auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel();
  aCtx->GetDrawTarget()->Destination(
      mDestinationName.get(),
      NSPointToPoint(GetPaintRect(aBuilder, aCtx).TopLeft(), appPerDev));
}

void nsDisplayListCollection::SerializeWithCorrectZOrder(
    nsDisplayList* aOutResultList, nsIContent* aContent) {
  // Sort PositionedDescendants() in CSS 'z-order' order.  The list is already
  // in content document order and SortByZOrder is a stable sort which
  // guarantees that boxes produced by the same element are placed together
  // in the sort. Consider a position:relative inline element that breaks
  // across lines and has absolutely positioned children; all the abs-pos
  // children should be z-ordered after all the boxes for the position:relative
  // element itself.
  PositionedDescendants()->SortByZOrder();

  // Now follow the rules of http://www.w3.org/TR/CSS21/zindex.html
  // 1,2: backgrounds and borders
  aOutResultList->AppendToTop(BorderBackground());
  // 3: negative z-index children.
  for (auto* item : PositionedDescendants()->TakeItems()) {
    if (item->ZIndex() < 0) {
      aOutResultList->AppendToTop(item);
    } else {
      PositionedDescendants()->AppendToTop(item);
    }
  }

  // 4: block backgrounds
  aOutResultList->AppendToTop(BlockBorderBackgrounds());
  // 5: floats
  aOutResultList->AppendToTop(Floats());
  // 7: general content
  aOutResultList->AppendToTop(Content());
  // 7.5: outlines, in content tree order. We need to sort by content order
  // because an element with outline that breaks and has children with outline
  // might have placed child outline items between its own outline items.
  // The element's outline items need to all come before any child outline
  // items.
  if (aContent) {
    Outlines()->SortByContentOrder(aContent);
  }
  aOutResultList->AppendToTop(Outlines());
  // 8, 9: non-negative z-index children
  aOutResultList->AppendToTop(PositionedDescendants());
}

uint32_t PaintTelemetry::sPaintLevel = 0;

PaintTelemetry::AutoRecordPaint::AutoRecordPaint() {
  // Don't record nested paints.
  if (sPaintLevel++ > 0) {
    return;
  }

  mStart = TimeStamp::Now();
}

PaintTelemetry::AutoRecordPaint::~AutoRecordPaint() {
  MOZ_ASSERT(sPaintLevel != 0);
  if (--sPaintLevel > 0) {
    return;
  }

  // If we're in multi-process mode, don't include paint times for the parent
  // process.
  if (gfxVars::BrowserTabsRemoteAutostart() && XRE_IsParentProcess()) {
    return;
  }

  double totalMs = (TimeStamp::Now() - mStart).ToMilliseconds();

  // Record the total time.
  Telemetry::Accumulate(Telemetry::CONTENT_PAINT_TIME,
                        static_cast<uint32_t>(totalMs));
}

static nsIFrame* GetSelfOrPlaceholderFor(nsIFrame* aFrame) {
  if (aFrame->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
    return aFrame;
  }

  if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
      !aFrame->GetPrevInFlow()) {
    return aFrame->GetPlaceholderFrame();
  }

  return aFrame;
}

static nsIFrame* GetAncestorFor(nsIFrame* aFrame) {
  nsIFrame* f = GetSelfOrPlaceholderFor(aFrame);
  MOZ_ASSERT(f);
  return nsLayoutUtils::GetCrossDocParentFrameInProcess(f);
}

nsDisplayListBuilder::AutoBuildingDisplayList::AutoBuildingDisplayList(
    nsDisplayListBuilder* aBuilder, nsIFrame* aForChild,
    const nsRect& aVisibleRect, const nsRect& aDirtyRect,
    const bool aIsTransformed)
    : mBuilder(aBuilder),
      mPrevFrame(aBuilder->mCurrentFrame),
      mPrevReferenceFrame(aBuilder->mCurrentReferenceFrame),
      mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame),
      mPrevAdditionalOffset(aBuilder->mAdditionalOffset),
      mPrevVisibleRect(aBuilder->mVisibleRect),
      mPrevDirtyRect(aBuilder->mDirtyRect),
      mPrevCompositorHitTestInfo(aBuilder->mCompositorHitTestInfo),
      mPrevAncestorHasApzAwareEventHandler(
          aBuilder->mAncestorHasApzAwareEventHandler),
      mPrevBuildingInvisibleItems(aBuilder->mBuildingInvisibleItems),
      mPrevInInvalidSubtree(aBuilder->mInInvalidSubtree) {
  if (aIsTransformed) {
    aBuilder->mCurrentOffsetToReferenceFrame =
        aBuilder->AdditionalOffset().refOr(nsPoint());
    aBuilder->mCurrentReferenceFrame = aForChild;
  } else if (aBuilder->mCurrentFrame == aForChild->GetParent()) {
    aBuilder->mCurrentOffsetToReferenceFrame += aForChild->GetPosition();
  } else {
    aBuilder->mCurrentReferenceFrame = aBuilder->FindReferenceFrameFor(
        aForChild, &aBuilder->mCurrentOffsetToReferenceFrame);
  }

  // If aForChild is being visited from a frame other than it's ancestor frame,
  // mInInvalidSubtree will need to be recalculated the slow way.
  if (aForChild == mPrevFrame || GetAncestorFor(aForChild) == mPrevFrame) {
    aBuilder->mInInvalidSubtree =
        aBuilder->mInInvalidSubtree || aForChild->IsFrameModified();
  } else {
    aBuilder->mInInvalidSubtree = AnyContentAncestorModified(aForChild);
  }

  aBuilder->mCurrentFrame = aForChild;
  aBuilder->mVisibleRect = aVisibleRect;
  aBuilder->mDirtyRect =
      aBuilder->mInInvalidSubtree ? aVisibleRect : aDirtyRect;
}

}  // namespace mozilla
