layout/generic/nsFrame.cpp
author Bogdan Tara <btara@mozilla.com>
Thu, 10 Jan 2019 18:49:22 +0200
changeset 510378 842b7a62d9cefd5030c5561d536af8414899af7a
parent 510368 d8eef8f3e396d3a6f82607f53fc280316f184e86
child 510489 b6c42df7ce091d7f2417e13203a50c60cd144571
permissions -rw-r--r--
Backed out 15 changesets (bug 1305957) for ASAN failures CLOSED TREE Backed out changeset 4d5eb85d3155 (bug 1305957) Backed out changeset 51c86d025ecb (bug 1305957) Backed out changeset d8eef8f3e396 (bug 1305957) Backed out changeset 950bf6ad1ef2 (bug 1305957) Backed out changeset b4cb2cbebdb6 (bug 1305957) Backed out changeset bfca5019a9cc (bug 1305957) Backed out changeset e76b842c7b7f (bug 1305957) Backed out changeset d9445a5f3458 (bug 1305957) Backed out changeset d9052f7b34d9 (bug 1305957) Backed out changeset e7124fecb721 (bug 1305957) Backed out changeset bdb766faa867 (bug 1305957) Backed out changeset 3033401ef320 (bug 1305957) Backed out changeset 6b96050386f6 (bug 1305957) Backed out changeset c66c00f73296 (bug 1305957) Backed out changeset 6bd0bdab93cb (bug 1305957)

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

/* base class of all rendering objects */

#include "nsFrame.h"

#include <stdarg.h>
#include <algorithm>

#include "gfx2DGlue.h"
#include "gfxUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/Sprintf.h"

#include "nsCOMPtr.h"
#include "nsFlexContainerFrame.h"
#include "nsFrameList.h"
#include "nsPlaceholderFrame.h"
#include "nsPluginFrame.h"
#include "nsIBaseWindow.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsContentUtils.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSProps.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSRendering.h"
#include "nsAtom.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsTableWrapperFrame.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsIScrollableFrame.h"
#include "nsPresContext.h"
#include "nsStyleConsts.h"
#include "nsIPresShell.h"
#include "mozilla/Logging.h"
#include "nsLayoutUtils.h"
#include "LayoutLogging.h"
#include "mozilla/RestyleManager.h"
#include "nsInlineFrame.h"
#include "nsFrameSelection.h"
#include "nsGkAtoms.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSClipPathInstance.h"

#include "nsFrameTraversal.h"
#include "nsRange.h"
#include "nsITextControlFrame.h"
#include "nsNameSpaceManager.h"
#include "nsIPercentBSizeObserver.h"
#include "nsStyleStructInlines.h"
#include "FrameLayerBuilder.h"
#include "ImageLayers.h"

#include "nsBidiPresUtils.h"
#include "RubyUtils.h"
#include "nsAnimationManager.h"

// For triple-click pref
#include "imgIContainer.h"
#include "imgIRequest.h"
#include "nsError.h"
#include "nsContainerFrame.h"
#include "nsBoxLayoutState.h"
#include "nsBlockFrame.h"
#include "nsDisplayList.h"
#include "nsSVGIntegrationUtils.h"
#include "SVGObserverUtils.h"
#include "nsSVGMaskFrame.h"
#include "nsChangeHint.h"
#include "nsDeckFrame.h"
#include "nsSubDocumentFrame.h"
#include "SVGTextFrame.h"
#include "RetainedDisplayListBuilder.h"

#include "gfxContext.h"
#include "gfxPrefs.h"
#include "nsAbsoluteContainingBlock.h"
#include "StickyScrollContainer.h"
#include "nsFontInflationData.h"
#include "nsRegion.h"
#include "nsIFrameInlines.h"
#include "nsStyleChangeList.h"
#include "nsWindowSizes.h"

#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EffectSet.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/ServoStyleSetInlines.h"
#include "mozilla/css/ImageLoader.h"
#include "mozilla/dom/TouchEvent.h"
#include "mozilla/gfx/Tools.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "nsPrintfCString.h"
#include "ActiveLayerTracker.h"

#include "nsITheme.h"

using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::layout;
typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;

const mozilla::LayoutFrameType nsIFrame::sLayoutFrameTypes[
#define FRAME_ID(...) 1 +
#define ABSTRACT_FRAME_ID(...)
#include "nsFrameIdList.h"
#undef FRAME_ID
#undef ABSTRACT_FRAME_ID
    0] = {
#define FRAME_ID(class_, type_, ...) mozilla::LayoutFrameType::type_,
#define ABSTRACT_FRAME_ID(...)
#include "nsFrameIdList.h"
#undef FRAME_ID
#undef ABSTRACT_FRAME_ID
};

const nsIFrame::FrameClassBits nsIFrame::sFrameClassBits[
#define FRAME_ID(...) 1 +
#define ABSTRACT_FRAME_ID(...)
#include "nsFrameIdList.h"
#undef FRAME_ID
#undef ABSTRACT_FRAME_ID
    0] = {
#define Leaf eFrameClassBitsLeaf
#define NotLeaf eFrameClassBitsNone
#define DynamicLeaf eFrameClassBitsDynamicLeaf
#define FRAME_ID(class_, type_, leaf_, ...) leaf_,
#define ABSTRACT_FRAME_ID(...)
#include "nsFrameIdList.h"
#undef Leaf
#undef NotLeaf
#undef DynamicLeaf
#undef FRAME_ID
#undef ABSTRACT_FRAME_ID
};

// Struct containing cached metrics for box-wrapped frames.
struct nsBoxLayoutMetrics {
  nsSize mPrefSize;
  nsSize mMinSize;
  nsSize mMaxSize;

  nsSize mBlockMinSize;
  nsSize mBlockPrefSize;
  nscoord mBlockAscent;

  nscoord mFlex;
  nscoord mAscent;

  nsSize mLastSize;
};

struct nsContentAndOffset {
  nsIContent* mContent;
  int32_t mOffset;
};

// Some Misc #defines
#define SELECTION_DEBUG 0
#define FORCE_SELECTION_UPDATE 1
#define CALC_DEBUG 0

// This is faster than nsBidiPresUtils::IsFrameInParagraphDirection,
// because it uses the frame pointer passed in without drilling down to
// the leaf frame.
static bool IsReversedDirectionFrame(nsIFrame* aFrame) {
  FrameBidiData bidiData = aFrame->GetBidiData();
  return !IS_SAME_DIRECTION(bidiData.embeddingLevel, bidiData.baseLevel);
}

#include "nsILineIterator.h"
#include "prenv.h"

NS_DECLARE_FRAME_PROPERTY_DELETABLE(BoxMetricsProperty, nsBoxLayoutMetrics)

static void InitBoxMetrics(nsIFrame* aFrame, bool aClear) {
  if (aClear) {
    aFrame->DeleteProperty(BoxMetricsProperty());
  }

  nsBoxLayoutMetrics* metrics = new nsBoxLayoutMetrics();
  aFrame->SetProperty(BoxMetricsProperty(), metrics);

  static_cast<nsFrame*>(aFrame)->nsFrame::MarkIntrinsicISizesDirty();
  metrics->mBlockAscent = 0;
  metrics->mLastSize.SizeTo(0, 0);
}

// Utility function to set a nsRect-valued property table entry on aFrame,
// reusing the existing storage if the property happens to be already set.
template <typename T>
static void SetOrUpdateRectValuedProperty(
    nsIFrame* aFrame, FrameProperties::Descriptor<T> aProperty,
    const nsRect& aNewValue) {
  bool found;
  nsRect* rectStorage = aFrame->GetProperty(aProperty, &found);
  if (!found) {
    rectStorage = new nsRect(aNewValue);
    aFrame->AddProperty(aProperty, rectStorage);
  } else {
    *rectStorage = aNewValue;
  }
}

static bool IsXULBoxWrapped(const nsIFrame* aFrame) {
  return aFrame->GetParent() && aFrame->GetParent()->IsXULBoxFrame() &&
         !aFrame->IsXULBoxFrame();
}

void nsReflowStatus::UpdateTruncated(const ReflowInput& aReflowInput,
                                     const ReflowOutput& aMetrics) {
  const WritingMode containerWM = aMetrics.GetWritingMode();
  if (aReflowInput.GetWritingMode().IsOrthogonalTo(containerWM)) {
    // Orthogonal flows are always reflowed with an unconstrained dimension,
    // so should never end up truncated (see ReflowInput::Init()).
    mTruncated = false;
  } else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
             aReflowInput.AvailableBSize() < aMetrics.BSize(containerWM) &&
             !aReflowInput.mFlags.mIsTopOfPage) {
    mTruncated = true;
  } else {
    mTruncated = false;
  }
}

/* static */ void nsIFrame::DestroyAnonymousContent(
    nsPresContext* aPresContext, already_AddRefed<nsIContent>&& aContent) {
  if (nsCOMPtr<nsIContent> content = aContent) {
    aPresContext->EventStateManager()->NativeAnonymousContentRemoved(content);
    aPresContext->PresShell()->NativeAnonymousContentRemoved(content);
    content->UnbindFromTree();
  }
}

// Formerly the nsIFrameDebug interface

std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus) {
  char complete = 'Y';
  if (aStatus.IsIncomplete()) {
    complete = 'N';
  } else if (aStatus.IsOverflowIncomplete()) {
    complete = 'O';
  }

  char brk = 'N';
  if (aStatus.IsInlineBreakBefore()) {
    brk = 'B';
  } else if (aStatus.IsInlineBreakAfter()) {
    brk = 'A';
  }

  aStream << "["
          << "Complete=" << complete << ","
          << "NIF=" << (aStatus.NextInFlowNeedsReflow() ? 'Y' : 'N') << ","
          << "Truncated=" << (aStatus.IsTruncated() ? 'Y' : 'N') << ","
          << "Break=" << brk << ","
          << "FirstLetter=" << (aStatus.FirstLetterComplete() ? 'Y' : 'N')
          << "]";
  return aStream;
}

#ifdef DEBUG
static bool gShowFrameBorders = false;

void nsFrame::ShowFrameBorders(bool aEnable) { gShowFrameBorders = aEnable; }

bool nsFrame::GetShowFrameBorders() { return gShowFrameBorders; }

static bool gShowEventTargetFrameBorder = false;

void nsFrame::ShowEventTargetFrameBorder(bool aEnable) {
  gShowEventTargetFrameBorder = aEnable;
}

bool nsFrame::GetShowEventTargetFrameBorder() {
  return gShowEventTargetFrameBorder;
}

/**
 * Note: the log module is created during library initialization which
 * means that you cannot perform logging before then.
 */
mozilla::LazyLogModule nsFrame::sFrameLogModule("frame");

#endif

NS_DECLARE_FRAME_PROPERTY_DELETABLE(AbsoluteContainingBlockProperty,
                                    nsAbsoluteContainingBlock)

bool nsIFrame::HasAbsolutelyPositionedChildren() const {
  return IsAbsoluteContainer() &&
         GetAbsoluteContainingBlock()->HasAbsoluteFrames();
}

nsAbsoluteContainingBlock* nsIFrame::GetAbsoluteContainingBlock() const {
  NS_ASSERTION(IsAbsoluteContainer(),
               "The frame is not marked as an abspos container correctly");
  nsAbsoluteContainingBlock* absCB =
      GetProperty(AbsoluteContainingBlockProperty());
  NS_ASSERTION(absCB,
               "The frame is marked as an abspos container but doesn't have "
               "the property");
  return absCB;
}

void nsIFrame::MarkAsAbsoluteContainingBlock() {
  MOZ_ASSERT(GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
  NS_ASSERTION(!GetProperty(AbsoluteContainingBlockProperty()),
               "Already has an abs-pos containing block property?");
  NS_ASSERTION(!HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
               "Already has NS_FRAME_HAS_ABSPOS_CHILDREN state bit?");
  AddStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
  SetProperty(AbsoluteContainingBlockProperty(),
              new nsAbsoluteContainingBlock(GetAbsoluteListID()));
}

void nsIFrame::MarkAsNotAbsoluteContainingBlock() {
  NS_ASSERTION(!HasAbsolutelyPositionedChildren(), "Think of the children!");
  NS_ASSERTION(GetProperty(AbsoluteContainingBlockProperty()),
               "Should have an abs-pos containing block property");
  NS_ASSERTION(HasAnyStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN),
               "Should have NS_FRAME_HAS_ABSPOS_CHILDREN state bit");
  MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
  RemoveStateBits(NS_FRAME_HAS_ABSPOS_CHILDREN);
  DeleteProperty(AbsoluteContainingBlockProperty());
}

bool nsIFrame::CheckAndClearPaintedState() {
  bool result = (GetStateBits() & NS_FRAME_PAINTED_THEBES);
  RemoveStateBits(NS_FRAME_PAINTED_THEBES);

  nsIFrame::ChildListIterator lists(this);
  for (; !lists.IsDone(); lists.Next()) {
    nsFrameList::Enumerator childFrames(lists.CurrentList());
    for (; !childFrames.AtEnd(); childFrames.Next()) {
      nsIFrame* child = childFrames.get();
      if (child->CheckAndClearPaintedState()) {
        result = true;
      }
    }
  }
  return result;
}

bool nsIFrame::CheckAndClearDisplayListState() {
  bool result = BuiltDisplayList();
  SetBuiltDisplayList(false);

  nsIFrame::ChildListIterator lists(this);
  for (; !lists.IsDone(); lists.Next()) {
    nsFrameList::Enumerator childFrames(lists.CurrentList());
    for (; !childFrames.AtEnd(); childFrames.Next()) {
      nsIFrame* child = childFrames.get();
      if (child->CheckAndClearDisplayListState()) {
        result = true;
      }
    }
  }
  return result;
}

bool nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const {
  if (!StyleVisibility()->IsVisible()) {
    return false;
  }

  const nsIFrame* frame = this;
  while (frame) {
    nsView* view = frame->GetView();
    if (view && view->GetVisibility() == nsViewVisibility_kHide) return false;

    nsIFrame* parent = frame->GetParent();
    nsDeckFrame* deck = do_QueryFrame(parent);
    if (deck) {
      if (deck->GetSelectedBox() != frame) return false;
    }

    if (parent) {
      frame = parent;
    } else {
      parent = nsLayoutUtils::GetCrossDocParentFrame(frame);
      if (!parent) break;

      if ((aFlags & nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) == 0 &&
          parent->PresContext()->IsChrome() &&
          !frame->PresContext()->IsChrome()) {
        break;
      }

      if (!parent->StyleVisibility()->IsVisible()) return false;

      frame = parent;
    }
  }

  return true;
}

void nsIFrame::FindCloserFrameForSelection(
    const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) {
  if (nsLayoutUtils::PointIsCloserToRect(aPoint, mRect,
                                         aCurrentBestFrame->mXDistance,
                                         aCurrentBestFrame->mYDistance)) {
    aCurrentBestFrame->mFrame = this;
  }
}

void nsIFrame::ContentStatesChanged(mozilla::EventStates aStates) {}

AutoWeakFrame::AutoWeakFrame(const WeakFrame& aOther)
    : mPrev(nullptr), mFrame(nullptr) {
  Init(aOther.GetFrame());
}

void AutoWeakFrame::Init(nsIFrame* aFrame) {
  Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
  mFrame = aFrame;
  if (mFrame) {
    nsIPresShell* shell = mFrame->PresContext()->GetPresShell();
    NS_WARNING_ASSERTION(shell, "Null PresShell in AutoWeakFrame!");
    if (shell) {
      shell->AddAutoWeakFrame(this);
    } else {
      mFrame = nullptr;
    }
  }
}

void WeakFrame::Init(nsIFrame* aFrame) {
  Clear(mFrame ? mFrame->PresContext()->GetPresShell() : nullptr);
  mFrame = aFrame;
  if (mFrame) {
    nsIPresShell* shell = mFrame->PresContext()->GetPresShell();
    MOZ_ASSERT(shell, "Null PresShell in WeakFrame!");
    if (shell) {
      shell->AddWeakFrame(this);
    } else {
      mFrame = nullptr;
    }
  }
}

nsIFrame* NS_NewEmptyFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) {
  return new (aPresShell) nsFrame(aStyle);
}

nsFrame::nsFrame(ComputedStyle* aStyle, ClassID aID) : nsBox(aID) {
  MOZ_COUNT_CTOR(nsFrame);

  mComputedStyle = aStyle;
  mWritingMode = WritingMode(mComputedStyle);
}

nsFrame::~nsFrame() {
  MOZ_COUNT_DTOR(nsFrame);

  MOZ_ASSERT(GetVisibility() != Visibility::APPROXIMATELY_VISIBLE,
             "Visible nsFrame is being destroyed");
}

NS_IMPL_FRAMEARENA_HELPERS(nsFrame)

// Dummy operator delete.  Will never be called, but must be defined
// to satisfy some C++ ABIs.
void nsFrame::operator delete(void*, size_t) {
  MOZ_CRASH("nsFrame::operator delete should never be called");
}

NS_QUERYFRAME_HEAD(nsFrame)
  NS_QUERYFRAME_ENTRY(nsIFrame)
NS_QUERYFRAME_TAIL_INHERITANCE_ROOT

/////////////////////////////////////////////////////////////////////////////
// nsIFrame

static bool IsFontSizeInflationContainer(nsIFrame* aFrame,
                                         const nsStyleDisplay* aStyleDisplay) {
  /*
   * Font size inflation is built around the idea that we're inflating
   * the fonts for a pan-and-zoom UI so that when the user scales up a
   * block or other container to fill the width of the device, the fonts
   * will be readable.  To do this, we need to pick what counts as a
   * container.
   *
   * From a code perspective, the only hard requirement is that frames
   * that are line participants
   * (nsIFrame::IsFrameOfType(nsIFrame::eLineParticipant)) are never
   * containers, since line layout assumes that the inflation is
   * consistent within a line.
   *
   * This is not an imposition, since we obviously want a bunch of text
   * (possibly with inline elements) flowing within a block to count the
   * block (or higher) as its container.
   *
   * We also want form controls, including the text in the anonymous
   * content inside of them, to match each other and the text next to
   * them, so they and their anonymous content should also not be a
   * container.
   *
   * However, because we can't reliably compute sizes across XUL during
   * reflow, any XUL frame with a XUL parent is always a container.
   *
   * There are contexts where it would be nice if some blocks didn't
   * count as a container, so that, for example, an indented quotation
   * didn't end up with a smaller font size.  However, it's hard to
   * distinguish these situations where we really do want the indented
   * thing to count as a container, so we don't try, and blocks are
   * always containers.
   */

  // The root frame should always be an inflation container.
  if (!aFrame->GetParent()) {
    return true;
  }

  nsIContent* content = aFrame->GetContent();
  LayoutFrameType frameType = aFrame->Type();
  bool isInline =
      (aFrame->GetDisplay() == StyleDisplay::Inline ||
       RubyUtils::IsRubyBox(frameType) ||
       (aFrame->IsFloating() && frameType == LayoutFrameType::Letter) ||
       // Given multiple frames for the same node, only the
       // outer one should be considered a container.
       // (Important, e.g., for nsSelectsAreaFrame.)
       (aFrame->GetParent()->GetContent() == content) ||
       (content &&
        (content->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup,
                                      nsGkAtoms::select) ||
         content->IsInNativeAnonymousSubtree()))) &&
      !(aFrame->IsXULBoxFrame() && aFrame->GetParent()->IsXULBoxFrame());
  NS_ASSERTION(!aFrame->IsFrameOfType(nsIFrame::eLineParticipant) || isInline ||
                   // br frames and mathml frames report being line
                   // participants even when their position or display is
                   // set
                   aFrame->IsBrFrame() ||
                   aFrame->IsFrameOfType(nsIFrame::eMathML),
               "line participants must not be containers");
  NS_ASSERTION(!aFrame->IsBulletFrame() || isInline,
               "bullets should not be containers");
  return !isInline;
}

void nsFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
                   nsIFrame* aPrevInFlow) {
  MOZ_ASSERT(nsQueryFrame::FrameIID(mClass) == GetFrameId());
  MOZ_ASSERT(!mContent, "Double-initing a frame?");
  NS_ASSERTION(IsFrameOfType(eDEBUGAllFrames) && !IsFrameOfType(eDEBUGNoFrames),
               "IsFrameOfType implementation that doesn't call base class");

  mContent = aContent;
  mParent = aParent;
  MOZ_DIAGNOSTIC_ASSERT(!mParent || PresShell() == mParent->PresShell());

  if (aPrevInFlow) {
    mWritingMode = aPrevInFlow->GetWritingMode();

    // Make sure the general flags bits are the same
    nsFrameState state = aPrevInFlow->GetStateBits();

    // Make bits that are currently off (see constructor) the same:
    AddStateBits(state &
                 (NS_FRAME_INDEPENDENT_SELECTION | NS_FRAME_PART_OF_IBSPLIT |
                  NS_FRAME_MAY_BE_TRANSFORMED |
                  NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN));
  } else {
    PresContext()->ConstructedFrame();
  }
  if (GetParent()) {
    nsFrameState state = GetParent()->GetStateBits();

    // Make bits that are currently off (see constructor) the same:
    AddStateBits(state & (NS_FRAME_INDEPENDENT_SELECTION |
                          NS_FRAME_GENERATED_CONTENT | NS_FRAME_IS_SVG_TEXT |
                          NS_FRAME_IN_POPUP | NS_FRAME_IS_NONDISPLAY));

    if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) {
      // Assume all frames in popups are visible.
      IncApproximateVisibleCount();
    }
  }
  if (aPrevInFlow) {
    mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation();
    mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation();
  } else if (mContent) {
    EffectSet* effectSet = EffectSet::GetEffectSet(this);
    if (effectSet) {
      mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation();
      mMayHaveTransformAnimation = effectSet->MayHaveTransformAnimation();
    }
  }

  const nsStyleDisplay* disp = StyleDisplay();
  if (disp->HasTransform(this) ||
      (IsFrameOfType(eSupportsCSSTransforms) &&
       nsLayoutUtils::HasAnimationOfProperty(this, eCSSProperty_transform))) {
    // The frame gets reconstructed if we toggle the -moz-transform
    // property, so we can set this bit here and then ignore it.
    AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
  }
  if (disp->mPosition == NS_STYLE_POSITION_STICKY && !aPrevInFlow &&
      !(mState & NS_FRAME_IS_NONDISPLAY)) {
    // Note that we only add first continuations, but we really only
    // want to add first continuation-or-ib-split-siblings.  But since we
    // don't yet know if we're a later part of a block-in-inline split,
    // we'll just add later members of a block-in-inline split here, and
    // then StickyScrollContainer will remove them later.
    StickyScrollContainer* ssc =
        StickyScrollContainer::GetStickyScrollContainerForFrame(this);
    if (ssc) {
      ssc->AddFrame(this);
    }
  }

  if (disp->IsContainLayout() && disp->IsContainSize() &&
      // All frames that support contain:layout also support contain:size.
      IsFrameOfType(eSupportsContainLayoutAndPaint)) {
    // Frames that have contain:layout+size can be reflow roots.
    // Changes to `contain` force frame reconstructions, so this bit can be set
    // for the whole lifetime of this frame.
    AddStateBits(NS_FRAME_REFLOW_ROOT);
  }

  if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) ||
      !GetParent()
#ifdef DEBUG
      // We have assertions that check inflation invariants even when
      // font size inflation is not enabled.
      || true
#endif
  ) {
    if (IsFontSizeInflationContainer(this, disp)) {
      AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER);
      if (!GetParent() ||
          // I'd use NS_FRAME_OUT_OF_FLOW, but it's not set yet.
          disp->IsFloating(this) || disp->IsAbsolutelyPositioned(this)) {
        AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
      }
    }
    NS_ASSERTION(
        GetParent() || (GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER),
        "root frame should always be a container");
  }

  if (PresShell()->AssumeAllFramesVisible() && TrackingVisibility()) {
    IncApproximateVisibleCount();
  }

  DidSetComputedStyle(nullptr);

  if (::IsXULBoxWrapped(this)) ::InitBoxMetrics(this, false);

  // For a newly created frame, we need to update this frame's visibility state.
  // Usually we update the state when the frame is restyled and has a
  // VisibilityChange change hint but we don't generate any change hints for
  // newly created frames.
  // Note: We don't need to do this for placeholders since placeholders have
  // different styles so that the styles don't have visibility:hidden even if
  // the parent has visibility:hidden style.
  if (!IsPlaceholderFrame()) {
    UpdateVisibleDescendantsState();
  }
}

void nsFrame::DestroyFrom(nsIFrame* aDestructRoot,
                          PostDestroyData& aPostDestroyData) {
  NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
               "destroy called on frame while scripts not blocked");
  NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(),
               "Frames should be removed before destruction.");
  NS_ASSERTION(aDestructRoot, "Must specify destruct root");
  MOZ_ASSERT(!HasAbsolutelyPositionedChildren());
  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT),
             "NS_FRAME_PART_OF_IBSPLIT set on non-nsContainerFrame?");

  SVGObserverUtils::InvalidateDirectRenderingObservers(this);

  if (StyleDisplay()->mPosition == NS_STYLE_POSITION_STICKY) {
    StickyScrollContainer* ssc =
        StickyScrollContainer::GetStickyScrollContainerForFrame(this);
    if (ssc) {
      ssc->RemoveFrame(this);
    }
  }

  nsPresContext* presContext = PresContext();
  nsIPresShell* shell = presContext->GetPresShell();
  if (mState & NS_FRAME_OUT_OF_FLOW) {
    nsPlaceholderFrame* placeholder = GetPlaceholderFrame();
    NS_ASSERTION(
        !placeholder || (aDestructRoot != this),
        "Don't call Destroy() on OOFs, call Destroy() on the placeholder.");
    NS_ASSERTION(!placeholder || nsLayoutUtils::IsProperAncestorFrame(
                                     aDestructRoot, placeholder),
                 "Placeholder relationship should have been torn down already; "
                 "this might mean we have a stray placeholder in the tree.");
    if (placeholder) {
      placeholder->SetOutOfFlowFrame(nullptr);
    }
  }

  if (IsPrimaryFrame()) {
    // This needs to happen before we clear our Properties() table.
    ActiveLayerTracker::TransferActivityToContent(this, mContent);
  }

  if (HasCSSAnimations() || HasCSSTransitions() ||
      EffectSet::GetEffectSet(this)) {
    // If no new frame for this element is created by the end of the
    // restyling process, stop animations and transitions for this frame
    RestyleManager::AnimationsWithDestroyedFrame* adf =
        presContext->RestyleManager()->GetAnimationsWithDestroyedFrame();
    // AnimationsWithDestroyedFrame only lives during the restyling process.
    if (adf) {
      adf->Put(mContent, mComputedStyle);
    }
  }

  // Disable visibility tracking. Note that we have to do this before we clear
  // frame properties and lose track of whether we were previously visible.
  // XXX(seth): It'd be ideal to assert that we're already marked nonvisible
  // here, but it's unfortunately tricky to guarantee in the face of things like
  // frame reconstruction induced by style changes.
  DisableVisibilityTracking();

  // Ensure that we're not in the approximately visible list anymore.
  PresContext()->GetPresShell()->RemoveFrameFromApproximatelyVisibleList(this);

  shell->NotifyDestroyingFrame(this);

  if (mState & NS_FRAME_EXTERNAL_REFERENCE) {
    shell->ClearFrameRefs(this);
  }

  nsView* view = GetView();
  if (view) {
    view->SetFrame(nullptr);
    view->Destroy();
  }

  // Make sure that our deleted frame can't be returned from GetPrimaryFrame()
  if (IsPrimaryFrame()) {
    mContent->SetPrimaryFrame(nullptr);

    // Pass the root of a generated content subtree (e.g. ::after/::before) to
    // aPostDestroyData to unbind it after frame destruction is done.
    if (HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) &&
        mContent->IsRootOfNativeAnonymousSubtree()) {
      aPostDestroyData.AddAnonymousContent(mContent.forget());
    }
  }

  // Delete all properties attached to the frame, to ensure any property
  // destructors that need the frame pointer are handled properly.
  DeleteAllProperties();

  // Must retrieve the object ID before calling destructors, so the
  // vtable is still valid.
  //
  // Note to future tweakers: having the method that returns the
  // object size call the destructor will not avoid an indirect call;
  // the compiler cannot devirtualize the call to the destructor even
  // if it's from a method defined in the same class.

  nsQueryFrame::FrameIID id = GetFrameId();
  this->~nsFrame();

