layout/generic/nsFrame.cpp
author Daniel Holbert <dholbert@cs.stanford.edu>
Fri, 30 Mar 2018 16:50:49 -0700
changeset 410911 4df15097883c710d5799fe475bd4c927d14b2d5c
parent 410812 10c662d8416e84b44931d767ea1be2f4d0cc92ce
child 410918 d67b34f6abae3a9c8497b326afa35a1ebae7d06e
permissions -rw-r--r--
Bug 1105111 part 3: Add support for 'flex-basis:content' in layout. r=mats BACKGROUND: Early in flex layout, we have to resolve the 'flex-basis' value to produce the "flex base size" (basically, the flex-basis resolved to an absolute length). This resolution happens in two "phases" (which both happen within nsFlexContainer::GenerateFlexItemForChild()): First phase: we try to resolve the flex-basis by creating a ReflowInput for the flex item (which gets us some other things as well). Under the hood, we use the flex-basis when resolving this ReflowInput's main-axis size. The code for this lives in nsFrame::ComputeSize (and in nsFrame::ComputeSizeWithIntrinsicDimensions, via some frame classes' overrides of ComputeSize). Second phase: If the first phase didn't get us a definite size, then that means we have to do reflow to measure the content size & produce a resolved flex base size, which we do via ResolveAutoFlexBasisAndMinSize(). NOTES ON THIS PATCH: To add 'flex-basis:content' support to layout, this patch only needs to modify the first phase discussed above. If it turns out we also have some second-phase work to do (i.e. if we need to do reflow to resolve 'flex-basis:content'), this patch causes that reflow to happen by simply making us use eStyleUnit_Auto in the main axis's nsStyleCoord in the first phase. (And then, if that 'auto' nsStyleCoord really does require reflow, then that first phase will end up producing an unconstrained main-size in the flex item's ReflowInput, which will automatically trigger the second phase.) MozReview-Commit-ID: 2nH4Fh78C81

/* -*- 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/gfx/2D.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/Sprintf.h"

#include "nsCOMPtr.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 "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 "mozilla/Sprintf.h"
#include "nsLayoutUtils.h"
#include "LayoutLogging.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/RestyleManagerInlines.h"
#include "nsInlineFrame.h"
#include "nsIDOMNode.h"
#include "nsISelection.h"
#include "nsISelectionPrivate.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/css/ImageLoader.h"
#include "mozilla/gfx/Tools.h"
#include "nsPrintfCString.h"
#include "ActiveLayerTracker.h"

#include "nsITheme.h"
#include "nsThemeConstants.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);
}

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)
{
  aPresContext->PresShell()->FrameConstructor()
              ->DestroyAnonymousContent(Move(aContent));
}

// Formerly the nsIFrameDebug interface

#ifdef DEBUG
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;
}

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");

static mozilla::LazyLogModule sStyleVerifyTreeLogModuleInfo("styleverifytree");

static uint32_t gStyleVerifyTreeEnable = 0x55;

bool
nsFrame::GetVerifyStyleTreeEnable()
{
  if (gStyleVerifyTreeEnable == 0x55) {
      gStyleVerifyTreeEnable = 0 != (int)((mozilla::LogModule*)sStyleVerifyTreeLogModuleInfo)->Level();
  }
  return gStyleVerifyTreeEnable;
}

void
nsFrame::SetVerifyStyleTreeEnable(bool aEnabled)
{
  gStyleVerifyTreeEnable = aEnabled;
}

#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());
  NS_PRECONDITION(!mContent, "Double-initing a frame?");
  NS_ASSERTION(IsFrameOfType(eDEBUGAllFrames) &&
               !IsFrameOfType(eDEBUGNoFrames),
               "IsFrameOfType implementation that doesn't call base class");

  mContent = aContent;
  mParent = aParent;

  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_MAY_HAVE_GENERATED_CONTENT |
                          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 (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.
  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.AddGeneratedContent(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) {
      nsTArray<nsIFrame*>* modifiedFrames =
        rootFrame->GetProperty(nsIFrame::ModifiedFrameList());
      if (modifiedFrames) {
        MOZ_ASSERT(!modifiedFrames->Contains(this),
                   "A dtor added this frame to ModifiedFrameList");
      }
    }
  }
#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); }
  );
}

void
nsIFrame::AddDisplayItem(nsDisplayItem* aItem)
{
  DisplayItemArray* items = GetProperty(DisplayItems());
  if (!items) {
    items = new DisplayItemArray();
    AddProperty(DisplayItems(), items);
  }
  MOZ_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 (IsFrameModified()) {
    nsIFrame* rootFrame = PresShell()->GetRootFrame();
    MOZ_ASSERT(rootFrame);

    nsTArray<nsIFrame*>* modifiedFrames =
      rootFrame->GetProperty(nsIFrame::ModifiedFrameList());
    MOZ_ASSERT(modifiedFrames);

    for (auto& frame : *modifiedFrames) {
      if (frame == this) {
        frame = nullptr;
        break;
      }
    }
  }

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

    nsTArray<nsIFrame*>* frames =
      rootFrame->GetProperty(nsIFrame::OverriddenDirtyRectFrameList());
    MOZ_ASSERT(frames);

    for (auto& frame : *frames) {
      if (frame == this) {
        frame = nullptr;
        break;
      }
    }
  }
}

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

  if (Type() == LayoutFrameType::Placeholder) {
    // Do not mark placeholder frames modified.
    return;
  }

  nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
  MOZ_ASSERT(displayRoot);

  RetainedDisplayListBuilder* retainedBuilder =
    displayRoot->GetProperty(RetainedDisplayListBuilder::Cached());

  if (!retainedBuilder) {
    return;
  }

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

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

  nsTArray<nsIFrame*>* modifiedFrames =
    rootFrame->GetProperty(nsIFrame::ModifiedFrameList());

  if (!modifiedFrames) {
    modifiedFrames = new nsTArray<nsIFrame*>();
    rootFrame->SetProperty(nsIFrame::ModifiedFrameList(), modifiedFrames);
  }

  if (this == rootFrame) {
    // If this is the root frame, then marking us as needing a display
    // item rebuild implies the same for all our descendents. Clear them
    // all out to reduce the number of modified frames we keep around.
    for (nsIFrame* f : *modifiedFrames) {
      if (f) {
        f->SetFrameIsModified(false);
      }
    }
    modifiedFrames->Clear();
  } else if (modifiedFrames->Length() > gfxPrefs::LayoutRebuildFrameLimit()) {
    // If the list starts getting too big, then just mark the root frame
    // as needing a rebuild.
    rootFrame->MarkNeedsDisplayItemRebuild();
    return;
  }

  modifiedFrames->AppendElement(this);

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

  // 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->GetDependentFrame() == this &&
          !i->HasDeletedFrame()) {
        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);
    }
  }

  // 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);
  this->MarkNeedsDisplayItemRebuild();

  mMayHaveRoundedCorners = true;
}

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)) {
    nsIntMargin result;
    nsPresContext *presContext = PresContext();
    presContext->GetTheme()->GetWidgetBorder(presContext->DeviceContext(),
                                             mutable_this, disp->mAppearance,
                                             &result);
    border.left = presContext->DevPixelsToAppUnits(result.left);
    border.top = presContext->DevPixelsToAppUnits(result.top);
    border.right = presContext->DevPixelsToAppUnits(result.right);
    border.bottom = presContext->DevPixelsToAppUnits(result.bottom);
    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 *presContext = PresContext();
    nsIntMargin widget;
    if (presContext->GetTheme()->GetWidgetPadding(presContext->DeviceContext(),
                                                  mutable_this,
                                                  disp->mAppearance,
                                                  &widget)) {
      padding.top = presContext->DevPixelsToAppUnits(widget.top);
      padding.right = presContext->DevPixelsToAppUnits(widget.right);
      padding.bottom = presContext->DevPixelsToAppUnits(widget.bottom);
      padding.left = presContext->DevPixelsToAppUnits(widget.left);
      return padding;
    }
  }

  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::HasOpacityInternal(float aThreshold,
                             EffectSet* aEffectSet) const
{
  MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument");
  if (StyleEffects()->mOpacity < aThreshold ||
      (StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY)) {
    return true;
  }

  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, 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;
  }

  if (HasOpacity(aEffectSet)) {
    return false;
  }

  const nsStyleEffects* effects = StyleEffects();
  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 = GetInFlowParent();
  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 {
      NS_NOTREACHED("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
{
  if (!GetBorderRadii(aRadii)) {
    return false;
  }
  OutsetBorderRadii(aRadii, GetUsedMargin());
  NS_FOR_CSS_HALF_CORNERS(corner) {
    if (aRadii[corner]) {
      return true;
    }
  }
  return false;
}

bool
nsIFrame::GetPaddingBoxBorderRadii(nscoord aRadii[8]) const
{
  if (!GetBorderRadii(aRadii))
    return false;
  InsetBorderRadii(aRadii, GetUsedBorder());
  NS_FOR_CSS_HALF_CORNERS(corner) {
    if (aRadii[corner])
      return true;
  }
  return false;
}

bool
nsIFrame::GetContentBoxBorderRadii(nscoord aRadii[8]) const
{
  if (!GetBorderRadii(aRadii))
    return false;
  InsetBorderRadii(aRadii, GetUsedBorderAndPadding());
  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
{
  NS_PRECONDITION(aIndex >= 0, "invalid index number");
  return nullptr;
}

void
nsFrame::SetAdditionalComputedStyle(int32_t aIndex,
                                   ComputedStyle* aComputedStyle)
{
  NS_PRECONDITION(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::WebRenderLayerManager* 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 =
    mVisibleRect.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::WebRenderLayerManager* 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::mozSelection, Style());
  return sc.forget();
}

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

void
nsFrame::DisplaySelectionOverlay(nsDisplayListBuilder*   aBuilder,
                                 nsDisplayList*          aList,
                                 uint16_t                aContentType)
{
  if (!IsSelected() || !IsVisibleForPainting(aBuilder)) {
    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(aBuilder))
    return;

  DisplayOutlineUnconditional(aBuilder, aLists);
}

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

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

nscolor
nsIFrame::GetCaretColorAt(int32_t aOffset)
{
  return nsLayoutUtils::GetColor(this, &nsStyleUserInterface::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()->mAppearance) {
    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(aBuilder)) {
    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();
  if (disp->mOverflowClipBoxBlock == NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX &&
      disp->mOverflowClipBoxInline == NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX) {
    clipRect = aFrame->GetPaddingRectRelativeToSelf() +
      aBuilder->ToReferenceFrame(aFrame);
    haveRadii = aFrame->GetPaddingBoxBorderRadii(radii);
  } else {
    // 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) ==
               NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX;
    bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
                                : disp->mOverflowClipBoxBlock) ==
               NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX;
    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);
    // XXX border-radius
  }
  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->Extend3DContext());
  nsIFrame* frame;
  for (frame = aDescendant->GetInFlowParent();
       frame && aAncestor != frame;
       frame = frame->GetInFlowParent()) {
    if (!frame->Extend3DContext()) {
      return false;
    }
  }
  MOZ_ASSERT(frame == aAncestor);
  return true;
}

static bool
ItemParticipatesIn3DContext(nsIFrame* aAncestor, nsDisplayItem* aItem)
{
  nsIFrame* transformFrame;
  if (aItem->GetType() == DisplayItemType::TYPE_TRANSFORM) {
    transformFrame = aItem->Frame();
  } else if (aItem->GetType() == DisplayItemType::TYPE_PERSPECTIVE) {
    transformFrame = static_cast<nsDisplayPerspective*>(aItem)->TransformFrame();
  } else {
    return false;
  }
  if (aAncestor == transformFrame) {
    return true;
  }
  return FrameParticipatesIn3DContext(aAncestor, transformFrame);
}

static void
WrapSeparatorTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                       nsDisplayList* aSource, nsDisplayList* aTarget,
                       int aIndex) {
  if (!aSource->IsEmpty()) {
    nsDisplayTransform *sepIdItem =
      MakeDisplayItem<nsDisplayTransform>(aBuilder, aFrame, aSource,
                                        aBuilder->GetVisibleRect(), Matrix4x4(), aIndex);
    sepIdItem->SetNoExtendContext();
    aTarget->AppendToTop(sepIdItem);
  }
}

// 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,
                       bool aHandleOpacity)
{
  const nsStyleSVGReset* svgReset = aMaskedFrame->StyleSVGReset();

  nsSVGUtils::MaskUsage maskUsage;
  nsSVGUtils::DetermineMaskUsage(aMaskedFrame, aHandleOpacity, 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.shouldApplyBasicShape) {
    Rect result = nsCSSClipPathInstance::GetBoundingRectForBasicShapeClip(
        aMaskedFrame, svgReset->mClipPath);
    combinedClip = Some(ThebesRect(result));
  } else if (maskUsage.shouldApplyClipPath) {
    gfxRect result = nsSVGUtils::GetBBox(aMaskedFrame,
        nsSVGUtils::eBBoxIncludeClipped |
        nsSVGUtils::eBBoxIncludeFill |
        nsSVGUtils::eBBoxIncludeMarkers |
        nsSVGUtils::eBBoxIncludeStroke);
    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);
    SVGObserverUtils::EffectProperties effectProperties =
        SVGObserverUtils::GetEffectProperties(firstFrame);
    nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();

    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();
}

void
nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
                                             nsDisplayList*        aList,
                                             bool*                 aCreatedContainerItem) {
  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(aBuilder))
    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 needEventRegions =
    aBuilder->IsBuildingLayerEventRegions() &&
    StyleUserInterface()->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 (needEventRegions ||
        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();

  const bool isTransformed = IsTransformed(disp);
  const bool hasPerspective = isTransformed && HasPerspective(disp);
  const bool extend3DContext = Extend3DContext(disp, effectSet);
  const bool combines3DTransformWithAncestors =
    (extend3DContext || isTransformed) && Combines3DTransformWithAncestors(disp);
  const bool childrenHavePerspective = ChildrenHavePerspective(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->MarkFrameModifiedDuringBuilding(this);
    }
  }

  // nsDisplayPerspective items use an index to keep their PerFrameKey unique.
  // We need to make sure we build all of them for them to be consistent, so
  // rebuild all items if we have perspective. Bug 1431249 should remove
  // this requirement.
  if (aBuilder->IsRetainingDisplayList() && childrenHavePerspective) {
    dirtyRect = visibleRect;
    aBuilder->MarkFrameModifiedDuringBuilding(this);
  }

  // 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;
  }

  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;
    }
  }
  // Always build the entire display list if we previously had a blend
  // container since a partial build might make us think we no longer
  // need the container even though the merged result will.
  if (aBuilder->IsRetainingDisplayList() && BuiltBlendContainer()) {
    dirtyRect = visibleRect;
    aBuilder->MarkFrameModifiedDuringBuilding(this);
  }

  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);
  }

  // We build an opacity item if it's not going to be drawn by SVG content, or
  // SVG effects. SVG effects won't handle the opacity if we want an active
  // layer (for async animations), see
  // nsSVGIntegrationsUtils::PaintMaskAndClipPath or
  // nsSVGIntegrationsUtils::PaintFilter.
  bool useOpacity = HasVisualOpacity(effectSet) &&
                    !nsSVGUtils::CanOptimizeOpacity(this) &&
                    (!usingSVGEffects || nsDisplayOpacity::NeedsActiveLayer(aBuilder, this));
  bool useBlendMode = effects->mMixBlendMode != NS_STYLE_BLEND_NORMAL;
  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, !useOpacity);
  }

  nsDisplayListCollection set(aBuilder);
  {
    DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder);
    nsDisplayListBuilder::AutoInTransformSetter
      inTransformSetter(aBuilder, inTransform);
    nsDisplayListBuilder::AutoSaveRestorePerspectiveIndex
      perspectiveIndex(aBuilder, childrenHavePerspective);
    nsDisplayListBuilder::AutoFilterASRSetter
      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);

    nsDisplayLayerEventRegions* eventRegions = nullptr;
    if (aBuilder->IsBuildingLayerEventRegions()) {
      eventRegions = MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, this);
      eventRegions->AddFrame(aBuilder, this);
      aBuilder->SetLayerEventRegions(eventRegions);
    }

    aBuilder->BuildCompositorHitTestInfoIfNeeded(this, set.BorderBackground(),
                                                 true);

    MarkAbsoluteFramesForDisplayList(aBuilder);
    BuildDisplayList(aBuilder, set);

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

    // If we changed whether we're going to build a blend mode item,
    // then we need to make sure we're marked as invalid and we've built
    // the full display list.
    if (aBuilder->ContainsBlendMode() != BuiltBlendContainer() &&
        aBuilder->IsRetainingDisplayList()) {
      SetBuiltBlendContainer(aBuilder->ContainsBlendMode());
      aBuilder->MarkCurrentFrameModifiedDuringBuilding();

      // If we did a partial build then delete all the items we just built
      // and repeat building with the full area.
      if (!aBuilder->GetDirtyRect().Contains(aBuilder->GetVisibleRect())) {
        aBuilder->SetDirtyRect(aBuilder->GetVisibleRect());
        set.DeleteAll(aBuilder);

        if (eventRegions) {
          eventRegions->Destroy(aBuilder);
          eventRegions = MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, this);
          eventRegions->AddFrame(aBuilder, this);
          aBuilder->SetLayerEventRegions(eventRegions);
        }

        aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
                                                     set.BorderBackground(),
                                                     true);

        // If this is the root frame, then the previous call to
        // MarkAbsoluteFramesForDisplayList might have stored some fixed
        // background data. Clear that now.
        if (!GetParent()) {
          aBuilder->ClearFixedBackgroundDisplayData();
        }

        MarkAbsoluteFramesForDisplayList(aBuilder);
        BuildDisplayList(aBuilder, set);
      }
    }

    if (eventRegions) {
      // If the event regions item ended up empty, throw it away rather than
      // adding it to the display list.
      if (!eventRegions->IsEmpty()) {
        set.BorderBackground()->AppendToBottom(eventRegions);
      } else {
        aBuilder->SetLayerEventRegions(nullptr);
        eventRegions->Destroy(aBuilder);
        eventRegions = nullptr;
      }
    }
  }

  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();

  if (aCreatedContainerItem) {
    *aCreatedContainerItem = false;
  }

  /* 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));
    if (aCreatedContainerItem) {
      *aCreatedContainerItem = true;
    }
  }

  /* 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()) {
      // If we are going to create a mask display item, handle opacity effect
      // in that mask display item; Otherwise, take care of opacity in this
      // filter display item.
      bool handleOpacity = !usingMask && !useOpacity;

      /* List now emptied, so add the new list to the top. */
      resultList.AppendToTop(
        MakeDisplayItem<nsDisplayFilter>(aBuilder, this, &resultList,
                                       handleOpacity));
    }

    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 crrect, 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<nsDisplayMask>(aBuilder, this, &resultList, !useOpacity,
                                       maskASR));
    }

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

  /* 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);
    resultList.AppendToTop(
        MakeDisplayItem<nsDisplayOpacity>(aBuilder, this, &resultList,
                                        containerItemASR,
                                        opacityItemForEventsAndPluginsOnly));
    if (aCreatedContainerItem) {
      *aCreatedContainerItem = true;
    }
  }

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

    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++);
        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++);
    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);

    if (hasPerspective) {
      if (clipCapturedBy == ContainerItemType::ePerspective) {
        clipState.Restore();
      }
      resultList.AppendToTop(
        MakeDisplayItem<nsDisplayPerspective>(
          aBuilder, this,
          GetContainingBlock(0, disp)->GetContent()->GetPrimaryFrame(),
          &resultList));
    }

    if (aCreatedContainerItem) {
      *aCreatedContainerItem = true;
    }
  }

  if (clipCapturedBy == ContainerItemType::eOwnLayerForTransformWithRoundedClip) {
    clipState.Restore();
    resultList.AppendToTop(
      MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, this, &resultList,
                                       aBuilder->CurrentActiveScrolledRoot(),
                                       nsDisplayOwnLayerFlags::eNone,
                                       mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
                                       ScrollThumbData{}, /* aForceActive = */ false));
    if (aCreatedContainerItem) {
      *aCreatedContainerItem = true;
    }
  }

  /* 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.
    const ActiveScrolledRoot* fixedASR =
      ActiveScrolledRoot::PickAncestor(containerItemASR, aBuilder->CurrentActiveScrolledRoot());
    resultList.AppendToTop(
        MakeDisplayItem<nsDisplayFixedPosition>(aBuilder, this, &resultList, fixedASR));
    if (aCreatedContainerItem) {
      *aCreatedContainerItem = true;
    }
  } 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.
    const ActiveScrolledRoot* stickyASR =
      ActiveScrolledRoot::PickAncestor(containerItemASR, aBuilder->CurrentActiveScrolledRoot());
    resultList.AppendToTop(
        MakeDisplayItem<nsDisplayStickyPosition>(aBuilder, this, &resultList, stickyASR));
    if (aCreatedContainerItem) {
      *aCreatedContainerItem = true;
    }
  }

  /* 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));
    if (aCreatedContainerItem) {
      *aCreatedContainerItem = true;
    }
  }

  CreateOwnLayerIfNeeded(aBuilder, &resultList, aCreatedContainerItem);

  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, nsIFrame *aChild,
                 const nsRect& aVisible, const nsRect& aDirty)
{
  nsIFrame* child = aChild;
  const nsRect& dirty = aDirty;

  if (!(child->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) {
    // No need to descend into child to catch placeholders for visible
    // positioned stuff. So see if we can short-circuit frame traversal here.

    // We can stop if child's frame subtree's intersection with the
    // dirty area is empty.
    // 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.
    // 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.
    nsIPresShell* shell = child->PresShell();
    bool keepDescending = child == aBuilder->GetIgnoreScrollFrame() ||
      (shell->IgnoringViewportScrolling() && child == shell->GetRootScrollFrame());
    if (!keepDescending) {
      nsRect childDirty;
      if (!childDirty.IntersectRect(dirty, child->GetVisualOverflowRect()) &&
          (!child->ForceDescendIntoIfVisible())) {
        return false;
      }
      if (!childDirty.IntersectRect(aVisible, child->GetVisualOverflowRect())) {
        return false;
      }
      // Usually we could set dirty to childDirty now but there's no
      // benefit, and it can be confusing. It can especially confuse
      // situations where we're going to ignore a scrollframe's clipping;
      // we wouldn't want to clip the dirty area to the scrollframe's
      // bounds in that case.
    }
  }
  return true;
}

void
nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder*   aBuilder,
                                   nsIFrame*               aChild,
                                   const nsDisplayListSet& aLists,
                                   uint32_t                aFlags) {
  // If painting is restricted to just the background of the top level frame,
  // then we have nothing to do here.
  if (aBuilder->IsBackgroundOnly())
    return;

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

  nsIFrame* child = aChild;
  if (child->GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
    return;

  aBuilder->ClearWillChangeBudget(child);

  const bool shortcutPossible = aBuilder->IsPaintingToWindow() &&
    (aBuilder->IsBuildingLayerEventRegions() ||
     aBuilder->BuildCompositorHitTestInfo());

  const bool doingShortcut = shortcutPossible &&
    (child->GetStateBits() & NS_FRAME_SIMPLE_DISPLAYLIST) &&
    // Animations may change the value of |HasOpacity()|.
    !(child->GetContent() &&
      child->GetContent()->MayHaveAnimations());

  // 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;

  if (doingShortcut) {
    // This is the shortcut for frames been handled along the common
    // path, the most common one of THE COMMON CASE mentioned later.
    MOZ_ASSERT(child->Type() != LayoutFrameType::Placeholder);
    MOZ_ASSERT(!aBuilder->GetSelectedFramesOnly() &&
               !aBuilder->GetIncludeAllOutOfFlows(),
               "It should be held for painting to window");

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

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

    CheckForApzAwareEventHandlers(aBuilder, child);

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

    nsDisplayLayerEventRegions* eventRegions = aBuilder->GetLayerEventRegions();
    if (eventRegions) {
      eventRegions->AddFrame(aBuilder, child);
    }

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

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

  // It 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 = false;

  // 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;
  }

  nsDisplayListBuilder::OutOfFlowDisplayData* savedOutOfFlowData = nullptr;
  bool isPlaceholder = false;
  if (child->IsPlaceholderFrame()) {
    isPlaceholder = true;
    nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(child);
    child = placeholder->GetOutOfFlowFrame();
    aBuilder->ClearWillChangeBudget(child);
    NS_ASSERTION(child, "No out of flow frame?");
    // 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.
    if (!child || nsLayoutUtils::IsPopup(child) ||
        (child->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT))
      return;
    MOZ_ASSERT(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW);
    // 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.
    if (placeholder->GetStateBits() & PLACEHOLDER_FOR_TOPLAYER) {
      return;
    }
    // Recheck NS_FRAME_TOO_DEEP_IN_FRAME_TREE
    if (child->GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
      return;
    savedOutOfFlowData = nsDisplayListBuilder::GetOutOfFlowData(child);
    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();
    }

    pseudoStackingContext = true;
  }

  NS_ASSERTION(!child->IsPlaceholderFrame(),
               "Should have dealt with placeholders already");
  if (aBuilder->GetSelectedFramesOnly() &&
      child->IsLeaf() &&
      !aChild->IsSelected()) {
    return;
  }

  if (aBuilder->GetIncludeAllOutOfFlows() && isPlaceholder) {
    visible = child->GetVisualOverflowRect();
    dirty = child->GetVisualOverflowRect();
  } else if (!DescendIntoChild(aBuilder, child, visible, dirty)) {
    return;
  }

  // XXX need to have inline-block and inline-table set pseudoStackingContext

  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 (aBuilder->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..
  EffectSet* effectSet = EffectSet::GetEffectSet(child);
  const nsStyleDisplay* disp = child->StyleDisplay();
  const nsStyleEffects* effects = child->StyleEffects();
  const nsStylePosition* pos = child->StylePosition();

  const bool isVisuallyAtomic =
    child->IsVisuallyAtomic(effectSet, disp, effects);

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

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

  if (pseudoStackingContext || isStackingContext || isPositioned ||
      (!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) {
    if (effects->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
      aBuilder->SetContainsBlendMode(true);
    }
    // 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->DisplayCaret(child, &list)) {
      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);

    if (aBuilder->IsBuildingLayerEventRegions()) {
      // If this frame has a different animated geometry root than its parent,
      // make sure we accumulate event regions for its layer.
      if (buildingForChild.IsAnimatedGeometryRoot() || isPositioned) {
        nsDisplayLayerEventRegions* eventRegions =
          MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, child);
        eventRegions->AddFrame(aBuilder, child);
        aBuilder->SetLayerEventRegions(eventRegions);

        if (isPositioned) {
          // We need this nsDisplayLayerEventRegions to be sorted with the positioned
          // elements as positioned elements will be sorted on top of normal elements
          list.AppendToTop(eventRegions);
        } else {
          aLists.BorderBackground()->AppendToTop(eventRegions);
        }
      } else {
        nsDisplayLayerEventRegions* eventRegions = aBuilder->GetLayerEventRegions();
        if (eventRegions) {
          eventRegions->AddFrame(aBuilder, child);
        }
      }
    }

    const bool differentAGR =
      buildingForChild.IsAnimatedGeometryRoot() || isPositioned;

    if (!awayFromCommonPath && shortcutPossible &&
        !differentAGR && !buildingForChild.MaybeAnimatedGeometryRoot()) {
      // 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);
      child->BuildDisplayList(aBuilder, aLists);
      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);

    aBuilder->AdjustWindowDraggingRegion(child);
    nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
    child->BuildDisplayList(aBuilder, pseudoStack);
    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 || isVisuallyAtomic ||
      (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, true, false);
    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;
}

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

  *aParentContent = nullptr;
  *aContentOffset = 0;
  *aTarget = 0;

  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 = nsISelectionPrivate::TABLESELECTION_ROW;
  else if (selectColumn)
    *aTarget = nsISelectionPrivate::TABLESELECTION_COLUMN;
  else
#endif
  if (foundCell)
    *aTarget = nsISelectionPrivate::TABLESELECTION_CELL;
  else if (foundTable)
    *aTarget = nsISelectionPrivate::TABLESELECTION_TABLE;

  return NS_OK;
}

bool
nsIFrame::IsSelectable(StyleUserSelect* aSelectStyle) const
{
  // it's ok if aSelectStyle is null

  // Like 'visibility', we must check all the parents: if a parent
  // is not selectable, none of its children is selectable.
  //
  // The -moz-all value acts similarly: if a frame has 'user-select:-moz-all',
  // all its children are selectable, even those with 'user-select:none'.
  //
  // As a result, if 'none' and '-moz-all' are not present in the frame hierarchy,
  // aSelectStyle returns the first style that is not AUTO. If these values
  // are present in the frame hierarchy, aSelectStyle returns the style of the
  // topmost parent that has either 'none' or '-moz-all'.
  //
  // The -moz-text value acts as a way to override an ancestor's all/-moz-all value.
  //
  // For instance, if the frame hierarchy is:
  //    AUTO     -> _MOZ_ALL  -> NONE -> TEXT,      the returned value is ALL
  //    AUTO     -> _MOZ_ALL  -> NONE -> _MOZ_TEXT, the returned value is TEXT.
  //    TEXT     -> NONE      -> AUTO -> _MOZ_ALL,  the returned value is TEXT
  //    _MOZ_ALL -> TEXT      -> AUTO -> AUTO,      the returned value is ALL
  //    _MOZ_ALL -> _MOZ_TEXT -> AUTO -> AUTO,      the returned value is TEXT.
  //    AUTO     -> CELL      -> TEXT -> AUTO,      the returned value is TEXT
  //
  StyleUserSelect selectStyle  = StyleUserSelect::Auto;
  nsIFrame* frame              = const_cast<nsIFrame*>(this);
  bool containsEditable        = false;

  while (frame) {
    const nsStyleUIReset* userinterface = frame->StyleUIReset();
    switch (userinterface->mUserSelect) {
      case StyleUserSelect::All:
      case StyleUserSelect::MozAll:
      {
        // override the previous values
        if (selectStyle != StyleUserSelect::MozText) {
          selectStyle = userinterface->mUserSelect;
        }
        nsIContent* frameContent = frame->GetContent();
        containsEditable = frameContent &&
          frameContent->EditableDescendantCount() > 0;
        break;
      }
      default:
        // otherwise return the first value which is not 'auto'
        if (selectStyle == StyleUserSelect::Auto) {
          selectStyle = userinterface->mUserSelect;
        }
        break;
    }
    frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
  }

  // convert internal values to standard values
  if (selectStyle == StyleUserSelect::Auto ||
      selectStyle == StyleUserSelect::MozText) {
    selectStyle = StyleUserSelect::Text;
  } else if (selectStyle == StyleUserSelect::MozAll) {
    selectStyle = StyleUserSelect::All;
  }

  // If user tries to select all of a non-editable content,
  // prevent selection if it contains editable content.
  bool allowSelection = true;
  if (selectStyle == StyleUserSelect::All) {
    allowSelection = !containsEditable;
  }

  // return stuff
  if (aSelectStyle) {
    *aSelectStyle = selectStyle;
  }

  return !(mState & NS_FRAME_GENERATED_CONTENT) &&
         allowSelection &&
         selectStyle != 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;
  }

  // When implementing StyleUserSelect::Element, StyleUserSelect::Elements and
  // StyleUserSelect::Toggle, need to change this logic
  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;
  int32_t 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;
  int32_t 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,
                     int32_t                   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;
  int32_t targetForTableSel = 0;
  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) {
    nsIDocument* 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;
  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
    if (!SelfIsSelectable(frame, aFlags) || frame->IsEmpty() ||
        (canSkipBr && frame->IsBrFrame())) {
      continue;
    }
    canSkipBr = true;
    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::MozText) {
      // If we see a -moz-text element, we shouldn't look further up the parent
      // chain!
      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 && 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)
{
  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);
}

nsresult
nsFrame::GetCursor(const nsPoint& aPoint,
                   nsIFrame::Cursor& aCursor)
{
  FillCursorInformationFromStyle(StyleUserInterface(), 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 (GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
    nsFontInflationData::MarkFontInflationDataTextDirty(this);
  }
}

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

/* virtual */ nscoord
nsFrame::GetPrefISize(gfxContext *aRenderingContext)
{
  nscoord result = 0;
  DISPLAY_PREF_WIDTH(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;
    const WritingMode wm = mLineContainerWM;

    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->PhysicalBreakType(wm);
      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->PhysicalFloats(wm);
      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->PhysicalFloats(wm) != 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->PhysicalBreakType(wm);
          if (floatBreakType != aBreakType &&
              floatBreakType != StyleClear::None) {
            break;
          }
        }
      }
      newFloats.Reverse();
      mFloats = Move(newFloats);
    }
  }

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