#ifdef DEBUG
  {
    nsIFrame* rootFrame = shell->GetRootFrame();
    MOZ_ASSERT(rootFrame);
    if (this != rootFrame) {
      const RetainedDisplayListData* data =
          GetRetainedDisplayListData(rootFrame);

      const bool inModifiedList =
          data && (data->GetFlags(this) &
                   RetainedDisplayListData::FrameFlags::Modified);

      MOZ_ASSERT(!inModifiedList,
                 "A dtor added this frame to modified frames list!");
    }
  }
#endif

  // Now that we're totally cleaned out, we need to add ourselves to
  // the presshell's recycler.
  shell->FreeFrame(id, this);
}

nsresult nsFrame::GetOffsets(int32_t& aStart, int32_t& aEnd) const {
  aStart = 0;
  aEnd = 0;
  return NS_OK;
}

static void CompareLayers(
    const nsStyleImageLayers* aFirstLayers,
    const nsStyleImageLayers* aSecondLayers,
    const std::function<void(imgRequestProxy* aReq)>& aCallback) {
  NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, (*aFirstLayers)) {
    const nsStyleImage& image = aFirstLayers->mLayers[i].mImage;
    if (image.GetType() != eStyleImageType_Image || !image.IsResolved()) {
      continue;
    }

    // aCallback is called when the style image in aFirstLayers is thought to
    // be different with the corresponded one in aSecondLayers
    if (!aSecondLayers || i >= aSecondLayers->mImageCount ||
        (!aSecondLayers->mLayers[i].mImage.IsResolved() ||
         !image.ImageDataEquals(aSecondLayers->mLayers[i].mImage))) {
      if (imgRequestProxy* req = image.GetImageData()) {
        aCallback(req);
      }
    }
  }
}

static void AddAndRemoveImageAssociations(
    nsFrame* aFrame, const nsStyleImageLayers* aOldLayers,
    const nsStyleImageLayers* aNewLayers) {
  ImageLoader* imageLoader =
      aFrame->PresContext()->Document()->StyleImageLoader();

  // If the old context had a background-image image, or mask-image image,
  // and new context does not have the same image, clear the image load
  // notifier (which keeps the image loading, if it still is) for the frame.
  // We want to do this conservatively because some frames paint their
  // backgrounds from some other frame's style data, and we don't want
  // to clear those notifiers unless we have to.  (They'll be reset
  // when we paint, although we could miss a notification in that
  // interval.)
  if (aOldLayers && aFrame->HasImageRequest()) {
    CompareLayers(aOldLayers, aNewLayers,
                  [&imageLoader, aFrame](imgRequestProxy* aReq) {
                    imageLoader->DisassociateRequestFromFrame(aReq, aFrame);
                  });
  }

  CompareLayers(aNewLayers, aOldLayers,
                [&imageLoader, aFrame](imgRequestProxy* aReq) {
                  imageLoader->AssociateRequestToFrame(aReq, aFrame, 0);
                });
}

void nsIFrame::AddDisplayItem(nsDisplayItem* aItem) {
  DisplayItemArray* items = GetProperty(DisplayItems());
  if (!items) {
    items = new DisplayItemArray();
    AddProperty(DisplayItems(), items);
  }
  MOZ_DIAGNOSTIC_ASSERT(!items->Contains(aItem));
  items->AppendElement(aItem);
}

bool nsIFrame::RemoveDisplayItem(nsDisplayItem* aItem) {
  DisplayItemArray* items = GetProperty(DisplayItems());
  if (!items) {
    return false;
  }
  bool result = items->RemoveElement(aItem);
  if (items->IsEmpty()) {
    DeleteProperty(DisplayItems());
  }
  return result;
}

bool nsIFrame::HasDisplayItems() {
  DisplayItemArray* items = GetProperty(DisplayItems());
  return items != nullptr;
}

bool nsIFrame::HasDisplayItem(nsDisplayItem* aItem) {
  DisplayItemArray* items = GetProperty(DisplayItems());
  if (!items) {
    return false;
  }
  return items->Contains(aItem);
}

void nsIFrame::RemoveDisplayItemDataForDeletion() {
  FrameLayerBuilder::RemoveFrameFromLayerManager(this, DisplayItemData());
  DisplayItemData().Clear();

  DisplayItemArray* items = RemoveProperty(DisplayItems());
  if (items) {
    for (nsDisplayItem* i : *items) {
      if (i->GetDependentFrame() == this && !i->HasDeletedFrame()) {
        i->Frame()->MarkNeedsDisplayItemRebuild();
      }
      i->RemoveFrame(this);
    }
    delete items;
  }

  if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
    // Retained display lists are disabled, no need to update
    // RetainedDisplayListData.
    return;
  }

  const bool updateData = IsFrameModified() || HasOverrideDirtyRegion() ||
                          MayHaveWillChangeBudget();

  if (!updateData) {
    // No RetainedDisplayListData to update.
    return;
  }

  nsIFrame* rootFrame = PresShell()->GetRootFrame();
  MOZ_ASSERT(rootFrame);

  RetainedDisplayListData* data = GetOrSetRetainedDisplayListData(rootFrame);

  if (MayHaveWillChangeBudget()) {
    // Keep the frame in list, so it can be removed from the will-change budget.
    data->Flags(this) = RetainedDisplayListData::FrameFlags::HadWillChange;
    return;
  }

  if (IsFrameModified() || HasOverrideDirtyRegion()) {
    // Remove deleted frames from RetainedDisplayListData.
    DebugOnly<bool> removed = data->Remove(this);
    MOZ_ASSERT(removed,
               "Frame had flags set, but it was not found in DisplayListData!");
  }
}

void nsIFrame::MarkNeedsDisplayItemRebuild() {
  if (!nsLayoutUtils::AreRetainedDisplayListsEnabled() || IsFrameModified() ||
      HasAnyStateBits(NS_FRAME_IN_POPUP)) {
    // Skip frames that are already marked modified.
    return;
  }

  if (Type() == LayoutFrameType::Placeholder) {
    nsIFrame* oof = static_cast<nsPlaceholderFrame*>(this)->GetOutOfFlowFrame();
    if (oof) {
      oof->MarkNeedsDisplayItemRebuild();
    }
    // Do not mark placeholder frames modified.
    return;
  }

  if (!nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(this)) {
    return;
  }

  nsIFrame* rootFrame = PresShell()->GetRootFrame();
  MOZ_ASSERT(rootFrame);

  if (rootFrame->IsFrameModified()) {
    return;
  }

  RetainedDisplayListData* data = GetOrSetRetainedDisplayListData(rootFrame);

  if (data->ModifiedFramesCount() > gfxPrefs::LayoutRebuildFrameLimit()) {
    // If the modified frames count is above the rebuild limit, mark the root
    // frame modified, and stop marking additional frames modified.
    data->AddModifiedFrame(rootFrame);
    rootFrame->SetFrameIsModified(true);
    return;
  }

  data->AddModifiedFrame(this);
  SetFrameIsModified(true);

  MOZ_ASSERT(
      PresContext()->LayoutPhaseCount(eLayoutPhase_DisplayListBuilding) == 0);

  // Hopefully this is cheap, but we could use a frame state bit to note
  // the presence of dependencies to speed it up.
  DisplayItemArray* items = GetProperty(DisplayItems());
  if (items) {
    for (nsDisplayItem* i : *items) {
      if (i->HasDeletedFrame() || i->Frame() == this) {
        // Ignore the items with deleted frames, and the items with |this| as
        // the primary frame.
        continue;
      }

      if (i->GetDependentFrame() == this) {
        // For items with |this| as a dependent frame, mark the primary frame
        // for rebuild.
        i->Frame()->MarkNeedsDisplayItemRebuild();
      }
    }
  }
}

// Subclass hook for style post processing
/* virtual */ void nsFrame::DidSetComputedStyle(
    ComputedStyle* aOldComputedStyle) {
  if (nsSVGUtils::IsInSVGTextSubtree(this)) {
    SVGTextFrame* svgTextFrame = static_cast<SVGTextFrame*>(
        nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText));
    nsIFrame* anonBlock = svgTextFrame->PrincipalChildList().FirstChild();
    // Just as in SVGTextFrame::DidSetComputedStyle, we need to ensure that
    // any non-display SVGTextFrames get reflowed when a child text frame
    // gets new style.
    //
    // Note that we must check NS_FRAME_FIRST_REFLOW on our SVGTextFrame's
    // anonymous block frame rather than our self, since NS_FRAME_FIRST_REFLOW
    // may be set on us if we're a new frame that has been inserted after the
    // document's first reflow. (In which case this DidSetComputedStyle call may
    // be happening under frame construction under a Reflow() call.)
    if (anonBlock && !(anonBlock->GetStateBits() & NS_FRAME_FIRST_REFLOW) &&
        (svgTextFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) &&
        !(svgTextFrame->GetStateBits() & NS_STATE_SVG_TEXT_IN_REFLOW)) {
      svgTextFrame->ScheduleReflowSVGNonDisplayText(nsIPresShell::eStyleChange);
    }
  }

  const nsStyleImageLayers* oldLayers =
      aOldComputedStyle ? &aOldComputedStyle->StyleBackground()->mImage
                        : nullptr;
  const nsStyleImageLayers* newLayers = &StyleBackground()->mImage;
  AddAndRemoveImageAssociations(this, oldLayers, newLayers);

  oldLayers =
      aOldComputedStyle ? &aOldComputedStyle->StyleSVGReset()->mMask : nullptr;
  newLayers = &StyleSVGReset()->mMask;
  AddAndRemoveImageAssociations(this, oldLayers, newLayers);

  if (aOldComputedStyle) {
    // If we detect a change on margin, padding or border, we store the old
    // values on the frame itself between now and reflow, so if someone
    // calls GetUsed(Margin|Border|Padding)() before the next reflow, we
    // can give an accurate answer.
    // We don't want to set the property if one already exists.
    nsMargin oldValue(0, 0, 0, 0);
    nsMargin newValue(0, 0, 0, 0);
    const nsStyleMargin* oldMargin = aOldComputedStyle->PeekStyleMargin();
    if (oldMargin && oldMargin->GetMargin(oldValue)) {
      if ((!StyleMargin()->GetMargin(newValue) || oldValue != newValue) &&
          !HasProperty(UsedMarginProperty())) {
        AddProperty(UsedMarginProperty(), new nsMargin(oldValue));
      }
    }

    const nsStylePadding* oldPadding = aOldComputedStyle->PeekStylePadding();
    if (oldPadding && oldPadding->GetPadding(oldValue)) {
      if ((!StylePadding()->GetPadding(newValue) || oldValue != newValue) &&
          !HasProperty(UsedPaddingProperty())) {
        AddProperty(UsedPaddingProperty(), new nsMargin(oldValue));
      }
    }

    const nsStyleBorder* oldBorder = aOldComputedStyle->PeekStyleBorder();
    if (oldBorder) {
      oldValue = oldBorder->GetComputedBorder();
      newValue = StyleBorder()->GetComputedBorder();
      if (oldValue != newValue && !HasProperty(UsedBorderProperty())) {
        AddProperty(UsedBorderProperty(), new nsMargin(oldValue));
      }
    }
  }

  ImageLoader* imageLoader = PresContext()->Document()->StyleImageLoader();
  imgIRequest* oldBorderImage =
      aOldComputedStyle
          ? aOldComputedStyle->StyleBorder()->GetBorderImageRequest()
          : nullptr;
  imgIRequest* newBorderImage = StyleBorder()->GetBorderImageRequest();
  // FIXME (Bug 759996): The following is no longer true.
  // For border-images, we can't be as conservative (we need to set the
  // new loaders if there has been any change) since the CalcDifference
  // call depended on the result of GetComputedBorder() and that result
  // depends on whether the image has loaded, start the image load now
  // so that we'll get notified when it completes loading and can do a
  // restyle.  Otherwise, the image might finish loading from the
  // network before we start listening to its notifications, and then
  // we'll never know that it's finished loading.  Likewise, we want to
  // do this for freshly-created frames to prevent a similar race if the
  // image loads between reflow (which can depend on whether the image
  // is loaded) and paint.  We also don't really care about any callers who try
  // to paint borders with a different style, because they won't have the
  // correct size for the border either.
  if (oldBorderImage != newBorderImage) {
    // stop and restart the image loading/notification
    if (oldBorderImage && HasImageRequest()) {
      imageLoader->DisassociateRequestFromFrame(oldBorderImage, this);
    }
    if (newBorderImage) {
      imageLoader->AssociateRequestToFrame(newBorderImage, this, 0);
    }
  }

  imgIRequest* oldShapeImage =
      aOldComputedStyle
          ? aOldComputedStyle->StyleDisplay()->mShapeOutside.GetShapeImageData()
          : nullptr;
  imgIRequest* newShapeImage =
      StyleDisplay()->mShapeOutside.GetShapeImageData();

  if (oldShapeImage != newShapeImage) {
    if (oldShapeImage && HasImageRequest()) {
      imageLoader->DisassociateRequestFromFrame(oldShapeImage, this);
    }
    if (newShapeImage) {
      imageLoader->AssociateRequestToFrame(
          newShapeImage, this, ImageLoader::REQUEST_REQUIRES_REFLOW);
    }
  }

  // SVGObserverUtils::GetEffectProperties() asserts that we only invoke it with
  // the first continuation so we need to check that in advance. Continuing text
  // frame doesn't initialize its continuation pointer before reaching here for
  // the first time, so we have to exclude text frames. This doesn't affect
  // correctness because text nodes themselves shouldn't have effects applied.
  if (!IsTextFrame() && !GetPrevContinuation()) {
    // Kick off loading of external SVG resources referenced from properties if
    // any. This currently includes filter, clip-path, and mask.
    SVGObserverUtils::InitiateResourceDocLoads(this);
  }

  // If the page contains markup that overrides text direction, and
  // does not contain any characters that would activate the Unicode
  // bidi algorithm, we need to call |SetBidiEnabled| on the pres
  // context before reflow starts.  See bug 115921.
  if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
    PresContext()->SetBidiEnabled();
  }

  RemoveStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS | NS_FRAME_SIMPLE_DISPLAYLIST);

  mMayHaveRoundedCorners = true;
}

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
void nsIFrame::AssertNewStyleIsSane(ComputedStyle& aNewStyle) {
  MOZ_DIAGNOSTIC_ASSERT(PresShell() ==
                        aNewStyle.PresContextForFrame()->PresShell());
  MOZ_DIAGNOSTIC_ASSERT(
      aNewStyle.GetPseudo() == mComputedStyle->GetPseudo() ||
      // ::first-line continuations are weird, this should probably be fixed via
      // bug 1465474.
      (mComputedStyle->GetPseudo() == nsCSSPseudoElements::firstLine() &&
       aNewStyle.GetPseudo() == nsCSSAnonBoxes::mozLineFrame()) ||
      // ::first-letter continuations are broken, in particular floating ones,
      // see bug 1490281. The construction code tries to fix this up after the
      // fact, then restyling undoes it...
      (mComputedStyle->GetPseudo() == nsCSSAnonBoxes::mozText() &&
       aNewStyle.GetPseudo() == nsCSSAnonBoxes::firstLetterContinuation()) ||
      (mComputedStyle->GetPseudo() ==
           nsCSSAnonBoxes::firstLetterContinuation() &&
       aNewStyle.GetPseudo() == nsCSSAnonBoxes::mozText()));
}
#endif

void nsIFrame::ReparentFrameViewTo(nsViewManager* aViewManager,
                                   nsView* aNewParentView,
                                   nsView* aOldParentView) {
  if (HasView()) {
#ifdef MOZ_XUL
    if (IsMenuPopupFrame()) {
      // This view must be parented by the root view, don't reparent it.
      return;
    }
#endif
    nsView* view = GetView();
    // Verify that the current parent view is what we think it is
    // nsView*  parentView;
    // NS_ASSERTION(parentView == aOldParentView, "unexpected parent view");

    aViewManager->RemoveChild(view);

    // The view will remember the Z-order and other attributes that have been
    // set on it.
    nsView* insertBefore =
        nsLayoutUtils::FindSiblingViewFor(aNewParentView, this);
    aViewManager->InsertChild(aNewParentView, view, insertBefore,
                              insertBefore != nullptr);
  } else if (GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW) {
    nsIFrame::ChildListIterator lists(this);
    for (; !lists.IsDone(); lists.Next()) {
      // Iterate the child frames, and check each child frame to see if it has
      // a view
      nsFrameList::Enumerator childFrames(lists.CurrentList());
      for (; !childFrames.AtEnd(); childFrames.Next()) {
        childFrames.get()->ReparentFrameViewTo(aViewManager, aNewParentView,
                                               aOldParentView);
      }
    }
  }
}

void nsIFrame::SyncFrameViewProperties(nsView* aView) {
  if (!aView) {
    aView = GetView();
    if (!aView) {
      return;
    }
  }

  nsViewManager* vm = aView->GetViewManager();

  // Make sure visibility is correct. This only affects nsSubDocumentFrame.
  if (!SupportsVisibilityHidden()) {
    // See if the view should be hidden or visible
    ComputedStyle* sc = Style();
    vm->SetViewVisibility(aView, sc->StyleVisibility()->IsVisible()
                                     ? nsViewVisibility_kShow
                                     : nsViewVisibility_kHide);
  }

  int32_t zIndex = 0;
  bool autoZIndex = false;

  if (IsAbsPosContainingBlock()) {
    // Make sure z-index is correct
    ComputedStyle* sc = Style();
    const nsStylePosition* position = sc->StylePosition();
    if (position->mZIndex.GetUnit() == eStyleUnit_Integer) {
      zIndex = position->mZIndex.GetIntValue();
    } else if (position->mZIndex.GetUnit() == eStyleUnit_Auto) {
      autoZIndex = true;
    }
  } else {
    autoZIndex = true;
  }

  vm->SetViewZIndex(aView, autoZIndex, zIndex);
}

void nsFrame::CreateView() {
  MOZ_ASSERT(!HasView());

  nsView* parentView = GetParent()->GetClosestView();
  MOZ_ASSERT(parentView, "no parent with view");

  nsViewManager* viewManager = parentView->GetViewManager();
  MOZ_ASSERT(viewManager, "null view manager");

  nsView* view = viewManager->CreateView(GetRect(), parentView);
  SyncFrameViewProperties(view);

  nsView* insertBefore = nsLayoutUtils::FindSiblingViewFor(parentView, this);
  // we insert this view 'above' the insertBefore view, unless insertBefore is
  // null, in which case we want to call with aAbove == false to insert at the
  // beginning in document order
  viewManager->InsertChild(parentView, view, insertBefore,
                           insertBefore != nullptr);

  // REVIEW: Don't create a widget for fixed-pos elements anymore.
  // ComputeRepaintRegionForCopy will calculate the right area to repaint
  // when we scroll.
  // Reparent views on any child frames (or their descendants) to this
  // view. We can just call ReparentFrameViewTo on this frame because
  // we know this frame has no view, so it will crawl the children. Also,
  // we know that any descendants with views must have 'parentView' as their
  // parent view.
  ReparentFrameViewTo(viewManager, view, parentView);

  // Remember our view
  SetView(view);

  NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
               ("nsFrame::CreateView: frame=%p view=%p", this, view));
}

// MSVC fails with link error "one or more multiply defined symbols found",
// gcc fails with "hidden symbol `nsIFrame::kPrincipalList' isn't defined"
// etc if they are not defined.
#ifndef _MSC_VER
// static nsIFrame constants; initialized in the header file.
const nsIFrame::ChildListID nsIFrame::kPrincipalList;
const nsIFrame::ChildListID nsIFrame::kAbsoluteList;
const nsIFrame::ChildListID nsIFrame::kBulletList;
const nsIFrame::ChildListID nsIFrame::kCaptionList;
const nsIFrame::ChildListID nsIFrame::kColGroupList;
const nsIFrame::ChildListID nsIFrame::kExcessOverflowContainersList;
const nsIFrame::ChildListID nsIFrame::kFixedList;
const nsIFrame::ChildListID nsIFrame::kFloatList;
const nsIFrame::ChildListID nsIFrame::kOverflowContainersList;
const nsIFrame::ChildListID nsIFrame::kOverflowList;
const nsIFrame::ChildListID nsIFrame::kOverflowOutOfFlowList;
const nsIFrame::ChildListID nsIFrame::kPopupList;
const nsIFrame::ChildListID nsIFrame::kPushedFloatsList;
const nsIFrame::ChildListID nsIFrame::kSelectPopupList;
const nsIFrame::ChildListID nsIFrame::kNoReflowPrincipalList;
#endif

/* virtual */ nsMargin nsIFrame::GetUsedMargin() const {
  nsMargin margin(0, 0, 0, 0);
  if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
      nsSVGUtils::IsInSVGTextSubtree(this))
    return margin;

  nsMargin* m = GetProperty(UsedMarginProperty());
  if (m) {
    margin = *m;
  } else {
    if (!StyleMargin()->GetMargin(margin)) {
      // If we get here, our caller probably shouldn't be calling us...
      NS_ERROR(
          "Returning bogus 0-sized margin, because this margin "
          "depends on layout & isn't cached!");
    }
  }
  return margin;
}

/* virtual */ nsMargin nsIFrame::GetUsedBorder() const {
  nsMargin border(0, 0, 0, 0);
  if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
      nsSVGUtils::IsInSVGTextSubtree(this))
    return border;

  // Theme methods don't use const-ness.
  nsIFrame* mutable_this = const_cast<nsIFrame*>(this);

  const nsStyleDisplay* disp = StyleDisplay();
  if (mutable_this->IsThemed(disp)) {
    nsPresContext* pc = PresContext();
    LayoutDeviceIntMargin widgetBorder = pc->GetTheme()->GetWidgetBorder(
        pc->DeviceContext(), mutable_this, disp->mAppearance);
    border =
        LayoutDevicePixel::ToAppUnits(widgetBorder, pc->AppUnitsPerDevPixel());
    return border;
  }

  nsMargin* b = GetProperty(UsedBorderProperty());
  if (b) {
    border = *b;
  } else {
    border = StyleBorder()->GetComputedBorder();
  }
  return border;
}

/* virtual */ nsMargin nsIFrame::GetUsedPadding() const {
  nsMargin padding(0, 0, 0, 0);
  if (((mState & NS_FRAME_FIRST_REFLOW) && !(mState & NS_FRAME_IN_REFLOW)) ||
      nsSVGUtils::IsInSVGTextSubtree(this))
    return padding;

  // Theme methods don't use const-ness.
  nsIFrame* mutable_this = const_cast<nsIFrame*>(this);

  const nsStyleDisplay* disp = StyleDisplay();
  if (mutable_this->IsThemed(disp)) {
    nsPresContext* pc = PresContext();
    LayoutDeviceIntMargin widgetPadding;
    if (pc->GetTheme()->GetWidgetPadding(pc->DeviceContext(), mutable_this,
                                         disp->mAppearance, &widgetPadding)) {
      return LayoutDevicePixel::ToAppUnits(widgetPadding,
                                           pc->AppUnitsPerDevPixel());
    }
  }

  nsMargin* p = GetProperty(UsedPaddingProperty());
  if (p) {
    padding = *p;
  } else {
    if (!StylePadding()->GetPadding(padding)) {
      // If we get here, our caller probably shouldn't be calling us...
      NS_ERROR(
          "Returning bogus 0-sized padding, because this padding "
          "depends on layout & isn't cached!");
    }
  }
  return padding;
}

nsIFrame::Sides nsIFrame::GetSkipSides(const ReflowInput* aReflowInput) const {
  if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
                   StyleBoxDecorationBreak::Clone) &&
      !(GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
    return Sides();
  }

  // Convert the logical skip sides to physical sides using the frame's
  // writing mode
  WritingMode writingMode = GetWritingMode();
  LogicalSides logicalSkip = GetLogicalSkipSides(aReflowInput);
  Sides skip;

  if (logicalSkip.BStart()) {
    if (writingMode.IsVertical()) {
      skip |= writingMode.IsVerticalLR() ? eSideBitsLeft : eSideBitsRight;
    } else {
      skip |= eSideBitsTop;
    }
  }

  if (logicalSkip.BEnd()) {
    if (writingMode.IsVertical()) {
      skip |= writingMode.IsVerticalLR() ? eSideBitsRight : eSideBitsLeft;
    } else {
      skip |= eSideBitsBottom;
    }
  }

  if (logicalSkip.IStart()) {
    if (writingMode.IsVertical()) {
      skip |= eSideBitsTop;
    } else {
      skip |= writingMode.IsBidiLTR() ? eSideBitsLeft : eSideBitsRight;
    }
  }

  if (logicalSkip.IEnd()) {
    if (writingMode.IsVertical()) {
      skip |= eSideBitsBottom;
    } else {
      skip |= writingMode.IsBidiLTR() ? eSideBitsRight : eSideBitsLeft;
    }
  }
  return skip;
}

nsRect nsIFrame::GetPaddingRectRelativeToSelf() const {
  nsMargin border(GetUsedBorder());
  border.ApplySkipSides(GetSkipSides());
  nsRect r(0, 0, mRect.width, mRect.height);
  r.Deflate(border);
  return r;
}

nsRect nsIFrame::GetPaddingRect() const {
  return GetPaddingRectRelativeToSelf() + GetPosition();
}

WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM,
                                         nsIFrame* aSubFrame) const {
  MOZ_ASSERT(aSelfWM == GetWritingMode());
  WritingMode writingMode = aSelfWM;

  if (StyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
    nsBidiLevel frameLevel = nsBidiPresUtils::GetFrameBaseLevel(aSubFrame);
    writingMode.SetDirectionFromBidiLevel(frameLevel);
  }

  return writingMode;
}

nsRect nsIFrame::GetMarginRectRelativeToSelf() const {
  nsMargin m = GetUsedMargin();
  m.ApplySkipSides(GetSkipSides());
  nsRect r(0, 0, mRect.width, mRect.height);
  r.Inflate(m);
  return r;
}

bool nsIFrame::IsTransformed(const nsStyleDisplay* aStyleDisplay) const {
  return IsCSSTransformed(aStyleDisplay) || IsSVGTransformed();
}

bool nsIFrame::IsCSSTransformed(const nsStyleDisplay* aStyleDisplay) const {
  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
  return ((mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
          (aStyleDisplay->HasTransform(this) || HasAnimationOfTransform()));
}

bool nsIFrame::HasAnimationOfTransform() const {
  return IsPrimaryFrame() &&
         nsLayoutUtils::HasAnimationOfProperty(this, eCSSProperty_transform) &&
         IsFrameOfType(eSupportsCSSTransforms);
}

bool nsIFrame::ChildrenHavePerspective(
    const nsStyleDisplay* aStyleDisplay) const {
  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
  return aStyleDisplay->HasPerspective(this);
}

bool nsIFrame::HasOpacityInternal(float aThreshold,
                                  const nsStyleDisplay* aStyleDisplay,
                                  const nsStyleEffects* aStyleEffects,
                                  EffectSet* aEffectSet) const {
  MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument");
  if (aStyleEffects->mOpacity < aThreshold ||
      (aStyleDisplay->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY)) {
    return true;
  }

  if (!mMayHaveOpacityAnimation) {
    return false;
  }

  EffectSet* effects = aEffectSet ? aEffectSet : EffectSet::GetEffectSet(this);
  if (!effects) {
    return false;
  }

  return ((IsPrimaryFrame() ||
           nsLayoutUtils::FirstContinuationOrIBSplitSibling(this)
               ->IsPrimaryFrame()) &&
          nsLayoutUtils::HasAnimationOfProperty(effects, eCSSProperty_opacity));
}

bool nsIFrame::IsSVGTransformed(gfx::Matrix* aOwnTransforms,
                                gfx::Matrix* aFromParentTransforms) const {
  return false;
}

bool nsIFrame::Extend3DContext(const nsStyleDisplay* aStyleDisplay,
                               const nsStyleEffects* aStyleEffects,
                               mozilla::EffectSet* aEffectSet) const {
  if (!(mState & NS_FRAME_MAY_BE_TRANSFORMED)) {
    return false;
  }
  const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
  if (disp->mTransformStyle != NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D ||
      !IsFrameOfType(nsIFrame::eSupportsCSSTransforms)) {
    return false;
  }

  // If we're all scroll frame, then all descendants will be clipped, so we
  // can't preserve 3d.
  if (IsScrollFrame()) {
    return false;
  }

  const nsStyleEffects* effects = StyleEffectsWithOptionalParam(aStyleEffects);
  if (HasOpacity(disp, effects, aEffectSet)) {
    return false;
  }

  return !nsFrame::ShouldApplyOverflowClipping(this, disp) &&
         !GetClipPropClipRect(disp, effects, GetSize()) &&
         !nsSVGIntegrationUtils::UsingEffectsForFrame(this);
}

bool nsIFrame::Combines3DTransformWithAncestors(
    const nsStyleDisplay* aStyleDisplay) const {
  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
  nsIFrame* parent = GetClosestFlattenedTreeAncestorPrimaryFrame();
  if (!parent || !parent->Extend3DContext()) {
    return false;
  }
  return IsCSSTransformed(aStyleDisplay) || BackfaceIsHidden(aStyleDisplay);
}

bool nsIFrame::In3DContextAndBackfaceIsHidden() const {
  // While both tests fail most of the time, test BackfaceIsHidden()
  // first since it's likely to fail faster.
  const nsStyleDisplay* disp = StyleDisplay();
  return BackfaceIsHidden(disp) && Combines3DTransformWithAncestors(disp);
}

bool nsIFrame::HasPerspective(const nsStyleDisplay* aStyleDisplay) const {
  MOZ_ASSERT(aStyleDisplay == StyleDisplay());
  if (!IsTransformed(aStyleDisplay)) {
    return false;
  }
  nsIFrame* containingBlock =
      GetContainingBlock(SKIP_SCROLLED_FRAME, aStyleDisplay);
  if (!containingBlock) {
    return false;
  }
  return containingBlock->ChildrenHavePerspective();
}

nsRect nsIFrame::GetContentRectRelativeToSelf() const {
  nsMargin bp(GetUsedBorderAndPadding());
  bp.ApplySkipSides(GetSkipSides());
  nsRect r(0, 0, mRect.width, mRect.height);
  r.Deflate(bp);
  return r;
}

nsRect nsIFrame::GetContentRect() const {
  return GetContentRectRelativeToSelf() + GetPosition();
}

bool nsIFrame::ComputeBorderRadii(const nsStyleCorners& aBorderRadius,
                                  const nsSize& aFrameSize,
                                  const nsSize& aBorderArea, Sides aSkipSides,
                                  nscoord aRadii[8]) {
  // Percentages are relative to whichever side they're on.
  NS_FOR_CSS_HALF_CORNERS(i) {
    const nsStyleCoord c = aBorderRadius.Get(i);
    nscoord axis = HalfCornerIsX(i) ? aFrameSize.width : aFrameSize.height;

    if (c.IsCoordPercentCalcUnit()) {
      aRadii[i] = c.ComputeCoordPercentCalc(axis);
      if (aRadii[i] < 0) {
        // clamp calc()
        aRadii[i] = 0;
      }
    } else {
      MOZ_ASSERT_UNREACHABLE("ComputeBorderRadii: bad unit");
      aRadii[i] = 0;
    }
  }

  if (aSkipSides.Top()) {
    aRadii[eCornerTopLeftX] = 0;
    aRadii[eCornerTopLeftY] = 0;
    aRadii[eCornerTopRightX] = 0;
    aRadii[eCornerTopRightY] = 0;
  }

  if (aSkipSides.Right()) {
    aRadii[eCornerTopRightX] = 0;
    aRadii[eCornerTopRightY] = 0;
    aRadii[eCornerBottomRightX] = 0;
    aRadii[eCornerBottomRightY] = 0;
  }

  if (aSkipSides.Bottom()) {
    aRadii[eCornerBottomRightX] = 0;
    aRadii[eCornerBottomRightY] = 0;
    aRadii[eCornerBottomLeftX] = 0;
    aRadii[eCornerBottomLeftY] = 0;
  }

  if (aSkipSides.Left()) {
    aRadii[eCornerBottomLeftX] = 0;
    aRadii[eCornerBottomLeftY] = 0;
    aRadii[eCornerTopLeftX] = 0;
    aRadii[eCornerTopLeftY] = 0;
  }

  // css3-background specifies this algorithm for reducing
  // corner radii when they are too big.
  bool haveRadius = false;
  double ratio = 1.0f;
  NS_FOR_CSS_SIDES(side) {
    uint32_t hc1 = SideToHalfCorner(side, false, true);
    uint32_t hc2 = SideToHalfCorner(side, true, true);
    nscoord length =
        SideIsVertical(side) ? aBorderArea.height : aBorderArea.width;
    nscoord sum = aRadii[hc1] + aRadii[hc2];
    if (sum) haveRadius = true;

    // avoid floating point division in the normal case
    if (length < sum) ratio = std::min(ratio, double(length) / sum);
  }
  if (ratio < 1.0) {
    NS_FOR_CSS_HALF_CORNERS(corner) { aRadii[corner] *= ratio; }
  }

  return haveRadius;
}

/* static */ void nsIFrame::InsetBorderRadii(nscoord aRadii[8],
                                             const nsMargin& aOffsets) {
  NS_FOR_CSS_SIDES(side) {
    nscoord offset = aOffsets.Side(side);
    uint32_t hc1 = SideToHalfCorner(side, false, false);
    uint32_t hc2 = SideToHalfCorner(side, true, false);
    aRadii[hc1] = std::max(0, aRadii[hc1] - offset);
    aRadii[hc2] = std::max(0, aRadii[hc2] - offset);
  }
}

/* static */ void nsIFrame::OutsetBorderRadii(nscoord aRadii[8],
                                              const nsMargin& aOffsets) {
  auto AdjustOffset = [](const uint32_t aRadius, const nscoord aOffset) {
    // Implement the cubic formula to adjust offset when aOffset > 0 and
    // aRadius / aOffset < 1.
    // https://drafts.csswg.org/css-shapes/#valdef-shape-box-margin-box
    if (aOffset > 0) {
      const double ratio = aRadius / double(aOffset);
      if (ratio < 1.0) {
        return nscoord(aOffset * (1.0 + std::pow(ratio - 1, 3)));
      }
    }
    return aOffset;
  };

  NS_FOR_CSS_SIDES(side) {
    const nscoord offset = aOffsets.Side(side);
    const uint32_t hc1 = SideToHalfCorner(side, false, false);
    const uint32_t hc2 = SideToHalfCorner(side, true, false);
    if (aRadii[hc1] > 0) {
      const nscoord offset1 = AdjustOffset(aRadii[hc1], offset);
      aRadii[hc1] = std::max(0, aRadii[hc1] + offset1);
    }
    if (aRadii[hc2] > 0) {
      const nscoord offset2 = AdjustOffset(aRadii[hc2], offset);
      aRadii[hc2] = std::max(0, aRadii[hc2] + offset2);
    }
  }
}

/* virtual */ bool nsIFrame::GetBorderRadii(const nsSize& aFrameSize,
                                            const nsSize& aBorderArea,
                                            Sides aSkipSides,
                                            nscoord aRadii[8]) const {
  if (!mMayHaveRoundedCorners) {
    memset(aRadii, 0, sizeof(nscoord) * 8);
    return false;
  }

  if (IsThemed()) {
    // When we're themed, the native theme code draws the border and
    // background, and therefore it doesn't make sense to tell other
    // code that's interested in border-radius that we have any radii.
    //
    // In an ideal world, we might have a way for the them to tell us an
    // border radius, but since we don't, we're better off assuming
    // zero.
    NS_FOR_CSS_HALF_CORNERS(corner) { aRadii[corner] = 0; }
    return false;
  }

  const_cast<nsIFrame*>(this)->mMayHaveRoundedCorners =
      ComputeBorderRadii(StyleBorder()->mBorderRadius, aFrameSize, aBorderArea,
                         aSkipSides, aRadii);
  return mMayHaveRoundedCorners;
}

bool nsIFrame::GetBorderRadii(nscoord aRadii[8]) const {
  nsSize sz = GetSize();
  return GetBorderRadii(sz, sz, GetSkipSides(), aRadii);
}

bool nsIFrame::GetMarginBoxBorderRadii(nscoord aRadii[8]) const {
  return GetBoxBorderRadii(aRadii, GetUsedMargin(), true);
}

bool nsIFrame::GetPaddingBoxBorderRadii(nscoord aRadii[8]) const {
  return GetBoxBorderRadii(aRadii, GetUsedBorder(), false);
}

bool nsIFrame::GetContentBoxBorderRadii(nscoord aRadii[8]) const {
  return GetBoxBorderRadii(aRadii, GetUsedBorderAndPadding(), false);
}

bool nsIFrame::GetBoxBorderRadii(nscoord aRadii[8], nsMargin aOffset,
                                 bool aIsOutset) const {
  if (!GetBorderRadii(aRadii)) return false;
  if (aIsOutset) {
    OutsetBorderRadii(aRadii, aOffset);
  } else {
    InsetBorderRadii(aRadii, aOffset);
  }
  NS_FOR_CSS_HALF_CORNERS(corner) {
    if (aRadii[corner]) return true;
  }
  return false;
}

bool nsIFrame::GetShapeBoxBorderRadii(nscoord aRadii[8]) const {
  switch (StyleDisplay()->mShapeOutside.GetReferenceBox()) {
    case StyleGeometryBox::NoBox:
      return false;
    case StyleGeometryBox::ContentBox:
      return GetContentBoxBorderRadii(aRadii);
    case StyleGeometryBox::PaddingBox:
      return GetPaddingBoxBorderRadii(aRadii);
    case StyleGeometryBox::BorderBox:
      return GetBorderRadii(aRadii);
    case StyleGeometryBox::MarginBox:
      return GetMarginBoxBorderRadii(aRadii);
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected box value");
      return false;
  }
}

ComputedStyle* nsFrame::GetAdditionalComputedStyle(int32_t aIndex) const {
  MOZ_ASSERT(aIndex >= 0, "invalid index number");
  return nullptr;
}

void nsFrame::SetAdditionalComputedStyle(int32_t aIndex,
                                         ComputedStyle* aComputedStyle) {
  MOZ_ASSERT(aIndex >= 0, "invalid index number");
}

nscoord nsFrame::GetLogicalBaseline(WritingMode aWritingMode) const {
  NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "frame must not be dirty");
  // Baseline for inverted line content is the top (block-start) margin edge,
  // as the frame is in effect "flipped" for alignment purposes.
  if (aWritingMode.IsLineInverted()) {
    return -GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
  }
  // Otherwise, the bottom margin edge, per CSS2.1's definition of the
  // 'baseline' value of 'vertical-align'.
  return BSize(aWritingMode) +
         GetLogicalUsedMargin(aWritingMode).BEnd(aWritingMode);
}

const nsFrameList& nsFrame::GetChildList(ChildListID aListID) const {
  if (IsAbsoluteContainer() && aListID == GetAbsoluteListID()) {
    return GetAbsoluteContainingBlock()->GetChildList();
  } else {
    return nsFrameList::EmptyList();
  }
}

void nsFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
  if (IsAbsoluteContainer()) {
    nsFrameList absoluteList = GetAbsoluteContainingBlock()->GetChildList();
    absoluteList.AppendIfNonempty(aLists, GetAbsoluteListID());
  }
}

void nsIFrame::GetCrossDocChildLists(nsTArray<ChildList>* aLists) {
  nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(this);
  if (subdocumentFrame) {
    // Descend into the subdocument
    nsIFrame* root = subdocumentFrame->GetSubdocumentRootFrame();
    if (root) {
      aLists->AppendElement(nsIFrame::ChildList(
          nsFrameList(root, nsLayoutUtils::GetLastSibling(root)),
          nsIFrame::kPrincipalList));
    }
  }

  GetChildLists(aLists);
}

Visibility nsIFrame::GetVisibility() const {
  if (!(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED)) {
    return Visibility::UNTRACKED;
  }

  bool isSet = false;
  uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);

  MOZ_ASSERT(isSet,
             "Should have a VisibilityStateProperty value "
             "if NS_FRAME_VISIBILITY_IS_TRACKED is set");

  return visibleCount > 0 ? Visibility::APPROXIMATELY_VISIBLE
                          : Visibility::APPROXIMATELY_NONVISIBLE;
}

void nsIFrame::UpdateVisibilitySynchronously() {
  nsIPresShell* presShell = PresShell();
  if (!presShell) {
    return;
  }

  if (presShell->AssumeAllFramesVisible()) {
    presShell->EnsureFrameInApproximatelyVisibleList(this);
    return;
  }

  bool visible = StyleVisibility()->IsVisible();
  nsIFrame* f = GetParent();
  nsRect rect = GetRectRelativeToSelf();
  nsIFrame* rectFrame = this;
  while (f && visible) {
    nsIScrollableFrame* sf = do_QueryFrame(f);
    if (sf) {
      nsRect transformedRect =
          nsLayoutUtils::TransformFrameRectToAncestor(rectFrame, rect, f);
      if (!sf->IsRectNearlyVisible(transformedRect)) {
        visible = false;
        break;
      }

      // In this code we're trying to synchronously update *approximate*
      // visibility. (In the future we may update precise visibility here as
      // well, which is why the method name does not contain 'approximate'.) The
      // IsRectNearlyVisible() check above tells us that the rect we're checking
      // is approximately visible within the scrollframe, but we still need to
      // ensure that, even if it was scrolled into view, it'd be visible when we
      // consider the rest of the document. To do that, we move transformedRect
      // to be contained in the scrollport as best we can (it might not fit) to
      // pretend that it was scrolled into view.
      rect = transformedRect.MoveInsideAndClamp(sf->GetScrollPortRect());
      rectFrame = f;
    }
    nsIFrame* parent = f->GetParent();
    if (!parent) {
      parent = nsLayoutUtils::GetCrossDocParentFrame(f);
      if (parent && parent->PresContext()->IsChrome()) {
        break;
      }
    }
    f = parent;
  }

  if (visible) {
    presShell->EnsureFrameInApproximatelyVisibleList(this);
  } else {
    presShell->RemoveFrameFromApproximatelyVisibleList(this);
  }
}

void nsIFrame::EnableVisibilityTracking() {
  if (GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED) {
    return;  // Nothing to do.
  }

  MOZ_ASSERT(!HasProperty(VisibilityStateProperty()),
             "Shouldn't have a VisibilityStateProperty value "
             "if NS_FRAME_VISIBILITY_IS_TRACKED is not set");

  // Add the state bit so we know to track visibility for this frame, and
  // initialize the frame property.
  AddStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
  SetProperty(VisibilityStateProperty(), 0);

  nsIPresShell* presShell = PresShell();
  if (!presShell) {
    return;
  }

  // Schedule a visibility update. This method will virtually always be called
  // when layout has changed anyway, so it's very unlikely that any additional
  // visibility updates will be triggered by this, but this way we guarantee
  // that if this frame is currently visible we'll eventually find out.
  presShell->ScheduleApproximateFrameVisibilityUpdateSoon();
}

void nsIFrame::DisableVisibilityTracking() {
  if (!(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED)) {
    return;  // Nothing to do.
  }

  bool isSet = false;
  uint32_t visibleCount = RemoveProperty(VisibilityStateProperty(), &isSet);

  MOZ_ASSERT(isSet,
             "Should have a VisibilityStateProperty value "
             "if NS_FRAME_VISIBILITY_IS_TRACKED is set");

  RemoveStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);

  if (visibleCount == 0) {
    return;  // We were nonvisible.
  }

  // We were visible, so send an OnVisibilityChange() notification.
  OnVisibilityChange(Visibility::APPROXIMATELY_NONVISIBLE);
}

void nsIFrame::DecApproximateVisibleCount(
    const Maybe<OnNonvisible>& aNonvisibleAction
    /* = Nothing() */) {
  MOZ_ASSERT(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED);

  bool isSet = false;
  uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);

  MOZ_ASSERT(isSet,
             "Should have a VisibilityStateProperty value "
             "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
  MOZ_ASSERT(visibleCount > 0,
             "Frame is already nonvisible and we're "
             "decrementing its visible count?");

  visibleCount--;
  SetProperty(VisibilityStateProperty(), visibleCount);
  if (visibleCount > 0) {
    return;
  }

  // We just became nonvisible, so send an OnVisibilityChange() notification.
  OnVisibilityChange(Visibility::APPROXIMATELY_NONVISIBLE, aNonvisibleAction);
}

void nsIFrame::IncApproximateVisibleCount() {
  MOZ_ASSERT(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED);

  bool isSet = false;
  uint32_t visibleCount = GetProperty(VisibilityStateProperty(), &isSet);

  MOZ_ASSERT(isSet,
             "Should have a VisibilityStateProperty value "
             "if NS_FRAME_VISIBILITY_IS_TRACKED is set");

  visibleCount++;
  SetProperty(VisibilityStateProperty(), visibleCount);
  if (visibleCount > 1) {
    return;
  }

  // We just became visible, so send an OnVisibilityChange() notification.
  OnVisibilityChange(Visibility::APPROXIMATELY_VISIBLE);
}

void nsIFrame::OnVisibilityChange(Visibility aNewVisibility,
                                  const Maybe<OnNonvisible>& aNonvisibleAction
                                  /* = Nothing() */) {
  // XXX(seth): In bug 1218990 we'll implement visibility tracking for CSS
  // images here.
}

static nsIFrame* GetActiveSelectionFrame(nsPresContext* aPresContext,
                                         nsIFrame* aFrame) {
  nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
  if (capturingContent) {
    nsIFrame* activeFrame = aPresContext->GetPrimaryFrameFor(capturingContent);
    return activeFrame ? activeFrame : aFrame;
  }

  return aFrame;
}

int16_t nsFrame::DisplaySelection(nsPresContext* aPresContext,
                                  bool isOkToTurnOn) {
  int16_t selType = nsISelectionController::SELECTION_OFF;

  nsCOMPtr<nsISelectionController> selCon;
  nsresult result =
      GetSelectionController(aPresContext, getter_AddRefs(selCon));
  if (NS_SUCCEEDED(result) && selCon) {
    result = selCon->GetDisplaySelection(&selType);
    if (NS_SUCCEEDED(result) &&
        (selType != nsISelectionController::SELECTION_OFF)) {
      // Check whether style allows selection.
      if (!IsSelectable(nullptr)) {
        selType = nsISelectionController::SELECTION_OFF;
        isOkToTurnOn = false;
      }
    }
    if (isOkToTurnOn && (selType == nsISelectionController::SELECTION_OFF)) {
      selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
      selType = nsISelectionController::SELECTION_ON;
    }
  }
  return selType;
}

class nsDisplaySelectionOverlay : public nsDisplayItem {
 public:
  nsDisplaySelectionOverlay(nsDisplayListBuilder* aBuilder, nsFrame* aFrame,
                            int16_t aSelectionValue)
      : nsDisplayItem(aBuilder, aFrame), mSelectionValue(aSelectionValue) {
    MOZ_COUNT_CTOR(nsDisplaySelectionOverlay);
  }
#ifdef NS_BUILD_REFCNT_LOGGING
  virtual ~nsDisplaySelectionOverlay() {
    MOZ_COUNT_DTOR(nsDisplaySelectionOverlay);
  }
#endif

  virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
  bool CreateWebRenderCommands(
      mozilla::wr::DisplayListBuilder& aBuilder,
      mozilla::wr::IpcResourceUpdateQueue& aResources,
      const StackingContextHelper& aSc,
      mozilla::layers::RenderRootStateManager* aManager,
      nsDisplayListBuilder* aDisplayListBuilder) override;
  NS_DISPLAY_DECL_NAME("SelectionOverlay", TYPE_SELECTION_OVERLAY)
 private:
  Color ComputeColor() const;

  static Color ComputeColorFromSelectionStyle(ComputedStyle&);
  static Color ApplyTransparencyIfNecessary(nscolor);

  int16_t mSelectionValue;
};

Color nsDisplaySelectionOverlay::ApplyTransparencyIfNecessary(nscolor aColor) {
  // If it has already alpha, leave it like that.
  if (NS_GET_A(aColor) != 255) {
    return ToDeviceColor(aColor);
  }

  // NOTE(emilio): Blink and WebKit do something slightly different here, and
  // blend the color with white instead, both for overlays and text backgrounds.
  auto color = Color::FromABGR(aColor);
  color.a = 0.5;
  return ToDeviceColor(color);
}

Color nsDisplaySelectionOverlay::ComputeColorFromSelectionStyle(
    ComputedStyle& aStyle) {
  return ApplyTransparencyIfNecessary(
      aStyle.GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor));
}

Color nsDisplaySelectionOverlay::ComputeColor() const {
  LookAndFeel::ColorID colorID;
  if (mSelectionValue == nsISelectionController::SELECTION_ON) {
    if (RefPtr<ComputedStyle> style = mFrame->ComputeSelectionStyle()) {
      return ComputeColorFromSelectionStyle(*style);
    }
    colorID = LookAndFeel::eColorID_TextSelectBackground;
  } else if (mSelectionValue == nsISelectionController::SELECTION_ATTENTION) {
    colorID = LookAndFeel::eColorID_TextSelectBackgroundAttention;
  } else {
    colorID = LookAndFeel::eColorID_TextSelectBackgroundDisabled;
  }

  return ApplyTransparencyIfNecessary(
      LookAndFeel::GetColor(colorID, NS_RGB(255, 255, 255)));
}

void nsDisplaySelectionOverlay::Paint(nsDisplayListBuilder* aBuilder,
                                      gfxContext* aCtx) {
  DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
  ColorPattern color(ComputeColor());

  nsIntRect pxRect = GetPaintRect().ToOutsidePixels(
      mFrame->PresContext()->AppUnitsPerDevPixel());
  Rect rect(pxRect.x, pxRect.y, pxRect.width, pxRect.height);
  MaybeSnapToDevicePixels(rect, aDrawTarget, true);

  aDrawTarget.FillRect(rect, color);
}

bool nsDisplaySelectionOverlay::CreateWebRenderCommands(
    mozilla::wr::DisplayListBuilder& aBuilder,
    mozilla::wr::IpcResourceUpdateQueue& aResources,
    const StackingContextHelper& aSc,
    mozilla::layers::RenderRootStateManager* aManager,
    nsDisplayListBuilder* aDisplayListBuilder) {
  wr::LayoutRect bounds =
      wr::ToRoundedLayoutRect(LayoutDeviceRect::FromAppUnits(
          nsRect(ToReferenceFrame(), Frame()->GetSize()),
          mFrame->PresContext()->AppUnitsPerDevPixel()));
  aBuilder.PushRect(bounds, bounds, !BackfaceIsHidden(),
                    wr::ToColorF(ComputeColor()));
  return true;
}

static Element* FindElementAncestorForMozSelection(nsIContent* aContent) {
  NS_ENSURE_TRUE(aContent, nullptr);
  while (aContent && aContent->IsInNativeAnonymousSubtree()) {
    aContent = aContent->GetBindingParent();
  }
  NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
  while (aContent && !aContent->IsElement()) {
    aContent = aContent->GetParent();
  }
  return aContent ? aContent->AsElement() : nullptr;
}

already_AddRefed<ComputedStyle> nsIFrame::ComputeSelectionStyle() const {
  Element* element = FindElementAncestorForMozSelection(GetContent());
  if (!element) {
    return nullptr;
  }
  RefPtr<ComputedStyle> sc = PresContext()->StyleSet()->ProbePseudoElementStyle(
      *element, CSSPseudoElementType::selection, Style());
  return sc.forget();
}

/********************************************************
 * Refreshes each content's frame
 *********************************************************/

void nsFrame::DisplaySelectionOverlay(nsDisplayListBuilder* aBuilder,
                                      nsDisplayList* aList,
                                      uint16_t aContentType) {
  if (!IsSelected() || !IsVisibleForPainting()) {
    return;
  }

  int16_t displaySelection = PresShell()->GetSelectionFlags();
  if (!(displaySelection & aContentType)) {
    return;
  }

  const nsFrameSelection* frameSelection = GetConstFrameSelection();
  int16_t selectionValue = frameSelection->GetDisplaySelection();

  if (selectionValue <= nsISelectionController::SELECTION_HIDDEN) {
    return;  // selection is hidden or off
  }

  nsIContent* newContent = mContent->GetParent();

  // check to see if we are anonymous content
  int32_t offset = 0;
  if (newContent) {
    // XXXbz there has GOT to be a better way of determining this!
    offset = newContent->ComputeIndexOf(mContent);
  }

  // look up to see what selection(s) are on this frame
  UniquePtr<SelectionDetails> details =
      frameSelection->LookUpSelection(newContent, offset, 1, false);
  if (!details) return;

  bool normal = false;
  for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
    if (sd->mSelectionType == SelectionType::eNormal) {
      normal = true;
    }
  }

  if (!normal && aContentType == nsISelectionDisplay::DISPLAY_IMAGES) {
    // Don't overlay an image if it's not in the primary selection.
    return;
  }

  aList->AppendToTop(MakeDisplayItem<nsDisplaySelectionOverlay>(
      aBuilder, this, selectionValue));
}

void nsFrame::DisplayOutlineUnconditional(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayListSet& aLists) {
  if (!StyleOutline()->ShouldPaintOutline()) {
    return;
  }

  aLists.Outlines()->AppendToTop(
      MakeDisplayItem<nsDisplayOutline>(aBuilder, this));
}

void nsFrame::DisplayOutline(nsDisplayListBuilder* aBuilder,
                             const nsDisplayListSet& aLists) {
  if (!IsVisibleForPainting()) return;

  DisplayOutlineUnconditional(aBuilder, aLists);
}

void nsIFrame::DisplayCaret(nsDisplayListBuilder* aBuilder,
                            nsDisplayList* aList) {
  if (!IsVisibleForPainting()) return;

  aList->AppendToTop(MakeDisplayItem<nsDisplayCaret>(aBuilder, this));
}

nscolor nsIFrame::GetCaretColorAt(int32_t aOffset) {
  return nsLayoutUtils::GetColor(this, &nsStyleUI::mCaretColor);
}

bool nsFrame::DisplayBackgroundUnconditional(nsDisplayListBuilder* aBuilder,
                                             const nsDisplayListSet& aLists,
                                             bool aForceBackground) {
  // Here we don't try to detect background propagation. Frames that might
  // receive a propagated background should just set aForceBackground to
  // true.
  if (aBuilder->IsForEventDelivery() || aForceBackground ||
      !StyleBackground()->IsTransparent(this) ||
      StyleDisplay()->HasAppearance()) {
    return nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
        aBuilder, this, GetRectRelativeToSelf(), aLists.BorderBackground());
  }
  return false;
}

void nsFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder* aBuilder,
                                             const nsDisplayListSet& aLists,
                                             bool aForceBackground) {
  // The visibility check belongs here since child elements have the
  // opportunity to override the visibility property and display even if
  // their parent is hidden.
  if (!IsVisibleForPainting()) {
    return;
  }

  nsCSSShadowArray* shadows = StyleEffects()->mBoxShadow;
  if (shadows && shadows->HasShadowWithInset(false)) {
    aLists.BorderBackground()->AppendToTop(
        MakeDisplayItem<nsDisplayBoxShadowOuter>(aBuilder, this));
  }

  bool bgIsThemed =
      DisplayBackgroundUnconditional(aBuilder, aLists, aForceBackground);

  if (shadows && shadows->HasShadowWithInset(true)) {
    aLists.BorderBackground()->AppendToTop(
        MakeDisplayItem<nsDisplayBoxShadowInner>(aBuilder, this));
  }

  // If there's a themed background, we should not create a border item.
  // It won't be rendered.
  if (!bgIsThemed && StyleBorder()->HasBorder()) {
    aLists.BorderBackground()->AppendToTop(
        MakeDisplayItem<nsDisplayBorder>(aBuilder, this));
  }

  DisplayOutlineUnconditional(aBuilder, aLists);
}

inline static bool IsSVGContentWithCSSClip(const nsIFrame* aFrame) {
  // The CSS spec says that the 'clip' property only applies to absolutely
  // positioned elements, whereas the SVG spec says that it applies to SVG
  // elements regardless of the value of the 'position' property. Here we obey
  // the CSS spec for outer-<svg> (since that's what we generally do), but
  // obey the SVG spec for other SVG elements to which 'clip' applies.
  return (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) &&
         aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::svg,
                                                  nsGkAtoms::foreignObject);
}

Maybe<nsRect> nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp,
                                            const nsStyleEffects* aEffects,
                                            const nsSize& aSize) const {
  if (!(aEffects->mClipFlags & NS_STYLE_CLIP_RECT) ||
      !(aDisp->IsAbsolutelyPositioned(this) || IsSVGContentWithCSSClip(this))) {
    return Nothing();
  }

  nsRect rect = aEffects->mClip;
  if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak ==
                 StyleBoxDecorationBreak::Slice)) {
    // The clip applies to the joined boxes so it's relative the first
    // continuation.
    nscoord y = 0;
    for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) {
      y += f->GetRect().height;
    }
    rect.MoveBy(nsPoint(0, -y));
  }

  if (NS_STYLE_CLIP_RIGHT_AUTO & aEffects->mClipFlags) {
    rect.width = aSize.width - rect.x;
  }
  if (NS_STYLE_CLIP_BOTTOM_AUTO & aEffects->mClipFlags) {
    rect.height = aSize.height - rect.y;
  }
  return Some(rect);
}

/**
 * If the CSS 'overflow' property applies to this frame, and is not
 * handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping
 * for that overflow in aBuilder->ClipState() to clip all containing-block
 * descendants.
 *
 * Return true if clipping was applied.
 */
static bool ApplyOverflowClipping(
    nsDisplayListBuilder* aBuilder, const nsIFrame* aFrame,
    const nsStyleDisplay* aDisp,
    DisplayListClipState::AutoClipMultiple& aClipState) {
  // Only -moz-hidden-unscrollable is handled here (and 'hidden' for table
  // frames, and any non-visible value for blocks in a paginated context).
  // We allow -moz-hidden-unscrollable to apply to any kind of frame. This
  // is required by comboboxes which make their display text (an inline frame)
  // have clipping.
  if (!nsFrame::ShouldApplyOverflowClipping(aFrame, aDisp)) {
    return false;
  }
  nsRect clipRect;
  bool haveRadii = false;
  nscoord radii[8];
  auto* disp = aFrame->StyleDisplay();
  // Only deflate the padding if we clip to the content-box in that axis.
  auto wm = aFrame->GetWritingMode();
  bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
                              : disp->mOverflowClipBoxInline) ==
             StyleOverflowClipBox::ContentBox;
  bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
                              : disp->mOverflowClipBoxBlock) ==
             StyleOverflowClipBox::ContentBox;
  nsMargin bp = aFrame->GetUsedPadding();
  if (!cbH) {
    bp.left = bp.right = nscoord(0);
  }
  if (!cbV) {
    bp.top = bp.bottom = nscoord(0);
  }

  bp += aFrame->GetUsedBorder();
  bp.ApplySkipSides(aFrame->GetSkipSides());
  nsRect rect(nsPoint(0, 0), aFrame->GetSize());
  rect.Deflate(bp);
  clipRect = rect + aBuilder->ToReferenceFrame(aFrame);
  haveRadii = aFrame->GetBoxBorderRadii(radii, bp, false);
  aClipState.ClipContainingBlockDescendantsExtra(clipRect,
                                                 haveRadii ? radii : nullptr);
  return true;
}

#ifdef DEBUG
static void PaintDebugBorder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
                             const nsRect& aDirtyRect, nsPoint aPt) {
  nsRect r(aPt, aFrame->GetSize());
  int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
  Color blueOrRed(aFrame->HasView() ? Color(0.f, 0.f, 1.f, 1.f)
                                    : Color(1.f, 0.f, 0.f, 1.f));
  aDrawTarget->StrokeRect(NSRectToRect(r, appUnitsPerDevPixel),
                          ColorPattern(ToDeviceColor(blueOrRed)));
}

static void PaintEventTargetBorder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
                                   const nsRect& aDirtyRect, nsPoint aPt) {
  nsRect r(aPt, aFrame->GetSize());
  int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
  ColorPattern purple(ToDeviceColor(Color(.5f, 0.f, .5f, 1.f)));
  aDrawTarget->StrokeRect(NSRectToRect(r, appUnitsPerDevPixel), purple);
}

static void DisplayDebugBorders(nsDisplayListBuilder* aBuilder,
                                nsIFrame* aFrame,
                                const nsDisplayListSet& aLists) {
  // Draw a border around the child
  // REVIEW: From nsContainerFrame::PaintChild
  if (nsFrame::GetShowFrameBorders() && !aFrame->GetRect().IsEmpty()) {
    aLists.Outlines()->AppendToTop(MakeDisplayItem<nsDisplayGeneric>(
        aBuilder, aFrame, PaintDebugBorder, "DebugBorder",
        DisplayItemType::TYPE_DEBUG_BORDER));
  }
  // Draw a border around the current event target
  if (nsFrame::GetShowEventTargetFrameBorder() &&
      aFrame->PresShell()->GetDrawEventTargetFrame() == aFrame) {
    aLists.Outlines()->AppendToTop(MakeDisplayItem<nsDisplayGeneric>(
        aBuilder, aFrame, PaintEventTargetBorder, "EventTargetBorder",
        DisplayItemType::TYPE_EVENT_TARGET_BORDER));
  }
}
#endif

static bool IsScrollFrameActive(nsDisplayListBuilder* aBuilder,
                                nsIScrollableFrame* aScrollableFrame) {
  return aScrollableFrame && aScrollableFrame->IsScrollingActive(aBuilder);
}

/**
 * Returns whether a display item that gets created with the builder's current
 * state will have a scrolled clip, i.e. a clip that is scrolled by a scroll
 * frame which does not move the item itself.
 */
static bool BuilderHasScrolledClip(nsDisplayListBuilder* aBuilder) {
  const DisplayItemClipChain* currentClip =
      aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
  if (!currentClip) {
    return false;
  }

  const ActiveScrolledRoot* currentClipASR = currentClip->mASR;
  const ActiveScrolledRoot* currentASR = aBuilder->CurrentActiveScrolledRoot();
  return ActiveScrolledRoot::PickDescendant(currentClipASR, currentASR) !=
         currentASR;
}

class AutoSaveRestoreContainsBlendMode {
  nsDisplayListBuilder& mBuilder;
  bool mSavedContainsBlendMode;

 public:
  explicit AutoSaveRestoreContainsBlendMode(nsDisplayListBuilder& aBuilder)
      : mBuilder(aBuilder),
        mSavedContainsBlendMode(aBuilder.ContainsBlendMode()) {}

  ~AutoSaveRestoreContainsBlendMode() {
    mBuilder.SetContainsBlendMode(mSavedContainsBlendMode);
  }
};

static void CheckForApzAwareEventHandlers(nsDisplayListBuilder* aBuilder,
                                          nsIFrame* aFrame) {
  if (aBuilder->GetAncestorHasApzAwareEventHandler()) {
    return;
  }

  nsIContent* content = aFrame->GetContent();
  if (!content) {
    return;
  }

  if (content->IsNodeApzAware()) {
    aBuilder->SetAncestorHasApzAwareEventHandler(true);
  }
}

/**
 * True if aDescendant participates the context aAncestor participating.
 */
static bool FrameParticipatesIn3DContext(nsIFrame* aAncestor,
                                         nsIFrame* aDescendant) {
  MOZ_ASSERT(aAncestor != aDescendant);
  MOZ_ASSERT(aAncestor->GetContent() != aDescendant->GetContent());
  MOZ_ASSERT(aAncestor->Extend3DContext());

  nsIFrame* ancestor = aAncestor->FirstContinuation();
  MOZ_ASSERT(ancestor->IsPrimaryFrame());

  nsIFrame* frame;
  for (frame = aDescendant->GetClosestFlattenedTreeAncestorPrimaryFrame();
       frame && ancestor != frame;
       frame = frame->GetClosestFlattenedTreeAncestorPrimaryFrame()) {
    if (!frame->Extend3DContext()) {
      return false;
    }
  }

  MOZ_ASSERT(frame == ancestor);
  return true;
}

static bool ItemParticipatesIn3DContext(nsIFrame* aAncestor,
                                        nsDisplayItem* aItem) {
  auto type = aItem->GetType();
  if (type != DisplayItemType::TYPE_TRANSFORM &&
      type != DisplayItemType::TYPE_PERSPECTIVE) {
    return false;
  }
  nsIFrame* transformFrame = aItem->Frame();
  if (aAncestor->GetContent() == transformFrame->GetContent()) {
    return true;
  }
  return FrameParticipatesIn3DContext(aAncestor, transformFrame);
}

static void WrapSeparatorTransform(nsDisplayListBuilder* aBuilder,
                                   nsIFrame* aFrame,
                                   nsDisplayList* aNonParticipants,
                                   nsDisplayList* aParticipants, int aIndex,
                                   nsDisplayItem** aSeparator) {
  if (aNonParticipants->IsEmpty()) {
    return;
  }

  nsDisplayTransform* item = MakeDisplayItem<nsDisplayTransform>(
      aBuilder, aFrame, aNonParticipants, aBuilder->GetVisibleRect(),
      Matrix4x4(), aIndex);
  item->SetNoExtendContext();

  if (*aSeparator == nullptr) {
    *aSeparator = item;
  }

  aParticipants->AppendToTop(item);
}

// Try to compute a clip rect to bound the contents of the mask item
// that will be built for |aMaskedFrame|. If we're not able to compute
// one, return an empty Maybe.
// The returned clip rect, if there is one, is relative to |aMaskedFrame|.
static Maybe<nsRect> ComputeClipForMaskItem(nsDisplayListBuilder* aBuilder,
                                            nsIFrame* aMaskedFrame) {
  const nsStyleSVGReset* svgReset = aMaskedFrame->StyleSVGReset();

  nsSVGUtils::MaskUsage maskUsage;
  nsSVGUtils::DetermineMaskUsage(aMaskedFrame, false, maskUsage);

  nsPoint offsetToUserSpace =
      nsLayoutUtils::ComputeOffsetToUserSpace(aBuilder, aMaskedFrame);
  int32_t devPixelRatio = aMaskedFrame->PresContext()->AppUnitsPerDevPixel();
  gfxPoint devPixelOffsetToUserSpace =
      nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, devPixelRatio);
  gfxMatrix cssToDevMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(aMaskedFrame);

  nsPoint toReferenceFrame;
  aBuilder->FindReferenceFrameFor(aMaskedFrame, &toReferenceFrame);

  Maybe<gfxRect> combinedClip;
  if (maskUsage.shouldApplyBasicShapeOrPath) {
    Rect result = nsCSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip(
        aMaskedFrame, svgReset->mClipPath);
    combinedClip = Some(ThebesRect(result));
  } else if (maskUsage.shouldApplyClipPath) {
    gfxRect result = nsSVGUtils::GetBBox(
        aMaskedFrame,
        nsSVGUtils::eBBoxIncludeClipped | nsSVGUtils::eBBoxIncludeFill |
            nsSVGUtils::eBBoxIncludeMarkers | nsSVGUtils::eBBoxIncludeStroke |
            nsSVGUtils::eDoNotClipToBBoxOfContentInsideClipPath);
    combinedClip = Some(cssToDevMatrix.TransformBounds(result));
  } else {
    // The code for this case is adapted from ComputeMaskGeometry().

    nsRect borderArea(toReferenceFrame, aMaskedFrame->GetSize());
    borderArea -= offsetToUserSpace;

    // Use an infinite dirty rect to pass into nsCSSRendering::
    // GetImageLayerClip() because we don't have an actual dirty rect to
    // pass in. This is fine because the only time GetImageLayerClip() will
    // not intersect the incoming dirty rect with something is in the "NoClip"
    // case, and we handle that specially.
    nsRect dirtyRect(nscoord_MIN / 2, nscoord_MIN / 2, nscoord_MAX,
                     nscoord_MAX);

    nsIFrame* firstFrame =
        nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame);
    nsTArray<nsSVGMaskFrame*> maskFrames;
    // XXX check return value?
    SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);

    for (uint32_t i = 0; i < maskFrames.Length(); ++i) {
      gfxRect clipArea;
      if (maskFrames[i]) {
        clipArea = maskFrames[i]->GetMaskArea(aMaskedFrame);
        clipArea = cssToDevMatrix.TransformBounds(clipArea);
      } else {
        const auto& layer = svgReset->mMask.mLayers[i];
        if (layer.mClip == StyleGeometryBox::NoClip) {
          return Nothing();
        }

        nsCSSRendering::ImageLayerClipState clipState;
        nsCSSRendering::GetImageLayerClip(
            layer, aMaskedFrame, *aMaskedFrame->StyleBorder(), borderArea,
            dirtyRect, false /* aWillPaintBorder */, devPixelRatio, &clipState);
        clipArea = clipState.mDirtyRectInDevPx;
      }
      combinedClip = UnionMaybeRects(combinedClip, Some(clipArea));
    }
  }
  if (combinedClip) {
    if (combinedClip->IsEmpty()) {
      // *clipForMask might be empty if all mask references are not resolvable
      // or the size of them are empty. We still need to create a transparent
      // mask before bug 1276834 fixed, so don't clip ctx by an empty rectangle
      // for for now.
      return Nothing();
    }

    // Convert to user space.
    *combinedClip += devPixelOffsetToUserSpace;

    // Round the clip out. In FrameLayerBuilder we round clips to nearest
    // pixels, and if we have a really thin clip here, that can cause the
    // clip to become empty if we didn't round out here.
    // The rounding happens in coordinates that are relative to the reference
    // frame, which matches what FrameLayerBuilder does.
    combinedClip->RoundOut();

    // Convert to app units.
    nsRect result =
        nsLayoutUtils::RoundGfxRectToAppRect(*combinedClip, devPixelRatio);

    // The resulting clip is relative to the reference frame, but the caller
    // expects it to be relative to the masked frame, so adjust it.
    result -= toReferenceFrame;
    return Some(result);
  }
  return Nothing();
}

struct AutoCheckBuilder {
  explicit AutoCheckBuilder(nsDisplayListBuilder* aBuilder)
      : mBuilder(aBuilder) {
    aBuilder->Check();
  }

  ~AutoCheckBuilder() { mBuilder->Check(); }

  nsDisplayListBuilder* mBuilder;
};

/**
 * Helper class to track container creation. Stores the first tracked container.
 * Used to find the innermost container for hit test information, and to notify
 * callers whether a container item was created or not.
 */
struct ContainerTracker {
  void TrackContainer(nsDisplayItem* aContainer) {
    MOZ_ASSERT(aContainer);

    if (!mContainer) {
      mContainer = aContainer;
    }

    mCreatedContainer = true;
  }

  void ResetCreatedContainer() { mCreatedContainer = false; }

  nsDisplayItem* mContainer = nullptr;
  bool mCreatedContainer = false;
};

/**
 * Adds hit test information |aHitTestInfo| on the container item |aContainer|,
 * or if the container item is null, creates a separate hit test item that is
 * added to the bottom of the display list |aList|.
 */
static void AddHitTestInfo(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
                           nsDisplayItem* aContainer, nsIFrame* aFrame,
                           mozilla::UniquePtr<HitTestInfo>&& aHitTestInfo) {
  nsDisplayHitTestInfoItem* hitTestItem;

  if (aContainer) {
    MOZ_ASSERT(aContainer->IsHitTestItem());
    hitTestItem = static_cast<nsDisplayHitTestInfoItem*>(aContainer);
    hitTestItem->SetHitTestInfo(std::move(aHitTestInfo));
  } else {
    // No container item was created for this frame. Create a separate
    // nsDisplayCompositorHitTestInfo item instead.
    hitTestItem = MakeDisplayItem<nsDisplayCompositorHitTestInfo>(
        aBuilder, aFrame, std::move(aHitTestInfo));
    aList->AppendToBottom(hitTestItem);
  }
}