static void
AddCoord(const nsStyleCoord& aStyle,
         nsIFrame* aFrame,
         nscoord* aCoord, float* aPercent,
         bool aClampNegativeToZero)
{
  switch (aStyle.GetUnit()) {
    case eStyleUnit_Coord: {
      NS_ASSERTION(!aClampNegativeToZero || aStyle.GetCoordValue() >= 0,
                   "unexpected negative value");
      *aCoord += aStyle.GetCoordValue();
      return;
    }
    case eStyleUnit_Percent: {
      NS_ASSERTION(!aClampNegativeToZero || aStyle.GetPercentValue() >= 0.0f,
                   "unexpected negative value");
      *aPercent += aStyle.GetPercentValue();
      return;
    }
    case eStyleUnit_Calc: {
      const nsStyleCoord::Calc *calc = aStyle.GetCalcValue();
      if (aClampNegativeToZero) {
        // This is far from ideal when one is negative and one is positive.
        *aCoord += std::max(calc->mLength, 0);
        *aPercent += std::max(calc->mPercent, 0.0f);
      } else {
        *aCoord += calc->mLength;
        *aPercent += calc->mPercent;
      }
      return;
    }
    default: {
      return;
    }
  }
}

static nsIFrame::IntrinsicISizeOffsetData
IntrinsicSizeOffsets(nsIFrame* aFrame, bool aForISize)
{
  nsIFrame::IntrinsicISizeOffsetData result;
  WritingMode wm = aFrame->GetWritingMode();
  const nsStyleMargin* styleMargin = aFrame->StyleMargin();
  bool verticalAxis = aForISize == wm.IsVertical();
  AddCoord(verticalAxis ? styleMargin->mMargin.GetTop()
                        : styleMargin->mMargin.GetLeft(),
           aFrame, &result.hMargin, &result.hPctMargin,
           false);
  AddCoord(verticalAxis ? styleMargin->mMargin.GetBottom()
                        : styleMargin->mMargin.GetRight(),
           aFrame, &result.hMargin, &result.hPctMargin,
           false);

  const nsStylePadding* stylePadding = aFrame->StylePadding();
  AddCoord(verticalAxis ? stylePadding->mPadding.GetTop()
                        : stylePadding->mPadding.GetLeft(),
           aFrame, &result.hPadding, &result.hPctPadding,
           true);
  AddCoord(verticalAxis ? stylePadding->mPadding.GetBottom()
                        : stylePadding->mPadding.GetRight(),
           aFrame, &result.hPadding, &result.hPctPadding,
           true);

  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();

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

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

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

nsIFrame::IntrinsicISizeOffsetData
nsIFrame::IntrinsicBSizeOffsets()
{
  return IntrinsicSizeOffsets(this, 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 determining
    // inlineStyleCoord and blockStyleCoord in
    // nsFrame::ComputeSizeWithIntrinsicDimensions().
    const nsStyleCoord* flexBasis = &(stylePos->mFlexBasis);
    if (flexBasis->GetUnit() != eStyleUnit_Auto) {
      // Replace our main-axis styleCoord pointer with a different one,
      // depending on our flex-basis value.
      auto& mainAxisCoord = (flexMainAxis == eLogicalAxisInline
                             ? inlineStyleCoord : blockStyleCoord);
      if (flexBasis->GetUnit() == eStyleUnit_Enumerated &&
          flexBasis->GetIntValue() == NS_STYLE_FLEX_BASIS_CONTENT) {
        // We have 'flex-basis: content', which is equivalent to
        // 'flex-basis:auto; {main-size}: auto'. So, just swap in a dummy
        // 'auto' value to use for the main size property:
        static const nsStyleCoord autoStyleCoord(eStyleUnit_Auto);
        mainAxisCoord = &autoStyleCoord;
      } else {
        // For all other flex-basis values, we just swap in the flex-basis
        // itself for the main-size property here:
        mainAxisCoord = flexBasis;
      }
    }
  }

  // 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->GetUnit() == eStyleUnit_Auto &&
               !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 determining
      // inlineStyleCoord and blockStyleCoord in nsFrame::ComputeSize().
      const nsStyleCoord* flexBasis = &(stylePos->mFlexBasis);
      if (flexBasis->GetUnit() != eStyleUnit_Auto) {
        // Replace our main-axis styleCoord pointer with a different one,
        // depending on our flex-basis value.
        auto& mainAxisCoord = (flexMainAxis == eLogicalAxisInline
                               ? inlineStyleCoord : blockStyleCoord);

        if (flexBasis->GetUnit() == eStyleUnit_Enumerated &&
            flexBasis->GetIntValue() == NS_STYLE_FLEX_BASIS_CONTENT) {
          // We have 'flex-basis: content', which is equivalent to
          // 'flex-basis:auto; {main-size}: auto'. So, just swap in a dummy
          // 'auto' value to use for the main size property:
          static const nsStyleCoord autoStyleCoord(eStyleUnit_Auto);
          mainAxisCoord = &autoStyleCoord;
        } else {
          // For all other flex-basis values, we just swap in the flex-basis
          // itself for the main-size property here:
          mainAxisCoord = flexBasis;
        }
      }
    }
  }

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