void nsIFrame::BuildDisplayListForStackingContext(
    nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
    bool* aCreatedContainerItem) {
  AutoCheckBuilder check(aBuilder);
  if (GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE) return;

  // Replaced elements have their visibility handled here, because
  // they're visually atomic
  if (IsFrameOfType(eReplaced) && !IsVisibleForPainting()) return;

  const nsStyleDisplay* disp = StyleDisplay();
  const nsStyleEffects* effects = StyleEffects();
  EffectSet* effectSet = EffectSet::GetEffectSet(this);
  // We can stop right away if this is a zero-opacity stacking context and
  // we're painting, and we're not animating opacity. Don't do this
  // if we're going to compute plugin geometry, since opacity-0 plugins
  // need to have display items built for them.
  bool needHitTestInfo = aBuilder->BuildCompositorHitTestInfo() &&
                         StyleUI()->GetEffectivePointerEvents(this) !=
                             NS_STYLE_POINTER_EVENTS_NONE;
  bool opacityItemForEventsAndPluginsOnly = false;
  if (effects->mOpacity == 0.0 && aBuilder->IsForPainting() &&
      !(disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) &&
      !nsLayoutUtils::HasAnimationOfProperty(effectSet, eCSSProperty_opacity)) {
    if (needHitTestInfo || aBuilder->WillComputePluginGeometry()) {
      opacityItemForEventsAndPluginsOnly = true;
    } else {
      return;
    }
  }

  if (disp->mWillChangeBitField != 0) {
    aBuilder->AddToWillChangeBudget(this, GetSize());
  }

  // For preserves3d, use the dirty rect already installed on the
  // builder, since aDirtyRect maybe distorted for transforms along
  // the chain.
  nsRect visibleRect = aBuilder->GetVisibleRect();
  nsRect dirtyRect = aBuilder->GetDirtyRect();

  // We build an opacity item if it's not going to be drawn by SVG content.
  // We could in principle skip creating an nsDisplayOpacity item if
  // nsDisplayOpacity::NeedsActiveLayer returns false and usingSVGEffects is
  // true (the nsDisplayFilter/nsDisplayMasksAndClipPaths could handle the
  // opacity). Since SVG has perf issues where we sometimes spend a lot of
  // time creating display list items that might be helpful.  We'd need to
  // restore our mechanism to do that (changed in bug 1482403), and we'd
  // need to invalidate the frame if the value that would be return from
  // NeedsActiveLayer was to change, which we don't currently do.
  const bool useOpacity = HasVisualOpacity(disp, effects, effectSet) &&
                          !nsSVGUtils::CanOptimizeOpacity(this);

  const bool isTransformed = IsTransformed(disp);
  const bool hasPerspective = isTransformed && HasPerspective(disp);
  const bool extend3DContext = Extend3DContext(disp, effects, effectSet);
  const bool combines3DTransformWithAncestors =
      (extend3DContext || isTransformed) &&
      Combines3DTransformWithAncestors(disp);

  Maybe<nsDisplayListBuilder::AutoPreserves3DContext> autoPreserves3DContext;
  if (extend3DContext && !combines3DTransformWithAncestors) {
    // Start a new preserves3d context to keep informations on
    // nsDisplayListBuilder.
    autoPreserves3DContext.emplace(aBuilder);
    // Save dirty rect on the builder to avoid being distorted for
    // multiple transforms along the chain.
    aBuilder->SavePreserves3DRect();

    // We rebuild everything within preserve-3d and don't try
    // to retain, so override the dirty rect now.
    if (aBuilder->IsRetainingDisplayList()) {
      dirtyRect = visibleRect;
      aBuilder->SetDisablePartialUpdates(true);
    }
  }

  const bool useBlendMode = effects->mMixBlendMode != NS_STYLE_BLEND_NORMAL;
  if (useBlendMode) {
    aBuilder->SetContainsBlendMode(true);
  }

  // reset blend mode so we can keep track if this stacking context needs have
  // a nsDisplayBlendContainer. Set the blend mode back when the routine exits
  // so we keep track if the parent stacking context needs a container too.
  AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder);
  aBuilder->SetContainsBlendMode(false);

  nsRect visibleRectOutsideTransform = visibleRect;
  bool allowAsyncAnimation = false;
  bool inTransform = aBuilder->IsInTransform();
  if (isTransformed) {
    const nsRect overflow = GetVisualOverflowRectRelativeToSelf();
    nsDisplayTransform::PrerenderDecision decision =
        nsDisplayTransform::ShouldPrerenderTransformedContent(aBuilder, this,
                                                              &dirtyRect);
    switch (decision) {
      case nsDisplayTransform::FullPrerender:
        allowAsyncAnimation = true;
        visibleRect = dirtyRect;
        break;
      case nsDisplayTransform::PartialPrerender:
        allowAsyncAnimation = true;
        visibleRect = dirtyRect;
        MOZ_FALLTHROUGH;
        // fall through to the NoPrerender case
      case nsDisplayTransform::NoPrerender:
        if (overflow.IsEmpty() && !extend3DContext) {
          return;
        }

        // If we're in preserve-3d then grab the dirty rect that was given to
        // the root and transform using the combined transform.
        if (combines3DTransformWithAncestors) {
          visibleRect = dirtyRect = aBuilder->GetPreserves3DRect();
        }

        nsRect untransformedDirtyRect;
        if (nsDisplayTransform::UntransformRect(dirtyRect, overflow, this,
                                                &untransformedDirtyRect)) {
          dirtyRect = untransformedDirtyRect;
          nsDisplayTransform::UntransformRect(visibleRect, overflow, this,
                                              &visibleRect);
        } else {
          // This should only happen if the transform is singular, in which case
          // nothing is visible anyway
          dirtyRect.SetEmpty();
          visibleRect.SetEmpty();
        }
    }
    inTransform = true;
  } else if (IsFixedPosContainingBlock()) {
    // Restict the building area to the overflow rect for these frames, since
    // RetainedDisplayListBuilder uses it to know if the size of the stacking
    // context changed.
    visibleRect.IntersectRect(visibleRect, GetVisualOverflowRect());
    dirtyRect.IntersectRect(dirtyRect, GetVisualOverflowRect());
  }

  bool hasOverrideDirtyRect = false;
  // If we have an override dirty region, and neither us nor our ancestors are
  // modified, then use it.
  if (HasOverrideDirtyRegion() && !aBuilder->InInvalidSubtree() &&
      !IsFrameModified()) {
    nsDisplayListBuilder::DisplayListBuildingData* data =
        GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
    if (data) {
      dirtyRect = data->mDirtyRect.Intersect(visibleRect);
      hasOverrideDirtyRect = true;
    }
  }

  bool usingFilter = StyleEffects()->HasFilters();
  bool usingMask = nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(this);
  bool usingSVGEffects = usingFilter || usingMask;

  nsRect visibleRectOutsideSVGEffects = visibleRect;
  nsDisplayList hoistedScrollInfoItemsStorage;
  if (usingSVGEffects) {
    dirtyRect =
        nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, dirtyRect);
    visibleRect = nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(
        this, visibleRect);
    aBuilder->EnterSVGEffectsContents(&hoistedScrollInfoItemsStorage);
  }

  bool useStickyPosition =
      disp->mPosition == NS_STYLE_POSITION_STICKY &&
      IsScrollFrameActive(
          aBuilder,
          nsLayoutUtils::GetNearestScrollableFrame(
              GetParent(), nsLayoutUtils::SCROLLABLE_SAME_DOC |
                               nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN));
  bool useFixedPosition = disp->mPosition == NS_STYLE_POSITION_FIXED &&
                          (nsLayoutUtils::IsFixedPosFrameInDisplayPort(this) ||
                           BuilderHasScrolledClip(aBuilder));

  nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
      aBuilder, this, visibleRect, dirtyRect, true);

  // Depending on the effects that are applied to this frame, we can create
  // multiple container display items and wrap them around our contents.
  // This enum lists all the potential container display items, in the order
  // outside to inside.
  enum class ContainerItemType : uint8_t {
    eNone = 0,
    eOwnLayerIfNeeded,
    eBlendMode,
    eFixedPosition,
    eOwnLayerForTransformWithRoundedClip,
    ePerspective,
    eTransform,
    eSeparatorTransforms,
    eOpacity,
    eFilter,
    eBlendContainer
  };

  nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);

  DisplayListClipState::AutoSaveRestore clipState(aBuilder);

  // If there is a current clip, then depending on the container items we
  // create, different things can happen to it. Some container items simply
  // propagate the clip to their children and aren't clipped themselves.
  // But other container items, especially those that establish a different
  // geometry for their contents (e.g. transforms), capture the clip on
  // themselves and unset the clip for their contents. If we create more than
  // one of those container items, the clip will be captured on the outermost
  // one and the inner container items will be unclipped.
  ContainerItemType clipCapturedBy = ContainerItemType::eNone;
  if (useFixedPosition) {
    clipCapturedBy = ContainerItemType::eFixedPosition;
  } else if (isTransformed) {
    const DisplayItemClipChain* currentClip =
        aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
    if ((hasPerspective || extend3DContext) &&
        (currentClip && currentClip->HasRoundedCorners())) {
      // If we're creating an nsDisplayTransform item that is going to combine
      // its transform with its children (preserve-3d or perspective), then we
      // can't have an intermediate surface. Mask layers force an intermediate
      // surface, so if we're going to need both then create a separate
      // wrapping layer for the mask.
      clipCapturedBy = ContainerItemType::eOwnLayerForTransformWithRoundedClip;
    } else if (hasPerspective) {
      clipCapturedBy = ContainerItemType::ePerspective;
    } else {
      clipCapturedBy = ContainerItemType::eTransform;
    }
  } else if (usingFilter) {
    clipCapturedBy = ContainerItemType::eFilter;
  }

  if (clipCapturedBy != ContainerItemType::eNone) {
    clipState.Clear();
  }

  Maybe<nsRect> clipForMask;
  if (usingMask) {
    clipForMask = ComputeClipForMaskItem(aBuilder, this);
  }

  mozilla::UniquePtr<HitTestInfo> hitTestInfo;

  nsDisplayListCollection set(aBuilder);
  {
    DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder);
    nsDisplayListBuilder::AutoInTransformSetter inTransformSetter(aBuilder,
                                                                  inTransform);
    nsDisplayListBuilder::AutoEnterFilter filterASRSetter(aBuilder,
                                                          usingFilter);

    CheckForApzAwareEventHandlers(aBuilder, this);

    Maybe<nsRect> contentClip = GetClipPropClipRect(disp, effects, GetSize());

    if (usingMask) {
      contentClip = IntersectMaybeRects(contentClip, clipForMask);
    }

    if (contentClip) {
      aBuilder->IntersectDirtyRect(*contentClip);
      aBuilder->IntersectVisibleRect(*contentClip);
      nestedClipState.ClipContentDescendants(*contentClip +
                                             aBuilder->ToReferenceFrame(this));
    }

    // extend3DContext also guarantees that applyAbsPosClipping and
    // usingSVGEffects are false We only modify the preserve-3d rect if we are
    // the top of a preserve-3d heirarchy
    if (extend3DContext) {
      // Mark these first so MarkAbsoluteFramesForDisplayList knows if we are
      // going to be forced to descend into frames.
      aBuilder->MarkPreserve3DFramesForDisplayList(this);
    }

    aBuilder->AdjustWindowDraggingRegion(this);

    if (gfxVars::UseWebRender()) {
      aBuilder->BuildCompositorHitTestInfoIfNeeded(this, set.BorderBackground(),
                                                   true);
    } else {
      CompositorHitTestInfo info = aBuilder->BuildCompositorHitTestInfo()
                                       ? GetCompositorHitTestInfo(aBuilder)
                                       : CompositorHitTestInvisibleToHit;

      if (info != CompositorHitTestInvisibleToHit) {
        // Frame has hit test flags set, initialize the hit test info structure.
        hitTestInfo = mozilla::MakeUnique<HitTestInfo>(aBuilder, this, info);

        // Let child frames know the current hit test area and hit test flags.
        aBuilder->SetCompositorHitTestInfo(hitTestInfo->mArea,
                                           hitTestInfo->mFlags);
      }
    }

    MarkAbsoluteFramesForDisplayList(aBuilder);
    aBuilder->Check();
    BuildDisplayList(aBuilder, set);
    aBuilder->Check();
    aBuilder->DisplayCaret(this, set.Content());

    // Blend modes are a real pain for retained display lists. We build a blend
    // container item if the built list contains any blend mode items within
    // the current stacking context. This can change without an invalidation
    // to the stacking context frame, or the blend mode frame (e.g. by moving
    // an intermediate frame).
    // When we gain/remove a blend container item, we need to mark this frame
    // as invalid and have the full display list for merging to track
    // the change correctly.
    // It seems really hard to track this in advance, as the bookkeeping
    // required to note which stacking contexts have blend descendants
    // is complex and likely to be buggy.
    // Instead we're doing the sad thing, detecting it afterwards, and just
    // repeating display list building if it changed.
    // We have to repeat building for the entire display list (or at least
    // the outer stacking context), since we need to mark this frame as invalid
    // to remove any existing content that isn't wrapped in the blend container,
    // and then we need to build content infront/behind the blend container
    // to get correct positioning during merging.
    if (aBuilder->ContainsBlendMode() && aBuilder->IsRetainingDisplayList()) {
      if (!aBuilder->GetDirtyRect().Contains(aBuilder->GetVisibleRect())) {
        aBuilder->SetPartialBuildFailed(true);
      } else {
        aBuilder->SetDisablePartialUpdates(true);
      }
    }
  }

  if (aBuilder->IsBackgroundOnly()) {
    set.BlockBorderBackgrounds()->DeleteAll(aBuilder);
    set.Floats()->DeleteAll(aBuilder);
    set.Content()->DeleteAll(aBuilder);
    set.PositionedDescendants()->DeleteAll(aBuilder);
    set.Outlines()->DeleteAll(aBuilder);
  }

  if (hasOverrideDirtyRect && gfxPrefs::LayoutDisplayListShowArea()) {
    nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>(
        aBuilder, this,
        dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
        NS_RGBA(255, 0, 0, 64), false);
    color->SetOverrideZIndex(INT32_MAX);
    set.PositionedDescendants()->AppendToTop(color);
  }

  // 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.
  set.PositionedDescendants()->SortByZOrder();

  nsDisplayList resultList;
  // Now follow the rules of http://www.w3.org/TR/CSS21/zindex.html
  // 1,2: backgrounds and borders
  resultList.AppendToTop(set.BorderBackground());
  // 3: negative z-index children.
  for (;;) {
    nsDisplayItem* item = set.PositionedDescendants()->GetBottom();
    if (item && item->ZIndex() < 0) {
      set.PositionedDescendants()->RemoveBottom();
      resultList.AppendToTop(item);
      continue;
    }
    break;
  }
  // 4: block backgrounds
  resultList.AppendToTop(set.BlockBorderBackgrounds());
  // 5: floats
  resultList.AppendToTop(set.Floats());
  // 7: general content
  resultList.AppendToTop(set.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.
  nsIContent* content = GetContent();
  if (!content) {
    content = PresContext()->Document()->GetRootElement();
  }
  if (content) {
    set.Outlines()->SortByContentOrder(content);
  }
#ifdef DEBUG
  DisplayDebugBorders(aBuilder, this, set);
#endif
  resultList.AppendToTop(set.Outlines());
  // 8, 9: non-negative z-index children
  resultList.AppendToTop(set.PositionedDescendants());

  // Get the ASR to use for the container items that we create here.
  const ActiveScrolledRoot* containerItemASR = contASRTracker.GetContainerASR();

  ContainerTracker ct;

  /* If adding both a nsDisplayBlendContainer and a nsDisplayBlendMode to the
   * same list, the nsDisplayBlendContainer should be added first. This only
   * happens when the element creating this stacking context has mix-blend-mode
   * and also contains a child which has mix-blend-mode.
   * The nsDisplayBlendContainer must be added to the list first, so it does not
   * isolate the containing element blending as well.
   */
  if (aBuilder->ContainsBlendMode()) {
    DisplayListClipState::AutoSaveRestore blendContainerClipState(aBuilder);
    resultList.AppendToTop(nsDisplayBlendContainer::CreateForMixBlendMode(
        aBuilder, this, &resultList, containerItemASR));
    ct.TrackContainer(resultList.GetTop());
  }

  /* If there are any SVG effects, wrap the list up in an SVG effects item
   * (which also handles CSS group opacity). Note that we create an SVG effects
   * item even if resultList is empty, since a filter can produce graphical
   * output even if the element being filtered wouldn't otherwise do so.
   */
  if (usingSVGEffects) {
    MOZ_ASSERT(usingFilter || usingMask,
               "Beside filter & mask/clip-path, what else effect do we have?");

    if (clipCapturedBy == ContainerItemType::eFilter) {
      clipState.Restore();
    }
    // Revert to the post-filter dirty rect.
    aBuilder->SetVisibleRect(visibleRectOutsideSVGEffects);

    // Skip all filter effects while generating glyph mask.
    if (usingFilter && !aBuilder->IsForGenerateGlyphMask()) {
      /* List now emptied, so add the new list to the top. */
      resultList.AppendToTop(
          MakeDisplayItem<nsDisplayFilters>(aBuilder, this, &resultList));
      ct.TrackContainer(resultList.GetTop());
    }

    if (usingMask) {
      DisplayListClipState::AutoSaveRestore maskClipState(aBuilder);
      // The mask should move with aBuilder->CurrentActiveScrolledRoot(), so
      // that's the ASR we prefer to use for the mask item. However, we can
      // only do this if the mask if clipped with respect to that ASR, because
      // an item always needs to have finite bounds with respect to its ASR.
      // If we weren't able to compute a clip for the mask, we fall back to
      // using containerItemASR, which is the lowest common ancestor clip of
      // the mask's contents. That's not entirely correct, but it satisfies
      // the base requirement of the ASR system (that items have finite bounds
      // wrt. their ASR).
      const ActiveScrolledRoot* maskASR =
          clipForMask.isSome() ? aBuilder->CurrentActiveScrolledRoot()
                               : containerItemASR;
      /* List now emptied, so add the new list to the top. */
      resultList.AppendToTop(MakeDisplayItem<nsDisplayMasksAndClipPaths>(
          aBuilder, this, &resultList, maskASR));
      ct.TrackContainer(resultList.GetTop());
    }

    // TODO(miko): We could probably create a wraplist here and avoid creating
    // it later in |BuildDisplayListForChild()|.
    ct.ResetCreatedContainer();

    // Also add the hoisted scroll info items. We need those for APZ scrolling
    // because nsDisplayMasksAndClipPaths items can't build active layers.
    aBuilder->ExitSVGEffectsContents();
    resultList.AppendToTop(&hoistedScrollInfoItemsStorage);
  }

  /* If the list is non-empty and there is CSS group opacity without SVG
   * effects, wrap it up in an opacity item.
   */
  if (useOpacity) {
    // Don't clip nsDisplayOpacity items. We clip their descendants instead.
    // The clip we would set on an element with opacity would clip
    // all descendant content, but some should not be clipped.
    DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder);
    const bool needsActiveOpacityLayer =
        nsDisplayOpacity::NeedsActiveLayer(aBuilder, this);

    resultList.AppendToTop(MakeDisplayItem<nsDisplayOpacity>(
        aBuilder, this, &resultList, containerItemASR,
        opacityItemForEventsAndPluginsOnly, needsActiveOpacityLayer));
    ct.TrackContainer(resultList.GetTop());
  }

  /* If we're going to apply a transformation and don't have preserve-3d set,
   * wrap everything in an nsDisplayTransform. If there's nothing in the list,
   * don't add anything.
   *
   * For the preserve-3d case we want to individually wrap every child in the
   * list with a separate nsDisplayTransform instead. When the child is already
   * an nsDisplayTransform, we can skip this step, as the computed transform
   * will already include our own.
   *
   * We also traverse into sublists created by nsDisplayWrapList, so that we
   * find all the correct children.
   */
  if (isTransformed && extend3DContext) {
    // Install dummy nsDisplayTransform as a leaf containing
    // descendants not participating this 3D rendering context.
    nsDisplayList nonparticipants;
    nsDisplayList participants;
    int index = 1;

    nsDisplayItem* separator = nullptr;

    while (nsDisplayItem* item = resultList.RemoveBottom()) {
      if (ItemParticipatesIn3DContext(this, item) &&
          !item->GetClip().HasClip()) {
        // The frame of this item participates the same 3D context.
        WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
                               index++, &separator);

        participants.AppendToTop(item);
      } else {
        // The frame of the item doesn't participate the current
        // context, or has no transform.
        //
        // For items participating but not transformed, they are add
        // to nonparticipants to get a separator layer for handling
        // clips, if there is, on an intermediate surface.
        // \see ContainerLayer::DefaultComputeEffectiveTransforms().
        nonparticipants.AppendToTop(item);
      }
    }
    WrapSeparatorTransform(aBuilder, this, &nonparticipants, &participants,
                           index++, &separator);

    if (separator) {
      ct.TrackContainer(separator);
    }

    resultList.AppendToTop(&participants);
  }

  if (isTransformed) {
    if (clipCapturedBy == ContainerItemType::eTransform) {
      // Restore clip state now so nsDisplayTransform is clipped properly.
      clipState.Restore();
    }
    // Revert to the dirtyrect coming in from the parent, without our transform
    // taken into account.
    aBuilder->SetVisibleRect(visibleRectOutsideTransform);
    // Revert to the outer reference frame and offset because all display
    // items we create from now on are outside the transform.
    nsPoint toOuterReferenceFrame;
    const nsIFrame* outerReferenceFrame = this;
    if (this != aBuilder->RootReferenceFrame()) {
      outerReferenceFrame =
          aBuilder->FindReferenceFrameFor(GetParent(), &toOuterReferenceFrame);
    }
    buildingDisplayList.SetReferenceFrameAndCurrentOffset(
        outerReferenceFrame, GetOffsetToCrossDoc(outerReferenceFrame));

    nsDisplayTransform* transformItem = MakeDisplayItem<nsDisplayTransform>(
        aBuilder, this, &resultList, visibleRect, 0, allowAsyncAnimation);
    resultList.AppendToTop(transformItem);
    ct.TrackContainer(transformItem);

    if (hasPerspective) {
      if (clipCapturedBy == ContainerItemType::ePerspective) {
        clipState.Restore();
      }
      resultList.AppendToTop(
          MakeDisplayItem<nsDisplayPerspective>(aBuilder, this, &resultList));
      ct.TrackContainer(resultList.GetTop());
    }
  }

  if (clipCapturedBy ==
      ContainerItemType::eOwnLayerForTransformWithRoundedClip) {
    clipState.Restore();
    resultList.AppendToTop(MakeDisplayItem<nsDisplayOwnLayer>(
        aBuilder, this, &resultList, aBuilder->CurrentActiveScrolledRoot(),
        nsDisplayOwnLayerFlags::eNone, ScrollbarData{},
        /* aForceActive = */ false));
    ct.TrackContainer(resultList.GetTop());
  }

  /* If we have sticky positioning, wrap it in a sticky position item.
   */
  if (useFixedPosition) {
    if (clipCapturedBy == ContainerItemType::eFixedPosition) {
      clipState.Restore();
    }
    // The ASR for the fixed item should be the ASR of our containing block,
    // which has been set as the builder's current ASR, unless this frame is
    // invisible and we hadn't saved display item data for it. In that case,
    // we need to take the containerItemASR since we might have fixed children.
    // For WebRender, we want to the know what |containerItemASR| is for the
    // case where the fixed-pos item is not a "real" fixed-pos item (e.g. it's
    // nested inside a scrolling transform), so we stash that on the display
    // item as well.
    const ActiveScrolledRoot* fixedASR = ActiveScrolledRoot::PickAncestor(
        containerItemASR, aBuilder->CurrentActiveScrolledRoot());
    resultList.AppendToTop(MakeDisplayItem<nsDisplayFixedPosition>(
        aBuilder, this, &resultList, fixedASR, containerItemASR));
    ct.TrackContainer(resultList.GetTop());
  } else if (useStickyPosition) {
    // For position:sticky, the clip needs to be applied both to the sticky
    // container item and to the contents. The container item needs the clip
    // because a scrolled clip needs to move independently from the sticky
    // contents, and the contents need the clip so that they have finite
    // clipped bounds with respect to the container item's ASR. The latter is
    // a little tricky in the case where the sticky item has both fixed and
    // non-fixed descendants, because that means that the sticky container
    // item's ASR is the ASR of the fixed descendant.
    // For WebRender display list building, though, we still want to know the
    // the ASR that the sticky container item would normally have, so we stash
    // that on the display item as the "container ASR" (i.e. the normal ASR of
    // the container item, excluding the special behaviour induced by fixed
    // descendants).
    const ActiveScrolledRoot* stickyASR = ActiveScrolledRoot::PickAncestor(
        containerItemASR, aBuilder->CurrentActiveScrolledRoot());
    resultList.AppendToTop(MakeDisplayItem<nsDisplayStickyPosition>(
        aBuilder, this, &resultList, stickyASR,
        aBuilder->CurrentActiveScrolledRoot()));
    ct.TrackContainer(resultList.GetTop());
  }

  /* If there's blending, wrap up the list in a blend-mode item. Note
   * that opacity can be applied before blending as the blend color is
   * not affected by foreground opacity (only background alpha).
   */

  if (useBlendMode) {
    DisplayListClipState::AutoSaveRestore blendModeClipState(aBuilder);
    resultList.AppendToTop(MakeDisplayItem<nsDisplayBlendMode>(
        aBuilder, this, &resultList, effects->mMixBlendMode, containerItemASR));
    ct.TrackContainer(resultList.GetTop());
  }

  bool createdOwnLayer = false;
  CreateOwnLayerIfNeeded(aBuilder, &resultList, &createdOwnLayer);
  if (createdOwnLayer) {
    ct.TrackContainer(resultList.GetTop());
  }

  if (aCreatedContainerItem) {
    *aCreatedContainerItem = ct.mCreatedContainer;
  }

  if (hitTestInfo) {
    // WebRender support is not yet implemented.
    MOZ_ASSERT(!gfxVars::UseWebRender());
    AddHitTestInfo(aBuilder, &resultList, ct.mContainer, this,
                   std::move(hitTestInfo));
  }

  aList->AppendToTop(&resultList);
}

static nsDisplayItem* WrapInWrapList(nsDisplayListBuilder* aBuilder,
                                     nsIFrame* aFrame, nsDisplayList* aList,
                                     const ActiveScrolledRoot* aContainerASR,
                                     bool aCanSkipWrapList = false) {
  nsDisplayItem* item = aList->GetBottom();
  if (!item) {
    return nullptr;
  }

  if (aCanSkipWrapList) {
    MOZ_ASSERT(!item->GetAbove());
    aList->RemoveBottom();
    return item;
  }

  // Clear clip rect for the construction of the items below. Since we're
  // clipping all their contents, they themselves don't need to be clipped.
  return MakeDisplayItem<nsDisplayWrapList>(aBuilder, aFrame, aList,
                                            aContainerASR, true);
}

/**
 * Check if a frame should be visited for building display list.
 */
static bool DescendIntoChild(nsDisplayListBuilder* aBuilder,
                             const nsIFrame* aChild, const nsRect& aVisible,
                             const nsRect& aDirty) {
  if (aChild->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) {
    return true;
  }

  // If the child is a scrollframe that we want to ignore, then we need
  // to descend into it because its scrolled child may intersect the dirty
  // area even if the scrollframe itself doesn't.
  if (aChild == aBuilder->GetIgnoreScrollFrame()) {
    return true;
  }

  // There are cases where the "ignore scroll frame" on the builder is not set
  // correctly, and so we additionally want to catch cases where the child is
  // a root scrollframe and we are ignoring scrolling on the viewport.
  if (aChild == aBuilder->GetPresShellIgnoreScrollFrame()) {
    return true;
  }

  const nsRect overflow = aChild->GetVisualOverflowRect();

  if (aDirty.Intersects(overflow)) {
    return true;
  }

  if (aChild->ForceDescendIntoIfVisible() && aVisible.Intersects(overflow)) {
    return true;
  }

  return false;
}

void nsIFrame::BuildDisplayListForSimpleChild(nsDisplayListBuilder* aBuilder,
                                              nsIFrame* aChild,
                                              const nsDisplayListSet& aLists) {
  // This is the shortcut for frames been handled along the common
  // path, the most common one of THE COMMON CASE mentioned later.
  MOZ_ASSERT(aChild->Type() != LayoutFrameType::Placeholder);
  MOZ_ASSERT(!aBuilder->GetSelectedFramesOnly() &&
                 !aBuilder->GetIncludeAllOutOfFlows(),
             "It should be held for painting to window");
  MOZ_ASSERT(aChild->GetStateBits() & NS_FRAME_SIMPLE_DISPLAYLIST);

  const nsPoint offset = aChild->GetOffsetTo(this);
  const nsRect visible = aBuilder->GetVisibleRect() - offset;
  const nsRect dirty = aBuilder->GetDirtyRect() - offset;

  if (!DescendIntoChild(aBuilder, aChild, visible, dirty)) {
    return;
  }

  nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
      aBuilder, aChild, visible, dirty, false);

  CheckForApzAwareEventHandlers(aBuilder, aChild);

  aBuilder->BuildCompositorHitTestInfoIfNeeded(
      aChild, aLists.BorderBackground(), false);

  aChild->MarkAbsoluteFramesForDisplayList(aBuilder);
  aBuilder->AdjustWindowDraggingRegion(aChild);
  aBuilder->Check();
  aChild->BuildDisplayList(aBuilder, aLists);
  aBuilder->Check();
  aBuilder->DisplayCaret(aChild, aLists.Content());
#ifdef DEBUG
  DisplayDebugBorders(aBuilder, aChild, aLists);
#endif
}

static bool ShouldSkipFrame(nsDisplayListBuilder* aBuilder,
                            const nsIFrame* aFrame) {
  // If painting is restricted to just the background of the top level frame,
  // then we have nothing to do here.
  if (aBuilder->IsBackgroundOnly()) {
    return true;
  }

  if ((aBuilder->IsForGenerateGlyphMask() ||
       aBuilder->IsForPaintingSelectionBG()) &&
      (!aFrame->IsTextFrame() && aFrame->IsLeaf())) {
    return true;
  }

  // The placeholder frame should have the same content as the OOF frame.
  if (aBuilder->GetSelectedFramesOnly() &&
      (aFrame->IsLeaf() && !aFrame->IsSelected())) {
    return true;
  }

  static const nsFrameState skipFlags =
      (NS_FRAME_TOO_DEEP_IN_FRAME_TREE | NS_FRAME_IS_NONDISPLAY);

  return (aFrame->GetStateBits() & skipFlags);
}

void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
                                        nsIFrame* aChild,
                                        const nsDisplayListSet& aLists,
                                        uint32_t aFlags) {
  AutoCheckBuilder check(aBuilder);

  if (ShouldSkipFrame(aBuilder, aChild)) {
    return;
  }

  nsIFrame* child = aChild;
  aBuilder->RemoveFromWillChangeBudget(child);

  const bool isPaintingToWindow = aBuilder->IsPaintingToWindow();
  const bool doingShortcut =
      isPaintingToWindow &&
      (child->GetStateBits() & NS_FRAME_SIMPLE_DISPLAYLIST) &&
      // Animations may change the stacking context state.
      !(child->MayHaveTransformAnimation() || child->MayHaveOpacityAnimation());

  if (doingShortcut) {
    BuildDisplayListForSimpleChild(aBuilder, child, aLists);
    return;
  }

  // dirty rect in child-relative coordinates
  NS_ASSERTION(aBuilder->GetCurrentFrame() == this, "Wrong coord space!");
  const nsPoint offset = child->GetOffsetTo(this);
  nsRect visible = aBuilder->GetVisibleRect() - offset;
  nsRect dirty = aBuilder->GetDirtyRect() - offset;

  nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = nullptr;
  const bool isPlaceholder = child->IsPlaceholderFrame();
  if (isPlaceholder) {
    nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(child);
    if (placeholder->GetStateBits() & PLACEHOLDER_FOR_TOPLAYER) {
      // If the out-of-flow frame is in the top layer, the viewport frame
      // will paint it. Skip it here. Note that, only out-of-flow frames
      // with this property should be skipped, because non-HTML elements
      // may stop their children from being out-of-flow. Those frames
      // should still be handled in the normal in-flow path.
      return;
    }

    child = placeholder->GetOutOfFlowFrame();
    NS_ASSERTION(child, "No out of flow frame?");

    if (child) {
      aBuilder->RemoveFromWillChangeBudget(child);
    }

    // If 'child' is a pushed float then it's owned by a block that's not an
    // ancestor of the placeholder, and it will be painted by that block and
    // should not be painted through the placeholder. Also recheck
    // NS_FRAME_TOO_DEEP_IN_FRAME_TREE and NS_FRAME_IS_NONDISPLAY.
    static const nsFrameState skipFlags =
        (NS_FRAME_IS_PUSHED_FLOAT | NS_FRAME_TOO_DEEP_IN_FRAME_TREE |
         NS_FRAME_IS_NONDISPLAY);
    if (!child || (child->GetStateBits() & skipFlags) ||
        nsLayoutUtils::IsPopup(child)) {
      return;
    }

    MOZ_ASSERT(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW);
    savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(child);

    if (aBuilder->GetIncludeAllOutOfFlows()) {
      visible = child->GetVisualOverflowRect();
      dirty = child->GetVisualOverflowRect();
    } else if (savedOutOfFlowData) {
      visible =
          savedOutOfFlowData->GetVisibleRectForFrame(aBuilder, child, &dirty);
    } else {
      // The out-of-flow frame did not intersect the dirty area. We may still
      // need to traverse into it, since it may contain placeholders we need
      // to enter to reach other out-of-flow frames that are visible.
      visible.SetEmpty();
      dirty.SetEmpty();
    }
  }

  NS_ASSERTION(!child->IsPlaceholderFrame(),
               "Should have dealt with placeholders already");

  if (!DescendIntoChild(aBuilder, child, visible, dirty)) {
    return;
  }

  const bool isSVG = child->GetStateBits() & NS_FRAME_SVG_LAYOUT;

  // This flag is raised if the control flow strays off the common path.
  // The common path is the most common one of THE COMMON CASE mentioned later.
  bool awayFromCommonPath = !isPaintingToWindow;

  // true if this is a real or pseudo stacking context
  bool pseudoStackingContext =
      (aFlags & DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT) != 0;

  if (!pseudoStackingContext && !isSVG && (aFlags & DISPLAY_CHILD_INLINE) &&
      !child->IsFrameOfType(eLineParticipant)) {
    // child is a non-inline frame in an inline context, i.e.,
    // it acts like inline-block or inline-table. Therefore it is a
    // pseudo-stacking-context.
    pseudoStackingContext = true;
  }

  const nsStyleDisplay* ourDisp = StyleDisplay();
  // REVIEW: Taken from nsBoxFrame::Paint
  // Don't paint our children if the theme object is a leaf.
  if (IsThemed(ourDisp) &&
      !PresContext()->GetTheme()->WidgetIsContainer(ourDisp->mAppearance))
    return;

  // Since we're now sure that we're adding this frame to the display list
  // (which means we're painting it, modulo occlusion), mark it as visible
  // within the displayport.
  if (isPaintingToWindow && child->TrackingVisibility()) {
    child->PresShell()->EnsureFrameInApproximatelyVisibleList(child);
    awayFromCommonPath = true;
  }

  child->SetBuiltDisplayList(true);

  // Child is composited if it's transformed, partially transparent, or has
  // SVG effects or a blend mode..
  const nsStyleDisplay* disp = child->StyleDisplay();
  const nsStyleEffects* effects = child->StyleEffects();
  const nsStylePosition* pos = child->StylePosition();

  const bool isPositioned = disp->IsAbsPosContainingBlock(child);

  const bool isStackingContext =
      (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT) ||
      child->IsStackingContext(disp, pos, effects, isPositioned);

  if (pseudoStackingContext || isStackingContext || isPositioned ||
      isPlaceholder || (!isSVG && disp->IsFloating(child)) ||
      (isSVG && (effects->mClipFlags & NS_STYLE_CLIP_RECT) &&
       IsSVGContentWithCSSClip(child))) {
    pseudoStackingContext = true;
    awayFromCommonPath = true;
  }

  NS_ASSERTION(!isStackingContext || pseudoStackingContext,
               "Stacking contexts must also be pseudo-stacking-contexts");

  nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
      aBuilder, child, visible, dirty, pseudoStackingContext);
  DisplayListClipState::AutoClipMultiple clipState(aBuilder);
  nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
  CheckForApzAwareEventHandlers(aBuilder, child);

  if (savedOutOfFlowData) {
    aBuilder->SetBuildingInvisibleItems(false);

    clipState.SetClipChainForContainingBlockDescendants(
        savedOutOfFlowData->mContainingBlockClipChain);
    asrSetter.SetCurrentActiveScrolledRoot(
        savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
    MOZ_ASSERT(awayFromCommonPath,
               "It is impossible when savedOutOfFlowData is true");
  } else if (GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO &&
             isPlaceholder) {
    NS_ASSERTION(visible.IsEmpty(), "should have empty visible rect");
    // Every item we build from now until we descent into an out of flow that
    // does have saved out of flow data should be invisible. This state gets
    // restored when AutoBuildingDisplayList gets out of scope.
    aBuilder->SetBuildingInvisibleItems(true);

    // If we have nested out-of-flow frames and the outer one isn't visible
    // then we won't have stored clip data for it. We can just clear the clip
    // instead since we know we won't render anything, and the inner out-of-flow
    // frame will setup the correct clip for itself.
    clipState.SetClipChainForContainingBlockDescendants(nullptr);
  }

  // Setup clipping for the parent's overflow:-moz-hidden-unscrollable,
  // or overflow:hidden on elements that don't support scrolling (and therefore
  // don't create nsHTML/XULScrollFrame). This clipping needs to not clip
  // anything directly rendered by the parent, only the rendering of its
  // children.
  // Don't use overflowClip to restrict the dirty rect, since some of the
  // descendants may not be clipped by it. Even if we end up with unnecessary
  // display items, they'll be pruned during ComputeVisibility.
  nsIFrame* parent = child->GetParent();
  const nsStyleDisplay* parentDisp =
      parent == this ? ourDisp : parent->StyleDisplay();
  if (ApplyOverflowClipping(aBuilder, parent, parentDisp, clipState)) {
    awayFromCommonPath = true;
  }

  nsDisplayList list;
  nsDisplayList extraPositionedDescendants;
  const ActiveScrolledRoot* wrapListASR;
  bool canSkipWrapList = false;
  if (isStackingContext) {
    // True stacking context.
    // For stacking contexts, BuildDisplayListForStackingContext handles
    // clipping and MarkAbsoluteFramesForDisplayList.
    nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
    child->BuildDisplayListForStackingContext(aBuilder, &list,
                                              &canSkipWrapList);
    wrapListASR = contASRTracker.GetContainerASR();
    if (aBuilder->GetCaretFrame() == child) {
      canSkipWrapList = false;
    }
  } else {
    Maybe<nsRect> clipPropClip =
        child->GetClipPropClipRect(disp, effects, child->GetSize());
    if (clipPropClip) {
      aBuilder->IntersectVisibleRect(*clipPropClip);
      aBuilder->IntersectDirtyRect(*clipPropClip);
      clipState.ClipContentDescendants(*clipPropClip +
                                       aBuilder->ToReferenceFrame(child));
      awayFromCommonPath = true;
    }

    child->MarkAbsoluteFramesForDisplayList(aBuilder);

    const bool differentAGR = buildingForChild.IsAnimatedGeometryRoot();

    if (!awayFromCommonPath) {
      // The shortcut is available for the child for next time.
      child->AddStateBits(NS_FRAME_SIMPLE_DISPLAYLIST);
    }

    if (!pseudoStackingContext) {
      // THIS IS THE COMMON CASE.
      // Not a pseudo or real stacking context. Do the simple thing and
      // return early.

      aBuilder->BuildCompositorHitTestInfoIfNeeded(
          child, aLists.BorderBackground(), differentAGR);

      aBuilder->AdjustWindowDraggingRegion(child);
      aBuilder->Check();
      child->BuildDisplayList(aBuilder, aLists);
      aBuilder->Check();
      aBuilder->DisplayCaret(child, aLists.Content());
#ifdef DEBUG
      DisplayDebugBorders(aBuilder, child, aLists);
#endif
      return;
    }

    // A pseudo-stacking context (e.g., a positioned element with z-index auto).
    // We allow positioned descendants of the child to escape to our parent
    // stacking context's positioned descendant list, because they might be
    // z-index:non-auto
    nsDisplayListCollection pseudoStack(aBuilder);

    aBuilder->BuildCompositorHitTestInfoIfNeeded(
        child, pseudoStack.BorderBackground(), differentAGR || isPositioned);

    aBuilder->AdjustWindowDraggingRegion(child);
    nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
    aBuilder->Check();
    child->BuildDisplayList(aBuilder, pseudoStack);
    aBuilder->Check();
    if (aBuilder->DisplayCaret(child, pseudoStack.Content())) {
      canSkipWrapList = false;
    }
    wrapListASR = contASRTracker.GetContainerASR();

    list.AppendToTop(pseudoStack.BorderBackground());
    list.AppendToTop(pseudoStack.BlockBorderBackgrounds());
    list.AppendToTop(pseudoStack.Floats());
    list.AppendToTop(pseudoStack.Content());
    list.AppendToTop(pseudoStack.Outlines());
    extraPositionedDescendants.AppendToTop(pseudoStack.PositionedDescendants());
#ifdef DEBUG
    DisplayDebugBorders(aBuilder, child, aLists);
#endif
  }

  buildingForChild.RestoreBuildingInvisibleItemsValue();

  if (isPositioned || isStackingContext ||
      (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT)) {
    // Genuine stacking contexts, and positioned pseudo-stacking-contexts,
    // go in this level.
    if (!list.IsEmpty()) {
      nsDisplayItem* item =
          WrapInWrapList(aBuilder, child, &list, wrapListASR, canSkipWrapList);
      if (isSVG) {
        aLists.Content()->AppendToTop(item);
      } else {
        aLists.PositionedDescendants()->AppendToTop(item);
      }
    }
  } else if (!isSVG && disp->IsFloating(child)) {
    if (!list.IsEmpty()) {
      aLists.Floats()->AppendToTop(
          WrapInWrapList(aBuilder, child, &list, wrapListASR));
    }
  } else {
    aLists.Content()->AppendToTop(&list);
  }
  // We delay placing the positioned descendants of positioned frames to here,
  // because in the absence of z-index this is the correct order for them.
  // This doesn't affect correctness because the positioned descendants list
  // is sorted by z-order and content in BuildDisplayListForStackingContext,
  // but it means that sort routine needs to do less work.
  aLists.PositionedDescendants()->AppendToTop(&extraPositionedDescendants);
}

void nsIFrame::MarkAbsoluteFramesForDisplayList(
    nsDisplayListBuilder* aBuilder) {
  if (IsAbsoluteContainer()) {
    aBuilder->MarkFramesForDisplayList(
        this, GetAbsoluteContainingBlock()->GetChildList());
  }
}

nsresult nsFrame::GetContentForEvent(WidgetEvent* aEvent,
                                     nsIContent** aContent) {
  nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
  *aContent = f->GetContent();
  NS_IF_ADDREF(*aContent);
  return NS_OK;
}

void nsFrame::FireDOMEvent(const nsAString& aDOMEventName,
                           nsIContent* aContent) {
  nsIContent* target = aContent ? aContent : GetContent();

  if (target) {
    RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
        target, aDOMEventName, CanBubble::eYes, ChromeOnlyDispatch::eNo);
    DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
    NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
  }
}

nsresult nsFrame::HandleEvent(nsPresContext* aPresContext,
                              WidgetGUIEvent* aEvent,
                              nsEventStatus* aEventStatus) {
  if (aEvent->mMessage == eMouseMove) {
    // XXX If the second argument of HandleDrag() is WidgetMouseEvent,
    //     the implementation becomes simpler.
    return HandleDrag(aPresContext, aEvent, aEventStatus);
  }

  if ((aEvent->mClass == eMouseEventClass &&
       aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) ||
      aEvent->mClass == eTouchEventClass) {
    if (aEvent->mMessage == eMouseDown || aEvent->mMessage == eTouchStart) {
      HandlePress(aPresContext, aEvent, aEventStatus);
    } else if (aEvent->mMessage == eMouseUp || aEvent->mMessage == eTouchEnd) {
      HandleRelease(aPresContext, aEvent, aEventStatus);
    }
  }
  return NS_OK;
}

nsresult nsFrame::GetDataForTableSelection(
    const nsFrameSelection* aFrameSelection, nsIPresShell* aPresShell,
    WidgetMouseEvent* aMouseEvent, nsIContent** aParentContent,
    int32_t* aContentOffset, TableSelection* aTarget) {
  if (!aFrameSelection || !aPresShell || !aMouseEvent || !aParentContent ||
      !aContentOffset || !aTarget)
    return NS_ERROR_NULL_POINTER;

  *aParentContent = nullptr;
  *aContentOffset = 0;
  *aTarget = TableSelection::None;

  int16_t displaySelection = aPresShell->GetSelectionFlags();

  bool selectingTableCells = aFrameSelection->GetTableCellSelection();

  // DISPLAY_ALL means we're in an editor.
  // If already in cell selection mode,
  //  continue selecting with mouse drag or end on mouse up,
  //  or when using shift key to extend block of cells
  //  (Mouse down does normal selection unless Ctrl/Cmd is pressed)
  bool doTableSelection =
      displaySelection == nsISelectionDisplay::DISPLAY_ALL &&
      selectingTableCells &&
      (aMouseEvent->mMessage == eMouseMove ||
       (aMouseEvent->mMessage == eMouseUp &&
        aMouseEvent->button == WidgetMouseEvent::eLeftButton) ||
       aMouseEvent->IsShift());

  if (!doTableSelection) {
    // In Browser, special 'table selection' key must be pressed for table
    // selection or when just Shift is pressed and we're already in table/cell
    // selection mode
#ifdef XP_MACOSX
    doTableSelection = aMouseEvent->IsMeta() ||
                       (aMouseEvent->IsShift() && selectingTableCells);
#else
    doTableSelection = aMouseEvent->IsControl() ||
                       (aMouseEvent->IsShift() && selectingTableCells);
#endif
  }
  if (!doTableSelection) return NS_OK;

  // Get the cell frame or table frame (or parent) of the current content node
  nsIFrame* frame = this;
  bool foundCell = false;
  bool foundTable = false;

  // Get the limiting node to stop parent frame search
  nsIContent* limiter = aFrameSelection->GetLimiter();

  // If our content node is an ancestor of the limiting node,
  // we should stop the search right now.
  if (limiter && nsContentUtils::ContentIsDescendantOf(limiter, GetContent()))
    return NS_OK;

  // We don't initiate row/col selection from here now,
  //  but we may in future
  // bool selectColumn = false;
  // bool selectRow = false;

  while (frame) {
    // Check for a table cell by querying to a known CellFrame interface
    nsITableCellLayout* cellElement = do_QueryFrame(frame);
    if (cellElement) {
      foundCell = true;
      // TODO: If we want to use proximity to top or left border
      //      for row and column selection, this is the place to do it
      break;
    } else {
      // If not a cell, check for table
      // This will happen when starting frame is the table or child of a table,
      //  such as a row (we were inbetween cells or in table border)
      nsTableWrapperFrame* tableFrame = do_QueryFrame(frame);
      if (tableFrame) {
        foundTable = true;
        // TODO: How can we select row when along left table edge
        //  or select column when along top edge?
        break;
      } else {
        frame = frame->GetParent();
        // Stop if we have hit the selection's limiting content node
        if (frame && frame->GetContent() == limiter) break;
      }
    }
  }
  // We aren't in a cell or table
  if (!foundCell && !foundTable) return NS_OK;

  nsIContent* tableOrCellContent = frame->GetContent();
  if (!tableOrCellContent) return NS_ERROR_FAILURE;

  nsCOMPtr<nsIContent> parentContent = tableOrCellContent->GetParent();
  if (!parentContent) return NS_ERROR_FAILURE;

  int32_t offset = parentContent->ComputeIndexOf(tableOrCellContent);
  // Not likely?
  if (offset < 0) return NS_ERROR_FAILURE;

  // Everything is OK -- set the return values
  parentContent.forget(aParentContent);

  *aContentOffset = offset;

#if 0
  if (selectRow)
    *aTarget = TableSelection::Row;
  else if (selectColumn)
    *aTarget = TableSelection::Column;
  else
#endif
  if (foundCell)
    *aTarget = TableSelection::Cell;
  else if (foundTable)
    *aTarget = TableSelection::Table;

  return NS_OK;
}

static bool IsEditingHost(const nsIFrame* aFrame) {
  auto* element = nsGenericHTMLElement::FromNodeOrNull(aFrame->GetContent());
  return element && element->IsEditableRoot();
}

static StyleUserSelect UsedUserSelect(const nsIFrame* aFrame) {
  if (aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) {
    return StyleUserSelect::None;
  }

  // Per https://drafts.csswg.org/css-ui-4/#content-selection:
  //
  // The computed value is the specified value, except:
  //
  //   1 - on editable elements where the computed value is always 'contain'
  //       regardless of the specified value.
  //   2 - when the specified value is auto, which computes to one of the other
  //       values [...]
  //
  // See https://github.com/w3c/csswg-drafts/issues/3344 to see why we do this
  // at used-value time instead of at computed-value time.
  //
  // Also, we check for auto first to allow explicitly overriding the value for
  // the editing host.
  auto style = aFrame->StyleUIReset()->mUserSelect;
  if (style != StyleUserSelect::Auto) {
    return style;
  }

  if (IsEditingHost(aFrame)) {
    // We don't implement 'contain' itself, but we make 'text' behave as
    // 'contain' for contenteditable elements anyway so this is ok.
    return StyleUserSelect::Text;
  }

  auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
  return parent ? UsedUserSelect(parent) : StyleUserSelect::Text;
}

bool nsIFrame::IsSelectable(StyleUserSelect* aSelectStyle) const {
  auto style = UsedUserSelect(this);
  if (aSelectStyle) {
    *aSelectStyle = style;
  }
  return style != StyleUserSelect::None;
}

/**
 * Handles the Mouse Press Event for the frame
 */
NS_IMETHODIMP
nsFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
                     nsEventStatus* aEventStatus) {
  NS_ENSURE_ARG_POINTER(aEventStatus);
  if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
    return NS_OK;
  }

  NS_ENSURE_ARG_POINTER(aEvent);
  if (aEvent->mClass == eTouchEventClass) {
    return NS_OK;
  }

  // We often get out of sync state issues with mousedown events that
  // get interrupted by alerts/dialogs.
  // Check with the ESM to see if we should process this one
  if (!aPresContext->EventStateManager()->EventStatusOK(aEvent)) return NS_OK;

  nsIPresShell* shell = aPresContext->GetPresShell();
  if (!shell) return NS_ERROR_FAILURE;

  // if we are in Navigator and the click is in a draggable node, we don't want
  // to start selection because we don't want to interfere with a potential
  // drag of said node and steal all its glory.
  int16_t isEditor = shell->GetSelectionFlags();
  // weaaak. only the editor can display frame selection not just text and
  // images
  isEditor = isEditor == nsISelectionDisplay::DISPLAY_ALL;

  WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();

  if (!mouseEvent->IsAlt()) {
    for (nsIContent* content = mContent; content;
         content = content->GetParent()) {
      if (nsContentUtils::ContentIsDraggable(content) &&
          !content->IsEditable()) {
        // coordinate stuff is the fix for bug #55921
        if ((mRect - GetPosition())
                .Contains(nsLayoutUtils::GetEventCoordinatesRelativeTo(
                    mouseEvent, this))) {
          return NS_OK;
        }
      }
    }
  }

  // check whether style allows selection
  // if not, don't tell selection the mouse event even occurred.
  StyleUserSelect selectStyle;
  // check for select: none
  if (!IsSelectable(&selectStyle)) {
    return NS_OK;
  }

  bool useFrameSelection = (selectStyle == StyleUserSelect::Text);

  // If the mouse is dragged outside the nearest enclosing scrollable area
  // while making a selection, the area will be scrolled. To do this, capture
  // the mouse on the nearest scrollable frame. If there isn't a scrollable
  // frame, or something else is already capturing the mouse, there's no
  // reason to capture.
  if (!nsIPresShell::GetCapturingContent()) {
    nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
        this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
                  nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
    if (scrollFrame) {
      nsIFrame* capturingFrame = do_QueryFrame(scrollFrame);
      nsIPresShell::SetCapturingContent(capturingFrame->GetContent(),
                                        CAPTURE_IGNOREALLOWED);
    }
  }

  // XXX This is screwy; it really should use the selection frame, not the
  // event frame
  const nsFrameSelection* frameselection = nullptr;
  if (useFrameSelection)
    frameselection = GetConstFrameSelection();
  else
    frameselection = shell->ConstFrameSelection();

  if (!frameselection || frameselection->GetDisplaySelection() ==
                             nsISelectionController::SELECTION_OFF)
    return NS_OK;  // nothing to do we cannot affect selection from here

#ifdef XP_MACOSX
  if (mouseEvent->IsControl())
    return NS_OK;  // short circuit. hard coded for mac due to time restraints.
  bool control = mouseEvent->IsMeta();
#else
  bool control = mouseEvent->IsControl();
#endif

  RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection);
  if (mouseEvent->mClickCount > 1) {
    // These methods aren't const but can't actually delete anything,
    // so no need for AutoWeakFrame.
    fc->SetDragState(true);
    fc->SetMouseDoubleDown(true);
    return HandleMultiplePress(aPresContext, mouseEvent, aEventStatus, control);
  }

  nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent, this);
  ContentOffsets offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);

  if (!offsets.content) return NS_ERROR_FAILURE;

  // Let Ctrl/Cmd+mouse down do table selection instead of drag initiation
  nsCOMPtr<nsIContent> parentContent;
  int32_t contentOffset;
  TableSelection target;
  nsresult rv;
  rv = GetDataForTableSelection(frameselection, shell, mouseEvent,
                                getter_AddRefs(parentContent), &contentOffset,
                                &target);
  if (NS_SUCCEEDED(rv) && parentContent) {
    fc->SetDragState(true);
    return fc->HandleTableSelection(parentContent, contentOffset, target,
                                    mouseEvent);
  }

  fc->SetDelayedCaretData(0);

  // Check if any part of this frame is selected, and if the
  // user clicked inside the selected region. If so, we delay
  // starting a new selection since the user may be trying to
  // drag the selected region to some other app.

  if (GetContent() && GetContent()->IsSelectionDescendant()) {
    bool inSelection = false;
    UniquePtr<SelectionDetails> details = frameselection->LookUpSelection(
        offsets.content, 0, offsets.EndOffset(), false);

    //
    // If there are any details, check to see if the user clicked
    // within any selected region of the frame.
    //

    for (SelectionDetails* curDetail = details.get(); curDetail;
         curDetail = curDetail->mNext.get()) {
      //
      // If the user clicked inside a selection, then just
      // return without doing anything. We will handle placing
      // the caret later on when the mouse is released. We ignore
      // the spellcheck, find and url formatting selections.
      //
      if (curDetail->mSelectionType != SelectionType::eSpellCheck &&
          curDetail->mSelectionType != SelectionType::eFind &&
          curDetail->mSelectionType != SelectionType::eURLSecondary &&
          curDetail->mSelectionType != SelectionType::eURLStrikeout &&
          curDetail->mStart <= offsets.StartOffset() &&
          offsets.EndOffset() <= curDetail->mEnd) {
        inSelection = true;
      }
    }

    if (inSelection) {
      fc->SetDragState(false);
      fc->SetDelayedCaretData(mouseEvent);
      return NS_OK;
    }
  }

  fc->SetDragState(true);

  // Do not touch any nsFrame members after this point without adding
  // weakFrame checks.
  rv = fc->HandleClick(offsets.content, offsets.StartOffset(),
                       offsets.EndOffset(), mouseEvent->IsShift(), control,
                       offsets.associate);

  if (NS_FAILED(rv)) return rv;

  if (offsets.offset != offsets.secondaryOffset) fc->MaintainSelection();

  if (isEditor && !mouseEvent->IsShift() &&
      (offsets.EndOffset() - offsets.StartOffset()) == 1) {
    // A single node is selected and we aren't extending an existing
    // selection, which means the user clicked directly on an object (either
    // -moz-user-select: all or a non-text node without children).
    // Therefore, disable selection extension during mouse moves.
    // XXX This is a bit hacky; shouldn't editor be able to deal with this?
    fc->SetDragState(false);
  }

  return rv;
}

/*
 * SelectByTypeAtPoint
 *
 * Search for selectable content at point and attempt to select
 * based on the start and end selection behaviours.
 *
 * @param aPresContext Presentation context
 * @param aPoint Point at which selection will occur. Coordinates
 * should be relaitve to this frame.
 * @param aBeginAmountType, aEndAmountType Selection behavior, see
 * nsIFrame for definitions.
 * @param aSelectFlags Selection flags defined in nsFame.h.
 * @return success or failure at finding suitable content to select.
 */
nsresult nsFrame::SelectByTypeAtPoint(nsPresContext* aPresContext,
                                      const nsPoint& aPoint,
                                      nsSelectionAmount aBeginAmountType,
                                      nsSelectionAmount aEndAmountType,
                                      uint32_t aSelectFlags) {
  NS_ENSURE_ARG_POINTER(aPresContext);

  // No point in selecting if selection is turned off
  if (DisplaySelection(aPresContext) == nsISelectionController::SELECTION_OFF)
    return NS_OK;

  ContentOffsets offsets = GetContentOffsetsFromPoint(aPoint, SKIP_HIDDEN);
  if (!offsets.content) return NS_ERROR_FAILURE;

  int32_t offset;
  const nsFrameSelection* frameSelection =
      PresContext()->GetPresShell()->ConstFrameSelection();
  nsIFrame* theFrame = frameSelection->GetFrameForNodeOffset(
      offsets.content, offsets.offset, offsets.associate, &offset);
  if (!theFrame) return NS_ERROR_FAILURE;

  nsFrame* frame = static_cast<nsFrame*>(theFrame);
  return frame->PeekBackwardAndForward(aBeginAmountType, aEndAmountType, offset,
                                       aBeginAmountType != eSelectWord,
                                       aSelectFlags);
}

/**
 * Multiple Mouse Press -- line or paragraph selection -- for the frame.
 * Wouldn't it be nice if this didn't have to be hardwired into Frame code?
 */
NS_IMETHODIMP
nsFrame::HandleMultiplePress(nsPresContext* aPresContext,
                             WidgetGUIEvent* aEvent,
                             nsEventStatus* aEventStatus, bool aControlHeld) {
  NS_ENSURE_ARG_POINTER(aEvent);
  NS_ENSURE_ARG_POINTER(aEventStatus);

  if (nsEventStatus_eConsumeNoDefault == *aEventStatus ||
      DisplaySelection(aPresContext) == nsISelectionController::SELECTION_OFF) {
    return NS_OK;
  }

  // Find out whether we're doing line or paragraph selection.
  // If browser.triple_click_selects_paragraph is true, triple-click selects
  // paragraph. Otherwise, triple-click selects line, and quadruple-click
  // selects paragraph (on platforms that support quadruple-click).
  nsSelectionAmount beginAmount, endAmount;
  WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
  if (!mouseEvent) {
    return NS_OK;
  }

  if (mouseEvent->mClickCount == 4) {
    beginAmount = endAmount = eSelectParagraph;
  } else if (mouseEvent->mClickCount == 3) {
    if (Preferences::GetBool("browser.triple_click_selects_paragraph")) {
      beginAmount = endAmount = eSelectParagraph;
    } else {
      beginAmount = eSelectBeginLine;
      endAmount = eSelectEndLine;
    }
  } else if (mouseEvent->mClickCount == 2) {
    // We only want inline frames; PeekBackwardAndForward dislikes blocks
    beginAmount = endAmount = eSelectWord;
  } else {
    return NS_OK;
  }

  nsPoint relPoint =
      nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent, this);
  return SelectByTypeAtPoint(aPresContext, relPoint, beginAmount, endAmount,
                             (aControlHeld ? SELECT_ACCUMULATE : 0));
}

nsresult nsFrame::PeekBackwardAndForward(nsSelectionAmount aAmountBack,
                                         nsSelectionAmount aAmountForward,
                                         int32_t aStartPos, bool aJumpLines,
                                         uint32_t aSelectFlags) {
  nsIFrame* baseFrame = this;
  int32_t baseOffset = aStartPos;
  nsresult rv;

  if (aAmountBack == eSelectWord) {
    // To avoid selecting the previous word when at start of word,
    // first move one character forward.
    nsPeekOffsetStruct pos(eSelectCharacter, eDirNext, aStartPos, nsPoint(0, 0),
                           aJumpLines,
                           true,  // limit on scrolled views
                           false, false, false);
    rv = PeekOffset(&pos);
    if (NS_SUCCEEDED(rv)) {
      baseFrame = pos.mResultFrame;
      baseOffset = pos.mContentOffset;
    }
  }

  // Use peek offset one way then the other:
  nsPeekOffsetStruct startpos(aAmountBack, eDirPrevious, baseOffset,
                              nsPoint(0, 0), aJumpLines,
                              true,  // limit on scrolled views
                              false, false, false);
  rv = baseFrame->PeekOffset(&startpos);
  if (NS_FAILED(rv)) return rv;

  nsPeekOffsetStruct endpos(aAmountForward, eDirNext, aStartPos, nsPoint(0, 0),
                            aJumpLines,
                            true,  // limit on scrolled views
                            false, false, false);
  rv = PeekOffset(&endpos);
  if (NS_FAILED(rv)) return rv;

  // Keep frameSelection alive.
  RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();

  rv = frameSelection->HandleClick(
      startpos.mResultContent, startpos.mContentOffset, startpos.mContentOffset,
      false, (aSelectFlags & SELECT_ACCUMULATE), CARET_ASSOCIATE_AFTER);
  if (NS_FAILED(rv)) return rv;

  rv = frameSelection->HandleClick(endpos.mResultContent, endpos.mContentOffset,
                                   endpos.mContentOffset, true, false,
                                   CARET_ASSOCIATE_BEFORE);
  if (NS_FAILED(rv)) return rv;

  // maintain selection
  return frameSelection->MaintainSelection(aAmountBack);
}

NS_IMETHODIMP nsFrame::HandleDrag(nsPresContext* aPresContext,
                                  WidgetGUIEvent* aEvent,
                                  nsEventStatus* aEventStatus) {
  MOZ_ASSERT(aEvent->mClass == eMouseEventClass,
             "HandleDrag can only handle mouse event");

  RefPtr<nsFrameSelection> frameselection = GetFrameSelection();
  bool mouseDown = frameselection->GetDragState();
  if (!mouseDown) {
    return NS_OK;
  }

  nsIFrame* scrollbar =
      nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::Scrollbar);
  if (!scrollbar) {
    // XXX Do we really need to exclude non-selectable content here?
    // GetContentOffsetsFromPoint can handle it just fine, although some
    // other stuff might not like it.
    // NOTE: DisplaySelection() returns SELECTION_OFF for non-selectable frames.
    if (DisplaySelection(aPresContext) ==
        nsISelectionController::SELECTION_OFF) {
      return NS_OK;
    }
  }

  frameselection->StopAutoScrollTimer();

  // Check if we are dragging in a table cell
  nsCOMPtr<nsIContent> parentContent;
  int32_t contentOffset;
  TableSelection target;
  WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
  nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
  nsresult result;
  result = GetDataForTableSelection(frameselection, presShell, mouseEvent,
                                    getter_AddRefs(parentContent),
                                    &contentOffset, &target);

  AutoWeakFrame weakThis = this;
  if (NS_SUCCEEDED(result) && parentContent) {
    frameselection->HandleTableSelection(parentContent, contentOffset, target,
                                         mouseEvent);
  } else {
    nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent, this);
    frameselection->HandleDrag(this, pt);
  }

  // The frameselection object notifies selection listeners synchronously above
  // which might have killed us.
  if (!weakThis.IsAlive()) {
    return NS_OK;
  }

  // get the nearest scrollframe
  nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
      this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
                nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);

  if (scrollFrame) {
    nsIFrame* capturingFrame = scrollFrame->GetScrolledFrame();
    if (capturingFrame) {
      nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mouseEvent,
                                                                capturingFrame);
      frameselection->StartAutoScrollTimer(capturingFrame, pt, 30);
    }
  }

  return NS_OK;
}

/**
 * This static method handles part of the nsFrame::HandleRelease in a way
 * which doesn't rely on the nsFrame object to stay alive.
 */
static nsresult HandleFrameSelection(
    nsFrameSelection* aFrameSelection, nsIFrame::ContentOffsets& aOffsets,
    bool aHandleTableSel, int32_t aContentOffsetForTableSel,
    TableSelection aTargetForTableSel, nsIContent* aParentContentForTableSel,
    WidgetGUIEvent* aEvent, nsEventStatus* aEventStatus) {
  if (!aFrameSelection) {
    return NS_OK;
  }

  nsresult rv = NS_OK;

  if (nsEventStatus_eConsumeNoDefault != *aEventStatus) {
    if (!aHandleTableSel) {
      if (!aOffsets.content || !aFrameSelection->HasDelayedCaretData()) {
        return NS_ERROR_FAILURE;
      }

      // We are doing this to simulate what we would have done on HandlePress.
      // We didn't do it there to give the user an opportunity to drag
      // the text, but since they didn't drag, we want to place the
      // caret.
      // However, we'll use the mouse position from the release, since:
      //  * it's easier
      //  * that's the normal click position to use (although really, in
      //    the normal case, small movements that don't count as a drag
      //    can do selection)
      aFrameSelection->SetDragState(true);

      rv = aFrameSelection->HandleClick(
          aOffsets.content, aOffsets.StartOffset(), aOffsets.EndOffset(),
          aFrameSelection->IsShiftDownInDelayedCaretData(), false,
          aOffsets.associate);
      if (NS_FAILED(rv)) {
        return rv;
      }
    } else if (aParentContentForTableSel) {
      aFrameSelection->SetDragState(false);
      rv = aFrameSelection->HandleTableSelection(
          aParentContentForTableSel, aContentOffsetForTableSel,
          aTargetForTableSel, aEvent->AsMouseEvent());
      if (NS_FAILED(rv)) {
        return rv;
      }
    }
    aFrameSelection->SetDelayedCaretData(0);
  }

  aFrameSelection->SetDragState(false);
  aFrameSelection->StopAutoScrollTimer();

  return NS_OK;
}

NS_IMETHODIMP nsFrame::HandleRelease(nsPresContext* aPresContext,
                                     WidgetGUIEvent* aEvent,
                                     nsEventStatus* aEventStatus) {
  if (aEvent->mClass != eMouseEventClass) {
    return NS_OK;
  }

  nsIFrame* activeFrame = GetActiveSelectionFrame(aPresContext, this);

  nsCOMPtr<nsIContent> captureContent = nsIPresShell::GetCapturingContent();

  // We can unconditionally stop capturing because
  // we should never be capturing when the mouse button is up
  nsIPresShell::SetCapturingContent(nullptr, 0);

  bool selectionOff =
      (DisplaySelection(aPresContext) == nsISelectionController::SELECTION_OFF);

  RefPtr<nsFrameSelection> frameselection;
  ContentOffsets offsets;
  nsCOMPtr<nsIContent> parentContent;
  int32_t contentOffsetForTableSel = 0;
  TableSelection targetForTableSel = TableSelection::None;
  bool handleTableSelection = true;

  if (!selectionOff) {
    frameselection = GetFrameSelection();
    if (nsEventStatus_eConsumeNoDefault != *aEventStatus && frameselection) {
      // Check if the frameselection recorded the mouse going down.
      // If not, the user must have clicked in a part of the selection.
      // Place the caret before continuing!

      if (frameselection->MouseDownRecorded()) {
        nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
        offsets = GetContentOffsetsFromPoint(pt, SKIP_HIDDEN);
        handleTableSelection = false;
      } else {
        GetDataForTableSelection(frameselection, PresShell(),
                                 aEvent->AsMouseEvent(),
                                 getter_AddRefs(parentContent),
                                 &contentOffsetForTableSel, &targetForTableSel);
      }
    }
  }

  // We might be capturing in some other document and the event just happened to
  // trickle down here. Make sure that document's frame selection is notified.
  // Note, this may cause the current nsFrame object to be deleted, bug 336592.
  RefPtr<nsFrameSelection> frameSelection;
  if (activeFrame != this &&
      static_cast<nsFrame*>(activeFrame)
              ->DisplaySelection(activeFrame->PresContext()) !=
          nsISelectionController::SELECTION_OFF) {
    frameSelection = activeFrame->GetFrameSelection();
  }

  // Also check the selection of the capturing content which might be in a
  // different document.
  if (!frameSelection && captureContent) {
    Document* doc = captureContent->GetUncomposedDoc();
    if (doc) {
      nsIPresShell* capturingShell = doc->GetShell();
      if (capturingShell && capturingShell != PresContext()->GetPresShell()) {
        frameSelection = capturingShell->FrameSelection();
      }
    }
  }

  if (frameSelection) {
    frameSelection->SetDragState(false);
    frameSelection->StopAutoScrollTimer();
    nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
        this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
                  nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
    if (scrollFrame) {
      // Perform any additional scrolling needed to maintain CSS snap point
      // requirements when autoscrolling is over.
      scrollFrame->ScrollSnap();
    }
  }

  // Do not call any methods of the current object after this point!!!
  // The object is perhaps dead!

  return selectionOff ? NS_OK
                      : HandleFrameSelection(
                            frameselection, offsets, handleTableSelection,
                            contentOffsetForTableSel, targetForTableSel,
                            parentContent, aEvent, aEventStatus);
}

struct MOZ_STACK_CLASS FrameContentRange {
  FrameContentRange(nsIContent* aContent, int32_t aStart, int32_t aEnd)
      : content(aContent), start(aStart), end(aEnd) {}
  nsCOMPtr<nsIContent> content;
  int32_t start;
  int32_t end;
};

// Retrieve the content offsets of a frame
static FrameContentRange GetRangeForFrame(nsIFrame* aFrame) {
  nsIContent* content = aFrame->GetContent();
  if (!content) {
    NS_WARNING("Frame has no content");
    return FrameContentRange(nullptr, -1, -1);
  }

  LayoutFrameType type = aFrame->Type();
  if (type == LayoutFrameType::Text) {
    int32_t offset, offsetEnd;
    aFrame->GetOffsets(offset, offsetEnd);
    return FrameContentRange(content, offset, offsetEnd);
  }

  if (type == LayoutFrameType::Br) {
    nsIContent* parent = content->GetParent();
    int32_t beginOffset = parent->ComputeIndexOf(content);
    return FrameContentRange(parent, beginOffset, beginOffset);
  }

  while (content->IsRootOfAnonymousSubtree()) {
    content = content->GetParent();
  }

  nsIContent* parent = content->GetParent();
  if (nsLayoutUtils::GetAsBlock(aFrame) || !parent) {
    return FrameContentRange(content, 0, content->GetChildCount());
  }

  // TODO(emilio): Revise this in presence of Shadow DOM / display: contents,
  // it's likely that we don't want to just walk the light tree, and we need to
  // change the representation of FrameContentRange.
  int32_t index = parent->ComputeIndexOf(content);
  MOZ_ASSERT(index >= 0);
  return FrameContentRange(parent, index, index + 1);
}

// The FrameTarget represents the closest frame to a point that can be selected
// The frame is the frame represented, frameEdge says whether one end of the
// frame is the result (in which case different handling is needed), and
// afterFrame says which end is repersented if frameEdge is true
struct FrameTarget {
  FrameTarget(nsIFrame* aFrame, bool aFrameEdge, bool aAfterFrame)
      : frame(aFrame), frameEdge(aFrameEdge), afterFrame(aAfterFrame) {}

  static FrameTarget Null() { return FrameTarget(nullptr, false, false); }

  bool IsNull() { return !frame; }
  nsIFrame* frame;
  bool frameEdge;
  bool afterFrame;
};

// See function implementation for information
static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame,
                                            const nsPoint& aPoint,
                                            uint32_t aFlags);

static bool SelfIsSelectable(nsIFrame* aFrame, uint32_t aFlags) {
  if ((aFlags & nsIFrame::SKIP_HIDDEN) &&
      !aFrame->StyleVisibility()->IsVisible()) {
    return false;
  }
  return !aFrame->IsGeneratedContentFrame() &&
         aFrame->StyleUIReset()->mUserSelect != StyleUserSelect::None;
}

static bool SelectionDescendToKids(nsIFrame* aFrame) {
  StyleUserSelect style = aFrame->StyleUIReset()->mUserSelect;
  nsIFrame* parent = aFrame->GetParent();
  // If we are only near (not directly over) then don't traverse
  // frames with independent selection (e.g. text and list controls)
  // unless we're already inside such a frame (see bug 268497).  Note that this
  // prevents any of the users of this method from entering form controls.
  // XXX We might want some way to allow using the up-arrow to go into a form
  // control, but the focus didn't work right anyway; it'd probably be enough
  // if the left and right arrows could enter textboxes (which I don't believe
  // they can at the moment)
  return !aFrame->IsGeneratedContentFrame() && style != StyleUserSelect::All &&
         style != StyleUserSelect::None &&
         ((parent->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION) ||
          !(aFrame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION));
}

static FrameTarget GetSelectionClosestFrameForChild(nsIFrame* aChild,
                                                    const nsPoint& aPoint,
                                                    uint32_t aFlags) {
  nsIFrame* parent = aChild->GetParent();
  if (SelectionDescendToKids(aChild)) {
    nsPoint pt = aPoint - aChild->GetOffsetTo(parent);
    return GetSelectionClosestFrame(aChild, pt, aFlags);
  }
  return FrameTarget(aChild, false, false);
}

// When the cursor needs to be at the beginning of a block, it shouldn't be
// before the first child.  A click on a block whose first child is a block
// should put the cursor in the child.  The cursor shouldn't be between the
// blocks, because that's not where it's expected.
// Note that this method is guaranteed to succeed.
static FrameTarget DrillDownToSelectionFrame(nsIFrame* aFrame, bool aEndFrame,
                                             uint32_t aFlags) {
  if (SelectionDescendToKids(aFrame)) {
    nsIFrame* result = nullptr;
    nsIFrame* frame = aFrame->PrincipalChildList().FirstChild();
    if (!aEndFrame) {
      while (frame && (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty()))
        frame = frame->GetNextSibling();
      if (frame) result = frame;
    } else {
      // Because the frame tree is singly linked, to find the last frame,
      // we have to iterate through all the frames
      // XXX I have a feeling this could be slow for long blocks, although
      //     I can't find any slowdowns
      while (frame) {
        if (!frame->IsEmpty() && SelfIsSelectable(frame, aFlags))
          result = frame;
        frame = frame->GetNextSibling();
      }
    }
    if (result) return DrillDownToSelectionFrame(result, aEndFrame, aFlags);
  }
  // If the current frame has no targetable children, target the current frame
  return FrameTarget(aFrame, true, aEndFrame);
}

// This method finds the closest valid FrameTarget on a given line; if there is
// no valid FrameTarget on the line, it returns a null FrameTarget
static FrameTarget GetSelectionClosestFrameForLine(
    nsBlockFrame* aParent, nsBlockFrame::LineIterator aLine,
    const nsPoint& aPoint, uint32_t aFlags) {
  // Account for end of lines (any iterator from the block is valid)
  if (aLine == aParent->LinesEnd())
    return DrillDownToSelectionFrame(aParent, true, aFlags);
  nsIFrame* frame = aLine->mFirstChild;
  nsIFrame* closestFromIStart = nullptr;
  nsIFrame* closestFromIEnd = nullptr;
  nscoord closestIStart = aLine->IStart(), closestIEnd = aLine->IEnd();
  WritingMode wm = aLine->mWritingMode;
  LogicalPoint pt(wm, aPoint, aLine->mContainerSize);
  bool canSkipBr = false;
  bool lastFrameWasEditable = false;
  for (int32_t n = aLine->GetChildCount(); n;
       --n, frame = frame->GetNextSibling()) {
    // Skip brFrames. Can only skip if the line contains at least
    // one selectable and non-empty frame before. Also, avoid skipping brs if
    // the previous thing had a different editableness than us, since then we
    // may end up not being able to select after it if the br is the last thing
    // on the line.
    if (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty() ||
        (canSkipBr && frame->IsBrFrame() &&
         lastFrameWasEditable == frame->GetContent()->IsEditable())) {
      continue;
    }
    canSkipBr = true;
    lastFrameWasEditable =
        frame->GetContent() && frame->GetContent()->IsEditable();
    LogicalRect frameRect =
        LogicalRect(wm, frame->GetRect(), aLine->mContainerSize);
    if (pt.I(wm) >= frameRect.IStart(wm)) {
      if (pt.I(wm) < frameRect.IEnd(wm)) {
        return GetSelectionClosestFrameForChild(frame, aPoint, aFlags);
      }
      if (frameRect.IEnd(wm) >= closestIStart) {
        closestFromIStart = frame;
        closestIStart = frameRect.IEnd(wm);
      }
    } else {
      if (frameRect.IStart(wm) <= closestIEnd) {
        closestFromIEnd = frame;
        closestIEnd = frameRect.IStart(wm);
      }
    }
  }
  if (!closestFromIStart && !closestFromIEnd) {
    // We should only get here if there are no selectable frames on a line
    // XXX Do we need more elaborate handling here?
    return FrameTarget::Null();
  }
  if (closestFromIStart &&
      (!closestFromIEnd ||
       (abs(pt.I(wm) - closestIStart) <= abs(pt.I(wm) - closestIEnd)))) {
    return GetSelectionClosestFrameForChild(closestFromIStart, aPoint, aFlags);
  }
  return GetSelectionClosestFrameForChild(closestFromIEnd, aPoint, aFlags);
}

// This method is for the special handling we do for block frames; they're
// special because they represent paragraphs and because they are organized
// into lines, which have bounds that are not stored elsewhere in the
// frame tree.  Returns a null FrameTarget for frames which are not
// blocks or blocks with no lines except editable one.
static FrameTarget GetSelectionClosestFrameForBlock(nsIFrame* aFrame,
                                                    const nsPoint& aPoint,
                                                    uint32_t aFlags) {
  nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(aFrame);  // used only for QI
  if (!bf) return FrameTarget::Null();

  // This code searches for the correct line
  nsBlockFrame::LineIterator end = bf->LinesEnd();
  nsBlockFrame::LineIterator curLine = bf->LinesBegin();
  nsBlockFrame::LineIterator closestLine = end;

  if (curLine != end) {
    // Convert aPoint into a LogicalPoint in the writing-mode of this block
    WritingMode wm = curLine->mWritingMode;
    LogicalPoint pt(wm, aPoint, curLine->mContainerSize);
    do {
      // Check to see if our point lies within the line's block-direction bounds
      nscoord BCoord = pt.B(wm) - curLine->BStart();
      nscoord BSize = curLine->BSize();
      if (BCoord >= 0 && BCoord < BSize) {
        closestLine = curLine;
        break;  // We found the line; stop looking
      }
      if (BCoord < 0) break;
      ++curLine;
    } while (curLine != end);

    if (closestLine == end) {
      nsBlockFrame::LineIterator prevLine = curLine.prev();
      nsBlockFrame::LineIterator nextLine = curLine;
      // Avoid empty lines
      while (nextLine != end && nextLine->IsEmpty()) ++nextLine;
      while (prevLine != end && prevLine->IsEmpty()) --prevLine;

      // This hidden pref dictates whether a point above or below all lines
      // comes up with a line or the beginning or end of the frame; 0 on
      // Windows, 1 on other platforms by default at the writing of this code
      int32_t dragOutOfFrame =
          Preferences::GetInt("browser.drag_out_of_frame_style");

      if (prevLine == end) {
        if (dragOutOfFrame == 1 || nextLine == end)
          return DrillDownToSelectionFrame(aFrame, false, aFlags);
        closestLine = nextLine;
      } else if (nextLine == end) {
        if (dragOutOfFrame == 1)
          return DrillDownToSelectionFrame(aFrame, true, aFlags);
        closestLine = prevLine;
      } else {  // Figure out which line is closer
        if (pt.B(wm) - prevLine->BEnd() < nextLine->BStart() - pt.B(wm))
          closestLine = prevLine;
        else
          closestLine = nextLine;
      }
    }
  }

  do {
    FrameTarget target =
        GetSelectionClosestFrameForLine(bf, closestLine, aPoint, aFlags);
    if (!target.IsNull()) return target;
    ++closestLine;
  } while (closestLine != end);

  // Fall back to just targeting the last targetable place
  return DrillDownToSelectionFrame(aFrame, true, aFlags);
}

// GetSelectionClosestFrame is the helper function that calculates the closest
// frame to the given point.
// It doesn't completely account for offset styles, so needs to be used in
// restricted environments.
// Cannot handle overlapping frames correctly, so it should receive the output
// of GetFrameForPoint
// Guaranteed to return a valid FrameTarget
static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame,
                                            const nsPoint& aPoint,
                                            uint32_t aFlags) {
  {
    // Handle blocks; if the frame isn't a block, the method fails
    FrameTarget target =
        GetSelectionClosestFrameForBlock(aFrame, aPoint, aFlags);
    if (!target.IsNull()) return target;
  }

  nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();

  if (kid) {
    // Go through all the child frames to find the closest one
    nsIFrame::FrameWithDistance closest = {nullptr, nscoord_MAX, nscoord_MAX};
    for (; kid; kid = kid->GetNextSibling()) {
      if (!SelfIsSelectable(kid, aFlags) || kid->IsEmpty()) continue;

      kid->FindCloserFrameForSelection(aPoint, &closest);
    }
    if (closest.mFrame) {
      if (nsSVGUtils::IsInSVGTextSubtree(closest.mFrame))
        return FrameTarget(closest.mFrame, false, false);
      return GetSelectionClosestFrameForChild(closest.mFrame, aPoint, aFlags);
    }
  }
  return FrameTarget(aFrame, false, false);
}

static nsIFrame::ContentOffsets OffsetsForSingleFrame(nsIFrame* aFrame,
                                                      const nsPoint& aPoint) {
  nsIFrame::ContentOffsets offsets;
  FrameContentRange range = GetRangeForFrame(aFrame);
  offsets.content = range.content;
  // If there are continuations (meaning it's not one rectangle), this is the
  // best this function can do
  if (aFrame->GetNextContinuation() || aFrame->GetPrevContinuation()) {
    offsets.offset = range.start;
    offsets.secondaryOffset = range.end;
    offsets.associate = CARET_ASSOCIATE_AFTER;
    return offsets;
  }

  // Figure out whether the offsets should be over, after, or before the frame
  nsRect rect(nsPoint(0, 0), aFrame->GetSize());

  bool isBlock = aFrame->GetDisplay() != StyleDisplay::Inline;
  bool isRtl =
      (aFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL);
  if ((isBlock && rect.y < aPoint.y) ||
      (!isBlock && ((isRtl && rect.x + rect.width / 2 > aPoint.x) ||
                    (!isRtl && rect.x + rect.width / 2 < aPoint.x)))) {
    offsets.offset = range.end;
    if (rect.Contains(aPoint))
      offsets.secondaryOffset = range.start;
    else
      offsets.secondaryOffset = range.end;
  } else {
    offsets.offset = range.start;
    if (rect.Contains(aPoint))
      offsets.secondaryOffset = range.end;
    else
      offsets.secondaryOffset = range.start;
  }
  offsets.associate = offsets.offset == range.start ? CARET_ASSOCIATE_AFTER
                                                    : CARET_ASSOCIATE_BEFORE;
  return offsets;
}

static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) {
  nsIFrame* adjustedFrame = aFrame;
  for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
    // These are the conditions that make all children not able to handle
    // a cursor.
    StyleUserSelect userSelect = frame->StyleUIReset()->mUserSelect;
    if (userSelect != StyleUserSelect::Auto &&
        userSelect != StyleUserSelect::All) {
      break;
    }
    if (userSelect == StyleUserSelect::All ||
        frame->IsGeneratedContentFrame()) {
      adjustedFrame = frame;
    }
  }
  return adjustedFrame;
}

nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint(
    const nsPoint& aPoint, uint32_t aFlags) {
  nsIFrame* adjustedFrame;
  if (aFlags & IGNORE_SELECTION_STYLE) {
    adjustedFrame = this;
  } else {
    // This section of code deals with special selection styles.  Note that
    // -moz-all exists, even though it doesn't need to be explicitly handled.
    //
    // The offset is forced not to end up in generated content; content offsets
    // cannot represent content outside of the document's content tree.

    adjustedFrame = AdjustFrameForSelectionStyles(this);

    // -moz-user-select: all needs special handling, because clicking on it
    // should lead to the whole frame being selected
    if (adjustedFrame->StyleUIReset()->mUserSelect == StyleUserSelect::All) {
      nsPoint adjustedPoint = aPoint + this->GetOffsetTo(adjustedFrame);
      return OffsetsForSingleFrame(adjustedFrame, adjustedPoint);
    }

    // For other cases, try to find a closest frame starting from the parent of
    // the unselectable frame
    if (adjustedFrame != this) adjustedFrame = adjustedFrame->GetParent();
  }

  nsPoint adjustedPoint = aPoint + this->GetOffsetTo(adjustedFrame);

  FrameTarget closest =
      GetSelectionClosestFrame(adjustedFrame, adjustedPoint, aFlags);

  // If the correct offset is at one end of a frame, use offset-based
  // calculation method
  if (closest.frameEdge) {
    ContentOffsets offsets;
    FrameContentRange range = GetRangeForFrame(closest.frame);
    offsets.content = range.content;
    if (closest.afterFrame)
      offsets.offset = range.end;
    else
      offsets.offset = range.start;
    offsets.secondaryOffset = offsets.offset;
    offsets.associate = offsets.offset == range.start ? CARET_ASSOCIATE_AFTER
                                                      : CARET_ASSOCIATE_BEFORE;
    return offsets;
  }

  nsPoint pt;
  if (closest.frame != this) {
    if (nsSVGUtils::IsInSVGTextSubtree(closest.frame)) {
      pt = nsLayoutUtils::TransformAncestorPointToFrame(closest.frame, aPoint,
                                                        this);
    } else {
      pt = aPoint - closest.frame->GetOffsetTo(this);
    }
  } else {
    pt = aPoint;
  }
  return static_cast<nsFrame*>(closest.frame)
      ->CalcContentOffsetsFromFramePoint(pt);

  // XXX should I add some kind of offset standardization?
  // consider <b>xxxxx</b><i>zzzzz</i>; should any click between the last
  // x and first z put the cursor in the same logical position in addition
  // to the same visual position?
}

nsIFrame::ContentOffsets nsFrame::CalcContentOffsetsFromFramePoint(
    const nsPoint& aPoint) {
  return OffsetsForSingleFrame(this, aPoint);
}

void nsIFrame::AssociateImage(const nsStyleImage& aImage,
                              nsPresContext* aPresContext,
                              uint32_t aImageLoaderFlags) {
  if (aImage.GetType() != eStyleImageType_Image) {
    return;
  }

  imgRequestProxy* req = aImage.GetImageData();
  if (!req) {
    return;
  }
  mozilla::css::ImageLoader* loader =
      aPresContext->Document()->StyleImageLoader();

  // If this fails there's not much we can do ...
  loader->AssociateRequestToFrame(req, this, aImageLoaderFlags);
}

nsresult nsFrame::GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) {
  FillCursorInformationFromStyle(StyleUI(), aCursor);
  if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
    // If this is editable, I-beam cursor is better for most elements.
    aCursor.mCursor = (mContent && mContent->IsEditable())
                          ? NS_STYLE_CURSOR_TEXT
                          : NS_STYLE_CURSOR_DEFAULT;
  }
  if (NS_STYLE_CURSOR_TEXT == aCursor.mCursor &&
      GetWritingMode().IsVertical()) {
    // Per CSS UI spec, UA may treat value 'text' as
    // 'vertical-text' for vertical text.
    aCursor.mCursor = NS_STYLE_CURSOR_VERTICAL_TEXT;
  }

  return NS_OK;
}

// Resize and incremental reflow

/* virtual */ void nsFrame::MarkIntrinsicISizesDirty() {
  // This version is meant only for what used to be box-to-block adaptors.
  // It should not be called by other derived classes.
  if (::IsXULBoxWrapped(this)) {
    nsBoxLayoutMetrics* metrics = BoxMetrics();

    SizeNeedsRecalc(metrics->mPrefSize);
    SizeNeedsRecalc(metrics->mMinSize);
    SizeNeedsRecalc(metrics->mMaxSize);
    SizeNeedsRecalc(metrics->mBlockPrefSize);
    SizeNeedsRecalc(metrics->mBlockMinSize);
    CoordNeedsRecalc(metrics->mFlex);
    CoordNeedsRecalc(metrics->mAscent);
  }

  // If we're a flex item, clear our flex-item-specific cached measurements
  // (which likely depended on our now-stale intrinsic isize).
  auto* parentFrame = GetParent();
  if (parentFrame && parentFrame->IsFlexContainerFrame()) {
    nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(this);
  }

  if (GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
    nsFontInflationData::MarkFontInflationDataTextDirty(this);
  }
}

/* virtual */ nscoord nsFrame::GetMinISize(gfxContext* aRenderingContext) {
  nscoord result = 0;
  DISPLAY_MIN_INLINE_SIZE(this, result);
  return result;
}

/* virtual */ nscoord nsFrame::GetPrefISize(gfxContext* aRenderingContext) {
  nscoord result = 0;
  DISPLAY_PREF_INLINE_SIZE(this, result);
  return result;
}

/* virtual */ void nsFrame::AddInlineMinISize(
    gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
  nscoord isize = nsLayoutUtils::IntrinsicForContainer(
      aRenderingContext, this, nsLayoutUtils::MIN_ISIZE);
  aData->DefaultAddInlineMinISize(this, isize);
}

/* virtual */ void nsFrame::AddInlinePrefISize(
    gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) {
  nscoord isize = nsLayoutUtils::IntrinsicForContainer(
      aRenderingContext, this, nsLayoutUtils::PREF_ISIZE);
  aData->DefaultAddInlinePrefISize(isize);
}

void nsIFrame::InlineMinISizeData::DefaultAddInlineMinISize(nsIFrame* aFrame,
                                                            nscoord aISize,
                                                            bool aAllowBreak) {
  auto parent = aFrame->GetParent();
  MOZ_ASSERT(parent, "Must have a parent if we get here!");
  const bool mayBreak = aAllowBreak && !aFrame->CanContinueTextRun() &&
                        !parent->Style()->ShouldSuppressLineBreak() &&
                        parent->StyleText()->WhiteSpaceCanWrap(parent);
  if (mayBreak) {
    OptionallyBreak();
  }
  mTrailingWhitespace = 0;
  mSkipWhitespace = false;
  mCurrentLine += aISize;
  mAtStartOfLine = false;
  if (mayBreak) {
    OptionallyBreak();
  }
}

void nsIFrame::InlinePrefISizeData::DefaultAddInlinePrefISize(nscoord aISize) {
  mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, aISize);
  mTrailingWhitespace = 0;
  mSkipWhitespace = false;
  mLineIsEmpty = false;
}

void nsIFrame::InlineMinISizeData::ForceBreak() {
  mCurrentLine -= mTrailingWhitespace;
  mPrevLines = std::max(mPrevLines, mCurrentLine);
  mCurrentLine = mTrailingWhitespace = 0;

  for (uint32_t i = 0, i_end = mFloats.Length(); i != i_end; ++i) {
    nscoord float_min = mFloats[i].Width();
    if (float_min > mPrevLines) mPrevLines = float_min;
  }
  mFloats.Clear();
  mSkipWhitespace = true;
}

void nsIFrame::InlineMinISizeData::OptionallyBreak(nscoord aHyphenWidth) {
  // If we can fit more content into a smaller width by staying on this
  // line (because we're still at a negative offset due to negative
  // text-indent or negative margin), don't break.  Otherwise, do the
  // same as ForceBreak.  it doesn't really matter when we accumulate
  // floats.
  if (mCurrentLine + aHyphenWidth < 0 || mAtStartOfLine) return;
  mCurrentLine += aHyphenWidth;
  ForceBreak();
}

void nsIFrame::InlinePrefISizeData::ForceBreak(StyleClear aBreakType) {
  MOZ_ASSERT(aBreakType == StyleClear::None || aBreakType == StyleClear::Both ||
                 aBreakType == StyleClear::Left ||
                 aBreakType == StyleClear::Right,
             "Must be a physical break type");

  // If this force break is not clearing any float, we can leave all the
  // floats to the next force break.
  if (mFloats.Length() != 0 && aBreakType != StyleClear::None) {
    // preferred widths accumulated for floats that have already
    // been cleared past
    nscoord floats_done = 0,
            // preferred widths accumulated for floats that have not yet
            // been cleared past
        floats_cur_left = 0, floats_cur_right = 0;

    for (uint32_t i = 0, i_end = mFloats.Length(); i != i_end; ++i) {
      const FloatInfo& floatInfo = mFloats[i];
      const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
      StyleClear breakType = floatDisp->mBreakType;
      if (breakType == StyleClear::Left || breakType == StyleClear::Right ||
          breakType == StyleClear::Both) {
        nscoord floats_cur =
            NSCoordSaturatingAdd(floats_cur_left, floats_cur_right);
        if (floats_cur > floats_done) {
          floats_done = floats_cur;
        }
        if (breakType != StyleClear::Right) {
          floats_cur_left = 0;
        }
        if (breakType != StyleClear::Left) {
          floats_cur_right = 0;
        }
      }

      StyleFloat floatStyle = floatDisp->mFloat;
      nscoord& floats_cur =
          floatStyle == StyleFloat::Left ? floats_cur_left : floats_cur_right;
      nscoord floatWidth = floatInfo.Width();
      // Negative-width floats don't change the available space so they
      // shouldn't change our intrinsic line width either.
      floats_cur = NSCoordSaturatingAdd(floats_cur, std::max(0, floatWidth));
    }

    nscoord floats_cur =
        NSCoordSaturatingAdd(floats_cur_left, floats_cur_right);
    if (floats_cur > floats_done) floats_done = floats_cur;

    mCurrentLine = NSCoordSaturatingAdd(mCurrentLine, floats_done);

    if (aBreakType == StyleClear::Both) {
      mFloats.Clear();
    } else {
      // If the break type does not clear all floats, it means there may
      // be some floats whose isize should contribute to the intrinsic
      // isize of the next line. The code here scans the current mFloats
      // and keeps floats which are not cleared by this break. Note that
      // floats may be cleared directly or indirectly. See below.
      nsTArray<FloatInfo> newFloats;
      MOZ_ASSERT(
          aBreakType == StyleClear::Left || aBreakType == StyleClear::Right,
          "Other values should have been handled in other branches");
      StyleFloat clearFloatType =
          aBreakType == StyleClear::Left ? StyleFloat::Left : StyleFloat::Right;
      // Iterate the array in reverse so that we can stop when there are
      // no longer any floats we need to keep. See below.
      for (FloatInfo& floatInfo : Reversed(mFloats)) {
        const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
        if (floatDisp->mFloat != clearFloatType) {
          newFloats.AppendElement(floatInfo);
        } else {
          // This is a float on the side that this break directly clears
          // which means we're not keeping it in mFloats. However, if
          // this float clears floats on the opposite side (via a value
          // of either 'both' or one of 'left'/'right'), any remaining
          // (earlier) floats on that side would be indirectly cleared
          // as well. Thus, we should break out of this loop and stop
          // considering earlier floats to be kept in mFloats.
          StyleClear floatBreakType = floatDisp->mBreakType;
          if (floatBreakType != aBreakType &&
              floatBreakType != StyleClear::None) {
            break;
          }
        }
      }
      newFloats.Reverse();
      mFloats = std::move(newFloats);
    }
  }

  mCurrentLine =
      NSCoordSaturatingSubtract(mCurrentLine, mTrailingWhitespace, nscoord_MAX);
  mPrevLines = std::max(mPrevLines, mCurrentLine);
  mCurrentLine = mTrailingWhitespace = 0;
  mSkipWhitespace = true;
  mLineIsEmpty = true;
}

static nscoord ResolveMargin(const nsStyleCoord& aStyle,
                             nscoord aPercentageBasis) {
  if (aStyle.GetUnit() == eStyleUnit_Auto) {
    return nscoord(0);
  }
  return nsLayoutUtils::ResolveToLength<false>(aStyle, aPercentageBasis);
}

static nscoord ResolvePadding(const nsStyleCoord& aStyle,
                              nscoord aPercentageBasis) {
  return nsLayoutUtils::ResolveToLength<true>(aStyle, aPercentageBasis);
}

static nsIFrame::IntrinsicISizeOffsetData IntrinsicSizeOffsets(
    nsIFrame* aFrame, nscoord aPercentageBasis, bool aForISize) {
  nsIFrame::IntrinsicISizeOffsetData result;
  WritingMode wm = aFrame->GetWritingMode();
  const auto& margin = aFrame->StyleMargin()->mMargin;
  bool verticalAxis = aForISize == wm.IsVertical();
  if (verticalAxis) {
    result.hMargin += ResolveMargin(margin.GetTop(), aPercentageBasis);
    result.hMargin += ResolveMargin(margin.GetBottom(), aPercentageBasis);
  } else {
    result.hMargin += ResolveMargin(margin.GetLeft(), aPercentageBasis);
    result.hMargin += ResolveMargin(margin.GetRight(), aPercentageBasis);
  }

  const auto& padding = aFrame->StylePadding()->mPadding;
  if (verticalAxis) {
    result.hPadding += ResolvePadding(padding.GetTop(), aPercentageBasis);
    result.hPadding += ResolvePadding(padding.GetBottom(), aPercentageBasis);
  } else {
    result.hPadding += ResolvePadding(padding.GetLeft(), aPercentageBasis);
    result.hPadding += ResolvePadding(padding.GetRight(), aPercentageBasis);
  }

  const nsStyleBorder* styleBorder = aFrame->StyleBorder();
  if (verticalAxis) {
    result.hBorder += styleBorder->GetComputedBorderWidth(eSideTop);
    result.hBorder += styleBorder->GetComputedBorderWidth(eSideBottom);
  } else {
    result.hBorder += styleBorder->GetComputedBorderWidth(eSideLeft);
    result.hBorder += styleBorder->GetComputedBorderWidth(eSideRight);
  }

  const nsStyleDisplay* disp = aFrame->StyleDisplay();
  if (aFrame->IsThemed(disp)) {
    nsPresContext* presContext = aFrame->PresContext();

    LayoutDeviceIntMargin border = presContext->GetTheme()->GetWidgetBorder(
        presContext->DeviceContext(), aFrame, disp->mAppearance);
    result.hBorder = presContext->DevPixelsToAppUnits(
        verticalAxis ? border.TopBottom() : border.LeftRight());

    LayoutDeviceIntMargin padding;
    if (presContext->GetTheme()->GetWidgetPadding(presContext->DeviceContext(),
                                                  aFrame, disp->mAppearance,
                                                  &padding)) {
      result.hPadding = presContext->DevPixelsToAppUnits(
          verticalAxis ? padding.TopBottom() : padding.LeftRight());
    }
  }
  return result;
}

/* virtual */ nsIFrame::IntrinsicISizeOffsetData nsFrame::IntrinsicISizeOffsets(
    nscoord aPercentageBasis) {
  return IntrinsicSizeOffsets(this, aPercentageBasis, true);
}

nsIFrame::IntrinsicISizeOffsetData nsIFrame::IntrinsicBSizeOffsets(
    nscoord aPercentageBasis) {
  return IntrinsicSizeOffsets(this, aPercentageBasis, false);
}

/* virtual */ IntrinsicSize nsFrame::GetIntrinsicSize() {
  return IntrinsicSize();  // default is width/height set to eStyleUnit_None
}

/* virtual */ nsSize nsFrame::GetIntrinsicRatio() { return nsSize(0, 0); }

/* virtual */
LogicalSize nsFrame::ComputeSize(gfxContext* aRenderingContext, WritingMode aWM,
                                 const LogicalSize& aCBSize,
                                 nscoord aAvailableISize,
                                 const LogicalSize& aMargin,
                                 const LogicalSize& aBorder,
                                 const LogicalSize& aPadding,
                                 ComputeSizeFlags aFlags) {
  MOZ_ASSERT(GetIntrinsicRatio() == nsSize(0, 0),
             "Please override this method and call "
             "nsFrame::ComputeSizeWithIntrinsicDimensions instead.");
  LogicalSize result =
      ComputeAutoSize(aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
                      aBorder, aPadding, aFlags);
  const nsStylePosition* stylePos = StylePosition();

  LogicalSize boxSizingAdjust(aWM);
  if (stylePos->mBoxSizing == StyleBoxSizing::Border) {
    boxSizingAdjust = aBorder + aPadding;
  }
  nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) + aBorder.ISize(aWM) +
                                       aPadding.ISize(aWM) -
                                       boxSizingAdjust.ISize(aWM);

  const nsStyleCoord* inlineStyleCoord = &stylePos->ISize(aWM);
  const nsStyleCoord* blockStyleCoord = &stylePos->BSize(aWM);

  auto parentFrame = GetParent();
  auto alignCB = parentFrame;
  bool isGridItem = parentFrame && parentFrame->IsGridContainerFrame() &&
                    !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
  if (parentFrame && parentFrame->IsTableWrapperFrame() && IsTableFrame()) {
    // An inner table frame is sized as a grid item if its table wrapper is,
    // because they actually have the same CB (the wrapper's CB).
    // @see ReflowInput::InitCBReflowInput
    auto tableWrapper = GetParent();
    auto grandParent = tableWrapper->GetParent();
    isGridItem = (grandParent->IsGridContainerFrame() &&
                  !(tableWrapper->GetStateBits() & NS_FRAME_OUT_OF_FLOW));
    if (isGridItem) {
      // When resolving justify/align-self below, we want to use the grid
      // container's justify/align-items value and WritingMode.
      alignCB = grandParent;
    }
  }
  bool isFlexItem =
      parentFrame && parentFrame->IsFlexContainerFrame() &&
      !parentFrame->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX) &&
      !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
  // This variable only gets set (and used) if isFlexItem is true.  It
  // indicates which axis (in this frame's own WM) corresponds to its
  // flex container's main axis.
  LogicalAxis flexMainAxis =
      eLogicalAxisInline;  // (init to make valgrind happy)
  if (isFlexItem) {
    // Flex items use their "flex-basis" property in place of their main-size
    // property for sizing purposes, *unless* they have "flex-basis:auto", in
    // which case they use their main-size property after all.
    flexMainAxis = nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)
                       ? eLogicalAxisInline
                       : eLogicalAxisBlock;

    // NOTE: The logic here should match the similar chunk for updating
    // mainAxisCoord in nsFrame::ComputeSizeWithIntrinsicDimensions() (aside
    // from using a different dummy value in the IsUsedFlexBasisContent() case).
    const nsStyleCoord* flexBasis = &(stylePos->mFlexBasis);
    auto& mainAxisCoord =
        (flexMainAxis == eLogicalAxisInline ? inlineStyleCoord
                                            : blockStyleCoord);

    // NOTE: If we're a table-wrapper frame, we skip this clause and just stick
    // with 'main-size:auto' behavior (which -- unlike 'content'
    // i.e. 'max-content' -- will give us the ability to honor percent sizes on
    // our table-box child when resolving the flex base size). The flexbox spec
    // doesn't call for this special case, but webcompat & regression-avoidance
    // seems to require it, for the time being... Tables sure are special.
    if (nsFlexContainerFrame::IsUsedFlexBasisContent(flexBasis,
                                                     mainAxisCoord) &&
        MOZ_LIKELY(!IsTableWrapperFrame())) {
      static const nsStyleCoord maxContStyleCoord(NS_STYLE_WIDTH_MAX_CONTENT,
                                                  eStyleUnit_Enumerated);
      mainAxisCoord = &maxContStyleCoord;
      // (Note: if our main axis is the block axis, then this 'max-content'
      // value will be treated like 'auto', via the IsAutoBSize() call below.)
    } else if (flexBasis->GetUnit() != eStyleUnit_Auto) {
      // For all other non-'auto' flex-basis values, we just swap in the
      // flex-basis itself for the main-size property.
      mainAxisCoord = flexBasis;
    }  // else: flex-basis is 'auto', which is deferring to some explicit value
       // in mainAxisCoord. So we proceed w/o touching mainAxisCoord.
  }

  // Compute inline-axis size
  if (inlineStyleCoord->GetUnit() != eStyleUnit_Auto) {
    result.ISize(aWM) = ComputeISizeValue(
        aRenderingContext, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM),
        boxSizingToMarginEdgeISize, *inlineStyleCoord, aFlags);
  } else if (MOZ_UNLIKELY(isGridItem) && !IS_TRUE_OVERFLOW_CONTAINER(this)) {
    // 'auto' inline-size for grid-level box - fill the CB for 'stretch' /
    // 'normal' and clamp it to the CB if requested:
    bool stretch = false;
    if (!(aFlags & nsIFrame::eShrinkWrap) &&
        !StyleMargin()->HasInlineAxisAuto(aWM)) {
      auto inlineAxisAlignment =
          aWM.IsOrthogonalTo(alignCB->GetWritingMode())
              ? StylePosition()->UsedAlignSelf(alignCB->Style())
              : StylePosition()->UsedJustifySelf(alignCB->Style());
      stretch = inlineAxisAlignment == NS_STYLE_ALIGN_NORMAL ||
                inlineAxisAlignment == NS_STYLE_ALIGN_STRETCH;
    }
    if (stretch || (aFlags & ComputeSizeFlags::eIClampMarginBoxMinSize)) {
      auto iSizeToFillCB =
          std::max(nscoord(0), aCBSize.ISize(aWM) - aPadding.ISize(aWM) -
                                   aBorder.ISize(aWM) - aMargin.ISize(aWM));
      if (stretch || result.ISize(aWM) > iSizeToFillCB) {
        result.ISize(aWM) = iSizeToFillCB;
      }
    }
  }

  // Flex items ignore their min & max sizing properties in their
  // flex container's main-axis.  (Those properties get applied later in
  // the flexbox algorithm.)
  const nsStyleCoord& maxISizeCoord = stylePos->MaxISize(aWM);
  nscoord maxISize = NS_UNCONSTRAINEDSIZE;
  if (maxISizeCoord.GetUnit() != eStyleUnit_None &&
      !(isFlexItem && flexMainAxis == eLogicalAxisInline)) {
    maxISize = ComputeISizeValue(
        aRenderingContext, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM),
        boxSizingToMarginEdgeISize, maxISizeCoord, aFlags);
    result.ISize(aWM) = std::min(maxISize, result.ISize(aWM));
  }

  const nsStyleCoord& minISizeCoord = stylePos->MinISize(aWM);
  nscoord minISize;
  if (minISizeCoord.GetUnit() != eStyleUnit_Auto &&
      !(isFlexItem && flexMainAxis == eLogicalAxisInline)) {
    minISize = ComputeISizeValue(
        aRenderingContext, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM),
        boxSizingToMarginEdgeISize, minISizeCoord, aFlags);
  } else if (MOZ_UNLIKELY(aFlags & eIApplyAutoMinSize)) {
    // This implements "Implied Minimum Size of Grid Items".
    // https://drafts.csswg.org/css-grid/#min-size-auto
    minISize = std::min(maxISize, GetMinISize(aRenderingContext));
    if (inlineStyleCoord->IsCoordPercentCalcUnit()) {
      minISize = std::min(minISize, result.ISize(aWM));
    } else if (aFlags & eIClampMarginBoxMinSize) {
      // "if the grid item spans only grid tracks that have a fixed max track
      // sizing function, its automatic minimum size in that dimension is
      // further clamped to less than or equal to the size necessary to fit
      // its margin box within the resulting grid area (flooring at zero)"
      // https://drafts.csswg.org/css-grid/#min-size-auto
      auto maxMinISize =
          std::max(nscoord(0), aCBSize.ISize(aWM) - aPadding.ISize(aWM) -
                                   aBorder.ISize(aWM) - aMargin.ISize(aWM));
      minISize = std::min(minISize, maxMinISize);
    }
  } else {
    // Treat "min-width: auto" as 0.
    // NOTE: Technically, "auto" is supposed to behave like "min-content" on
    // flex items. However, we don't need to worry about that here, because
    // flex items' min-sizes are intentionally ignored until the flex
    // container explicitly considers them during space distribution.
    minISize = 0;
  }
  result.ISize(aWM) = std::max(minISize, result.ISize(aWM));

  // Compute block-axis size
  // (but not if we have auto bsize or if we received the "eUseAutoBSize"
  // flag -- then, we'll just stick with the bsize that we already calculated
  // in the initial ComputeAutoSize() call.)
  if (!(aFlags & nsIFrame::eUseAutoBSize)) {
    if (!nsLayoutUtils::IsAutoBSize(*blockStyleCoord, aCBSize.BSize(aWM))) {
      result.BSize(aWM) = nsLayoutUtils::ComputeBSizeValue(
          aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM), *blockStyleCoord);
    } else if (MOZ_UNLIKELY(isGridItem) && blockStyleCoord->IsAutoOrEnum() &&
               !IS_TRUE_OVERFLOW_CONTAINER(this)) {
      auto cbSize = aCBSize.BSize(aWM);
      if (cbSize != NS_AUTOHEIGHT) {
        // 'auto' block-size for grid-level box - fill the CB for 'stretch' /
        // 'normal' and clamp it to the CB if requested:
        bool stretch = false;
        if (!StyleMargin()->HasBlockAxisAuto(aWM)) {
          auto blockAxisAlignment =
              !aWM.IsOrthogonalTo(alignCB->GetWritingMode())
                  ? StylePosition()->UsedAlignSelf(alignCB->Style())
                  : StylePosition()->UsedJustifySelf(alignCB->Style());
          stretch = blockAxisAlignment == NS_STYLE_ALIGN_NORMAL ||
                    blockAxisAlignment == NS_STYLE_ALIGN_STRETCH;
        }
        if (stretch || (aFlags & ComputeSizeFlags::eBClampMarginBoxMinSize)) {
          auto bSizeToFillCB =
              std::max(nscoord(0), cbSize - aPadding.BSize(aWM) -
                                       aBorder.BSize(aWM) - aMargin.BSize(aWM));
          if (stretch || (result.BSize(aWM) != NS_AUTOHEIGHT &&
                          result.BSize(aWM) > bSizeToFillCB)) {
            result.BSize(aWM) = bSizeToFillCB;
          }
        }
      }
    }
  }

  const nsStyleCoord& maxBSizeCoord = stylePos->MaxBSize(aWM);

  if (result.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
    if (!nsLayoutUtils::IsAutoBSize(maxBSizeCoord, aCBSize.BSize(aWM)) &&
        !(isFlexItem && flexMainAxis == eLogicalAxisBlock)) {
      nscoord maxBSize = nsLayoutUtils::ComputeBSizeValue(
          aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM), maxBSizeCoord);
      result.BSize(aWM) = std::min(maxBSize, result.BSize(aWM));
    }

    const nsStyleCoord& minBSizeCoord = stylePos->MinBSize(aWM);

    if (!nsLayoutUtils::IsAutoBSize(minBSizeCoord, aCBSize.BSize(aWM)) &&
        !(isFlexItem && flexMainAxis == eLogicalAxisBlock)) {
      nscoord minBSize = nsLayoutUtils::ComputeBSizeValue(
          aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM), minBSizeCoord);
      result.BSize(aWM) = std::max(minBSize, result.BSize(aWM));
    }
  }

  const nsStyleDisplay* disp = StyleDisplay();
  if (IsThemed(disp)) {
    LayoutDeviceIntSize widget;
    bool canOverride = true;
    nsPresContext* presContext = PresContext();
    presContext->GetTheme()->GetMinimumWidgetSize(
        presContext, this, disp->mAppearance, &widget, &canOverride);

    // Convert themed widget's physical dimensions to logical coords
    LogicalSize size(aWM,
                     nsSize(presContext->DevPixelsToAppUnits(widget.width),
                            presContext->DevPixelsToAppUnits(widget.height)));

    // GMWS() returns border-box; we need content-box
    size.ISize(aWM) -= aBorder.ISize(aWM) + aPadding.ISize(aWM);
    size.BSize(aWM) -= aBorder.BSize(aWM) + aPadding.BSize(aWM);

    if (size.BSize(aWM) > result.BSize(aWM) || !canOverride) {
      result.BSize(aWM) = size.BSize(aWM);
    }
    if (size.ISize(aWM) > result.ISize(aWM) || !canOverride) {
      result.ISize(aWM) = size.ISize(aWM);
    }
  }

  result.ISize(aWM) = std::max(0, result.ISize(aWM));
  result.BSize(aWM) = std::max(0, result.BSize(aWM));

  return result;
}

LogicalSize nsFrame::ComputeSizeWithIntrinsicDimensions(
    gfxContext* aRenderingContext, WritingMode aWM,
    const IntrinsicSize& aIntrinsicSize, nsSize aIntrinsicRatio,
    const LogicalSize& aCBSize, const LogicalSize& aMargin,
    const LogicalSize& aBorder, const LogicalSize& aPadding,
    ComputeSizeFlags aFlags) {
  const nsStylePosition* stylePos = StylePosition();
  const nsStyleCoord* inlineStyleCoord = &stylePos->ISize(aWM);
  const nsStyleCoord* blockStyleCoord = &stylePos->BSize(aWM);
  auto* parentFrame = GetParent();
  const bool isGridItem = parentFrame && parentFrame->IsGridContainerFrame() &&
                          !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
  const bool isFlexItem =
      parentFrame && parentFrame->IsFlexContainerFrame() &&
      !parentFrame->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX) &&
      !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
  // This variable only gets set (and used) if isFlexItem is true.  It
  // indicates which axis (in this frame's own WM) corresponds to its
  // flex container's main axis.
  LogicalAxis flexMainAxis =
      eLogicalAxisInline;  // (init to make valgrind happy)
  Maybe<nsStyleCoord> imposedMainSizeStyleCoord;

  // If this is a flex item, and we're measuring its cross size after flexing
  // to resolve its main size, then we need to use the resolved main size
  // that the container provides to us *instead of* the main-size coordinate
  // from our style struct. (Otherwise, we'll be using an irrelevant value in
  // the aspect-ratio calculations below.)
  if (isFlexItem) {
    flexMainAxis = nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)
                       ? eLogicalAxisInline
                       : eLogicalAxisBlock;

    // If FlexItemMainSizeOverride frame-property is set, then that means the
    // flex container is imposing a main-size on this flex item for it to use
    // as its size in the container's main axis.
    bool didImposeMainSize;
    nscoord imposedMainSize =
        GetProperty(nsIFrame::FlexItemMainSizeOverride(), &didImposeMainSize);
    if (didImposeMainSize) {
      imposedMainSizeStyleCoord.emplace(imposedMainSize,
                                        nsStyleCoord::CoordConstructor);
      if (flexMainAxis == eLogicalAxisInline) {
        inlineStyleCoord = imposedMainSizeStyleCoord.ptr();
      } else {
        blockStyleCoord = imposedMainSizeStyleCoord.ptr();
      }

    } else {
      // Flex items use their "flex-basis" property in place of their main-size
      // property (e.g. "width") for sizing purposes, *unless* they have
      // "flex-basis:auto", in which case they use their main-size property
      // after all.
      // NOTE: The logic here should match the similar chunk for updating
      // mainAxisCoord in nsFrame::ComputeSize() (aside from using a different
      // dummy value in the IsUsedFlexBasisContent() case).
      const nsStyleCoord* flexBasis = &(stylePos->mFlexBasis);
      auto& mainAxisCoord =
          (flexMainAxis == eLogicalAxisInline ? inlineStyleCoord
                                              : blockStyleCoord);

      if (nsFlexContainerFrame::IsUsedFlexBasisContent(flexBasis,
                                                       mainAxisCoord)) {
        // If we get here, we're resolving the flex base size for a flex item,
        // and we fall into the flexbox spec section 9.2 step 3, substep C (if
        // we have a definite cross size) or E (if not). And specifically:
        //
        // * If we have a definite cross size, we're supposed to resolve our
        //   main-size based on that and our intrinsic ratio.
        // * Otherwise, we're supposed to produce our max-content size.
        //
        // Conveniently, we can handle both of those scenarios (regardless of
        // which substep we fall into) by using the 'auto' keyword for our
        // main-axis coordinate here. (This makes sense, because the spec is
        // effectively trying to produce the 'auto' sizing behavior).
        static const nsStyleCoord autoStyleCoord(eStyleUnit_Auto);
        mainAxisCoord = &autoStyleCoord;
      } else if (flexBasis->GetUnit() != eStyleUnit_Auto) {
        // For all other non-'auto' flex-basis values, we just swap in the
        // flex-basis itself for the main-size property.
        mainAxisCoord = flexBasis;
      }  // else: flex-basis is 'auto', which is deferring to some explicit
         // value in mainAxisCoord. So we proceed w/o touching mainAxisCoord.
    }
  }

  // Handle intrinsic sizes and their interaction with
  // {min-,max-,}{width,height} according to the rules in
  // http://www.w3.org/TR/CSS21/visudet.html#min-max-widths

  // Note: throughout the following section of the function, I avoid
  // a * (b / c) because of its reduced accuracy relative to a * b / c
  // or (a * b) / c (which are equivalent).

  const bool isAutoISize = inlineStyleCoord->GetUnit() == eStyleUnit_Auto;
  const bool isAutoBSize =
      nsLayoutUtils::IsAutoBSize(*blockStyleCoord, aCBSize.BSize(aWM));

  LogicalSize boxSizingAdjust(aWM);
  if (stylePos->mBoxSizing == StyleBoxSizing::Border) {
    boxSizingAdjust = aBorder + aPadding;
  }
  nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) + aBorder.ISize(aWM) +
                                       aPadding.ISize(aWM) -
                                       boxSizingAdjust.ISize(aWM);

  nscoord iSize, minISize, maxISize, bSize, minBSize, maxBSize;
  enum class Stretch {
    // stretch to fill the CB (preserving intrinsic ratio) in the relevant axis
    eStretchPreservingRatio,  // XXX not used yet
    // stretch to fill the CB in the relevant axis
    eStretch,
    // no stretching in the relevant axis
    eNoStretch,
  };
  // just to avoid having to type these out everywhere:
  const auto eStretchPreservingRatio = Stretch::eStretchPreservingRatio;
  const auto eStretch = Stretch::eStretch;
  const auto eNoStretch = Stretch::eNoStretch;

  Stretch stretchI = eNoStretch;  // stretch behavior in the inline axis
  Stretch stretchB = eNoStretch;  // stretch behavior in the block axis

  const bool isVertical = aWM.IsVertical();
  const nsStyleCoord& isizeCoord =
      isVertical ? aIntrinsicSize.height : aIntrinsicSize.width;
  const bool hasIntrinsicISize = isizeCoord.GetUnit() == eStyleUnit_Coord;
  nscoord intrinsicISize;
  if (hasIntrinsicISize) {
    intrinsicISize = std::max(nscoord(0), isizeCoord.GetCoordValue());
  } else {
    NS_ASSERTION(isizeCoord.GetUnit() == eStyleUnit_None, "unexpected unit");
    intrinsicISize = 0;
  }

  const nsStyleCoord& bsizeCoord =
      isVertical ? aIntrinsicSize.width : aIntrinsicSize.height;
  const bool hasIntrinsicBSize = bsizeCoord.GetUnit() == eStyleUnit_Coord;
  nscoord intrinsicBSize;
  if (hasIntrinsicBSize) {
    intrinsicBSize = std::max(nscoord(0), bsizeCoord.GetCoordValue());
  } else {
    NS_ASSERTION(bsizeCoord.GetUnit() == eStyleUnit_None, "unexpected unit");
    intrinsicBSize = 0;
  }

  NS_ASSERTION(aIntrinsicRatio.width >= 0 && aIntrinsicRatio.height >= 0,
               "Intrinsic ratio has a negative component!");
  LogicalSize logicalRatio(aWM, aIntrinsicRatio);

  if (!isAutoISize) {
    iSize = ComputeISizeValue(
        aRenderingContext, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM),
        boxSizingToMarginEdgeISize, *inlineStyleCoord, aFlags);
  } else if (MOZ_UNLIKELY(isGridItem)) {
    MOZ_ASSERT(!IS_TRUE_OVERFLOW_CONTAINER(this));
    // 'auto' inline-size for grid-level box - apply 'stretch' as needed: