layout/base/PresShell.cpp
author Kartikaya Gupta <kgupta@mozilla.com>
Mon, 24 Jun 2019 20:10:40 +0000
changeset 479971 92a3a2e17fdb8b252e73d989cbb2dde0590e8813
parent 479011 f0310aa07b8f479e9f444a64bedd3d2244b6dd32
child 480115 fb0a0b7ac94c921338b3bdcda9f11de01f25408b
permissions -rw-r--r--
Bug 1560587 - Remove incorrect event redirection. r=tnikkel In this codepath, the parent process may be sending an event to a particular content process. The last-saved mouse target might be stale, and using that to redirect the event can result in the event getting sent to the wront content process. Deleting this erroneous change restores correct behaviour. Differential Revision: https://phabricator.services.mozilla.com/D35697

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

/* a presentation of a document, part 2 */

#include "mozilla/PresShell.h"

#include "mozilla/dom/FontFaceSet.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/GeckoMVMContext.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/Likely.h"
#include "mozilla/Logging.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PerfStats.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "MobileViewportManager.h"
#include <algorithm>

#ifdef XP_WIN
#  include "winuser.h"
#endif

#include "gfxContext.h"
#include "gfxUserFontSet.h"
#include "nsContentList.h"
#include "nsPresContext.h"
#include "nsIContent.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/PointerEventHandler.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "nsAnimationManager.h"
#include "nsNameSpaceManager.h"  // for Pref-related rule management (bugs 22963,20760,31816)
#include "nsFrame.h"
#include "FrameLayerBuilder.h"
#include "nsViewManager.h"
#include "nsView.h"
#include "nsCRTGlue.h"
#include "prinrval.h"
#include "nsTArray.h"
#include "nsCOMArray.h"
#include "nsContainerFrame.h"
#include "mozilla/dom/Selection.h"
#include "nsGkAtoms.h"
#include "nsRange.h"
#include "nsWindowSizes.h"
#include "nsCOMPtr.h"
#include "nsReadableUtils.h"
#include "nsPageSequenceFrame.h"
#include "nsIPermissionManager.h"
#include "nsIMozBrowserFrame.h"
#include "nsCaret.h"
#include "mozilla/AccessibleCaretEventHub.h"
#include "nsFrameManager.h"
#include "nsXPCOM.h"
#include "nsILayoutHistoryState.h"
#include "nsILineIterator.h"  // for ScrollContentIntoView
#include "PLDHashTable.h"
#include "mozilla/dom/Touch.h"
#include "mozilla/dom/TouchEvent.h"
#include "mozilla/dom/PointerEventBinding.h"
#include "mozilla/dom/ShadowIncludingTreeIterator.h"
#include "nsIObserverService.h"
#include "nsDocShell.h"  // for reflow observation
#include "nsIBaseWindow.h"
#include "nsError.h"
#include "nsLayoutUtils.h"
#include "nsViewportInfo.h"
#include "nsCSSRendering.h"
// for |#ifdef DEBUG| code
#include "prenv.h"
#include "nsDisplayList.h"
#include "nsRegion.h"
#include "nsAutoLayoutPhase.h"
#ifdef MOZ_GECKO_PROFILER
#  include "AutoProfilerStyleMarker.h"
#endif
#ifdef MOZ_REFLOW_PERF
#  include "nsFontMetrics.h"
#endif
#include "MobileViewportManager.h"
#include "OverflowChangedTracker.h"
#include "PositionedEventTargeting.h"

#include "nsIReflowCallback.h"

#include "nsPIDOMWindow.h"
#include "nsFocusManager.h"
#include "nsIObjectFrame.h"
#include "nsIObjectLoadingContent.h"
#include "nsNetUtil.h"
#include "nsThreadUtils.h"
#include "nsStyleSheetService.h"
#include "gfxUtils.h"
#include "mozilla/SMILAnimationController.h"
#include "SVGContentUtils.h"
#include "SVGObserverUtils.h"
#include "SVGFragmentIdentifier.h"
#include "nsFrameSelection.h"

#include "mozilla/dom/Performance.h"
#include "nsRefreshDriver.h"
#include "nsDOMNavigationTiming.h"

// Drag & Drop, Clipboard
#include "nsIDocShellTreeItem.h"
#include "nsIURI.h"
#include "nsIScrollableFrame.h"
#include "nsITimer.h"
#ifdef ACCESSIBILITY
#  include "nsAccessibilityService.h"
#  include "mozilla/a11y/DocAccessible.h"
#  ifdef DEBUG
#    include "mozilla/a11y/Logging.h"
#  endif
#endif

// For style data reconstruction
#include "nsStyleChangeList.h"
#include "nsCSSFrameConstructor.h"
#ifdef MOZ_XUL
#  include "nsMenuFrame.h"
#  include "nsTreeBodyFrame.h"
#  include "XULTreeElement.h"
#  include "nsMenuPopupFrame.h"
#  include "nsTreeColumns.h"
#  include "nsIDOMXULMultSelectCntrlEl.h"
#  include "nsIDOMXULSelectCntrlItemEl.h"
#  include "nsIDOMXULMenuListElement.h"
#  include "nsXULElement.h"
#endif  // MOZ_XUL

#include "mozilla/layers/CompositorBridgeChild.h"
#include "ClientLayerManager.h"
#include "GeckoProfiler.h"
#include "gfxPlatform.h"
#include "Layers.h"
#include "LayerTreeInvalidation.h"
#include "mozilla/css/ImageLoader.h"
#include "mozilla/dom/DocumentTimeline.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "nsCanvasFrame.h"
#include "nsIImageLoadingContent.h"
#include "nsImageFrame.h"
#include "nsIScreen.h"
#include "nsIScreenManager.h"
#include "nsPlaceholderFrame.h"
#include "nsTransitionManager.h"
#include "ChildIterator.h"
#include "mozilla/RestyleManager.h"
#include "nsIDragSession.h"
#include "nsIFrameInlines.h"
#include "mozilla/gfx/2D.h"
#include "nsSubDocumentFrame.h"
#include "nsQueryObject.h"
#include "nsLayoutStylesheetCache.h"
#include "mozilla/layers/InputAPZContext.h"
#include "mozilla/layers/FocusTarget.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "mozilla/layout/ScrollAnchorContainer.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/dom/ImageTracker.h"
#include "nsIDocShellTreeOwner.h"
#include "nsBindingManager.h"
#include "nsClassHashtable.h"
#include "nsHashKeys.h"
#include "VisualViewport.h"

#ifdef MOZ_TASK_TRACER
#  include "GeckoTaskTracer.h"
using namespace mozilla::tasktracer;
#endif

// define the scalfactor of drag and drop images
// relative to the max screen height/width
#define RELATIVE_SCALEFACTOR 0.0925f

using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::gfx;
using namespace mozilla::layout;
using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
typedef ScrollableLayerGuid::ViewID ViewID;

PresShell::CapturingContentInfo PresShell::sCapturingContentInfo;

// RangePaintInfo is used to paint ranges to offscreen buffers
struct RangePaintInfo {
  RefPtr<nsRange> mRange;
  nsDisplayListBuilder mBuilder;
  nsDisplayList mList;

  // offset of builder's reference frame to the root frame
  nsPoint mRootOffset;

  RangePaintInfo(nsRange* aRange, nsIFrame* aFrame)
      : mRange(aRange),
        mBuilder(aFrame, nsDisplayListBuilderMode::Painting, false) {
    MOZ_COUNT_CTOR(RangePaintInfo);
    mBuilder.BeginFrame();
  }

  ~RangePaintInfo() {
    mList.DeleteAll(&mBuilder);
    mBuilder.EndFrame();
    MOZ_COUNT_DTOR(RangePaintInfo);
  }
};

#undef NOISY

// ----------------------------------------------------------------------

#ifdef DEBUG
// Set the environment variable GECKO_VERIFY_REFLOW_FLAGS to one or
// more of the following flags (comma separated) for handy debug
// output.
static VerifyReflowFlags gVerifyReflowFlags;

struct VerifyReflowFlagData {
  const char* name;
  VerifyReflowFlags bit;
};

static const VerifyReflowFlagData gFlags[] = {
    // clang-format off
  { "verify",                VerifyReflowFlags::On },
  { "reflow",                VerifyReflowFlags::Noisy },
  { "all",                   VerifyReflowFlags::All },
  { "list-commands",         VerifyReflowFlags::DumpCommands },
  { "noisy-commands",        VerifyReflowFlags::NoisyCommands },
  { "really-noisy-commands", VerifyReflowFlags::ReallyNoisyCommands },
  { "resize",                VerifyReflowFlags::DuringResizeReflow },
    // clang-format on
};

#  define NUM_VERIFY_REFLOW_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))

static void ShowVerifyReflowFlags() {
  printf("Here are the available GECKO_VERIFY_REFLOW_FLAGS:\n");
  const VerifyReflowFlagData* flag = gFlags;
  const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
  while (flag < limit) {
    printf("  %s\n", flag->name);
    ++flag;
  }
  printf("Note: GECKO_VERIFY_REFLOW_FLAGS is a comma separated list of flag\n");
  printf("names (no whitespace)\n");
}
#endif

//========================================================================
//========================================================================
//========================================================================
#ifdef MOZ_REFLOW_PERF
class ReflowCountMgr;

static const char kGrandTotalsStr[] = "Grand Totals";

// Counting Class
class ReflowCounter {
 public:
  explicit ReflowCounter(ReflowCountMgr* aMgr = nullptr);
  ~ReflowCounter();

  void ClearTotals();
  void DisplayTotals(const char* aStr);
  void DisplayDiffTotals(const char* aStr);
  void DisplayHTMLTotals(const char* aStr);

  void Add() { mTotal++; }
  void Add(uint32_t aTotal) { mTotal += aTotal; }

  void CalcDiffInTotals();
  void SetTotalsCache();

  void SetMgr(ReflowCountMgr* aMgr) { mMgr = aMgr; }

  uint32_t GetTotal() { return mTotal; }

 protected:
  void DisplayTotals(uint32_t aTotal, const char* aTitle);
  void DisplayHTMLTotals(uint32_t aTotal, const char* aTitle);

  uint32_t mTotal;
  uint32_t mCacheTotal;

  ReflowCountMgr* mMgr;  // weak reference (don't delete)
};

// Counting Class
class IndiReflowCounter {
 public:
  explicit IndiReflowCounter(ReflowCountMgr* aMgr = nullptr)
      : mFrame(nullptr),
        mCount(0),
        mMgr(aMgr),
        mCounter(aMgr),
        mHasBeenOutput(false) {}
  virtual ~IndiReflowCounter() {}

  nsAutoString mName;
  nsIFrame* mFrame;  // weak reference (don't delete)
  int32_t mCount;

  ReflowCountMgr* mMgr;  // weak reference (don't delete)

  ReflowCounter mCounter;
  bool mHasBeenOutput;
};

//--------------------
// Manager Class
//--------------------
class ReflowCountMgr {
 public:
  ReflowCountMgr();
  virtual ~ReflowCountMgr();

  void ClearTotals();
  void ClearGrandTotals();
  void DisplayTotals(const char* aStr);
  void DisplayHTMLTotals(const char* aStr);
  void DisplayDiffsInTotals();

  void Add(const char* aName, nsIFrame* aFrame);
  ReflowCounter* LookUp(const char* aName);

  void PaintCount(const char* aName, gfxContext* aRenderingContext,
                  nsPresContext* aPresContext, nsIFrame* aFrame,
                  const nsPoint& aOffset, uint32_t aColor);

  FILE* GetOutFile() { return mFD; }

  void SetPresContext(nsPresContext* aPresContext) {
    mPresContext = aPresContext;  // weak reference
  }
  void SetPresShell(PresShell* aPresShell) {
    mPresShell = aPresShell;  // weak reference
  }

  void SetDumpFrameCounts(bool aVal) { mDumpFrameCounts = aVal; }
  void SetDumpFrameByFrameCounts(bool aVal) { mDumpFrameByFrameCounts = aVal; }
  void SetPaintFrameCounts(bool aVal) { mPaintFrameByFrameCounts = aVal; }

  bool IsPaintingFrameCounts() { return mPaintFrameByFrameCounts; }

 protected:
  void DisplayTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
  void DisplayHTMLTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);

  void DoGrandTotals();
  void DoIndiTotalsTree();

  // HTML Output Methods
  void DoGrandHTMLTotals();

  nsClassHashtable<nsCharPtrHashKey, ReflowCounter> mCounts;
  nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter> mIndiFrameCounts;
  FILE* mFD;

  bool mDumpFrameCounts;
  bool mDumpFrameByFrameCounts;
  bool mPaintFrameByFrameCounts;

  bool mCycledOnce;

  // Root Frame for Individual Tracking
  nsPresContext* mPresContext;
  PresShell* mPresShell;

  // ReflowCountMgr gReflowCountMgr;
};
#endif
//========================================================================

// comment out to hide caret
#define SHOW_CARET

// The upper bound on the amount of time to spend reflowing, in
// microseconds.  When this bound is exceeded and reflow commands are
// still queued up, a reflow event is posted.  The idea is for reflow
// to not hog the processor beyond the time specifed in
// gMaxRCProcessingTime.  This data member is initialized from the
// layout.reflow.timeslice pref.
#define NS_MAX_REFLOW_TIME 1000000
static int32_t gMaxRCProcessingTime = -1;

struct nsCallbackEventRequest {
  nsIReflowCallback* callback;
  nsCallbackEventRequest* next;
};

// ----------------------------------------------------------------------------
//
// NOTE(emilio): It'd be nice for this to assert that our document isn't in the
// bfcache, but font pref changes don't care about that, and maybe / probably
// shouldn't.
#ifdef DEBUG
#  define ASSERT_REFLOW_SCHEDULED_STATE()                                   \
    {                                                                       \
      if (ObservingLayoutFlushes()) {                                       \
        MOZ_ASSERT(                                                         \
            mDocument->GetBFCacheEntry() ||                                 \
                mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
            "Unexpected state");                                            \
      } else {                                                              \
        MOZ_ASSERT(                                                         \
            !mPresContext->RefreshDriver()->IsLayoutFlushObserver(this),    \
            "Unexpected state");                                            \
      }                                                                     \
    }
#else
#  define ASSERT_REFLOW_SCHEDULED_STATE() /* nothing */
#endif

class nsAutoCauseReflowNotifier {
 public:
  explicit nsAutoCauseReflowNotifier(PresShell* aPresShell)
      : mPresShell(aPresShell) {
    mPresShell->WillCauseReflow();
  }
  ~nsAutoCauseReflowNotifier() {
    // This check should not be needed. Currently the only place that seem
    // to need it is the code that deals with bug 337586.
    if (!mPresShell->mHaveShutDown) {
      mPresShell->DidCauseReflow();
    } else {
      nsContentUtils::RemoveScriptBlocker();
    }
  }

  PresShell* mPresShell;
};

class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback {
 public:
  explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {}

  MOZ_CAN_RUN_SCRIPT
  virtual void HandleEvent(EventChainPostVisitor& aVisitor) override {
    if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) {
      if (aVisitor.mEvent->mMessage == eMouseDown ||
          aVisitor.mEvent->mMessage == eMouseUp) {
        // Mouse-up and mouse-down events call nsFrame::HandlePress/Release
        // which call GetContentOffsetsFromPoint which requires up-to-date
        // layout. Bring layout up-to-date now so that GetCurrentEventFrame()
        // below will return a real frame and we don't have to worry about
        // destroying it by flushing later.
        MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
      } else if (aVisitor.mEvent->mMessage == eWheel &&
                 aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
        nsIFrame* frame = mPresShell->GetCurrentEventFrame();
        if (frame) {
          // chrome (including addons) should be able to know if content
          // handles both D3E "wheel" event and legacy mouse scroll events.
          // We should dispatch legacy mouse events before dispatching the
          // "wheel" event into system group.
          RefPtr<EventStateManager> esm =
              aVisitor.mPresContext->EventStateManager();
          esm->DispatchLegacyMouseScrollEvents(
              frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus);
        }
      }
      nsIFrame* frame = mPresShell->GetCurrentEventFrame();
      if (!frame && (aVisitor.mEvent->mMessage == eMouseUp ||
                     aVisitor.mEvent->mMessage == eTouchEnd)) {
        // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure
        // that capturing is released.
        frame = mPresShell->GetRootFrame();
      }
      if (frame) {
        frame->HandleEvent(MOZ_KnownLive(aVisitor.mPresContext),
                           aVisitor.mEvent->AsGUIEvent(),
                           &aVisitor.mEventStatus);
      }
    }
  }

  RefPtr<PresShell> mPresShell;
};

class nsBeforeFirstPaintDispatcher : public Runnable {
 public:
  explicit nsBeforeFirstPaintDispatcher(Document* aDocument)
      : mozilla::Runnable("nsBeforeFirstPaintDispatcher"),
        mDocument(aDocument) {}

  // Fires the "before-first-paint" event so that interested parties (right now,
  // the mobile browser) are aware of it.
  NS_IMETHOD Run() override {
    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    if (observerService) {
      observerService->NotifyObservers(ToSupports(mDocument),
                                       "before-first-paint", nullptr);
    }
    return NS_OK;
  }

 private:
  RefPtr<Document> mDocument;
};

// This is a helper class to track whether the targeted frame is destroyed after
// dispatching pointer events. In that case, we need the original targeted
// content so that we can dispatch the mouse events to it.
class MOZ_STACK_CLASS AutoPointerEventTargetUpdater final {
 public:
  AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent,
                                nsIFrame* aFrame, nsIContent** aTargetContent) {
    MOZ_ASSERT(aEvent);
    if (!aTargetContent || aEvent->mClass != ePointerEventClass) {
      // Make the destructor happy.
      mTargetContent = nullptr;
      return;
    }
    MOZ_ASSERT(aShell);
    MOZ_ASSERT(aFrame);
    MOZ_ASSERT(!aFrame->GetContent() ||
               aShell->GetDocument() == aFrame->GetContent()->OwnerDoc());

    MOZ_ASSERT(StaticPrefs::dom_w3c_pointer_events_enabled());
    mShell = aShell;
    mWeakFrame = aFrame;
    mTargetContent = aTargetContent;
    aShell->mPointerEventTarget = aFrame->GetContent();
  }

  ~AutoPointerEventTargetUpdater() {
    if (!mTargetContent || !mShell || mWeakFrame.IsAlive()) {
      return;
    }
    mShell->mPointerEventTarget.swap(*mTargetContent);
  }

 private:
  RefPtr<PresShell> mShell;
  AutoWeakFrame mWeakFrame;
  nsIContent** mTargetContent;
};

void PresShell::DirtyRootsList::Add(nsIFrame* aFrame) {
  // Is this root already scheduled for reflow?
  // FIXME: This could possibly be changed to a uniqueness assertion, with some
  // work in ResizeReflowIgnoreOverride (and maybe others?)
  if (mList.Contains(aFrame)) {
    // We don't expect frame to change depths.
    MOZ_ASSERT(aFrame->GetDepthInFrameTree() ==
               mList[mList.IndexOf(aFrame)].mDepth);
    return;
  }

  mList.InsertElementSorted(
      FrameAndDepth{aFrame, aFrame->GetDepthInFrameTree()},
      FrameAndDepth::CompareByReverseDepth{});
}

void PresShell::DirtyRootsList::Remove(nsIFrame* aFrame) {
  mList.RemoveElement(aFrame);
}

nsIFrame* PresShell::DirtyRootsList::PopShallowestRoot() {
  // List is sorted in order of decreasing depth, so there are no deeper
  // frames than the last one.
  const FrameAndDepth& lastFAD = mList.LastElement();
  nsIFrame* frame = lastFAD.mFrame;
  // We don't expect frame to change depths.
  MOZ_ASSERT(frame->GetDepthInFrameTree() == lastFAD.mDepth);
  mList.RemoveLastElement();
  return frame;
}

void PresShell::DirtyRootsList::Clear() { mList.Clear(); }

bool PresShell::DirtyRootsList::Contains(nsIFrame* aFrame) const {
  return mList.Contains(aFrame);
}

bool PresShell::DirtyRootsList::IsEmpty() const { return mList.IsEmpty(); }

bool PresShell::DirtyRootsList::FrameIsAncestorOfDirtyRoot(
    nsIFrame* aFrame) const {
  MOZ_ASSERT(aFrame);

  // Look for a path from any dirty roots to aFrame, following GetParent().
  // This check mirrors what FrameNeedsReflow() would have done if the reflow
  // root didn't get in the way.
  for (nsIFrame* dirtyFrame : mList) {
    do {
      if (dirtyFrame == aFrame) {
        return true;
      }
      dirtyFrame = dirtyFrame->GetParent();
    } while (dirtyFrame);
  }

  return false;
}

bool PresShell::sDisableNonTestMouseEvents = false;

LazyLogModule PresShell::gLog("PresShell");

TimeStamp PresShell::EventHandler::sLastInputCreated;
TimeStamp PresShell::EventHandler::sLastInputProcessed;
StaticRefPtr<Element> PresShell::EventHandler::sLastKeyDownEventTargetElement;

bool PresShell::sProcessInteractable = false;

static bool gVerifyReflowEnabled;

bool PresShell::GetVerifyReflowEnable() {
#ifdef DEBUG
  static bool firstTime = true;
  if (firstTime) {
    firstTime = false;
    char* flags = PR_GetEnv("GECKO_VERIFY_REFLOW_FLAGS");
    if (flags) {
      bool error = false;

      for (;;) {
        char* comma = PL_strchr(flags, ',');
        if (comma) *comma = '\0';

        bool found = false;
        const VerifyReflowFlagData* flag = gFlags;
        const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
        while (flag < limit) {
          if (PL_strcasecmp(flag->name, flags) == 0) {
            gVerifyReflowFlags |= flag->bit;
            found = true;
            break;
          }
          ++flag;
        }

        if (!found) error = true;

        if (!comma) break;

        *comma = ',';
        flags = comma + 1;
      }

      if (error) ShowVerifyReflowFlags();
    }

    if (VerifyReflowFlags::On & gVerifyReflowFlags) {
      gVerifyReflowEnabled = true;

      printf("Note: verifyreflow is enabled");
      if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
        printf(" (noisy)");
      }
      if (VerifyReflowFlags::All & gVerifyReflowFlags) {
        printf(" (all)");
      }
      if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
        printf(" (show reflow commands)");
      }
      if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
        printf(" (noisy reflow commands)");
        if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
          printf(" (REALLY noisy reflow commands)");
        }
      }
      printf("\n");
    }
  }
#endif
  return gVerifyReflowEnabled;
}

void PresShell::SetVerifyReflowEnable(bool aEnabled) {
  gVerifyReflowEnabled = aEnabled;
}

void PresShell::AddAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
  if (aWeakFrame->GetFrame()) {
    aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
  }
  aWeakFrame->SetPreviousWeakFrame(mAutoWeakFrames);
  mAutoWeakFrames = aWeakFrame;
}

void PresShell::AddWeakFrame(WeakFrame* aWeakFrame) {
  if (aWeakFrame->GetFrame()) {
    aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
  }
  MOZ_ASSERT(!mWeakFrames.GetEntry(aWeakFrame));
  mWeakFrames.PutEntry(aWeakFrame);
}

void PresShell::RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
  if (mAutoWeakFrames == aWeakFrame) {
    mAutoWeakFrames = aWeakFrame->GetPreviousWeakFrame();
    return;
  }
  AutoWeakFrame* nextWeak = mAutoWeakFrames;
  while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) {
    nextWeak = nextWeak->GetPreviousWeakFrame();
  }
  if (nextWeak) {
    nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame());
  }
}

void PresShell::RemoveWeakFrame(WeakFrame* aWeakFrame) {
  MOZ_ASSERT(mWeakFrames.GetEntry(aWeakFrame));
  mWeakFrames.RemoveEntry(aWeakFrame);
}

already_AddRefed<nsFrameSelection> PresShell::FrameSelection() {
  RefPtr<nsFrameSelection> ret = mSelection;
  return ret.forget();
}

//----------------------------------------------------------------------

static bool sSynthMouseMove = true;
static uint32_t sNextPresShellId;

/* static */
bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) {
  // If the pref forces it on, then enable it.
  if (StaticPrefs::layout_accessiblecaret_enabled()) {
    return true;
  }
  // If the touch pref is on, and touch events are enabled (this depends
  // on the specific device running), then enable it.
  if (StaticPrefs::layout_accessiblecaret_enabled_on_touch() &&
      dom::TouchEvent::PrefEnabled(aDocShell)) {
    return true;
  }
  // Otherwise, disabled.
  return false;
}

PresShell::PresShell()
    : mViewManager(nullptr),
      mFrameManager(nullptr),
      mAutoWeakFrames(nullptr),
#ifdef ACCESSIBILITY
      mDocAccessible(nullptr),
#endif  // #ifdef ACCESSIBILITY
      mCurrentEventFrame(nullptr),
      mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
      mPaintCount(0),
      mAPZFocusSequenceNumber(0),
      mCanvasBackgroundColor(NS_RGBA(0, 0, 0, 0)),
      mActiveSuppressDisplayport(0),
      mPresShellId(sNextPresShellId++),
      mFontSizeInflationEmPerLine(0),
      mFontSizeInflationMinTwips(0),
      mFontSizeInflationLineThreshold(0),
      mSelectionFlags(nsISelectionDisplay::DISPLAY_TEXT |
                      nsISelectionDisplay::DISPLAY_IMAGES),
      mChangeNestCount(0),
      mRenderingStateFlags(RenderingStateFlags::None),
      mInFlush(false),
      mCaretEnabled(false),
      mNeedLayoutFlush(true),
      mNeedStyleFlush(true),
      mNeedThrottledAnimationFlush(true),
      mVisualViewportSizeSet(false),
      mDidInitialize(false),
      mIsDestroying(false),
      mIsReflowing(false),
      mIsObservingDocument(false),
      mForbiddenToFlush(false),
      mIsDocumentGone(false),
      mHaveShutDown(false),
      mPaintingSuppressed(false),
      mLastRootReflowHadUnconstrainedBSize(false),
      mShouldUnsuppressPainting(false),
      mIgnoreFrameDestruction(false),
      mIsActive(true),
      mFrozen(false),
      mIsFirstPaint(true),  // FIXME/bug 735029: find a better solution
      mObservesMutationsForPrint(false),
      mWasLastReflowInterrupted(false),
      mObservingStyleFlushes(false),
      mObservingLayoutFlushes(false),
      mResizeEventPending(false),
      mFontSizeInflationForceEnabled(false),
      mFontSizeInflationDisabledInMasterProcess(false),
      mFontSizeInflationEnabled(false),
      mPaintingIsFrozen(false),
      mIsNeverPainting(false),
      mResolutionUpdated(false),
      mResolutionUpdatedByApz(false),
      mUnderHiddenEmbedderElement(false),
      mDocumentLoading(false),
      mNoDelayedMouseEvents(false),
      mNoDelayedKeyEvents(false),
      mApproximateFrameVisibilityVisited(false),
      mNextPaintCompressed(false),
      mHasCSSBackgroundColor(true),
      mIsLastChromeOnlyEscapeKeyConsumed(false),
      mHasReceivedPaintMessage(false),
      mIsLastKeyDownCanceled(false),
      mHasHandledUserInput(false),
      mForceDispatchKeyPressEventsForNonPrintableKeys(false),
      mForceUseLegacyKeyCodeAndCharCodeValues(false),
      mInitializedWithKeyPressEventDispatchingBlacklist(false),
      mForceUseLegacyNonPrimaryDispatch(false),
      mInitializedWithClickEventDispatchingBlacklist(false) {
  MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));

#ifdef MOZ_REFLOW_PERF
  mReflowCountMgr = MakeUnique<ReflowCountMgr>();
  mReflowCountMgr->SetPresContext(mPresContext);
  mReflowCountMgr->SetPresShell(this);
#endif
  mLastOSWake = mLoadBegin = TimeStamp::Now();

  static bool addedSynthMouseMove = false;
  if (!addedSynthMouseMove) {
    Preferences::AddBoolVarCache(&sSynthMouseMove,
                                 "layout.reflow.synthMouseMove", true);
    addedSynthMouseMove = true;
  }
  PointerEventHandler::Initialize();
}

NS_INTERFACE_TABLE_HEAD(PresShell)
  NS_INTERFACE_TABLE_BEGIN
    // In most cases, PresShell should be treated as concrete class, but need to
    // QI for weak reference.  Therefore, the case needed by do_QueryReferent()
    // should be tested first.
    NS_INTERFACE_TABLE_ENTRY(PresShell, PresShell)
    NS_INTERFACE_TABLE_ENTRY(PresShell, nsIDocumentObserver)
    NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionController)
    NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionDisplay)
    NS_INTERFACE_TABLE_ENTRY(PresShell, nsIObserver)
    NS_INTERFACE_TABLE_ENTRY(PresShell, nsISupportsWeakReference)
    NS_INTERFACE_TABLE_ENTRY(PresShell, nsIMutationObserver)
    NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(PresShell, nsISupports, nsIObserver)
  NS_INTERFACE_TABLE_END
  NS_INTERFACE_TABLE_TO_MAP_SEGUE
NS_INTERFACE_MAP_END

NS_IMPL_ADDREF(PresShell)
NS_IMPL_RELEASE(PresShell)

PresShell::~PresShell() {
  MOZ_RELEASE_ASSERT(!mForbiddenToFlush,
                     "Flag should only be set temporarily, while doing things "
                     "that shouldn't cause destruction");
  MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::~PresShell this=%p", this));

  if (!mHaveShutDown) {
    MOZ_ASSERT_UNREACHABLE("Someone did not call PresShell::Destroy()");
    Destroy();
  }

  NS_ASSERTION(mCurrentEventContentStack.Count() == 0,
               "Huh, event content left on the stack in pres shell dtor!");
  NS_ASSERTION(mFirstCallbackEventRequest == nullptr &&
                   mLastCallbackEventRequest == nullptr,
               "post-reflow queues not empty.  This means we're leaking");

  // Verify that if painting was frozen, but we're being removed from the tree,
  // that we now re-enable painting on our refresh driver, since it may need to
  // be re-used by another presentation.
  if (mPaintingIsFrozen) {
    mPresContext->RefreshDriver()->Thaw();
  }

  MOZ_ASSERT(mAllocatedPointers.IsEmpty(),
             "Some pres arena objects were not freed");

  mFrameManager = nullptr;
  mFrameConstructor = nullptr;

  mCurrentEventContent = nullptr;
}

/**
 * Initialize the presentation shell. Create view manager and style
 * manager.
 * Note this can't be merged into our constructor because caret initialization
 * calls AddRef() on us.
 */
void PresShell::Init(Document* aDocument, nsPresContext* aPresContext,
                     nsViewManager* aViewManager) {
  MOZ_ASSERT(aDocument, "null ptr");
  MOZ_ASSERT(aPresContext, "null ptr");
  MOZ_ASSERT(aViewManager, "null ptr");
  MOZ_ASSERT(!mDocument, "already initialized");

  if (!aDocument || !aPresContext || !aViewManager || mDocument) {
    return;
  }

  mDocument = aDocument;
  mViewManager = aViewManager;

  // mDocument is now set.  It might have a display document whose "need layout/
  // style" flush flags are not set, but ours will be set.  To keep these
  // consistent, call the flag setting functions to propagate those flags up
  // to the display document.
  SetNeedLayoutFlush();
  SetNeedStyleFlush();

  // Create our frame constructor.
  mFrameConstructor = MakeUnique<nsCSSFrameConstructor>(mDocument, this);

  mFrameManager = mFrameConstructor.get();

  // The document viewer owns both view manager and pres shell.
  mViewManager->SetPresShell(this);

  // Bind the context to the presentation shell.
  mPresContext = aPresContext;
  mPresContext->AttachPresShell(this);

  mPresContext->DeviceContext()->InitFontCache();

  // FIXME(emilio, bug 1544185): Some Android code somehow depends on the shell
  // being eagerly registered as a style flush observer. This shouldn't be
  // needed otherwise.
  EnsureStyleFlush();

  // Add the preference style sheet.
  UpdatePreferenceStyles();

  bool accessibleCaretEnabled =
      AccessibleCaretEnabled(mDocument->GetDocShell());
  if (accessibleCaretEnabled) {
    // Need to happen before nsFrameSelection has been set up.
    mAccessibleCaretEventHub = new AccessibleCaretEventHub(this);
  }

  mSelection = new nsFrameSelection();

  RefPtr<nsFrameSelection> frameSelection = mSelection;
  frameSelection->Init(this, nullptr, accessibleCaretEnabled);

  // Important: this has to happen after the selection has been set up
#ifdef SHOW_CARET
  // make the caret
  mCaret = new nsCaret();
  mCaret->Init(this);
  mOriginalCaret = mCaret;

  // SetCaretEnabled(true);       // make it show in browser windows
#endif
  // set up selection to be displayed in document
  // Don't enable selection for print media
  nsPresContext::nsPresContextType type = aPresContext->Type();
  if (type != nsPresContext::eContext_PrintPreview &&
      type != nsPresContext::eContext_Print)
    SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);

  if (gMaxRCProcessingTime == -1) {
    gMaxRCProcessingTime =
        Preferences::GetInt("layout.reflow.timeslice", NS_MAX_REFLOW_TIME);
  }

  if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
    ss->RegisterPresShell(this);
  }

  {
    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    if (os) {
      os->AddObserver(this, "memory-pressure", false);
      os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
      if (XRE_IsParentProcess() && !sProcessInteractable) {
        os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false);
      }
      os->AddObserver(this, "font-info-updated", false);
    }
  }

#ifdef MOZ_REFLOW_PERF
  if (mReflowCountMgr) {
    bool paintFrameCounts =
        Preferences::GetBool("layout.reflow.showframecounts");

    bool dumpFrameCounts =
        Preferences::GetBool("layout.reflow.dumpframecounts");

    bool dumpFrameByFrameCounts =
        Preferences::GetBool("layout.reflow.dumpframebyframecounts");

    mReflowCountMgr->SetDumpFrameCounts(dumpFrameCounts);
    mReflowCountMgr->SetDumpFrameByFrameCounts(dumpFrameByFrameCounts);
    mReflowCountMgr->SetPaintFrameCounts(paintFrameCounts);
  }
#endif

  if (mDocument->HasAnimationController()) {
    SMILAnimationController* animCtrl = mDocument->GetAnimationController();
    animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
  }

  for (DocumentTimeline* timeline : mDocument->Timelines()) {
    timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
  }

  // Get our activeness from the docShell.
  QueryIsActive();

  // Setup our font inflation preferences.
  mFontSizeInflationEmPerLine = nsLayoutUtils::FontSizeInflationEmPerLine();
  mFontSizeInflationMinTwips = nsLayoutUtils::FontSizeInflationMinTwips();
  mFontSizeInflationLineThreshold =
      nsLayoutUtils::FontSizeInflationLineThreshold();
  mFontSizeInflationForceEnabled =
      nsLayoutUtils::FontSizeInflationForceEnabled();
  mFontSizeInflationDisabledInMasterProcess =
      nsLayoutUtils::FontSizeInflationDisabledInMasterProcess();
  // We'll compute the font size inflation state in Initialize(), when we know
  // the document type.

  mTouchManager.Init(this, mDocument);

  if (mPresContext->IsRootContentDocument()) {
    mZoomConstraintsClient = new ZoomConstraintsClient();
    mZoomConstraintsClient->Init(this, mDocument);

    // We call this to create mMobileViewportManager, if it is needed.
    UpdateViewportOverridden(false);
  }

  if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
    BrowsingContext* bc = nsDocShell::Cast(docShell)->GetBrowsingContext();
    bool embedderFrameIsHidden = true;
    if (Element* embedderElement = bc->GetEmbedderElement()) {
      if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
        embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
      }
    }

    if (BrowsingContext* parent = bc->GetParent()) {
      if (nsCOMPtr<nsIDocShell> parentDocShell = parent->GetDocShell()) {
        if (PresShell* parentPresShell = parentDocShell->GetPresShell()) {
          mUnderHiddenEmbedderElement =
              parentPresShell->IsUnderHiddenEmbedderElement() ||
              embedderFrameIsHidden;
        }
      }
    }
  }
}

enum TextPerfLogType { eLog_reflow, eLog_loaddone, eLog_totals };

static void LogTextPerfStats(gfxTextPerfMetrics* aTextPerf,
                             PresShell* aPresShell,
                             const gfxTextPerfMetrics::TextCounts& aCounts,
                             float aTime, TextPerfLogType aLogType,
                             const char* aURL) {
  LogModule* tpLog = gfxPlatform::GetLog(eGfxLog_textperf);

  // ignore XUL contexts unless at debug level
  mozilla::LogLevel logLevel = LogLevel::Warning;
  if (aCounts.numContentTextRuns == 0) {
    logLevel = LogLevel::Debug;
  }

  if (!MOZ_LOG_TEST(tpLog, logLevel)) {
    return;
  }

  char prefix[256];

  switch (aLogType) {
    case eLog_reflow:
      SprintfLiteral(prefix, "(textperf-reflow) %p time-ms: %7.0f", aPresShell,
                     aTime);
      break;
    case eLog_loaddone:
      SprintfLiteral(prefix, "(textperf-loaddone) %p time-ms: %7.0f",
                     aPresShell, aTime);
      break;
    default:
      MOZ_ASSERT(aLogType == eLog_totals, "unknown textperf log type");
      SprintfLiteral(prefix, "(textperf-totals) %p", aPresShell);
  }

  double hitRatio = 0.0;
  uint32_t lookups = aCounts.wordCacheHit + aCounts.wordCacheMiss;
  if (lookups) {
    hitRatio = double(aCounts.wordCacheHit) / double(lookups);
  }

  if (aLogType == eLog_loaddone) {
    MOZ_LOG(
        tpLog, logLevel,
        ("%s reflow: %d chars: %d "
         "[%s] "
         "content-textruns: %d chrome-textruns: %d "
         "max-textrun-len: %d "
         "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
         "word-cache-space: %d word-cache-long: %d "
         "pref-fallbacks: %d system-fallbacks: %d "
         "textruns-const: %d textruns-destr: %d "
         "generic-lookups: %d "
         "cumulative-textruns-destr: %d\n",
         prefix, aTextPerf->reflowCount, aCounts.numChars, (aURL ? aURL : ""),
         aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
         aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
         aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
         aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
         aTextPerf->cumulative.textrunDestr));
  } else {
    MOZ_LOG(
        tpLog, logLevel,
        ("%s reflow: %d chars: %d "
         "content-textruns: %d chrome-textruns: %d "
         "max-textrun-len: %d "
         "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
         "word-cache-space: %d word-cache-long: %d "
         "pref-fallbacks: %d system-fallbacks: %d "
         "textruns-const: %d textruns-destr: %d "
         "generic-lookups: %d "
         "cumulative-textruns-destr: %d\n",
         prefix, aTextPerf->reflowCount, aCounts.numChars,
         aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
         aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
         aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
         aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
         aTextPerf->cumulative.textrunDestr));
  }
}

void PresShell::Destroy() {
  // Do not add code before this line please!
  if (mHaveShutDown) {
    return;
  }

  NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
               "destroy called on presshell while scripts not blocked");

  AUTO_PROFILER_LABEL("PresShell::Destroy", LAYOUT);

  // dump out cumulative text perf metrics
  gfxTextPerfMetrics* tp;
  if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) {
    tp->Accumulate();
    if (tp->cumulative.numChars > 0) {
      LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr);
    }
  }
  if (mPresContext) {
    if (gfxUserFontSet* fs = mPresContext->GetUserFontSet()) {
      uint32_t fontCount;
      uint64_t fontSize;
      fs->GetLoadStatistics(fontCount, fontSize);
      Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, fontCount);
      Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE,
                            uint32_t(fontSize / 1024));
    } else {
      Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0);
      Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0);
    }
  }

#ifdef MOZ_REFLOW_PERF
  DumpReflows();
  mReflowCountMgr = nullptr;
#endif

  if (mZoomConstraintsClient) {
    mZoomConstraintsClient->Destroy();
    mZoomConstraintsClient = nullptr;
  }
  if (mMobileViewportManager) {
    mMobileViewportManager->Destroy();
    mMobileViewportManager = nullptr;
    mMVMContext = nullptr;
  }

#ifdef ACCESSIBILITY
  if (mDocAccessible) {
#  ifdef DEBUG
    if (a11y::logging::IsEnabled(a11y::logging::eDocDestroy))
      a11y::logging::DocDestroy("presshell destroyed", mDocument);
#  endif

    mDocAccessible->Shutdown();
    mDocAccessible = nullptr;
  }
#endif  // ACCESSIBILITY

  MaybeReleaseCapturingContent();

  EventHandler::OnPresShellDestroy(mDocument);

  if (mContentToScrollTo) {
    mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
    mContentToScrollTo = nullptr;
  }

  if (mPresContext) {
    // We need to notify the destroying the nsPresContext to ESM for
    // suppressing to use from ESM.
    mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext);
  }

  if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
    ss->UnregisterPresShell(this);
  }

  {
    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    if (os) {
      os->RemoveObserver(this, "memory-pressure");
      os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
      if (XRE_IsParentProcess()) {
        os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
      }
      os->RemoveObserver(this, "font-info-updated");
    }
  }

  // If our paint suppression timer is still active, kill it.
  if (mPaintSuppressionTimer) {
    mPaintSuppressionTimer->Cancel();
    mPaintSuppressionTimer = nullptr;
  }

  // Same for our reflow continuation timer
  if (mReflowContinueTimer) {
    mReflowContinueTimer->Cancel();
    mReflowContinueTimer = nullptr;
  }

  if (mDelayedPaintTimer) {
    mDelayedPaintTimer->Cancel();
    mDelayedPaintTimer = nullptr;
  }

  mSynthMouseMoveEvent.Revoke();

  mUpdateApproximateFrameVisibilityEvent.Revoke();

  ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));

  if (mCaret) {
    mCaret->Terminate();
    mCaret = nullptr;
  }

  if (mSelection) {
    RefPtr<nsFrameSelection> frameSelection = mSelection;
    frameSelection->DisconnectFromPresShell();
  }

  if (mAccessibleCaretEventHub) {
    mAccessibleCaretEventHub->Terminate();
    mAccessibleCaretEventHub = nullptr;
  }

  // release our pref style sheet, if we have one still
  //
  // TODO(emilio): Should we move the preference sheet tracking to the Document?
  RemovePreferenceStyles();

  mIsDestroying = true;

  // We can't release all the event content in
  // mCurrentEventContentStack here since there might be code on the
  // stack that will release the event content too. Double release
  // bad!

  // The frames will be torn down, so remove them from the current
  // event frame stack (since they'd be dangling references if we'd
  // leave them in) and null out the mCurrentEventFrame pointer as
  // well.

  mCurrentEventFrame = nullptr;

  int32_t i, count = mCurrentEventFrameStack.Length();
  for (i = 0; i < count; i++) {
    mCurrentEventFrameStack[i] = nullptr;
  }

  mFramesToDirty.Clear();
  mPendingScrollAnchorSelection.Clear();
  mPendingScrollAnchorAdjustment.Clear();

  if (mViewManager) {
    // Clear the view manager's weak pointer back to |this| in case it
    // was leaked.
    mViewManager->SetPresShell(nullptr);
    mViewManager = nullptr;
  }

  nsRefreshDriver* rd = GetPresContext()->RefreshDriver();

  // This shell must be removed from the document before the frame
  // hierarchy is torn down to avoid finding deleted frames through
  // this presshell while the frames are being torn down
  if (mDocument) {
    NS_ASSERTION(mDocument->GetPresShell() == this, "Wrong shell?");
    mDocument->ClearServoRestyleRoot();
    mDocument->DeletePresShell();

    if (mDocument->HasAnimationController()) {
      mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
    }
    for (DocumentTimeline* timeline : mDocument->Timelines()) {
      timeline->NotifyRefreshDriverDestroying(rd);
    }
  }

  if (mPresContext) {
    rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher());
  }

  // Revoke any pending events.  We need to do this and cancel pending reflows
  // before we destroy the frame manager, since apparently frame destruction
  // sometimes spins the event queue when plug-ins are involved(!).
  StopObservingRefreshDriver();

  if (rd->GetPresContext() == GetPresContext()) {
    rd->RevokeViewManagerFlush();
  }

  CancelAllPendingReflows();
  CancelPostedReflowCallbacks();

  // Destroy the frame manager. This will destroy the frame hierarchy
  mFrameConstructor->WillDestroyFrameTree();

  NS_WARNING_ASSERTION(!mAutoWeakFrames && mWeakFrames.IsEmpty(),
                       "Weak frames alive after destroying FrameManager");
  while (mAutoWeakFrames) {
    mAutoWeakFrames->Clear(this);
  }
  nsTArray<WeakFrame*> toRemove(mWeakFrames.Count());
  for (auto iter = mWeakFrames.Iter(); !iter.Done(); iter.Next()) {
    toRemove.AppendElement(iter.Get()->GetKey());
  }
  for (WeakFrame* weakFrame : toRemove) {
    weakFrame->Clear(this);
  }

  if (mPresContext) {
    // We hold a reference to the pres context, and it holds a weak link back
    // to us. To avoid the pres context having a dangling reference, set its
    // pres shell to nullptr
    mPresContext->DetachPresShell();

    // Clear the link handler (weak reference) as well
    mPresContext->SetLinkHandler(nullptr);
  }

  mHaveShutDown = true;

  mTouchManager.Destroy();
}

void PresShell::StopObservingRefreshDriver() {
  nsRefreshDriver* rd = mPresContext->RefreshDriver();
  if (mResizeEventPending) {
    rd->RemoveResizeEventFlushObserver(this);
  }
  if (mObservingLayoutFlushes) {
    rd->RemoveLayoutFlushObserver(this);
  }
  if (mObservingStyleFlushes) {
    rd->RemoveStyleFlushObserver(this);
  }
}

void PresShell::StartObservingRefreshDriver() {
  nsRefreshDriver* rd = mPresContext->RefreshDriver();
  if (mResizeEventPending) {
    rd->AddResizeEventFlushObserver(this);
  }
  if (mObservingLayoutFlushes) {
    rd->AddLayoutFlushObserver(this);
  }
  if (mObservingStyleFlushes) {
    rd->AddStyleFlushObserver(this);
  }
}

nsRefreshDriver* PresShell::GetRefreshDriver() const {
  return mPresContext ? mPresContext->RefreshDriver() : nullptr;
}

void PresShell::SetAuthorStyleDisabled(bool aStyleDisabled) {
  if (aStyleDisabled != StyleSet()->GetAuthorStyleDisabled()) {
    StyleSet()->SetAuthorStyleDisabled(aStyleDisabled);
    mDocument->ApplicableStylesChanged();

    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    if (observerService) {
      observerService->NotifyObservers(
          ToSupports(mDocument), "author-style-disabled-changed", nullptr);
    }
  }
}

bool PresShell::GetAuthorStyleDisabled() const {
  return StyleSet()->GetAuthorStyleDisabled();
}

void PresShell::UpdatePreferenceStyles() {
  if (!mDocument) {
    return;
  }

  // If the document doesn't have a window there's no need to notify
  // its presshell about changes to preferences since the document is
  // in a state where it doesn't matter any more (see
  // nsDocumentViewer::Close()).
  if (!mDocument->GetWindow()) {
    return;
  }

  // Documents in chrome shells do not have any preference style rules applied.
  if (nsContentUtils::IsInChromeDocshell(mDocument)) {
    return;
  }

  PreferenceSheet::EnsureInitialized();
  auto cache = nsLayoutStylesheetCache::Singleton();

  RefPtr<StyleSheet> newPrefSheet =
      PreferenceSheet::ShouldUseChromePrefs(*mDocument)
          ? cache->ChromePreferenceSheet()
          : cache->ContentPreferenceSheet();

  if (mPrefStyleSheet == newPrefSheet) {
    return;
  }

  RemovePreferenceStyles();

  // NOTE(emilio): This sheet is added as an agent sheet, because we don't want
  // it to be modifiable from devtools and similar, see bugs 1239336 and
  // 1436782. I think it conceptually should be a user sheet, and could be
  // without too much trouble I'd think.
  StyleSet()->AppendStyleSheet(StyleOrigin::UserAgent, newPrefSheet);
  mPrefStyleSheet = newPrefSheet;
}

void PresShell::RemovePreferenceStyles() {
  if (mPrefStyleSheet) {
    StyleSet()->RemoveStyleSheet(StyleOrigin::UserAgent, mPrefStyleSheet);
    mPrefStyleSheet = nullptr;
  }
}

void PresShell::AddUserSheet(StyleSheet* aSheet) {
  // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt
  // ordering. We want this new sheet to come after all the existing stylesheet
  // service sheets (which are at the start), but before other user sheets; see
  // nsIStyleSheetService.idl for the ordering.

  nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
  nsTArray<RefPtr<StyleSheet>>& userSheets = *sheetService->UserStyleSheets();

  // Search for the place to insert the new user sheet. Since all of the
  // stylesheet service provided user sheets should be at the start of the style
  // set's list, and aSheet should be at the end of userSheets. Given that, we
  // can find the right place to insert the new sheet based on the length of
  // userSheets.
  MOZ_ASSERT(aSheet);
  MOZ_ASSERT(userSheets.LastElement() == aSheet);

  size_t index = userSheets.Length() - 1;

  // Assert that all of userSheets (except for the last, new element) matches up
  // with what's in the style set.
  for (size_t i = 0; i < index; ++i) {
    MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]);
  }

  if (index == static_cast<size_t>(StyleSet()->SheetCount(StyleOrigin::User))) {
    StyleSet()->AppendStyleSheet(StyleOrigin::User, aSheet);
  } else {
    StyleSheet* ref = StyleSet()->SheetAt(StyleOrigin::User, index);
    StyleSet()->InsertStyleSheetBefore(StyleOrigin::User, aSheet, ref);
  }

  mDocument->ApplicableStylesChanged();
}

void PresShell::AddAgentSheet(StyleSheet* aSheet) {
  // Make sure this does what nsDocumentViewer::CreateStyleSet does
  // wrt ordering.
  StyleSet()->AppendStyleSheet(StyleOrigin::UserAgent, aSheet);
  mDocument->ApplicableStylesChanged();
}

void PresShell::AddAuthorSheet(StyleSheet* aSheet) {
  // Document specific "additional" Author sheets should be stronger than the
  // ones added with the StyleSheetService.
  StyleSheet* firstAuthorSheet = mDocument->GetFirstAdditionalAuthorSheet();
  if (firstAuthorSheet) {
    StyleSet()->InsertStyleSheetBefore(StyleOrigin::Author, aSheet,
                                       firstAuthorSheet);
  } else {
    StyleSet()->AppendStyleSheet(StyleOrigin::Author, aSheet);
  }

  mDocument->ApplicableStylesChanged();
}

void PresShell::RemoveSheet(StyleOrigin aOrigin, StyleSheet* aSheet) {
  StyleSet()->RemoveStyleSheet(aOrigin, aSheet);
  mDocument->ApplicableStylesChanged();
}

NS_IMETHODIMP
PresShell::SetDisplaySelection(int16_t aToggle) {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  frameSelection->SetDisplaySelection(aToggle);
  return NS_OK;
}

NS_IMETHODIMP
PresShell::GetDisplaySelection(int16_t* aToggle) {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  *aToggle = frameSelection->GetDisplaySelection();
  return NS_OK;
}

NS_IMETHODIMP
PresShell::GetSelectionFromScript(RawSelectionType aRawSelectionType,
                                  Selection** aSelection) {
  if (!aSelection || !mSelection) return NS_ERROR_NULL_POINTER;

  RefPtr<nsFrameSelection> frameSelection = mSelection;
  RefPtr<Selection> selection =
      frameSelection->GetSelection(ToSelectionType(aRawSelectionType));

  if (!selection) {
    return NS_ERROR_INVALID_ARG;
  }

  selection.forget(aSelection);
  return NS_OK;
}

Selection* PresShell::GetSelection(RawSelectionType aRawSelectionType) {
  if (!mSelection) {
    return nullptr;
  }

  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
}

Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) {
  if (!mSelection) return nullptr;

  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->GetSelection(aSelectionType);
}

already_AddRefed<nsISelectionController>
PresShell::GetSelectionControllerForFocusedContent(
    nsIContent** aFocusedContent) {
  if (aFocusedContent) {
    *aFocusedContent = nullptr;
  }

  if (mDocument) {
    nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
    nsCOMPtr<nsIContent> focusedContent = nsFocusManager::GetFocusedDescendant(
        mDocument->GetWindow(), nsFocusManager::eOnlyCurrentWindow,
        getter_AddRefs(focusedWindow));
    if (focusedContent) {
      nsIFrame* frame = focusedContent->GetPrimaryFrame();
      if (frame) {
        nsCOMPtr<nsISelectionController> selectionController;
        frame->GetSelectionController(mPresContext,
                                      getter_AddRefs(selectionController));
        if (selectionController) {
          if (aFocusedContent) {
            focusedContent.forget(aFocusedContent);
          }
          return selectionController.forget();
        }
      }
    }
  }
  nsCOMPtr<nsISelectionController> self(this);
  return self.forget();
}

NS_IMETHODIMP
PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
                                   SelectionRegion aRegion, int16_t aFlags) {
  if (!mSelection) return NS_ERROR_NULL_POINTER;

  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->ScrollSelectionIntoView(
      ToSelectionType(aRawSelectionType), aRegion, aFlags);
}

NS_IMETHODIMP
PresShell::RepaintSelection(RawSelectionType aRawSelectionType) {
  if (!mSelection) {
    return NS_ERROR_NULL_POINTER;
  }

  if (MOZ_UNLIKELY(mIsDestroying)) {
    return NS_OK;
  }

  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
}

// Make shell be a document observer
void PresShell::BeginObservingDocument() {
  if (mDocument && !mIsDestroying) {
    mIsObservingDocument = true;
    if (mIsDocumentGone) {
      NS_WARNING(
          "Adding a presshell that was disconnected from the document "
          "as a document observer?  Sounds wrong...");
      mIsDocumentGone = false;
    }
  }
}

// Make shell stop being a document observer
void PresShell::EndObservingDocument() {
  // XXXbz do we need to tell the frame constructor that the document
  // is gone, perhaps?  Except for printing it's NOT gone, sometimes.
  mIsDocumentGone = true;
  mIsObservingDocument = false;
}

#ifdef DEBUG_kipp
char* nsPresShell_ReflowStackPointerTop;
#endif

class XBLConstructorRunner : public Runnable {
 public:
  explicit XBLConstructorRunner(Document* aDocument)
      : Runnable("XBLConstructorRunner"), mDocument(aDocument) {}

  NS_IMETHOD Run() override {
    mDocument->BindingManager()->ProcessAttachedQueue();
    return NS_OK;
  }

 private:
  RefPtr<Document> mDocument;
};

nsresult PresShell::Initialize() {
  if (mIsDestroying) {
    return NS_OK;
  }

  if (!mDocument) {
    // Nothing to do
    return NS_OK;
  }

  MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::Initialize this=%p", this));

  NS_ASSERTION(!mDidInitialize, "Why are we being called?");

  RefPtr<PresShell> kungFuDeathGrip(this);

  RecomputeFontSizeInflationEnabled();
  MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);

  // Ensure the pres context doesn't think it has changed, since we haven't even
  // started layout. This avoids spurious restyles / reflows afterwards.
  //
  // Note that this is very intentionally before setting mDidInitialize so it
  // doesn't notify the document, or run media query change events.
  mPresContext->FlushPendingMediaFeatureValuesChanged();
  MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);

  mDidInitialize = true;

#ifdef DEBUG
  if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
    if (mDocument) {
      nsIURI* uri = mDocument->GetDocumentURI();
      if (uri) {
        printf("*** PresShell::Initialize (this=%p, url='%s')\n", (void*)this,
               uri->GetSpecOrDefault().get());
      }
    }
  }
#endif

  // Get the root frame from the frame manager
  // XXXbz it would be nice to move this somewhere else... like frame manager
  // Init(), say.  But we need to make sure our views are all set up by the
  // time we do this!
  nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
  NS_ASSERTION(!rootFrame, "How did that happen, exactly?");

  if (!rootFrame) {
    nsAutoScriptBlocker scriptBlocker;
    rootFrame = mFrameConstructor->ConstructRootFrame();
    mFrameConstructor->SetRootFrame(rootFrame);
  }

  NS_ENSURE_STATE(!mHaveShutDown);

  if (!rootFrame) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  if (Element* root = mDocument->GetRootElement()) {
    {
      nsAutoCauseReflowNotifier reflowNotifier(this);
      // Have the style sheet processor construct frame for the root
      // content object down
      mFrameConstructor->ContentInserted(
          root, nullptr, nsCSSFrameConstructor::InsertionKind::Sync);

      // Something in mFrameConstructor->ContentInserted may have caused
      // Destroy() to get called, bug 337586.
      NS_ENSURE_STATE(!mHaveShutDown);
    }

    // nsAutoCauseReflowNotifier (which sets up a script blocker) going out of
    // scope may have killed us too
    NS_ENSURE_STATE(!mHaveShutDown);

    // Run the XBL binding constructors for any new frames we've constructed.
    // (Do this in a script runner, since our caller might have a script
    // blocker on the stack.)
    nsContentUtils::AddScriptRunner(new XBLConstructorRunner(mDocument));

    // XBLConstructorRunner might destroy us.
    NS_ENSURE_STATE(!mHaveShutDown);
  }

  mDocument->TriggerAutoFocus();

  NS_ASSERTION(rootFrame, "How did that happen?");

  // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
  // set, but XBL processing could have caused a reflow which clears it.
  if (MOZ_LIKELY(rootFrame->GetStateBits() & NS_FRAME_IS_DIRTY)) {
    // Unset the DIRTY bits so that FrameNeedsReflow() will work right.
    rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
    NS_ASSERTION(!mDirtyRoots.Contains(rootFrame),
                 "Why is the root in mDirtyRoots already?");
    FrameNeedsReflow(rootFrame, IntrinsicDirty::Resize, NS_FRAME_IS_DIRTY);
    NS_ASSERTION(mDirtyRoots.Contains(rootFrame),
                 "Should be in mDirtyRoots now");
    NS_ASSERTION(mObservingLayoutFlushes, "Why no reflow scheduled?");
  }

  // Restore our root scroll position now if we're getting here after EndLoad
  // got called, since this is our one chance to do it.  Note that we need not
  // have reflowed for this to work; when the scrollframe is finally reflowed
  // it'll pick up the position we store in it here.
  if (!mDocumentLoading) {
    RestoreRootScrollPosition();
  }

  // For printing, we just immediately unsuppress.
  if (!mPresContext->IsPaginated()) {
    // Kick off a one-shot timer based off our pref value.  When this timer
    // fires, if painting is still locked down, then we will go ahead and
    // trigger a full invalidate and allow painting to proceed normally.
    mPaintingSuppressed = true;
    // Don't suppress painting if the document isn't loading.
    Document::ReadyState readyState = mDocument->GetReadyStateEnum();
    if (readyState != Document::READYSTATE_COMPLETE) {
      mPaintSuppressionTimer = NS_NewTimer();
    }
    if (!mPaintSuppressionTimer) {
      mPaintingSuppressed = false;
    } else {
      // Initialize the timer.

      // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value.
      int32_t delay = Preferences::GetInt("nglayout.initialpaint.delay",
                                          PAINTLOCK_EVENT_DELAY);

      mPaintSuppressionTimer->SetTarget(
          mDocument->EventTargetFor(TaskCategory::Other));
      mPaintSuppressionTimer->InitWithNamedFuncCallback(
          sPaintSuppressionCallback, this, delay, nsITimer::TYPE_ONE_SHOT,
          "PresShell::sPaintSuppressionCallback");
    }
  }

  // If we get here and painting is not suppressed, we still want to run the
  // unsuppression logic, so set mShouldUnsuppressPainting to true.
  if (!mPaintingSuppressed) {
    mShouldUnsuppressPainting = true;
  }

  return NS_OK;  // XXX this needs to be real. MMP
}

void PresShell::sPaintSuppressionCallback(nsITimer* aTimer, void* aPresShell) {
  RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
  if (self) self->UnsuppressPainting();
}

nsresult PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight,
                                 nscoord aOldWidth, nscoord aOldHeight,
                                 ResizeReflowOptions aOptions) {
  if (mZoomConstraintsClient) {
    // If we have a ZoomConstraintsClient and the available screen area
    // changed, then we might need to disable double-tap-to-zoom, so notify
    // the ZCC to update itself.
    mZoomConstraintsClient->ScreenSizeChanged();
  }
  if (mMobileViewportManager) {
    // If we have a mobile viewport manager, request a reflow from it. It can
    // recompute the final CSS viewport and trigger a call to
    // ResizeReflowIgnoreOverride if it changed. We don't force adjusting
    // of resolution, because that is only necessary when we are destroying
    // the MVM.
    mMobileViewportManager->RequestReflow(false);
    return NS_OK;
  }

  return ResizeReflowIgnoreOverride(aWidth, aHeight, aOldWidth, aOldHeight,
                                    aOptions);
}

nsresult PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
                                               nscoord aOldWidth,
                                               nscoord aOldHeight,
                                               ResizeReflowOptions aOptions) {
  MOZ_ASSERT(!mIsReflowing, "Shouldn't be in reflow here!");

  nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
  if (!rootFrame) {
    // If we don't have a root frame yet, that means we haven't had our initial
    // reflow... If that's the case, and aWidth or aHeight is unconstrained,
    // ignore them altogether.
    if (aHeight == NS_UNCONSTRAINEDSIZE || aWidth == NS_UNCONSTRAINEDSIZE) {
      // We can't do the work needed for SizeToContent without a root
      // frame, and we want to return before setting the visible area.
      return NS_ERROR_NOT_AVAILABLE;
    }

    mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
    // There isn't anything useful we can do if the initial reflow hasn't
    // happened.
    return NS_OK;
  }

  WritingMode wm = rootFrame->GetWritingMode();
  const bool shrinkToFit = !!(aOptions & ResizeReflowOptions::BSizeLimit);
  MOZ_ASSERT(shrinkToFit ||
                 (wm.IsVertical() ? aWidth : aHeight) != NS_UNCONSTRAINEDSIZE,
             "unconstrained bsize only usable with eBSizeLimit");
  MOZ_ASSERT((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE,
             "unconstrained isize not allowed");
  bool isBSizeChanging =
      wm.IsVertical() ? aOldWidth != aWidth : aOldHeight != aHeight;
  nscoord targetWidth = aWidth;
  nscoord targetHeight = aHeight;

  if (shrinkToFit) {
    if (wm.IsVertical()) {
      targetWidth = NS_UNCONSTRAINEDSIZE;
    } else {
      targetHeight = NS_UNCONSTRAINEDSIZE;
    }
    isBSizeChanging = true;
  }

  const bool suppressingResizeReflow =
      GetPresContext()->SuppressingResizeReflow();

  RefPtr<nsViewManager> viewManager = mViewManager;
  RefPtr<PresShell> kungFuDeathGrip(this);

  if (!suppressingResizeReflow && shrinkToFit) {
    // Make sure that style is flushed before setting the pres context
    // VisibleArea if we're shrinking to fit.
    //
    // Otherwise we may end up with bogus viewport units resolved against the
    // unconstrained bsize, or restyling the whole document resolving viewport
    // units against targetWidth, which may end up doing wasteful work.
    mDocument->FlushPendingNotifications(FlushType::Frames);
  }

  if (!mIsDestroying) {
    mPresContext->SetVisibleArea(nsRect(0, 0, targetWidth, targetHeight));
  }

  if (!mIsDestroying && !suppressingResizeReflow) {
    if (!shrinkToFit) {
      // Flush styles _now_ (with the correct visible area) if not computing the
      // shrink-to-fit size.
      //
      // We've asserted above that sizes are not unconstrained, so this is going
      // to be the final size, which means that we'll get the (correct) final
      // styles now, and avoid a further potentially-wasteful full recascade on
      // the next flush.
      mDocument->FlushPendingNotifications(FlushType::Frames);
    }

    rootFrame = mFrameConstructor->GetRootFrame();
    if (!mIsDestroying && rootFrame) {
      // XXX Do a full invalidate at the beginning so that invalidates along
      // the way don't have region accumulation issues?

      if (isBSizeChanging) {
        // For BSize changes driven by style, RestyleManager handles this.
        // For height:auto BSizes (i.e. layout-controlled), descendant
        // intrinsic sizes can't depend on them. So the only other case is
        // viewport-controlled BSizes which we handle here.
        nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
      }

      {
        nsAutoCauseReflowNotifier crNotifier(this);
        WillDoReflow();

        // Kick off a top-down reflow
        AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
        nsViewManager::AutoDisableRefresh refreshBlocker(viewManager);

        mDirtyRoots.Remove(rootFrame);
        DoReflow(rootFrame, true, nullptr);

        if (shrinkToFit) {
          const bool reflowAgain =
              wm.IsVertical() ? mPresContext->GetVisibleArea().width > aWidth
                              : mPresContext->GetVisibleArea().height > aHeight;

          if (reflowAgain) {
            mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
            DoReflow(rootFrame, true, nullptr);
          }
        }
      }

      // the first DoReflow above should've set our bsize if it was
      // NS_UNCONSTRAINEDSIZE, and the isize shouldn't be NS_UNCONSTRAINEDSIZE
      // anyway
      NS_ASSERTION(mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE,
                   "width should not be NS_UNCONSTRAINEDSIZE after reflow");
      NS_ASSERTION(
          mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE,
          "height should not be NS_UNCONSTRAINEDSIZE after reflow");

      DidDoReflow(true);
    }
  }

  rootFrame = mFrameConstructor->GetRootFrame();
  if (rootFrame) {
    wm = rootFrame->GetWritingMode();
    // reflow did not happen; if the reflow happened, our bsize should not be
    // NS_UNCONSTRAINEDSIZE because DoReflow will fix it up to the same values
    // as below
    if (wm.IsVertical()) {
      if (mPresContext->GetVisibleArea().width == NS_UNCONSTRAINEDSIZE) {
        mPresContext->SetVisibleArea(
            nsRect(0, 0, rootFrame->GetRect().width, aHeight));
      }
    } else {
      if (mPresContext->GetVisibleArea().height == NS_UNCONSTRAINEDSIZE) {
        mPresContext->SetVisibleArea(
            nsRect(0, 0, aWidth, rootFrame->GetRect().height));
      }
    }
  }

  if (!mIsDestroying && !mResizeEventPending &&
      !(aOptions & ResizeReflowOptions::SuppressResizeEvent)) {
    mResizeEventPending = true;
    if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
      mPresContext->RefreshDriver()->AddResizeEventFlushObserver(this);
    }
  }

  return NS_OK;  // XXX this needs to be real. MMP
}

void PresShell::FireResizeEvent() {
  if (mIsDocumentGone) {
    return;
  }

  // If event handling is suppressed, repost the resize event to the refresh
  // driver. The event is marked as delayed so that the refresh driver does not
  // continue ticking.
  if (mDocument->EventHandlingSuppressed()) {
    if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
      mDocument->SetHasDelayedRefreshEvent();
      mPresContext->RefreshDriver()->AddResizeEventFlushObserver(
          this, /* aDelayed = */ true);
    }
    return;
  }

  mResizeEventPending = false;

  // Send resize event from here.
  WidgetEvent event(true, mozilla::eResize);
  nsEventStatus status = nsEventStatus_eIgnore;

  if (nsPIDOMWindowOuter* window = mDocument->GetWindow()) {
    EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
  }
}

static nsIContent* GetNativeAnonymousSubtreeRoot(nsIContent* aContent) {
  if (!aContent || !aContent->IsInNativeAnonymousSubtree()) {
    return nullptr;
  }
  auto* current = aContent;
  // FIXME(emilio): This should not need to worry about current being null, but
  // editor removes nodes in native anonymous subtrees, and we don't clean nodes
  // from the current event content stack from ContentRemoved, so it can
  // actually happen, see bug 1510208.
  while (current && !current->IsRootOfNativeAnonymousSubtree()) {
    current = current->GetFlattenedTreeParent();
  }
  return current;
}

void PresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent) {
  MOZ_ASSERT(aAnonContent->IsRootOfNativeAnonymousSubtree());
  if (nsIContent* root = GetNativeAnonymousSubtreeRoot(mCurrentEventContent)) {
    if (aAnonContent == root) {
      mCurrentEventContent = aAnonContent->GetFlattenedTreeParent();
      mCurrentEventFrame = nullptr;
    }
  }

  for (unsigned int i = 0; i < mCurrentEventContentStack.Length(); i++) {
    nsIContent* anon =
        GetNativeAnonymousSubtreeRoot(mCurrentEventContentStack.ElementAt(i));
    if (aAnonContent == anon) {
      mCurrentEventContentStack.ReplaceObjectAt(
          aAnonContent->GetFlattenedTreeParent(), i);
      mCurrentEventFrameStack[i] = nullptr;
    }
  }
}

void PresShell::SetIgnoreFrameDestruction(bool aIgnore) {
  if (mDocument) {
    // We need to tell the ImageLoader to drop all its references to frames
    // because they're about to go away and it won't get notifications of that.
    mDocument->StyleImageLoader()->ClearFrames(mPresContext);
  }
  mIgnoreFrameDestruction = aIgnore;
}

void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) {
  // We must remove these from FrameLayerBuilder::DisplayItemData::mFrameList
  // here, otherwise the DisplayItemData destructor will use the destroyed frame
  // when it tries to remove it from the (array) value of this property.
  aFrame->RemoveDisplayItemDataForDeletion();

  if (!mIgnoreFrameDestruction) {
    if (aFrame->HasImageRequest()) {
      mDocument->StyleImageLoader()->DropRequestsForFrame(aFrame);
    }

    mFrameConstructor->NotifyDestroyingFrame(aFrame);

    mDirtyRoots.Remove(aFrame);

    // Remove frame properties
    aFrame->DeleteAllProperties();

    if (aFrame == mCurrentEventFrame) {
      mCurrentEventContent = aFrame->GetContent();
      mCurrentEventFrame = nullptr;
    }

#ifdef DEBUG
    if (aFrame == mDrawEventTargetFrame) {
      mDrawEventTargetFrame = nullptr;
    }
#endif

    for (unsigned int i = 0; i < mCurrentEventFrameStack.Length(); i++) {
      if (aFrame == mCurrentEventFrameStack.ElementAt(i)) {
        // One of our stack frames was deleted.  Get its content so that when we
        // pop it we can still get its new frame from its content
        nsIContent* currentEventContent = aFrame->GetContent();
        mCurrentEventContentStack.ReplaceObjectAt(currentEventContent, i);
        mCurrentEventFrameStack[i] = nullptr;
      }
    }

    mFramesToDirty.RemoveEntry(aFrame);

    nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
    if (scrollableFrame) {
      mPendingScrollAnchorSelection.RemoveEntry(scrollableFrame);
      mPendingScrollAnchorAdjustment.RemoveEntry(scrollableFrame);
    }
  }
}

already_AddRefed<nsCaret> PresShell::GetCaret() const {
  RefPtr<nsCaret> caret = mCaret;
  return caret.forget();
}

already_AddRefed<AccessibleCaretEventHub>
PresShell::GetAccessibleCaretEventHub() const {
  RefPtr<AccessibleCaretEventHub> eventHub = mAccessibleCaretEventHub;
  return eventHub.forget();
}

void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; }

void PresShell::RestoreCaret() { mCaret = mOriginalCaret; }

NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) {
  bool oldEnabled = mCaretEnabled;

  mCaretEnabled = aInEnable;

  if (mCaretEnabled != oldEnabled) {
    MOZ_ASSERT(mCaret);
    if (mCaret) {
      mCaret->SetVisible(mCaretEnabled);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly) {
  if (mCaret) mCaret->SetCaretReadOnly(aReadOnly);
  return NS_OK;
}

NS_IMETHODIMP PresShell::GetCaretEnabled(bool* aOutEnabled) {
  NS_ENSURE_ARG_POINTER(aOutEnabled);
  *aOutEnabled = mCaretEnabled;
  return NS_OK;
}

NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility) {
  if (mCaret) mCaret->SetVisibilityDuringSelection(aVisibility);
  return NS_OK;
}

NS_IMETHODIMP PresShell::GetCaretVisible(bool* aOutIsVisible) {
  *aOutIsVisible = false;
  if (mCaret) {
    *aOutIsVisible = mCaret->IsVisible();
  }
  return NS_OK;
}

NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t aInEnable) {
  mSelectionFlags = aInEnable;
  return NS_OK;
}

NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t* aOutEnable) {
  if (!aOutEnable) return NS_ERROR_INVALID_ARG;
  *aOutEnable = mSelectionFlags;
  return NS_OK;
}

// implementation of nsISelectionController

NS_IMETHODIMP
PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
}

NS_IMETHODIMP
PresShell::CharacterMove(bool aForward, bool aExtend) {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->CharacterMove(aForward, aExtend);
}

NS_IMETHODIMP
PresShell::CharacterExtendForDelete() {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->CharacterExtendForDelete();
}

NS_IMETHODIMP
PresShell::CharacterExtendForBackspace() {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->CharacterExtendForBackspace();
}

NS_IMETHODIMP
PresShell::WordMove(bool aForward, bool aExtend) {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  nsresult result = frameSelection->WordMove(aForward, aExtend);
  // if we can't go down/up any more we must then move caret completely to
  // end/beginning respectively.
  if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
  return result;
}

NS_IMETHODIMP
PresShell::WordExtendForDelete(bool aForward) {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->WordExtendForDelete(aForward);
}

NS_IMETHODIMP
PresShell::LineMove(bool aForward, bool aExtend) {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  nsresult result = frameSelection->LineMove(aForward, aExtend);
  // if we can't go down/up any more we must then move caret completely to
  // end/beginning respectively.
  if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
  return result;
}

NS_IMETHODIMP
PresShell::IntraLineMove(bool aForward, bool aExtend) {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->IntraLineMove(aForward, aExtend);
}

NS_IMETHODIMP
PresShell::PageMove(bool aForward, bool aExtend) {
  nsIFrame* frame = nullptr;
  if (!aExtend) {
    frame = do_QueryFrame(
        GetScrollableFrameToScroll(ScrollableDirection::Vertical));
    // If there is no scrollable frame, get the frame to move caret instead.
  }
  if (!frame) {
    frame = mSelection->GetFrameToPageSelect();
    if (!frame) {
      return NS_OK;
    }
  }
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  frameSelection->CommonPageMove(aForward, aExtend, frame);
  // After ScrollSelectionIntoView(), the pending notifications might be
  // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
  return ScrollSelectionIntoView(
      nsISelectionController::SELECTION_NORMAL,
      nsISelectionController::SELECTION_FOCUS_REGION,
      nsISelectionController::SCROLL_SYNCHRONOUS |
          nsISelectionController::SCROLL_FOR_CARET_MOVE);
}

NS_IMETHODIMP
PresShell::ScrollPage(bool aForward) {
  nsIScrollableFrame* scrollFrame =
      GetScrollableFrameToScroll(ScrollableDirection::Vertical);
  if (scrollFrame) {
    scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
                          nsIScrollableFrame::PAGES, ScrollMode::Smooth,
                          nullptr, nullptr, nsIScrollableFrame::NOT_MOMENTUM,
                          nsIScrollableFrame::ENABLE_SNAP);
  }
  return NS_OK;
}

NS_IMETHODIMP
PresShell::ScrollLine(bool aForward) {
  nsIScrollableFrame* scrollFrame =
      GetScrollableFrameToScroll(ScrollableDirection::Vertical);
  if (scrollFrame) {
    int32_t lineCount =
        Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
                            NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
    scrollFrame->ScrollBy(nsIntPoint(0, aForward ? lineCount : -lineCount),
                          nsIScrollableFrame::LINES, ScrollMode::Smooth,
                          nullptr, nullptr, nsIScrollableFrame::NOT_MOMENTUM,
                          nsIScrollableFrame::ENABLE_SNAP);
  }
  return NS_OK;
}

NS_IMETHODIMP
PresShell::ScrollCharacter(bool aRight) {
  nsIScrollableFrame* scrollFrame =
      GetScrollableFrameToScroll(ScrollableDirection::Horizontal);
  if (scrollFrame) {
    int32_t h =
        Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
                            NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
    scrollFrame->ScrollBy(nsIntPoint(aRight ? h : -h, 0),
                          nsIScrollableFrame::LINES, ScrollMode::Smooth,
                          nullptr, nullptr, nsIScrollableFrame::NOT_MOMENTUM,
                          nsIScrollableFrame::ENABLE_SNAP);
  }
  return NS_OK;
}

NS_IMETHODIMP
PresShell::CompleteScroll(bool aForward) {
  nsIScrollableFrame* scrollFrame =
      GetScrollableFrameToScroll(ScrollableDirection::Vertical);
  if (scrollFrame) {
    scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
                          nsIScrollableFrame::WHOLE, ScrollMode::Smooth,
                          nullptr, nullptr, nsIScrollableFrame::NOT_MOMENTUM,
                          nsIScrollableFrame::ENABLE_SNAP);
  }
  return NS_OK;
}

NS_IMETHODIMP
PresShell::CompleteMove(bool aForward, bool aExtend) {
  // Beware! This may flush notifications via synchronous
  // ScrollSelectionIntoView.
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  nsIContent* limiter = frameSelection->GetAncestorLimiter();
  nsIFrame* frame = limiter ? limiter->GetPrimaryFrame()
                            : FrameConstructor()->GetRootElementFrame();
  if (!frame) return NS_ERROR_FAILURE;
  nsIFrame::CaretPosition pos = frame->GetExtremeCaretPosition(!aForward);
  frameSelection->HandleClick(
      pos.mResultContent, pos.mContentOffset, pos.mContentOffset, aExtend,
      false, aForward ? CARET_ASSOCIATE_AFTER : CARET_ASSOCIATE_BEFORE);
  if (limiter) {
    // HandleClick resets ancestorLimiter, so set it again.
    frameSelection->SetAncestorLimiter(limiter);
  }

  // After ScrollSelectionIntoView(), the pending notifications might be
  // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
  return ScrollSelectionIntoView(
      nsISelectionController::SELECTION_NORMAL,
      nsISelectionController::SELECTION_FOCUS_REGION,
      nsISelectionController::SCROLL_SYNCHRONOUS |
          nsISelectionController::SCROLL_FOR_CARET_MOVE);
}

NS_IMETHODIMP
PresShell::SelectAll() {
  RefPtr<nsFrameSelection> frameSelection = mSelection;
  return frameSelection->SelectAll();
}

static void DoCheckVisibility(nsPresContext* aPresContext, nsIContent* aNode,
                              int16_t aStartOffset, int16_t aEndOffset,
                              bool* aRetval) {
  nsIFrame* frame = aNode->GetPrimaryFrame();
  if (!frame) {
    // No frame to look at so it must not be visible.
    return;
  }

  // Start process now to go through all frames to find startOffset. Then check
  // chars after that to see if anything until EndOffset is visible.
  bool finished = false;
  frame->CheckVisibility(aPresContext, aStartOffset, aEndOffset, true,
                         &finished, aRetval);
  // Don't worry about other return value.
}

NS_IMETHODIMP
PresShell::CheckVisibility(nsINode* node, int16_t startOffset,
                           int16_t EndOffset, bool* _retval) {
  if (!node || startOffset > EndOffset || !_retval || startOffset < 0 ||
      EndOffset < 0)
    return NS_ERROR_INVALID_ARG;
  *_retval = false;  // initialize return parameter
  nsCOMPtr<nsIContent> content(do_QueryInterface(node));
  if (!content) return NS_ERROR_FAILURE;

  DoCheckVisibility(mPresContext, content, startOffset, EndOffset, _retval);
  return NS_OK;
}

nsresult PresShell::CheckVisibilityContent(nsIContent* aNode,
                                           int16_t aStartOffset,
                                           int16_t aEndOffset, bool* aRetval) {
  if (!aNode || aStartOffset > aEndOffset || !aRetval || aStartOffset < 0 ||
      aEndOffset < 0) {
    return NS_ERROR_INVALID_ARG;
  }

  *aRetval = false;
  DoCheckVisibility(mPresContext, aNode, aStartOffset, aEndOffset, aRetval);
  return NS_OK;
}

// end implementations nsISelectionController

nsIFrame* PresShell::GetRootScrollFrame() const {
  nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
  // Ensure root frame is a viewport frame
  if (!rootFrame || !rootFrame->IsViewportFrame()) return nullptr;
  nsIFrame* theFrame = rootFrame->PrincipalChildList().FirstChild();
  if (!theFrame || !theFrame->IsScrollFrame()) return nullptr;
  return theFrame;
}

nsIScrollableFrame* PresShell::GetRootScrollFrameAsScrollable() const {
  nsIFrame* frame = GetRootScrollFrame();
  if (!frame) return nullptr;
  nsIScrollableFrame* scrollableFrame = do_QueryFrame(frame);
  NS_ASSERTION(scrollableFrame,
               "All scroll frames must implement nsIScrollableFrame");
  return scrollableFrame;
}

nsPageSequenceFrame* PresShell::GetPageSequenceFrame() const {
  return mFrameConstructor->GetPageSequenceFrame();
}

nsCanvasFrame* PresShell::GetCanvasFrame() const {
  nsIFrame* frame = mFrameConstructor->GetDocElementContainingBlock();
  return do_QueryFrame(frame);
}

void PresShell::RestoreRootScrollPosition() {
  nsIScrollableFrame* scrollableFrame = GetRootScrollFrameAsScrollable();
  if (scrollableFrame) {
    scrollableFrame->ScrollToRestoredPosition();
  }
}

void PresShell::MaybeReleaseCapturingContent() {
  RefPtr<nsFrameSelection> frameSelection = FrameSelection();
  if (frameSelection) {
    frameSelection->SetDragState(false);
  }
  if (sCapturingContentInfo.mContent &&
      sCapturingContentInfo.mContent->OwnerDoc() == mDocument) {
    PresShell::ReleaseCapturingContent();
  }
}

void PresShell::BeginLoad(Document* aDocument) {
  mDocumentLoading = true;

  gfxTextPerfMetrics* tp = nullptr;
  if (mPresContext) {
    tp = mPresContext->GetTextPerfMetrics();
  }

  bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
  if (shouldLog || tp) {
    mLoadBegin = TimeStamp::Now();
  }

  if (shouldLog) {
    nsIURI* uri = mDocument->GetDocumentURI();
    MOZ_LOG(gLog, LogLevel::Debug,
            ("(presshell) %p load begin [%s]\n", this,
             uri ? uri->GetSpecOrDefault().get() : ""));
  }
}

void PresShell::EndLoad(Document* aDocument) {
  MOZ_ASSERT(aDocument == mDocument, "Wrong document");

  RestoreRootScrollPosition();

  mDocumentLoading = false;
}

void PresShell::LoadComplete() {
  gfxTextPerfMetrics* tp = nullptr;
  if (mPresContext) {
    tp = mPresContext->GetTextPerfMetrics();
  }

  // log load
  bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
  if (shouldLog || tp) {
    TimeDuration loadTime = TimeStamp::Now() - mLoadBegin;
    nsIURI* uri = mDocument->GetDocumentURI();
    nsAutoCString spec;
    if (uri) {
      spec = uri->GetSpecOrDefault();
    }
    if (shouldLog) {
      MOZ_LOG(gLog, LogLevel::Debug,
              ("(presshell) %p load done time-ms: %9.2f [%s]\n", this,
               loadTime.ToMilliseconds(), spec.get()));
    }
    if (tp) {
      tp->Accumulate();
      if (tp->cumulative.numChars > 0) {
        LogTextPerfStats(tp, this, tp->cumulative, loadTime.ToMilliseconds(),
                         eLog_loaddone, spec.get());
      }
    }
  }
}

#ifdef DEBUG
void PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame) {
  // XXXbz due to bug 372769, can't actually assert anything here...
  return;

  // XXXbz shouldn't need this part; remove it once FrameNeedsReflow
  // handles the root frame correctly.
  if (!aFrame->GetParent()) {
    return;
  }

  // Make sure that there is a reflow root ancestor of |aFrame| that's
  // in mDirtyRoots already.
  while (aFrame && (aFrame->GetStateBits() & NS_FRAME_HAS_DIRTY_CHILDREN)) {
    if ((aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
                                 NS_FRAME_DYNAMIC_REFLOW_ROOT) ||
         !aFrame->GetParent()) &&
        mDirtyRoots.Contains(aFrame)) {
      return;
    }

    aFrame = aFrame->GetParent();
  }

  MOZ_ASSERT_UNREACHABLE(
      "Frame has dirty bits set but isn't scheduled to be "
      "reflowed?");
}
#endif

void PresShell::PostPendingScrollAnchorSelection(
    mozilla::layout::ScrollAnchorContainer* aContainer) {
  mPendingScrollAnchorSelection.PutEntry(aContainer->ScrollableFrame());
}

void PresShell::FlushPendingScrollAnchorSelections() {
  for (auto iter = mPendingScrollAnchorSelection.Iter(); !iter.Done();
       iter.Next()) {
    nsIScrollableFrame* scroll = iter.Get()->GetKey();
    scroll->Anchor()->SelectAnchor();
  }
  mPendingScrollAnchorSelection.Clear();
}

void PresShell::PostPendingScrollAnchorAdjustment(
    ScrollAnchorContainer* aContainer) {
  mPendingScrollAnchorAdjustment.PutEntry(aContainer->ScrollableFrame());
}

void PresShell::FlushPendingScrollAnchorAdjustments() {
  for (auto iter = mPendingScrollAnchorAdjustment.Iter(); !iter.Done();
       iter.Next()) {
    nsIScrollableFrame* scroll = iter.Get()->GetKey();
    scroll->Anchor()->ApplyAdjustments();
  }
  mPendingScrollAnchorAdjustment.Clear();
}

void PresShell::FrameNeedsReflow(nsIFrame* aFrame,
                                 IntrinsicDirty aIntrinsicDirty,
                                 nsFrameState aBitToAdd,
                                 ReflowRootHandling aRootHandling) {
  MOZ_ASSERT(aBitToAdd == NS_FRAME_IS_DIRTY ||
                 aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN || !aBitToAdd,
             "Unexpected bits being added");

  // FIXME bug 478135
  NS_ASSERTION(!(aIntrinsicDirty == IntrinsicDirty::StyleChange &&
                 aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN),
               "bits don't correspond to style change reason");

  // FIXME bug 457400
  NS_ASSERTION(!mIsReflowing, "can't mark frame dirty during reflow");

  // If we've not yet done the initial reflow, then don't bother
  // enqueuing a reflow command yet.
  if (!mDidInitialize) return;

  // If we're already destroying, don't bother with this either.
  if (mIsDestroying) return;

#ifdef DEBUG
  // printf("gShellCounter: %d\n", gShellCounter++);
  if (mInVerifyReflow) return;

  if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
    printf("\nPresShell@%p: frame %p needs reflow\n", (void*)this,
           (void*)aFrame);
    if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
      printf("Current content model:\n");
      Element* rootElement = mDocument->GetRootElement();
      if (rootElement) {
        rootElement->List(stdout, 0);
      }
    }
  }
#endif

  AutoTArray<nsIFrame*, 4> subtrees;
  subtrees.AppendElement(aFrame);

  do {
    nsIFrame* subtreeRoot = subtrees.PopLastElement();

    // Grab |wasDirty| now so we can go ahead and update the bits on
    // subtreeRoot.
    bool wasDirty = NS_SUBTREE_DIRTY(subtreeRoot);
    subtreeRoot->AddStateBits(aBitToAdd);

    // Determine whether we need to keep looking for the next ancestor
    // reflow root if subtreeRoot itself is a reflow root.
    bool targetNeedsReflowFromParent;
    switch (aRootHandling) {
      case ReflowRootHandling::PositionOrSizeChange:
        targetNeedsReflowFromParent = true;
        break;
      case ReflowRootHandling::NoPositionOrSizeChange:
        targetNeedsReflowFromParent = false;
        break;
      case ReflowRootHandling::InferFromBitToAdd:
        targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY);
        break;
    }

#define FRAME_IS_REFLOW_ROOT(_f)                          \
  ((_f)->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |           \
                         NS_FRAME_DYNAMIC_REFLOW_ROOT) && \
   ((_f) != subtreeRoot || !targetNeedsReflowFromParent))

    // Mark the intrinsic widths as dirty on the frame, all of its ancestors,
    // and all of its descendants, if needed:

    if (aIntrinsicDirty != IntrinsicDirty::Resize) {
      // Mark argument and all ancestors dirty. (Unless we hit a reflow
      // root that should contain the reflow.  That root could be
      // subtreeRoot itself if it's not dirty, or it could be some
      // ancestor of subtreeRoot.)
      for (nsIFrame* a = subtreeRoot; a && !FRAME_IS_REFLOW_ROOT(a);
           a = a->GetParent()) {
        a->MarkIntrinsicISizesDirty();
        if (a->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
            a->IsAbsolutelyPositioned()) {
          // If we get here, 'a' is abspos, so its subtree's intrinsic sizing
          // has no effect on its ancestors' intrinsic sizing. So, don't loop
          // upwards any further.
          break;
        }
      }
    }

    if (aIntrinsicDirty == IntrinsicDirty::StyleChange) {
      // Mark all descendants dirty (using an nsTArray stack rather than
      // recursion).
      // Note that ReflowInput::InitResizeFlags has some similar
      // code; see comments there for how and why it differs.
      AutoTArray<nsIFrame*, 32> stack;
      stack.AppendElement(subtreeRoot);

      do {
        nsIFrame* f = stack.PopLastElement();

        if (f->IsPlaceholderFrame()) {
          nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
          if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
            // We have another distinct subtree we need to mark.
            subtrees.AppendElement(oof);
          }
        }

        nsIFrame::ChildListIterator lists(f);
        for (; !lists.IsDone(); lists.Next()) {
          for (nsIFrame* kid : lists.CurrentList()) {
            kid->MarkIntrinsicISizesDirty();
            stack.AppendElement(kid);
          }
        }
      } while (stack.Length() != 0);
    }

    // Skip setting dirty bits up the tree if we weren't given a bit to add.
    if (!aBitToAdd) {
      continue;
    }

    // Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty)
    // up the tree until we reach either a frame that's already dirty or
    // a reflow root.
    nsIFrame* f = subtreeRoot;
    for (;;) {
      if (FRAME_IS_REFLOW_ROOT(f) || !f->GetParent()) {
        // we've hit a reflow root or the root frame
        if (!wasDirty) {
          mDirtyRoots.Add(f);
          SetNeedLayoutFlush();
        }
#ifdef DEBUG
        else {
          VerifyHasDirtyRootAncestor(f);
        }
#endif

        break;
      }

      nsIFrame* child = f;
      f = f->GetParent();
      wasDirty = NS_SUBTREE_DIRTY(f);
      f->ChildIsDirty(child);
      NS_ASSERTION(f->GetStateBits() & NS_FRAME_HAS_DIRTY_CHILDREN,
                   "ChildIsDirty didn't do its job");
      if (wasDirty) {
        // This frame was already marked dirty.
#ifdef DEBUG
        VerifyHasDirtyRootAncestor(f);
#endif
        break;
      }
    }
  } while (subtrees.Length() != 0);

  MaybeScheduleReflow();
}

void PresShell::FrameNeedsToContinueReflow(nsIFrame* aFrame) {
  NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty.");
  MOZ_ASSERT(mCurrentReflowRoot, "Must have a current reflow root here");
  NS_ASSERTION(
      aFrame == mCurrentReflowRoot ||
          nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame),
      "Frame passed in is not the descendant of mCurrentReflowRoot");
  NS_ASSERTION(aFrame->GetStateBits() & NS_FRAME_IN_REFLOW,
               "Frame passed in not in reflow?");

  mFramesToDirty.PutEntry(aFrame);
}

already_AddRefed<nsIContent> PresShell::GetContentForScrolling() const {
  if (nsCOMPtr<nsIContent> focused = GetFocusedContentInOurWindow()) {
    return focused.forget();
  }
  return GetSelectedContentForScrolling();
}

already_AddRefed<nsIContent> PresShell::GetSelectedContentForScrolling() const {
  nsCOMPtr<nsIContent> selectedContent;
  if (mSelection) {
    Selection* domSelection = mSelection->GetSelection(SelectionType::eNormal);
    if (domSelection) {
      selectedContent =
          nsIContent::FromNodeOrNull(domSelection->GetFocusNode());
    }
  }
  return selectedContent.forget();
}

nsIScrollableFrame* PresShell::GetNearestScrollableFrame(
    nsIFrame* aFrame, ScrollableDirection aDirection) {
  if (aDirection == ScrollableDirection::Either) {
    return nsLayoutUtils::GetNearestScrollableFrame(aFrame);
  }

  return nsLayoutUtils::GetNearestScrollableFrameForDirection(
      aFrame, aDirection == ScrollableDirection::Vertical
                  ? nsLayoutUtils::eVertical
                  : nsLayoutUtils::eHorizontal);
}

nsIScrollableFrame* PresShell::GetScrollableFrameToScrollForContent(
    nsIContent* aContent, ScrollableDirection aDirection) {
  nsIScrollableFrame* scrollFrame = nullptr;
  if (aContent) {
    nsIFrame* startFrame = aContent->GetPrimaryFrame();
    if (startFrame) {
      scrollFrame = startFrame->GetScrollTargetFrame();
      if (scrollFrame) {
        startFrame = scrollFrame->GetScrolledFrame();
      }
      scrollFrame = GetNearestScrollableFrame(startFrame, aDirection);
    }
  }
  if (!scrollFrame) {
    scrollFrame = GetRootScrollFrameAsScrollable();
    if (!scrollFrame || !scrollFrame->GetScrolledFrame()) {
      return nullptr;
    }
    scrollFrame =
        GetNearestScrollableFrame(scrollFrame->GetScrolledFrame(), aDirection);
  }
  return scrollFrame;
}

nsIScrollableFrame* PresShell::GetScrollableFrameToScroll(
    ScrollableDirection aDirection) {
  nsCOMPtr<nsIContent> content = GetContentForScrolling();
  return GetScrollableFrameToScrollForContent(content.get(), aDirection);
}

void PresShell::CancelAllPendingReflows() {
  mDirtyRoots.Clear();

  if (mObservingLayoutFlushes) {
    GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this);
    mObservingLayoutFlushes = false;
  }

  ASSERT_REFLOW_SCHEDULED_STATE();
}

static bool DestroyFramesAndStyleDataFor(
    Element* aElement, nsPresContext& aPresContext,
    RestyleManager::IncludeRoot aIncludeRoot) {
  bool didReconstruct =
      aPresContext.FrameConstructor()->DestroyFramesFor(aElement);
  RestyleManager::ClearServoDataFromSubtree(aElement, aIncludeRoot);
  return didReconstruct;
}

void PresShell::SlotAssignmentWillChange(Element& aElement,
                                         HTMLSlotElement* aOldSlot,
                                         HTMLSlotElement* aNewSlot) {
  MOZ_ASSERT(aOldSlot != aNewSlot);

  if (MOZ_UNLIKELY(!mDidInitialize)) {
    return;
  }

  // If the old slot is about to become empty, let layout know that it needs to
  // do work.
  if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1) {
    DestroyFramesForAndRestyle(aOldSlot);
  }

  // Ensure the new element starts off clean.
  DestroyFramesAndStyleDataFor(&aElement, *mPresContext,
                               RestyleManager::IncludeRoot::Yes);

  if (aNewSlot) {
    // If the new slot will stop showing fallback content, we need to reframe it
    // altogether.
    if (aNewSlot->AssignedNodes().IsEmpty()) {
      DestroyFramesForAndRestyle(aNewSlot);
      // Otherwise we just care about the element, but we need to ensure that
      // something takes care of traversing to the relevant slot, if needed.
    } else if (aNewSlot->HasServoData() &&
               !Servo_Element_IsDisplayNone(aNewSlot)) {
      // Set the reframe bits...
      aNewSlot->NoteDescendantsNeedFramesForServo();
      aElement.SetFlags(NODE_NEEDS_FRAME);
      // Now the style dirty bits. Note that we can't just do
      // aElement.NoteDirtyForServo(), because the new slot is not setup yet.
      aNewSlot->SetHasDirtyDescendantsForServo();
      aNewSlot->NoteDirtySubtreeForServo();
    }
  }
}

#ifdef DEBUG
static void AssertNoFramesInSubtree(nsIContent* aContent) {
  for (nsINode* node : ShadowIncludingTreeIterator(*aContent)) {
    nsIContent* c = nsIContent::FromNode(node);
    MOZ_ASSERT(!c->GetPrimaryFrame());
    if (auto* binding = c->GetXBLBinding()) {
      if (auto* bindingWithContent = binding->GetBindingWithContent()) {
        nsIContent* anonContent = bindingWithContent->GetAnonymousContent();
        MOZ_ASSERT(!anonContent->GetPrimaryFrame());

        // Need to do this instead of just AssertNoFramesInSubtree(anonContent),
        // because the parent of the children of the <content> element isn't the
        // <content> element, but the bound element, and that confuses
        // GetNextNode a lot.
        for (nsIContent* child = anonContent->GetFirstChild(); child;
             child = child->GetNextSibling()) {
          AssertNoFramesInSubtree(child);
        }
      }
    }
  }
}
#endif

void PresShell::DestroyFramesForAndRestyle(Element* aElement) {
#ifdef DEBUG
  auto postCondition =
      mozilla::MakeScopeExit([&]() { AssertNoFramesInSubtree(aElement); });
#endif

  MOZ_ASSERT(aElement);
  if (MOZ_UNLIKELY(!mDidInitialize)) {
    return;
  }

  if (!aElement->GetFlattenedTreeParentNode()) {
    // Nothing to do here, the element already is out of the frame tree.
    return;
  }

  nsAutoScriptBlocker scriptBlocker;

  // Mark ourselves as not safe to flush while we're doing frame destruction.
  ++mChangeNestCount;

  const bool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement);

  // Clear the style data from all the flattened tree descendants, but _not_
  // from us, since otherwise we wouldn't see the reframe.
  RestyleManager::ClearServoDataFromSubtree(aElement,
                                            RestyleManager::IncludeRoot::No);

  auto changeHint =
      didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame;

  mPresContext->RestyleManager()->PostRestyleEvent(
      aElement, RestyleHint::RestyleSubtree(), changeHint);

  --mChangeNestCount;
}

void PresShell::PostRecreateFramesFor(Element* aElement) {
  if (MOZ_UNLIKELY(!mDidInitialize)) {
    // Nothing to do here. In fact, if we proceed and aElement is the root, we
    // will crash.
    return;
  }

  mPresContext->RestyleManager()->PostRestyleEvent(
      aElement, RestyleHint{0}, nsChangeHint_ReconstructFrame);
}

void PresShell::RestyleForAnimation(Element* aElement, RestyleHint aHint) {
  // Now that we no longer have separate non-animation and animation
  // restyles, this method having a distinct identity is less important,
  // but it still seems useful to offer as a "more public" API and as a
  // chokepoint for these restyles to go through.
  mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint,
                                                   nsChangeHint(0));
}

void PresShell::SetForwardingContainer(const WeakPtr<nsDocShell>& aContainer) {
  mForwardingContainer = aContainer;
}

void PresShell::ClearFrameRefs(nsIFrame* aFrame) {
  mPresContext->EventStateManager()->ClearFrameRefs(aFrame);

  AutoWeakFrame* weakFrame = mAutoWeakFrames;
  while (weakFrame) {
    AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame();
    if (weakFrame->GetFrame() == aFrame) {
      // This removes weakFrame from mAutoWeakFrames.
      weakFrame->Clear(this);
    }
    weakFrame = prev;
  }

  AutoTArray<WeakFrame*, 4> toRemove;
  for (auto iter = mWeakFrames.Iter(); !iter.Done(); iter.Next()) {
    WeakFrame* weakFrame = iter.Get()->GetKey();
    if (weakFrame->GetFrame() == aFrame) {
      toRemove.AppendElement(weakFrame);
    }
  }
  for (WeakFrame* weakFrame : toRemove) {
    weakFrame->Clear(this);
  }
}

already_AddRefed<gfxContext> PresShell::CreateReferenceRenderingContext() {
  nsDeviceContext* devCtx = mPresContext->DeviceContext();
  RefPtr<gfxContext> rc;
  if (mPresContext->IsScreen()) {
    rc = gfxContext::CreateOrNull(
        gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
  } else {
    // We assume the devCtx has positive width and height for this call.
    // However, width and height, may be outside of the reasonable range
    // so rc may still be null.
    rc = devCtx->CreateReferenceRenderingContext();
  }

  return rc ? rc.forget() : nullptr;
}

nsresult PresShell::GoToAnchor(const nsAString& aAnchorName, bool aScroll,
                               ScrollFlags aAdditionalScrollFlags) {
  if (!mDocument) {
    return NS_ERROR_FAILURE;
  }

  const Element* root = mDocument->GetRootElement();
  if (root && root->IsSVGElement(nsGkAtoms::svg)) {
    // We need to execute this even if there is an empty anchor name
    // so that any existing SVG fragment identifier effect is removed
    if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument,
                                                         aAnchorName)) {
      return NS_OK;
    }
  }

  // Hold a reference to the ESM in case event dispatch tears us down.
  RefPtr<EventStateManager> esm = mPresContext->EventStateManager();

  if (aAnchorName.IsEmpty()) {
    NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
    esm->SetContentState(nullptr, NS_EVENT_STATE_URLTARGET);
    return NS_OK;
  }

  nsresult rv = NS_OK;
  nsCOMPtr<nsIContent> content;

  // Search for an element with a matching "id" attribute
  if (mDocument) {
    content = mDocument->GetElementById(aAnchorName);
  }

  // Search for an anchor element with a matching "name" attribute
  if (!content && mDocument->IsHTMLDocument()) {
    // Find a matching list of named nodes
    nsCOMPtr<nsINodeList> list = mDocument->GetElementsByName(aAnchorName);
    if (list) {
      // Loop through the named nodes looking for the first anchor
      uint32_t length = list->Length();
      for (uint32_t i = 0; i < length; i++) {
        nsIContent* node = list->Item(i);
        if (node->IsHTMLElement(nsGkAtoms::a)) {
          content = node;
          break;
        }
      }
    }
  }

  // Search for anchor in the HTML namespace with a matching name
  if (!content && !mDocument->IsHTMLDocument()) {
    NS_NAMED_LITERAL_STRING(nameSpace, "http://www.w3.org/1999/xhtml");
    // Get the list of anchor elements
    nsCOMPtr<nsINodeList> list =
        mDocument->GetElementsByTagNameNS(nameSpace, NS_LITERAL_STRING("a"));
    // Loop through the anchors looking for the first one with the given name.
    for (uint32_t i = 0; true; i++) {
      nsIContent* node = list->Item(i);
      if (!node) {  // End of list
        break;
      }

      // Compare the name attribute
      if (node->IsElement() &&
          node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
                                         aAnchorName, eCaseMatters)) {
        content = node;
        break;
      }
    }
  }

  esm->SetContentState(content, NS_EVENT_STATE_URLTARGET);

#ifdef ACCESSIBILITY
  nsIContent* anchorTarget = content;
#endif

  nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
  if (rootScroll && rootScroll->DidHistoryRestore()) {
    // Scroll position restored from history trumps scrolling to anchor.
    aScroll = false;
    rootScroll->ClearDidHistoryRestore();
  }

  if (content) {
    if (aScroll) {
      rv = ScrollContentIntoView(
          content, ScrollAxis(kScrollToTop, WhenToScroll::Always), ScrollAxis(),
          ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags);
      NS_ENSURE_SUCCESS(rv, rv);

      nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
      if (rootScroll) {
        mLastAnchorScrolledTo = content;
        mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y;
      }
    }

    // Should we select the target? This action is controlled by a
    // preference: the default is to not select.
    bool selectAnchor = Preferences::GetBool("layout.selectanchor");

    // Even if select anchor pref is false, we must still move the
    // caret there. That way tabbing will start from the new
    // location
    RefPtr<nsRange> jumpToRange = new nsRange(mDocument);
    while (content && content->GetFirstChild()) {
      content = content->GetFirstChild();
    }
    jumpToRange->SelectNodeContents(*content, IgnoreErrors());
    // Select the anchor
    RefPtr<Selection> sel = mSelection->GetSelection(SelectionType::eNormal);
    if (sel) {
      sel->RemoveAllRanges(IgnoreErrors());
      sel->AddRange(*jumpToRange, IgnoreErrors());
      if (!selectAnchor) {
        // Use a caret (collapsed selection) at the start of the anchor
        sel->CollapseToStart(IgnoreErrors());
      }
    }
    // Selection is at anchor.
    // Now focus the document itself if focus is on an element within it.
    nsPIDOMWindowOuter* win = mDocument->GetWindow();

    nsIFocusManager* fm = nsFocusManager::GetFocusManager();
    if (fm && win) {
      nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
      fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
      if (SameCOMIdentity(win, focusedWindow)) {
        fm->ClearFocus(focusedWindow);
      }
    }

    // If the target is an animation element, activate the animation
    if (content->IsNodeOfType(nsINode::eANIMATION)) {
      SVGContentUtils::ActivateByHyperlink(content.get());
    }
  } else {
    rv = NS_ERROR_FAILURE;
    NS_NAMED_LITERAL_STRING(top, "top");
    if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, top)) {
      // Scroll to the top/left if aAnchorName is "top" and there is no element
      // with such a name or id.
      rv = NS_OK;
      nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable();
      // Check |aScroll| after setting |rv| so we set |rv| to the same
      // thing whether or not |aScroll| is true.
      if (aScroll && sf) {
        // Scroll to the top of the page
        sf->ScrollTo(nsPoint(0, 0), ScrollMode::Instant);
      }
    }
  }

#ifdef ACCESSIBILITY
  if (anchorTarget) {
    if (nsAccessibilityService* accService = GetAccessibilityService()) {
      accService->NotifyOfAnchorJumpTo(anchorTarget);
    }
  }
#endif  // #ifdef ACCESSIBILITY

  return rv;
}

nsresult PresShell::ScrollToAnchor() {
  if (!mLastAnchorScrolledTo) {
    return NS_OK;
  }
  NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");

  nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
  if (!rootScroll ||
      mLastAnchorScrollPositionY != rootScroll->GetScrollPosition().y) {
    return NS_OK;
  }
  nsCOMPtr<nsIContent> lastAnchorScrollTo = mLastAnchorScrolledTo;
  nsresult rv = ScrollContentIntoView(
      lastAnchorScrollTo, ScrollAxis(kScrollToTop, WhenToScroll::Always),
      ScrollAxis(), ScrollFlags::AnchorScrollFlags);
  mLastAnchorScrolledTo = nullptr;
  return rv;
}

/*
 * Helper (per-continuation) for ScrollContentIntoView.
 *
 * @param aContainerFrame [in] the frame which aRect is relative to
 * @param aFrame [in] Frame whose bounds should be unioned
 * @param aUseWholeLineHeightForInlines [in] if true, then for inline frames
 * we should include the top of the line in the added rectangle
 * @param aRect [inout] rect into which its bounds should be unioned
 * @param aHaveRect [inout] whether aRect contains data yet
 * @param aPrevBlock [inout] the block aLines is a line iterator for
 * @param aLines [inout] the line iterator we're using
 * @param aCurLine [inout] the line to start looking from in this iterator
 */
static void AccumulateFrameBounds(nsIFrame* aContainerFrame, nsIFrame* aFrame,
                                  bool aUseWholeLineHeightForInlines,
                                  nsRect& aRect, bool& aHaveRect,
                                  nsIFrame*& aPrevBlock,
                                  nsAutoLineIterator& aLines,
                                  int32_t& aCurLine) {
  nsIFrame* frame = aFrame;
  nsRect frameBounds = nsRect(nsPoint(0, 0), aFrame->GetSize());

  // If this is an inline frame and either the bounds height is 0 (quirks
  // layout model) or aUseWholeLineHeightForInlines is set, we need to
  // change the top of the bounds to include the whole line.
  if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) {
    nsIFrame* prevFrame = aFrame;
    nsIFrame* f = aFrame;

    while (f && f->IsFrameOfType(nsIFrame::eLineParticipant) &&
           !f->IsTransformed() && !f->IsAbsPosContainingBlock()) {
      prevFrame = f;
      f = prevFrame->GetParent();
    }

    if (f != aFrame && f && f->IsBlockFrame()) {
      // find the line containing aFrame and increase the top of |offset|.
      if (f != aPrevBlock) {
        aLines = f->GetLineIterator();
        aPrevBlock = f;
        aCurLine = 0;
      }
      if (aLines) {
        int32_t index = aLines->FindLineContaining(prevFrame, aCurLine);
        if (index >= 0) {
          aCurLine = index;
          nsIFrame* trash1;
          int32_t trash2;
          nsRect lineBounds;

          if (NS_SUCCEEDED(
                  aLines->GetLine(index, &trash1, &trash2, lineBounds))) {
            frameBounds += frame->GetOffsetTo(f);
            frame = f;
            if (lineBounds.y < frameBounds.y) {
              frameBounds.height = frameBounds.YMost() - lineBounds.y;
              frameBounds.y = lineBounds.y;
            }
          }
        }
      }
    }
  }

  nsRect transformedBounds = nsLayoutUtils::TransformFrameRectToAncestor(
      frame, frameBounds, aContainerFrame);

  if (aHaveRect) {
    // We can't use nsRect::UnionRect since it drops empty rects on
    // the floor, and we need to include them.  (Thus we need
    // aHaveRect to know when to drop the initial value on the floor.)
    aRect.UnionRectEdges(aRect, transformedBounds);
  } else {
    aHaveRect = true;
    aRect = transformedBounds;
  }
}

static bool ComputeNeedToScroll(WhenToScroll aWhenToScroll, nscoord aLineSize,
                                nscoord aRectMin, nscoord aRectMax,
                                nscoord aViewMin, nscoord aViewMax) {
  // See how the rect should be positioned vertically
  if (WhenToScroll::Always == aWhenToScroll) {
    // The caller wants the frame as visible as possible
    return true;
  } else if (WhenToScroll::IfNotVisible == aWhenToScroll) {
    // Scroll only if no part of the frame is visible in this view
    return aRectMax - aLineSize <= aViewMin || aRectMin + aLineSize >= aViewMax;
  } else if (WhenToScroll::IfNotFullyVisible == aWhenToScroll) {
    // Scroll only if part of the frame is hidden and more can fit in view
    return !(aRectMin >= aViewMin && aRectMax <= aViewMax) &&
           std::min(aViewMax, aRectMax) - std::max(aRectMin, aViewMin) <
               aViewMax - aViewMin;
  }
  return false;
}

static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll,
                                    nscoord aOriginalCoord, nscoord aRectMin,
                                    nscoord aRectMax, nscoord aViewMin,
                                    nscoord aViewMax, nscoord* aRangeMin,
                                    nscoord* aRangeMax) {
  nscoord resultCoord = aOriginalCoord;
  nscoord scrollPortLength = aViewMax - aViewMin;
  if (kScrollMinimum == aWhereToScroll) {
    // Scroll the minimum amount necessary to show as much as possible of the
    // frame. If the frame is too large, don't hide any initially visible part
    // of it.
    nscoord min = std::min(aRectMin, aRectMax - scrollPortLength);
    nscoord max = std::max(aRectMin, aRectMax - scrollPortLength);
    resultCoord = std::min(std::max(aOriginalCoord, min), max);
  } else {
    nscoord frameAlignCoord = NSToCoordRound(
        aRectMin + (aRectMax - aRectMin) * (aWhereToScroll / 100.0f));
    resultCoord = NSToCoordRound(frameAlignCoord -
                                 scrollPortLength * (aWhereToScroll / 100.0f));
  }
  // Force the scroll range to extend to include resultCoord.
  *aRangeMin = std::min(resultCoord, aRectMax - scrollPortLength);
  *aRangeMax = std::max(resultCoord, aRectMin);
  return resultCoord;
}

/**
 * This function takes a scrollable frame, a rect in the coordinate system
 * of the scrolled frame, and a desired percentage-based scroll
 * position and attempts to scroll the rect to that position in the
 * visual viewport.
 *
 * This needs to work even if aRect has a width or height of zero.
 *
 * Note that, since we are performing a layout scroll, it's possible that
 * this fnction will sometimes be unsuccessful; the content will move as
 * fast as it can on the screen using layout viewport scrolling, and then
 * stop there, even if it could get closer to the desired position by
 * moving the visual viewport within the layout viewport.
 */
static void ScrollToShowRect(PresShell* aPresShell,
                             nsIScrollableFrame* aFrameAsScrollable,
                             const nsRect& aRect, ScrollAxis aVertical,
                             ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
  nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset();
  nsRect visibleRect(scrollPt, aFrameAsScrollable->GetVisualViewportSize());

  nsSize lineSize;
  // Don't call GetLineScrollAmount unless we actually need it. Not only
  // does this save time, but it's not safe to call GetLineScrollAmount
  // during reflow (because it depends on font size inflation and doesn't
  // use the in-reflow-safe font-size inflation path). If we did call it,
  // it would assert and possible give the wrong result.
  if (aVertical.mWhenToScroll == WhenToScroll::IfNotVisible ||
      aHorizontal.mWhenToScroll == WhenToScroll::IfNotVisible) {
    lineSize = aFrameAsScrollable->GetLineScrollAmount();
  }
  ScrollStyles ss = aFrameAsScrollable->GetScrollStyles();
  nsRect allowedRange(scrollPt, nsSize(0, 0));
  bool needToScroll = false;
  uint32_t directions = aFrameAsScrollable->GetPerceivedScrollingDirections();

  if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
       ss.mVertical != StyleOverflow::Hidden) &&
      (!aVertical.mOnlyIfPerceivedScrollableDirection ||
       (directions & nsIScrollableFrame::VERTICAL))) {
    if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y,
                            aRect.YMost(), visibleRect.y,
                            visibleRect.YMost())) {
      nscoord maxHeight;
      scrollPt.y = ComputeWhereToScroll(
          aVertical.mWhereToScroll, scrollPt.y, aRect.y, aRect.YMost(),
          visibleRect.y, visibleRect.YMost(), &allowedRange.y, &maxHeight);
      allowedRange.height = maxHeight - allowedRange.y;
      needToScroll = true;
    }
  }

  if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
       ss.mHorizontal != StyleOverflow::Hidden) &&
      (!aHorizontal.mOnlyIfPerceivedScrollableDirection ||
       (directions & nsIScrollableFrame::HORIZONTAL))) {
    if (ComputeNeedToScroll(aHorizontal.mWhenToScroll, lineSize.width, aRect.x,
                            aRect.XMost(), visibleRect.x,
                            visibleRect.XMost())) {
      nscoord maxWidth;
      scrollPt.x = ComputeWhereToScroll(
          aHorizontal.mWhereToScroll, scrollPt.x, aRect.x, aRect.XMost(),
          visibleRect.x, visibleRect.XMost(), &allowedRange.x, &maxWidth);
      allowedRange.width = maxWidth - allowedRange.x;
      needToScroll = true;
    }
  }

  // If we don't need to scroll, then don't try since it might cancel
  // a current smooth scroll operation.
  if (needToScroll) {
    ScrollMode scrollMode = ScrollMode::Instant;
    bool autoBehaviorIsSmooth =
        (aFrameAsScrollable->GetScrollStyles().mScrollBehavior ==
         NS_STYLE_SCROLL_BEHAVIOR_SMOOTH);
    bool smoothScroll = (aScrollFlags & ScrollFlags::ScrollSmooth) ||
                        ((aScrollFlags & ScrollFlags::ScrollSmoothAuto) &&
                         autoBehaviorIsSmooth);
    if (StaticPrefs::ScrollBehaviorEnabled() && smoothScroll) {
      scrollMode = ScrollMode::SmoothMsd;
    }
    nsIFrame* frame = do_QueryFrame(aFrameAsScrollable);
    AutoWeakFrame weakFrame(frame);
    aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange,
                                 aScrollFlags & ScrollFlags::ScrollSnap
                                     ? nsIScrollbarMediator::ENABLE_SNAP
                                     : nsIScrollbarMediator::DISABLE_SNAP);
    if (!weakFrame.IsAlive()) {
      return;
    }

    // If this is the RCD-RSF, also call ScrollToVisual() since we want to
    // scroll the rect into view visually, and that may require scrolling
    // the visual viewport in scenarios where there is not enough layout
    // scroll range.
    if (aFrameAsScrollable->IsRootScrollFrameOfDocument() &&
        aPresShell->GetPresContext()->IsRootContentDocument()) {
      aPresShell->ScrollToVisual(scrollPt, FrameMetrics::eMainThread,
                                 scrollMode);
    }
  }
}

nsresult PresShell::ScrollContentIntoView(nsIContent* aContent,
                                          ScrollAxis aVertical,
                                          ScrollAxis aHorizontal,
                                          ScrollFlags aScrollFlags) {
  NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
  RefPtr<Document> composedDoc = aContent->GetComposedDoc();
  NS_ENSURE_STATE(composedDoc);

  NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");

  if (mContentToScrollTo) {
    mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
  }
  mContentToScrollTo = aContent;
  ScrollIntoViewData* data = new ScrollIntoViewData();
  data->mContentScrollVAxis = aVertical;
  data->mContentScrollHAxis = aHorizontal;
  data->mContentToScrollToFlags = aScrollFlags;
  if (NS_FAILED(mContentToScrollTo->SetProperty(
          nsGkAtoms::scrolling, data,
          nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) {
    mContentToScrollTo = nullptr;
  }

  // Flush layout and attempt to scroll in the process.
  if (PresShell* presShell = composedDoc->GetPresShell()) {
    presShell->SetNeedLayoutFlush();
  }
  composedDoc->FlushPendingNotifications(FlushType::InterruptibleLayout);

  // If mContentToScrollTo is non-null, that means we interrupted the reflow
  // (or suppressed it altogether because we're suppressing interruptible
  // flushes right now) and won't necessarily get the position correct, but do
  // a best-effort scroll here.  The other option would be to do this inside
  // FlushPendingNotifications, but I'm not sure the repeated scrolling that
  // could trigger if reflows keep getting interrupted would be more desirable
  // than a single best-effort scroll followed by one final scroll on the first
  // completed reflow.
  if (mContentToScrollTo) {
    DoScrollContentIntoView();
  }
  return NS_OK;
}

void PresShell::DoScrollContentIntoView() {
  NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");

  nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame();
  if (!frame) {
    mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
    mContentToScrollTo = nullptr;
    return;
  }

  if (frame->GetStateBits() & NS_FRAME_FIRST_REFLOW) {
    // The reflow flush before this scroll got interrupted, and this frame's
    // coords and size are all zero, and it has no content showing anyway.
    // Don't bother scrolling to it.  We'll try again when we finish up layout.
    return;
  }

  // Make sure we skip 'frame' ... if it's scrollable, we should use its
  // scrollable ancestor as the container.
  nsIFrame* container = nsLayoutUtils::GetClosestFrameOfType(
      frame->GetParent(), LayoutFrameType::Scroll);
  if (!container) {
    // nothing can be scrolled
    return;
  }

  ScrollIntoViewData* data = static_cast<ScrollIntoViewData*>(
      mContentToScrollTo->GetProperty(nsGkAtoms::scrolling));
  if (MOZ_UNLIKELY(!data)) {
    mContentToScrollTo = nullptr;
    return;
  }

  // Get the scroll-margin here since |frame| is going to be changed to iterate
  // over all continuation frames below.
  nsMargin scrollMargin;
  if (!(data->mContentToScrollToFlags & ScrollFlags::IgnoreMarginAndPadding)) {
    scrollMargin = frame->StyleMargin()->GetScrollMargin();
  }

  // This is a two-step process.
  // Step 1: Find the bounds of the rect we want to scroll into view.  For
  //         example, for an inline frame we may want to scroll in the whole
  //         line, or we may want to scroll multiple lines into view.
  // Step 2: Walk container frame and its ancestors and scroll them
  //         appropriately.
  // frameBounds is relative to container. We're assuming
  // that scrollframes don't split so every continuation of frame will
  // be a descendant of container. (Things would still mostly work
  // even if that assumption was false.)
  nsRect frameBounds;
  bool haveRect = false;
  bool useWholeLineHeightForInlines = data->mContentScrollVAxis.mWhenToScroll !=
                                      WhenToScroll::IfNotFullyVisible;
  // Reuse the same line iterator across calls to AccumulateFrameBounds.  We set
  // it every time we detect a new block (stored in prevBlock).
  nsIFrame* prevBlock = nullptr;
  nsAutoLineIterator lines;
  // The last line we found a continuation on in |lines|.  We assume that later
  // continuations cannot come on earlier lines.
  int32_t curLine = 0;
  do {
    AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines,
                          frameBounds, haveRect, prevBlock, lines, curLine);
  } while ((frame = frame->GetNextContinuation()));

  frameBounds.Inflate(scrollMargin);

  ScrollFrameRectIntoView(container, frameBounds, data->mContentScrollVAxis,
                          data->mContentScrollHAxis,
                          data->mContentToScrollToFlags);
}

bool PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
                                        ScrollAxis aVertical,
                                        ScrollAxis aHorizontal,
                                        ScrollFlags aScrollFlags) {
  bool didScroll = false;
  // This function needs to work even if rect has a width or height of 0.
  nsRect rect = aRect;
  nsIFrame* container = aFrame;
  // Walk up the frame hierarchy scrolling the rect into view and
  // keeping rect relative to container
  do {
    nsIScrollableFrame* sf = do_QueryFrame(container);
    if (sf) {
      nsPoint oldPosition = sf->GetScrollPosition();
      nsRect targetRect = rect;
      // Inflate the scrolled rect by the container's padding in each dimension,
      // unless we have 'overflow-clip-box-*: content-box' in that dimension.
      auto* disp = container->StyleDisplay();
      if (disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
          disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) {
        WritingMode wm = container->GetWritingMode();
        bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
                                    : disp->mOverflowClipBoxInline) ==
                   StyleOverflowClipBox::ContentBox;
        bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
                                    : disp->mOverflowClipBoxBlock) ==
                   StyleOverflowClipBox::ContentBox;
        nsMargin padding = container->GetUsedPadding();
        if (!cbH) {
          padding.left = padding.right = nscoord(0);
        }
        if (!cbV) {
          padding.top = padding.bottom = nscoord(0);
        }
        targetRect.Inflate(padding);
      }

      targetRect -= sf->GetScrolledFrame()->GetPosition();
      if (!(aScrollFlags & ScrollFlags::IgnoreMarginAndPadding)) {
        nsMargin scrollPadding = sf->GetScrollPadding();
        targetRect.Inflate(scrollPadding);
        targetRect = targetRect.Intersect(sf->GetScrolledRect());
      }

      {
        AutoWeakFrame wf(container);
        ScrollToShowRect(this, sf, targetRect, aVertical, aHorizontal,
                         aScrollFlags);
        if (!wf.IsAlive()) {
          return didScroll;
        }
      }

      nsPoint newPosition = sf->LastScrollDestination();
      // If the scroll position increased, that means our content moved up,
      // so our rect's offset should decrease
      rect += oldPosition - newPosition;

      if (oldPosition != newPosition) {
        didScroll = true;
      }

      // only scroll one container when this flag is set
      if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) {
        break;
      }
    }
    nsIFrame* parent;
    if (container->IsTransformed()) {
      container->GetTransformMatrix(nullptr, &parent);
      rect =
          nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent);
    } else {
      rect += container->GetPosition();
      parent = container->GetParent();
    }
    if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) {
      nsPoint extraOffset(0, 0);
      parent = nsLayoutUtils::GetCrossDocParentFrame(container, &extraOffset);
      if (parent) {
        int32_t APD = container->PresContext()->AppUnitsPerDevPixel();
        int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel();
        rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD);
        rect += extraOffset;
      }
    }
    container = parent;
  } while (container);

  return didScroll;
}

RectVisibility PresShell::GetRectVisibility(nsIFrame* aFrame,
                                            const nsRect& aRect,
                                            nscoord aMinTwips) const {
  NS_ASSERTION(aFrame->PresContext() == GetPresContext(),
               "prescontext mismatch?");
  nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
  NS_ASSERTION(
      rootFrame,
      "How can someone have a frame for this presshell when there's no root?");
  nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable();
  nsRect scrollPortRect;
  if (sf) {
    scrollPortRect = sf->GetScrollPortRect();
    nsIFrame* f = do_QueryFrame(sf);
    scrollPortRect += f->GetOffsetTo(rootFrame);
  } else {
    scrollPortRect = nsRect(nsPoint(0, 0), rootFrame->GetSize());
  }

  // scrollPortRect has the viewport visible area relative to rootFrame.
  nsRect visibleAreaRect(scrollPortRect);
  // Find the intersection of this and the frame's ancestor scrollable
  // frames. We walk the whole ancestor chain to find all the scrollable
  // frames.
  nsIScrollableFrame* scrollAncestorFrame =
      nsLayoutUtils::GetNearestScrollableFrame(
          aFrame, nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
  while (scrollAncestorFrame) {
    nsRect scrollAncestorRect = scrollAncestorFrame->GetScrollPortRect();
    nsIFrame* f = do_QueryFrame(scrollAncestorFrame);
    scrollAncestorRect += f->GetOffsetTo(rootFrame);

    visibleAreaRect = visibleAreaRect.Intersect(scrollAncestorRect);

    // Continue up the chain.
    scrollAncestorFrame = nsLayoutUtils::GetNearestScrollableFrame(
        f->GetParent(), nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
  }

  // aRect is in the aFrame coordinate space, so bring it into rootFrame
  // coordinate space.
  nsRect r = aRect + aFrame->GetOffsetTo(rootFrame);
  // If aRect is entirely visible then we don't need to ensure that
  // at least aMinTwips of it is visible
  if (visibleAreaRect.Contains(r)) {
    return RectVisibility::Visible;
  }

  nsRect insetRect = visibleAreaRect;
  insetRect.Deflate(aMinTwips, aMinTwips);
  if (r.YMost() <= insetRect.y) {
    return RectVisibility::AboveViewport;
  }
  if (r.y >= insetRect.YMost()) {
    return RectVisibility::BelowViewport;
  }
  if (r.XMost() <= insetRect.x) {
    return RectVisibility::LeftOfViewport;
  }
  if (r.x >= insetRect.XMost()) {
    return RectVisibility::RightOfViewport;
  }
  return RectVisibility::Visible;
}

void PresShell::ScheduleViewManagerFlush(PaintType aType) {
  if (MOZ_UNLIKELY(mIsDestroying)) {
    return;
  }

  if (aType == PaintType::DelayedCompress) {
    // Delay paint for 1 second.
    static const uint32_t kPaintDelayPeriod = 1000;
    if (!mDelayedPaintTimer) {
      nsTimerCallbackFunc PaintTimerCallBack = [](nsITimer* aTimer,
                                                  void* aClosure) {
        // The passed-in PresShell is always alive here. Because if PresShell
        // died, mDelayedPaintTimer->Cancel() would be called during the
        // destruction and this callback would never be invoked.
        auto self = static_cast<PresShell*>(aClosure);
        self->SetNextPaintCompressed();
        self->ScheduleViewManagerFlush();
      };

      NS_NewTimerWithFuncCallback(
          getter_AddRefs(mDelayedPaintTimer), PaintTimerCallBack, this,
          kPaintDelayPeriod, nsITimer::TYPE_ONE_SHOT, "PaintTimerCallBack",
          mDocument->EventTargetFor(TaskCategory::Other));
    }
    return;
  }

  nsPresContext* presContext = GetPresContext();
  if (presContext) {
    presContext->RefreshDriver()->ScheduleViewManagerFlush();
  }
  SetNeedLayoutFlush();
}

void PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent) {
  AUTO_PROFILER_TRACING_DOCSHELL("Paint", "DispatchSynthMouseMove", GRAPHICS,
                                 mPresContext->GetDocShell());
  nsEventStatus status = nsEventStatus_eIgnore;
  nsView* targetView = nsView::GetViewFor(aEvent->mWidget);
  if (!targetView) return;
  RefPtr<nsViewManager> viewManager = targetView->GetViewManager();
  viewManager->DispatchEvent(aEvent, targetView, &status);
}

void PresShell::ClearMouseCaptureOnView(nsView* aView) {
  if (sCapturingContentInfo.mContent) {
    if (aView) {
      // if a view was specified, ensure that the captured content is within
      // this view.
      nsIFrame* frame = sCapturingContentInfo.mContent->GetPrimaryFrame();
      if (frame) {
        nsView* view = frame->GetClosestView();
        // if there is no view, capturing won't be handled any more, so
        // just release the capture.
        if (view) {
          do {
            if (view == aView) {
              sCapturingContentInfo.mContent = nullptr;
              // the view containing the captured content likely disappeared so
              // disable capture for now.
              sCapturingContentInfo.mAllowed = false;
              break;
            }

            view = view->GetParent();
          } while (view);
          // return if the view wasn't found
          return;
        }
      }
    }

    sCapturingContentInfo.mContent = nullptr;
  }

  // disable mouse capture until the next mousedown as a dialog has opened
  // or a drag has started. Otherwise, someone could start capture during
  // the modal dialog or drag.
  sCapturingContentInfo.mAllowed = false;
}

void PresShell::ClearMouseCapture(nsIFrame* aFrame) {
  if (!sCapturingContentInfo.mContent) {
    sCapturingContentInfo.mAllowed = false;
    return;
  }

  // null frame argument means clear the capture
  if (!aFrame) {
    sCapturingContentInfo.mContent = nullptr;
    sCapturingContentInfo.mAllowed = false;
    return;
  }

  nsIFrame* capturingFrame = sCapturingContentInfo.mContent->GetPrimaryFrame();
  if (!capturingFrame) {
    sCapturingContentInfo.mContent = nullptr;
    sCapturingContentInfo.mAllowed = false;
    return;
  }

  if (nsLayoutUtils::IsAncestorFrameCrossDoc(aFrame, capturingFrame)) {
    sCapturingContentInfo.mContent = nullptr;
    sCapturingContentInfo.mAllowed = false;
  }
}

nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) {
  MOZ_ASSERT(nullptr != aState, "null state pointer");

  // We actually have to mess with the docshell here, since we want to
  // store the state back in it.
  // XXXbz this isn't really right, since this is being called in the
  // content viewer's Hide() method...  by that point the docshell's
  // state could be wrong.  We should sort out a better ownership
  // model for the layout history state.
  nsCOMPtr<nsIDocShell> docShell(mPresContext->GetDocShell());
  if (!docShell) return NS_ERROR_FAILURE;

  nsCOMPtr<nsILayoutHistoryState> historyState;
  docShell->GetLayoutHistoryState(getter_AddRefs(historyState));
  if (!historyState) {
    // Create the document state object
    historyState = NS_NewLayoutHistoryState();
    docShell->SetLayoutHistoryState(historyState);
  }

  *aState = historyState;
  NS_IF_ADDREF(*aState);

  // Capture frame state for the entire frame hierarchy
  nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
  if (!rootFrame) return NS_OK;

  mFrameConstructor->CaptureFrameState(rootFrame, historyState);

  return NS_OK;
}

void PresShell::ScheduleBeforeFirstPaint() {
  if (!mDocument->IsResourceDoc()) {
    // Notify observers that a new page is about to be drawn. Execute this
    // as soon as it is safe to run JS, which is guaranteed to be before we
    // go back to the event loop and actually draw the page.
    MOZ_LOG(gLog, LogLevel::Debug,
            ("PresShell::ScheduleBeforeFirstPaint this=%p", this));

    nsContentUtils::AddScriptRunner(
        new nsBeforeFirstPaintDispatcher(mDocument));
  }
}

void PresShell::UnsuppressAndInvalidate() {
  // Note: We ignore the EnsureVisible check for resource documents, because
  // they won't have a docshell, so they'll always fail EnsureVisible.
  if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) ||
      mHaveShutDown) {
    // No point; we're about to be torn down anyway.
    return;
  }

  ScheduleBeforeFirstPaint();

  mPaintingSuppressed = false;
  nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
  if (rootFrame) {
    // let's assume that outline on a root frame is not supported
    rootFrame->InvalidateFrame();
  }

  // now that painting is unsuppressed, focus may be set on the document
  if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) win->SetReadyForFocus();

  if (!mHaveShutDown) {
    SynthesizeMouseMove(false);
    ScheduleApproximateFrameVisibilityUpdateNow();
  }
}

void PresShell::UnsuppressPainting() {
  if (mPaintSuppressionTimer) {
    mPaintSuppressionTimer->Cancel();
    mPaintSuppressionTimer = nullptr;
  }

  if (mIsDocumentGone || !mPaintingSuppressed) return;

  // If we have reflows pending, just wait until we process
  // the reflows and get all the frames where we want them
  // before actually unlocking the painting.  Otherwise
  // go ahead and unlock now.
  if (!mDirtyRoots.IsEmpty())
    mShouldUnsuppressPainting = true;
  else
    UnsuppressAndInvalidate();
}

// Post a request to handle an arbitrary callback after reflow has finished.
nsresult PresShell::PostReflowCallback(nsIReflowCallback* aCallback) {
  void* result = AllocateByObjectID(eArenaObjectID_nsCallbackEventRequest,
                                    sizeof(nsCallbackEventRequest));
  nsCallbackEventRequest* request = (nsCallbackEventRequest*)result;

  request->callback = aCallback;
  request->next = nullptr;

  if (mLastCallbackEventRequest) {
    mLastCallbackEventRequest = mLastCallbackEventRequest->next = request;
  } else {
    mFirstCallbackEventRequest = request;
    mLastCallbackEventRequest = request;
  }

  return NS_OK;
}

void PresShell::CancelReflowCallback(nsIReflowCallback* aCallback) {
  nsCallbackEventRequest* before = nullptr;
  nsCallbackEventRequest* node = mFirstCallbackEventRequest;
  while (node) {
    nsIReflowCallback* callback = node->callback;

    if (callback == aCallback) {
      nsCallbackEventRequest* toFree = node;
      if (node == mFirstCallbackEventRequest) {
        node = node->next;
        mFirstCallbackEventRequest = node;
        NS_ASSERTION(before == nullptr, "impossible");
      } else {
        node = node->next;
        before->next = node;
      }

      if (toFree == mLastCallbackEventRequest) {
        mLastCallbackEventRequest = before;
      }

      FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, toFree);
    } else {
      before = node;
      node = node->next;
    }
  }
}

void PresShell::CancelPostedReflowCallbacks() {
  while (mFirstCallbackEventRequest) {
    nsCallbackEventRequest* node = mFirstCallbackEventRequest;
    mFirstCallbackEventRequest = node->next;
    if (!mFirstCallbackEventRequest) {
      mLastCallbackEventRequest = nullptr;
    }
    nsIReflowCallback* callback = node->callback;
    FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
    if (callback) {
      callback->ReflowCallbackCanceled();
    }
  }
}

void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) {
  bool shouldFlush = false;

  while (mFirstCallbackEventRequest) {
    nsCallbackEventRequest* node = mFirstCallbackEventRequest;
    mFirstCallbackEventRequest = node->next;
    if (!mFirstCallbackEventRequest) {
      mLastCallbackEventRequest = nullptr;
    }
    nsIReflowCallback* callback = node->callback;
    FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
    if (callback) {
      if (callback->ReflowFinished()) {
        shouldFlush = true;
      }
    }
  }

  FlushType flushType =
      aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout;
  if (shouldFlush && !mIsDestroying) {
    FlushPendingNotifications(flushType);
  }
}

bool PresShell::IsSafeToFlush() const {
  // Not safe if we are getting torn down, reflowing, or in the middle of frame
  // construction.
  if (mIsReflowing || mChangeNestCount || mIsDestroying) {
    return false;
  }

  // Not safe if we are painting
  if (nsViewManager* viewManager = GetViewManager()) {
    bool isPainting = false;
    viewManager->IsPainting(isPainting);
    if (isPainting) {
      return false;
    }
  }

  return true;
}

void PresShell::NotifyFontFaceSetOnRefresh() {
  if (FontFaceSet* set = mDocument->GetFonts()) {
    set->DidRefresh();
  }
}

void PresShell::DoFlushPendingNotifications(FlushType aType) {
  // by default, flush animations if aType >= FlushType::Style
  mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
  FlushPendingNotifications(flush);
}

#ifdef DEBUG
static void AssertFrameSubtreeIsSane(const nsIFrame& aRoot) {
  if (const nsIContent* content = aRoot.GetContent()) {
    MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle(),
               "Node not in the flattened tree still has a frame?");
  }

  nsIFrame::ChildListIterator childLists(&aRoot);
  for (; !childLists.IsDone(); childLists.Next()) {
    for (const nsIFrame* child : childLists.CurrentList()) {
      AssertFrameSubtreeIsSane(*child);
    }
  }
}
#endif

static inline void AssertFrameTreeIsSane(const PresShell& aPresShell) {
#ifdef DEBUG
  if (const nsIFrame* root = aPresShell.GetRootFrame()) {
    AssertFrameSubtreeIsSane(*root);
  }
#endif
}

void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
  // FIXME(emilio, bug 1530177): Turn into a release assert when bug 1530188 and
  // bug 1530190 are fixed.
  MOZ_DIAGNOSTIC_ASSERT(!mForbiddenToFlush, "This is bad!");

  // Per our API contract, hold a strong ref to ourselves until we return.
  RefPtr<PresShell> kungFuDeathGrip = this;

  /**
   * VERY IMPORTANT: If you add some sort of new flushing to this
   * method, make sure to add the relevant SetNeedLayoutFlush or
   * SetNeedStyleFlush calls on the shell.
   */
  FlushType flushType = aFlush.mFlushType;

  MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?");

#ifdef MOZ_GECKO_PROFILER
  // clang-format off
  static const EnumeratedArray<FlushType, FlushType::Count, const char*>
      flushTypeNames = {
    "",
    "Event",
    "Content",
    "ContentAndNotify",
    // As far as the profiler is concerned, EnsurePresShellInitAndFrames and
    // Frames are the same
    "Style",
    "Style",
    "InterruptibleLayout",
    "Layout",
    "Display"
  };
  // clang-format on
  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("PresShell::DoFlushPendingNotifications",
                                   LAYOUT, flushTypeNames[flushType]);
#endif

#ifdef ACCESSIBILITY
#  ifdef DEBUG
  if (nsAccessibilityService* accService = GetAccService()) {
    NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
                 "Flush during accessible tree update!");
  }
#  endif
#endif

  NS_ASSERTION(flushType >= FlushType::Frames, "Why did we get called?");

  mNeedStyleFlush = false;
  mNeedThrottledAnimationFlush =
      mNeedThrottledAnimationFlush && !aFlush.mFlushAnimations;
  mNeedLayoutFlush =
      mNeedLayoutFlush && (flushType < FlushType::InterruptibleLayout);

  bool isSafeToFlush = IsSafeToFlush();

  // If layout could possibly trigger scripts, then it's only safe to flush if
  // it's safe to run script.
  bool hasHadScriptObject;
  if (mDocument->GetScriptHandlingObject(hasHadScriptObject) ||
      hasHadScriptObject) {
    isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
  }

  // Don't flush if the doc is already in the bfcache.
  if (MOZ_UNLIKELY(mDocument->GetPresShell() != this)) {
    MOZ_DIAGNOSTIC_ASSERT(!mDocument->GetPresShell(),
                          "Where did this shell come from?");
    isSafeToFlush = false;
  }

  MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying || !isSafeToFlush);
  MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mViewManager);
  MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mDocument->HasShellOrBFCacheEntry());

  // Make sure the view manager stays alive.
  RefPtr<nsViewManager> viewManager = mViewManager;
  bool didStyleFlush = false;
  bool didLayoutFlush = false;
  if (isSafeToFlush) {
    // Record that we are in a flush, so that our optimization in
    // Document::FlushPendingNotifications doesn't skip any re-entrant
    // calls to us.  Otherwise, we might miss some needed flushes, since
    // we clear mNeedStyleFlush / mNeedLayoutFlush here at the top of
    // the function but we might not have done the work yet.
    AutoRestore<bool> guard(mInFlush);
    mInFlush = true;

    // We need to make sure external resource documents are flushed too (for
    // example, svg filters that reference a filter in an external document
    // need the frames in the external document to be constructed for the
    // filter to work). We only need external resources to be flushed when the
    // main document is flushing >= FlushType::Frames, so we flush external
    // resources here instead of Document::FlushPendingNotifications.
    mDocument->FlushExternalResources(flushType);

    // Force flushing of any pending content notifications that might have
    // queued up while our event was pending.  That will ensure that we don't
    // construct frames for content right now that's still waiting to be
    // notified on,
    mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);

    mDocument->UpdateSVGUseElementShadowTrees();

    // Process pending restyles, since any flush of the presshell wants
    // up-to-date style data.
    if (MOZ_LIKELY(!mIsDestroying)) {
      viewManager->FlushDelayedResize(false);
      mPresContext->FlushPendingMediaFeatureValuesChanged();
    }

    if (MOZ_LIKELY(!mIsDestroying)) {
      // Now that we have flushed media queries, update the rules before looking
      // up @font-face / @counter-style / @font-feature-values rules.
      StyleSet()->UpdateStylistIfNeeded();

      // Flush any pending update of the user font set, since that could
      // cause style changes (for updating ex/ch units, and to cause a
      // reflow).
      mDocument->FlushUserFontSet();

      mPresContext->FlushCounterStyles();

      mPresContext->FlushFontFeatureValues();

      // Flush any requested SMIL samples.
      if (mDocument->HasAnimationController()) {
        mDocument->GetAnimationController()->FlushResampleRequests();
      }

      if (aFlush.mFlushAnimations && mPresContext->EffectCompositor()) {
        mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations();
      }
    }

    // The FlushResampleRequests() above flushed style changes.
    if (MOZ_LIKELY(!mIsDestroying)) {
      nsAutoScriptBlocker scriptBlocker;
#ifdef MOZ_GECKO_PROFILER
      nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell();
      DECLARE_DOCSHELL_AND_HISTORY_ID(docShell);
      AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
                                                docShellId, docShellHistoryId);
#endif
      PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;

      mPresContext->RestyleManager()->ProcessPendingRestyles();
    }

    // Process whatever XBL constructors those restyles queued up.  This
    // ensures that onload doesn't fire too early and that we won't do extra
    // reflows after those constructors run.
    if (MOZ_LIKELY(!mIsDestroying)) {
      mDocument->BindingManager()->ProcessAttachedQueue();
    }

    // Now those constructors or events might have posted restyle
    // events.  At the same time, we still need up-to-date style data.
    // In particular, reflow depends on style being completely up to
    // date.  If it's not, then style reparenting, which can
    // happen during reflow, might suddenly pick up the new rules and
    // we'll end up with frames whose style doesn't match the frame
    // type.
    if (MOZ_LIKELY(!mIsDestroying)) {
      nsAutoScriptBlocker scriptBlocker;
#ifdef MOZ_GECKO_PROFILER
      nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell();
      DECLARE_DOCSHELL_AND_HISTORY_ID(docShell);
      AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
                                                docShellId, docShellHistoryId);
#endif
      PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;

      mPresContext->RestyleManager()->ProcessPendingRestyles();
      // Clear mNeedStyleFlush here agagin to make this flag work properly for
      // optimization since the flag might have set in ProcessPendingRestyles().
      mNeedStyleFlush = false;
    }

    AssertFrameTreeIsSane(*this);

    didStyleFlush = true;

    // There might be more pending constructors now, but we're not going to
    // worry about them.  They can't be triggered during reflow, so we should
    // be good.

    if (flushType >= (SuppressInterruptibleReflows()
                          ? FlushType::Layout
                          : FlushType::InterruptibleLayout) &&
        !mIsDestroying) {
      didLayoutFlush = true;
      mFrameConstructor->RecalcQuotesAndCounters();
      viewManager->FlushDelayedResize(true);
      if (ProcessReflowCommands(flushType < FlushType::Layout)) {
        // We didn't get interrupted. Go ahead and perform scroll anchor
        // adjustments and scroll content into view
        FlushPendingScrollAnchorAdjustments();

        if (mContentToScrollTo) {
          DoScrollContentIntoView();
          if (mContentToScrollTo) {
            mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
            mContentToScrollTo = nullptr;
          }
        }
      }
    }

    if (flushType >= FlushType::Layout) {
      if (!mIsDestroying) {
        viewManager->UpdateWidgetGeometry();
      }
    }
  }

  if (!didStyleFlush && flushType >= FlushType::Style && !mIsDestroying) {
    SetNeedStyleFlush();
    if (aFlush.mFlushAnimations) {
      SetNeedThrottledAnimationFlush();
    }
  }

  if (!didLayoutFlush && flushType >= FlushType::InterruptibleLayout &&
      !mIsDestroying) {
    // We suppressed this flush either due to it not being safe to flush,
    // or due to SuppressInterruptibleReflows().  Either way, the
    // mNeedLayoutFlush flag needs to be re-set.
    SetNeedLayoutFlush();
  }
}

void PresShell::CharacterDataChanged(nsIContent* aContent,
                                     const CharacterDataChangeInfo& aInfo) {
  MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged");
  MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document");

  nsAutoCauseReflowNotifier crNotifier(this);

  mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo);
  mFrameConstructor->CharacterDataChanged(aContent, aInfo);
}

void PresShell::ContentStateChanged(Document* aDocument, nsIContent* aContent,
                                    EventStates aStateMask) {
  MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentStateChanged");
  MOZ_ASSERT(aDocument == mDocument, "Unexpected aDocument");

  if (mDidInitialize) {
    nsAutoCauseReflowNotifier crNotifier(this);
    mPresContext->RestyleManager()->ContentStateChanged(aContent, aStateMask);
  }
}

void PresShell::DocumentStatesChanged(EventStates aStateMask) {
  MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
  MOZ_ASSERT(mDocument);
  MOZ_ASSERT(!aStateMask.IsEmpty());

  if (mDidInitialize) {
    StyleSet()->InvalidateStyleForDocumentStateChanges(aStateMask);
  }

  if (aStateMask.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
    if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
      root->SchedulePaint();
    }
  }
}

void PresShell::AttributeWillChange(Element* aElement, int32_t aNameSpaceID,
                                    nsAtom* aAttribute, int32_t aModType) {
  MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeWillChange");
  MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");

  // XXXwaterson it might be more elegant to wait until after the
  // initial reflow to begin observing the document. That would
  // squelch any other inappropriate notifications as well.
  if (mDidInitialize) {
    nsAutoCauseReflowNotifier crNotifier(this);
    mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID,
                                                        aAttribute, aModType);
  }
}

void PresShell::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
                                 nsAtom* aAttribute, int32_t aModType,
                                 const nsAttrValue* aOldValue) {
  MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeChanged");
  MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");

  // XXXwaterson it might be more elegant to wait until after the
  // initial reflow to begin observing the document. That would
  // squelch any other inappropriate notifications as well.
  if (mDidInitialize) {
    nsAutoCauseReflowNotifier crNotifier(this);
    mPresContext->RestyleManager()->AttributeChanged(
        aElement, aNameSpaceID, aAttribute, aModType, aOldValue);
  }
}

void PresShell::ContentAppended(nsIContent* aFirstNewContent) {
  MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentAppended");
  MOZ_ASSERT(aFirstNewContent->OwnerDoc() == mDocument, "Unexpected document");

  // We never call ContentAppended with a document as the container, so we can
  // assert that we have an nsIContent parent.
  MOZ_ASSERT(aFirstNewContent->GetParent());
  MOZ_ASSERT(aFirstNewContent->GetParent()->IsElement() ||
             aFirstNewContent->GetParent()->IsShadowRoot());

  if (!mDidInitialize) {
    return;
  }

  nsAutoCauseReflowNotifier crNotifier(this);

  // Call this here so it only happens for real content mutations and
  // not cases when the frame constructor calls its own methods to force
  // frame reconstruction.
  mPresContext->RestyleManager()->ContentAppended(aFirstNewContent);

  mFrameConstructor->ContentAppended(
      aFirstNewContent, nsCSSFrameConstructor::InsertionKind::Async);
}

void PresShell::ContentInserted(nsIContent* aChild) {
  MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentInserted");
  MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");

  if (!mDidInitialize) {
    return;
  }

  nsAutoCauseReflowNotifier crNotifier(this);

  // Call this here so it only happens for real content mutations and
  // not cases when the frame constructor calls its own methods to force
  // frame reconstruction.
  mPresContext->RestyleManager()->ContentInserted(aChild);

  mFrameConstructor->ContentInserted(
      aChild, nullptr, nsCSSFrameConstructor::InsertionKind::Async);
}

void PresShell::ContentRemoved(nsIContent* aChild,
                               nsIContent* aPreviousSibling) {
  MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentRemoved");
  MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
  nsINode* container = aChild->GetParentNode();

  // Notify the ESM that the content has been removed, so that
  // it can clean up any state related to the content.

  mPresContext->EventStateManager()->ContentRemoved(mDocument, aChild);

  nsAutoCauseReflowNotifier crNotifier(this);

  // Call this here so it only happens for real content mutations and
  // not cases when the frame constructor calls its own methods to force
  // frame reconstruction.
  nsIContent* oldNextSibling = nullptr;

  // Editor calls into here with NAC via HTMLEditor::DeleteRefToAnonymousNode.
  // This could be asserted if that caller is fixed.
  if (MOZ_LIKELY(!aChild->IsRootOfAnonymousSubtree())) {
    oldNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling()
                                      : container->GetFirstChild();
  }

  // After removing aChild from tree we should save information about live
  // ancestor
  if (mPointerEventTarget &&
      nsContentUtils::ContentIsDescendantOf(mPointerEventTarget, aChild)) {
    mPointerEventTarget = aChild->GetParent();
  }

  mFrameConstructor->ContentRemoved(aChild, oldNextSibling,
                                    nsCSSFrameConstructor::REMOVE_CONTENT);

  // NOTE(emilio): It's important that this goes after the frame constructor
  // stuff, otherwise the frame constructor can't see elements which are
  // display: contents / display: none, because we'd have cleared all the style
  // data from there.
  mPresContext->RestyleManager()->ContentRemoved(aChild, oldNextSibling);
}

void PresShell::NotifyCounterStylesAreDirty() {
  nsAutoCauseReflowNotifier reflowNotifier(this);
  mFrameConstructor->NotifyCounterStylesAreDirty();
}

bool PresShell::FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const {
  return mDirtyRoots.FrameIsAncestorOfDirtyRoot(aFrame);
}

void PresShell::ReconstructFrames() {
  MOZ_ASSERT(!mFrameConstructor->GetRootFrame() || mDidInitialize,
             "Must not have root frame before initial reflow");
  if (!mDidInitialize || mIsDestroying) {
    // Nothing to do here
    return;
  }

  RefPtr<PresShell> kungFuDeathGrip(this);

  // Have to make sure that the content notifications are flushed before we
  // start messing with the frame model; otherwise we can get content doubling.
  //
  // Also make sure that styles are flushed before calling into the frame
  // constructor, since that's what it expects.
  mDocument->FlushPendingNotifications(FlushType::Style);

  if (mIsDestroying) {
    return;
  }

  nsAutoCauseReflowNotifier crNotifier(this);
  mFrameConstructor->ReconstructDocElementHierarchy(
      nsCSSFrameConstructor::InsertionKind::Sync);
}

nsresult PresShell::RenderDocument(const nsRect& aRect,
                                   RenderDocumentFlags aFlags,
                                   nscolor aBackgroundColor,
                                   gfxContext* aThebesContext) {
  NS_ENSURE_TRUE(!(aFlags & RenderDocumentFlags::IsUntrusted),
                 NS_ERROR_NOT_IMPLEMENTED);

  nsAutoScriptBlocker blockScripts;

  // Set up the rectangle as the path in aThebesContext
  gfxRect r(0, 0, nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
            nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
  aThebesContext->NewPath();
#ifdef MOZ_GFX_OPTIMIZE_MOBILE
  aThebesContext->SnappedRectangle(r);
#else
  aThebesContext->Rectangle(r);
#endif

  nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
  if (!rootFrame) {
    // Nothing to paint, just fill the rect
    aThebesContext->SetColor(Color::FromABGR(aBackgroundColor));
    aThebesContext->Fill();
    return NS_OK;
  }

  gfxContextAutoSaveRestore save(aThebesContext);

  MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER);

  aThebesContext->Clip();

  nsDeviceContext* devCtx = mPresContext->DeviceContext();

  gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
                  -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y));
  gfxFloat scale =
      gfxFloat(devCtx->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel();

  // Since canvas APIs use floats to set up their matrices, we may have some
  // slight rounding errors here.  We use NudgeToIntegers() here to adjust
  // matrix components that are integers up to the accuracy of floats to be
  // those integers.
  gfxMatrix newTM = aThebesContext->CurrentMatrixDouble()
                        .PreTranslate(offset)
                        .PreScale(scale, scale)
                        .NudgeToIntegers();
  aThebesContext->SetMatrixDouble(newTM);

  AutoSaveRestoreRenderingState _(this);

  bool wouldFlushRetainedLayers = false;
  PaintFrameFlags flags = PaintFrameFlags::IgnoreSuppression;
  if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
    flags |= PaintFrameFlags::InTransform;
  }
  if (!(aFlags & RenderDocumentFlags::AsyncDecodeImages)) {
    flags |= PaintFrameFlags::SyncDecodeImages;
  }
  if (aFlags & RenderDocumentFlags::UseWidgetLayers) {
    // We only support using widget layers on display root's with widgets.
    nsView* view = rootFrame->GetView();
    if (view && view->GetWidget() &&
        nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
      LayerManager* layerManager = view->GetWidget()->GetLayerManager();
      // ClientLayerManagers or WebRenderLayerManagers in content processes
      // don't support taking snapshots.
      if (layerManager &&
          (!layerManager->AsKnowsCompositor() || XRE_IsParentProcess())) {
        flags |= PaintFrameFlags::WidgetLayers;
      }
    }
  }
  if (!(aFlags & RenderDocumentFlags::DrawCaret)) {
    wouldFlushRetainedLayers = true;
    flags |= PaintFrameFlags::HideCaret;
  }
  if (aFlags & RenderDocumentFlags::IgnoreViewportScrolling) {
    wouldFlushRetainedLayers = !IgnoringViewportScrolling();
    mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling;
  }
  if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) {
    mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing;
  }
  if (aFlags & RenderDocumentFlags::DocumentRelative) {
    // XXX be smarter about this ... drawWindow might want a rect
    // that's "pretty close" to what our retained layer tree covers.
    // In that case, it wouldn't disturb normal rendering too much,
    // and we should allow it.
    wouldFlushRetainedLayers = true;
    flags |= PaintFrameFlags::DocumentRelative;
  }

  // Don't let drawWindow blow away our retained layer tree
  if ((flags & PaintFrameFlags::WidgetLayers) && wouldFlushRetainedLayers) {
    flags &= ~PaintFrameFlags::WidgetLayers;
  }

  nsLayoutUtils::PaintFrame(aThebesContext, rootFrame, nsRegion(aRect),
                            aBackgroundColor,
                            nsDisplayListBuilderMode::Painting, flags);

  return NS_OK;
}

/*
 * Clip the display list aList to a range. Returns the clipped
 * rectangle surrounding the range.
 */
nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder,
                                  nsDisplayList* aList, nsRange* aRange) {
  // iterate though the display items and add up the bounding boxes of each.
  // This will allow the total area of the frames within the range to be
  // determined. To do this, remove an item from the bottom of the list, check
  // whether it should be part of the range, and if so, append it to the top
  // of the temporary list tmpList. If the item is a text frame at the end of
  // the selection range, clip it to the portion of the text frame that is
  // part of the selection. Then, append the wrapper to the top of the list.
  // Otherwise, just delete the item and don't append it.
  nsRect surfaceRect;
  nsDisplayList tmpList;

  nsDisplayItem* i;
  while ((i = aList->RemoveBottom())) {
    if (i->GetType() == DisplayItemType::TYPE_CONTAINER) {
      tmpList.AppendToTop(i);
      surfaceRect.UnionRect(
          surfaceRect, ClipListToRange(aBuilder, i->GetChildren(), aRange));
      continue;
    }

    // itemToInsert indiciates the item that should be inserted into the
    // temporary list. If null, no item should be inserted.
    nsDisplayItem* itemToInsert = nullptr;
    nsIFrame* frame = i->Frame();
    nsIContent* content = frame->GetContent();
    if (content) {
      bool atStart = (content == aRange->GetStartContainer());
      bool atEnd = (content == aRange->GetEndContainer());
      if ((atStart || atEnd) && frame->IsTextFrame()) {
        int32_t frameStartOffset, frameEndOffset;
        frame->GetOffsets(frameStartOffset, frameEndOffset);

        int32_t hilightStart =
            atStart ? std::max(static_cast<int32_t>(aRange->StartOffset()),
                               frameStartOffset)
                    : frameStartOffset;
        int32_t hilightEnd =
            atEnd ? std::min(static_cast<int32_t>(aRange->EndOffset()),
                             frameEndOffset)
                  : frameEndOffset;
        if (hilightStart < hilightEnd) {
          // determine the location of the start and end edges of the range.
          nsPoint startPoint, endPoint;
          frame->GetPointFromOffset(hilightStart, &startPoint);
          frame->GetPointFromOffset(hilightEnd, &endPoint);

          // The clip rectangle is determined by taking the the start and
          // end points of the range, offset from the reference frame.
          // Because of rtl, the end point may be to the left of (or above,
          // in vertical mode) the start point, so x (or y) is set to the
          // lower of the values.
          nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize());
          if (frame->GetWritingMode().IsVertical()) {
            nscoord y = std::min(startPoint.y, endPoint.y);
            textRect.y += y;
            textRect.height = std::max(startPoint.y, endPoint.y) - y;
          } else {
            nscoord x = std::min(startPoint.x, endPoint.x);
            textRect.x += x;
            textRect.width = std::max(startPoint.x, endPoint.x) - x;
          }
          surfaceRect.UnionRect(surfaceRect, textRect);

          const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot();

          DisplayItemClip newClip;
          newClip.SetTo(textRect);

          const DisplayItemClipChain* newClipChain =
              aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);

          i->IntersectClip(aBuilder, newClipChain, true);
          itemToInsert = i;
        }
      }
      // Don't try to descend into subdocuments.
      // If this ever changes we'd need to add handling for subdocuments with
      // different zoom levels.
      else if (content->GetUncomposedDoc() ==
               aRange->GetStartContainer()->GetUncomposedDoc()) {
        // if the node is within the range, append it to the temporary list
        bool before, after;
        nsresult rv =
            nsRange::CompareNodeToRange(content, aRange, &before, &after);
        if (NS_SUCCEEDED(rv) && !before && !after) {
          itemToInsert = i;
          bool snap;
          surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap));
        }
      }
    }

    // insert the item into the list if necessary. If the item has a child
    // list, insert that as well
    nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
    if (itemToInsert || sublist) {
      tmpList.AppendToTop(itemToInsert ? itemToInsert : i);
      // if the item is a list, iterate over it as well
      if (sublist)
        surfaceRect.UnionRect(surfaceRect,
                              ClipListToRange(aBuilder, sublist, aRange));
    } else {
      // otherwise, just delete the item and don't readd it to the list
      i->Destroy(aBuilder);
    }
  }

  // now add all the items back onto the original list again
  aList->AppendToTop(&tmpList);

  return surfaceRect;
}

#ifdef DEBUG
#  include <stdio.h>

static bool gDumpRangePaintList = false;
#endif

UniquePtr<RangePaintInfo> PresShell::CreateRangePaintInfo(
    nsRange* aRange, nsRect& aSurfaceRect, bool aForPrimarySelection) {
  nsIFrame* ancestorFrame;
  nsIFrame* rootFrame = GetRootFrame();

  // If the start or end of the range is the document, just use the root
  // frame, otherwise get the common ancestor of the two endpoints of the
  // range.
  nsINode* startContainer = aRange->GetStartContainer();
  nsINode* endContainer = aRange->GetEndContainer();
  Document* doc = startContainer->GetComposedDoc();
  if (startContainer == doc || endContainer == doc) {
    ancestorFrame = rootFrame;
  } else {
    nsINode* ancestor =
        nsContentUtils::GetCommonAncestor(startContainer, endContainer);
    NS_ASSERTION(!ancestor || ancestor->IsContent(),
                 "common ancestor is not content");

    while (ancestor && ancestor->IsContent()) {
      ancestorFrame = ancestor->AsContent()->GetPrimaryFrame();
      if (ancestorFrame) {
        break;
      }

      ancestor = ancestor->GetParentOrHostNode();
    }

    // use the nearest ancestor frame that includes all continuations as the
    // root for building the display list
    while (ancestorFrame &&
           nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame))
      ancestorFrame = ancestorFrame->GetParent();
  }

  if (!ancestorFrame) {
    return nullptr;
  }

  // get a display list containing the range
  auto info = MakeUnique<RangePaintInfo>(aRange, ancestorFrame);
  info->mBuilder.SetIncludeAllOutOfFlows();
  if (aForPrimarySelection) {
    info->mBuilder.SetSelectedFramesOnly();
  }
  info->mBuilder.EnterPresShell(ancestorFrame);

  ContentSubtreeIterator subtreeIter;
  nsresult rv = subtreeIter.Init(aRange);
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  auto BuildDisplayListForNode = [&](nsINode* aNode) {
    if (MOZ_UNLIKELY(!aNode->IsContent())) {
      return;
    }
    nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
    // XXX deal with frame being null due to display:contents
    for (; frame;
         frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
      info->mBuilder.SetVisibleRect(frame->GetVisualOverflowRect());
      info->mBuilder.SetDirtyRect(frame->GetVisualOverflowRect());
      frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList);
    }
  };
  if (startContainer->NodeType() == nsINode::TEXT_NODE) {
    BuildDisplayListForNode(startContainer);
  }
  for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
    nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
    BuildDisplayListForNode(node);
  }
  if (endContainer != startContainer &&
      endContainer->NodeType() == nsINode::TEXT_NODE) {
    BuildDisplayListForNode(endContainer);
  }

#ifdef DEBUG
  if (gDumpRangePaintList) {
    fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n");
    nsFrame::PrintDisplayList(&(info->mBuilder), info->mList);
  }
#endif

  nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, aRange);

  info->mBuilder.LeavePresShell(ancestorFrame, &info->mList);

#ifdef DEBUG
  if (gDumpRangePaintList) {
    fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n");
    nsFrame::PrintDisplayList(&(info->mBuilder), info->mList);
  }
#endif

  // determine the offset of the reference frame for the display list
  // to the root frame. This will allow the coordinates used when painting
  // to all be offset from the same point
  info->mRootOffset = ancestorFrame->GetOffsetTo(rootFrame);
  rangeRect.MoveBy(info->mRootOffset);
  aSurfaceRect.UnionRect(aSurfaceRect, rangeRect);

  return info;
}

already_AddRefed<SourceSurface> PresShell::PaintRangePaintInfo(
    const nsTArray<UniquePtr<RangePaintInfo>>& aItems, Selection* aSelection,
    const Maybe<CSSIntRegion>& aRegion, nsRect aArea,
    const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
    RenderImageFlags aFlags) {
  nsPresContext* pc = GetPresContext();
  if (!pc || aArea.width == 0 || aArea.height == 0) return nullptr;

  // use the rectangle to create the surface
  nsIntRect pixelArea = aArea.ToOutsidePixels(pc->AppUnitsPerDevPixel());

  // if the image should not be resized, scale must be 1
  float scale = 1.0;
  nsIntRect rootScreenRect =
      GetRootFrame()->GetScreenRectInAppUnits().ToNearestPixels(
          pc->AppUnitsPerDevPixel());

  nsRect maxSize;
  pc->DeviceContext()->GetClientRect(maxSize);

  // check if the image should be resized
  bool resize = !!(aFlags & RenderImageFlags::AutoScale);

  if (resize) {
    // check if image-resizing-algorithm should be used
    if (aFlags & RenderImageFlags::IsImage) {
      // get max screensize
      nscoord maxWidth = pc->AppUnitsToDevPixels(maxSize.width);
      nscoord maxHeight = pc->AppUnitsToDevPixels(maxSize.height);
      // resize image relative to the screensize
      // get best height/width relative to screensize
      float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR;
      float bestWidth = float(maxWidth) * RELATIVE_SCALEFACTOR;
      // calculate scale for bestWidth
      float adjustedScale = bestWidth / float(pixelArea.width);
      // get the worst height (height when width is perfect)
      float worstHeight = float(pixelArea.height) * adjustedScale;
      // get the difference of best and worst height
      float difference = bestHeight - worstHeight;
      // halve the difference and add it to worstHeight to get
      // the best compromise between bestHeight and bestWidth,
      // then calculate the corresponding scale factor
      adjustedScale = (worstHeight + difference / 2) / float(pixelArea.height);
      // prevent upscaling
      scale = std::min(scale, adjustedScale);
    } else {
      // get half of max screensize
      nscoord maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1);
      nscoord maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1);
      if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) {
        // divide the maximum size by the image size in both directions.
        // Whichever direction produces the smallest result determines how much
        // should be scaled.
        if (pixelArea.width > maxWidth)
          scale = std::min(scale, float(maxWidth) / pixelArea.width);
        if (pixelArea.height > maxHeight)
          scale = std::min(scale, float(maxHeight) / pixelArea.height);
      }
    }

    pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale);
    pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale);
    if (!pixelArea.width || !pixelArea.height) return nullptr;

    // adjust the screen position based on the rescaled size
    nscoord left = rootScreenRect.x + pixelArea.x;
    nscoord top = rootScreenRect.y + pixelArea.y;
    aScreenRect->x = NSToIntFloor(aPoint.x - float(aPoint.x - left) * scale);
    aScreenRect->y = NSToIntFloor(aPoint.y - float(aPoint.y - top) * scale);
  } else {
    // move aScreenRect to the position of the surface in screen coordinates
    aScreenRect->MoveTo(rootScreenRect.x + pixelArea.x,
                        rootScreenRect.y + pixelArea.y);
  }
  aScreenRect->width = pixelArea.width;
  aScreenRect->height = pixelArea.height;

  RefPtr<DrawTarget> dt =
      gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
          IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8);
  if (!dt || !dt->IsValid()) {
    return nullptr;
  }

  RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
  MOZ_ASSERT(ctx);  // already checked the draw target above

  if (aRegion) {
    RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING);

    // Convert aRegion from CSS pixels to dev pixels
    nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel())
                             .ToOutsidePixels(pc->AppUnitsPerDevPixel());
    for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
      const IntRect& rect = iter.Get();

      builder->MoveTo(rect.TopLeft());
      builder->LineTo(rect.TopRight());
      builder->LineTo(rect.BottomRight());
      builder->LineTo(rect.BottomLeft());
      builder->LineTo(rect.TopLeft());
    }

    RefPtr<Path> path = builder->Finish();
    ctx->Clip(path);
  }

  gfxMatrix initialTM = ctx->CurrentMatrixDouble();

  if (resize) initialTM.PreScale(scale, scale);

  // translate so that points are relative to the surface area
  gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint(
      -aArea.TopLeft(), pc->AppUnitsPerDevPixel());
  initialTM.PreTranslate(surfaceOffset);

  // temporarily hide the selection so that text is drawn normally. If a
  // selection is being rendered, use that, otherwise use the presshell's
  // selection.
  RefPtr<nsFrameSelection> frameSelection;
  if (aSelection) {
    frameSelection = aSelection->GetFrameSelection();
  } else {
    frameSelection = FrameSelection();
  }
  int16_t oldDisplaySelection = frameSelection->GetDisplaySelection();
  frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);

  // next, paint each range in the selection
  for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
    // the display lists paint relative to the offset from the reference
    // frame, so account for that translation too:
    gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint(
        rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel());
    ctx->SetMatrixDouble(initialTM.PreTranslate(rootOffset));
    aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y);
    nsRegion visible(aArea);
    RefPtr<LayerManager> layerManager = rangeInfo->mList.PaintRoot(
        &rangeInfo->mBuilder, ctx, nsDisplayList::PAINT_DEFAULT);
    aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y);
  }

  // restore the old selection display state
  frameSelection->SetDisplaySelection(oldDisplaySelection);

  return dt->Snapshot();
}

already_AddRefed<SourceSurface> PresShell::RenderNode(
    nsINode* aNode, const Maybe<CSSIntRegion>& aRegion,
    const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
    RenderImageFlags aFlags) {
  // area will hold the size of the surface needed to draw the node, measured
  // from the root frame.
  nsRect area;
  nsTArray<UniquePtr<RangePaintInfo>> rangeItems;

  // nothing to draw if the node isn't in a document
  if (!aNode->IsInComposedDoc()) {
    return nullptr;
  }

  RefPtr<nsRange> range = new nsRange(aNode);
  IgnoredErrorResult rv;
  range->SelectNode(*aNode, rv);
  if (rv.Failed()) {
    return nullptr;
  }

  UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, false);
  if (info && !rangeItems.AppendElement(std::move(info))) {
    return nullptr;
  }

  Maybe<CSSIntRegion> region = aRegion;
  if (region) {
    // combine the area with the supplied region
    CSSIntRect rrectPixels = region->GetBounds();

    nsRect rrect = ToAppUnits(rrectPixels, AppUnitsPerCSSPixel());
    area.IntersectRect(area, rrect);

    nsPresContext* pc = GetPresContext();
    if (!pc) return nullptr;

    // move the region so that it is offset from the topleft corner of the
    // surface
    region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x),
                   -nsPresContext::AppUnitsToIntCSSPixels(area.y));
  }

  return PaintRangePaintInfo(rangeItems, nullptr, region, area, aPoint,
                             aScreenRect, aFlags);
}

already_AddRefed<SourceSurface> PresShell::RenderSelection(
    Selection* aSelection, const LayoutDeviceIntPoint aPoint,
    LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) {
  // area will hold the size of the surface needed to draw the selection,
  // measured from the root frame.
  nsRect area;
  nsTArray<UniquePtr<RangePaintInfo>> rangeItems;

  // iterate over each range and collect them into the rangeItems array.
  // This is done so that the size of selection can be determined so as
  // to allocate a surface area
  uint32_t numRanges = aSelection->RangeCount();
  NS_ASSERTION(numRanges > 0, "RenderSelection called with no selection");

  for (uint32_t r = 0; r < numRanges; r++) {
    RefPtr<nsRange> range = aSelection->GetRangeAt(r);

    UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, true);
    if (info && !rangeItems.AppendElement(std::move(info))) {
      return nullptr;
    }
  }

  return PaintRangePaintInfo(rangeItems, aSelection, Nothing(), area, aPoint,
                             aScreenRect, aFlags);
}

void PresShell::AddPrintPreviewBackgroundItem(nsDisplayListBuilder* aBuilder,
                                              nsDisplayList* aList,
                                              nsIFrame* aFrame,
                                              const nsRect& aBounds) {
  aList->AppendNewToBottom<nsDisplaySolidColor>(aBuilder, aFrame, aBounds,
                                                NS_RGB(115, 115, 115));
}

static bool AddCanvasBackgroundColor(const nsDisplayList* aList,
                                     nsIFrame* aCanvasFrame, nscolor aColor,
                                     bool aCSSBackgroundColor) {
  for (nsDisplayItem* i = aList->GetBottom(); i; i = i->GetAbove()) {
    const DisplayItemType type = i->GetType();

    if (i->Frame() == aCanvasFrame &&
        type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR) {
      auto* bg = static_cast<nsDisplayCanvasBackgroundColor*>(i);
      bg->SetExtraBackgroundColor(aColor);
      return true;
    }

    const bool isBlendContainer =
        type == DisplayItemType::TYPE_BLEND_CONTAINER ||
        type == DisplayItemType::TYPE_TABLE_BLEND_CONTAINER;

    nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
    if (sublist && !(isBlendContainer && !aCSSBackgroundColor) &&
        AddCanvasBackgroundColor(sublist, aCanvasFrame, aColor,
                                 aCSSBackgroundColor))
      return true;
  }
  return false;
}

void PresShell::AddCanvasBackgroundColorItem(
    nsDisplayListBuilder* aBuilder, nsDisplayList* aList, nsIFrame* aFrame,
    const nsRect& aBounds, nscolor aBackstopColor,
    AddCanvasBackgroundColorFlags aFlags) {
  if (aBounds.IsEmpty()) {
    return;
  }
  // We don't want to add an item for the canvas background color if the frame
  // (sub)tree we are painting doesn't include any canvas frames. There isn't
  // an easy way to check this directly, but if we check if the root of the
  // (sub)tree we are painting is a canvas frame that should cover us in all
  // cases (it will usually be a viewport frame when we have a canvas frame in
  // the (sub)tree).
  if (!(aFlags & AddCanvasBackgroundColorFlags::ForceDraw) &&
      !nsCSSRendering::IsCanvasFrame(aFrame)) {
    return;
  }

  nscolor bgcolor = NS_ComposeColors(aBackstopColor, mCanvasBackgroundColor);
  if (NS_GET_A(bgcolor) == 0) return;

  // To make layers work better, we want to avoid having a big non-scrolled
  // color background behind a scrolled transparent background. Instead,
  // we'll try to move the color background into the scrolled content
  // by making nsDisplayCanvasBackground paint it.
  // If we're only adding an unscrolled item, then pretend that we've
  // already done it.
  bool addedScrollingBackgroundColor =
      !!(aFlags & AddCanvasBackgroundColorFlags::AppendUnscrolledOnly);
  if (!aFrame->GetParent() && !addedScrollingBackgroundColor) {
    nsIScrollableFrame* sf =
        aFrame->PresShell()->GetRootScrollFrameAsScrollable();
    if (sf) {
      nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
      if (canvasFrame && canvasFrame->IsVisibleForPainting()) {
        addedScrollingBackgroundColor = AddCanvasBackgroundColor(
            aList, canvasFrame, bgcolor, mHasCSSBackgroundColor);
      }
    }
  }

  // With async scrolling, we'd like to have two instances of the background
  // color: one that scrolls with the content (for the reasons stated above),
  // and one underneath which does not scroll with the content, but which can
  // be shown during checkerboarding and overscroll.
  // We can only do that if the color is opaque.
  bool forceUnscrolledItem =
      nsLayoutUtils::UsesAsyncScrolling(aFrame) && NS_GET_A(bgcolor) == 255;
  if ((aFlags & AddCanvasBackgroundColorFlags::AddForSubDocument) &&
      StaticPrefs::LayoutUseContainersForRootFrames()) {
    // If we're using ContainerLayers for a subdoc, then any items we add here
    // will still be scrolled (since we're inside the container at this point),
    // so don't bother and we will do it manually later.
    forceUnscrolledItem = false;
  }

  if (!addedScrollingBackgroundColor || forceUnscrolledItem) {
    aList->AppendNewToBottom<nsDisplaySolidColor>(aBuilder, aFrame, aBounds,
                                                  bgcolor);
  }
}

static bool IsTransparentContainerElement(nsPresContext* aPresContext) {
  nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
  if (!docShell) {
    return false;
  }

  nsCOMPtr<nsPIDOMWindowOuter> pwin = docShell->GetWindow();
  if (!pwin) return false;
  nsCOMPtr<Element> containerElement = pwin->GetFrameElementInternal();

  BrowserChild* tab = BrowserChild::GetFrom(docShell);
  if (tab) {
    // Check if presShell is the top PresShell. Only the top can
    // influence the canvas background color.
    if (aPresContext->GetPresShell() != tab->GetTopLevelPresShell()) {
      tab = nullptr;
    }
  }

  return (containerElement && containerElement->HasAttr(
                                  kNameSpaceID_None, nsGkAtoms::transparent)) ||
         (tab && tab->IsTransparent());
}

nscolor PresShell::GetDefaultBackgroundColorToDraw() {
  if (!mPresContext || !mPresContext->GetBackgroundColorDraw()) {
    return NS_RGB(255, 255, 255);
  }
  return mPresContext->DefaultBackgroundColor();
}

void PresShell::UpdateCanvasBackground() {
  // If we have a frame tree and it has style information that
  // specifies the background color of the canvas, update our local
  // cache of that color.
  nsIFrame* rootStyleFrame = FrameConstructor()->GetRootElementStyleFrame();
  if (rootStyleFrame) {
    ComputedStyle* bgStyle =
        nsCSSRendering::FindRootFrameBackground(rootStyleFrame);
    // XXX We should really be passing the canvasframe, not the root element
    // style frame but we don't have access to the canvasframe here. It isn't
    // a problem because only a few frames can return something other than true
    // and none of them would be a canvas frame or root element style frame.
    bool drawBackgroundImage;
    bool drawBackgroundColor;
    mCanvasBackgroundColor = nsCSSRendering::DetermineBackgroundColor(
        mPresContext, bgStyle, rootStyleFrame, drawBackgroundImage,
        drawBackgroundColor);
    mHasCSSBackgroundColor = drawBackgroundColor;
    if (mPresContext->IsRootContentDocumentCrossProcess() &&
        !IsTransparentContainerElement(mPresContext)) {
      mCanvasBackgroundColor = NS_ComposeColors(
          GetDefaultBackgroundColorToDraw(), mCanvasBackgroundColor);
    }
  }

  // If the root element of the document (ie html) has style 'display: none'
  // then the document's background color does not get drawn; cache the
  // color we actually draw.
  if (!FrameConstructor()->GetRootElementFrame()) {
    mCanvasBackgroundColor = GetDefaultBackgroundColorToDraw();
  }
}

nscolor PresShell::ComputeBackstopColor(nsView* aDisplayRoot) {
  nsIWidget* widget = aDisplayRoot->GetWidget();
  if (widget && (widget->GetTransparencyMode() != eTransparencyOpaque ||
                 widget->WidgetPaintsBackground())) {
    // Within a transparent widget, so the backstop color must be
    // totally transparent.
    return NS_RGBA(0, 0, 0, 0);
  }
  // Within an opaque widget (or no widget at all), so the backstop
  // color must be totally opaque. The user's default background
  // as reported by the prescontext is guaranteed to be opaque.
  return GetDefaultBackgroundColorToDraw();
}

struct PaintParams {
  nscolor mBackgroundColor;
};

LayerManager* PresShell::GetLayerManager() {
  NS_ASSERTION(mViewManager, "Should have view manager");

  nsView* rootView = mViewManager->GetRootView();
  if (rootView) {
    if (nsIWidget* widget = rootView->GetWidget()) {
      return widget->GetLayerManager();
    }
  }
  return nullptr;
}

bool PresShell::AsyncPanZoomEnabled() {
  NS_ASSERTION(mViewManager, "Should have view manager");
  nsView* rootView = mViewManager->GetRootView();
  if (rootView) {
    if (nsIWidget* widget = rootView->GetWidget()) {
      return widget->AsyncPanZoomEnabled();
    }
  }
  return gfxPlatform::AsyncPanZoomEnabled();
}

void PresShell::SetIgnoreViewportScrolling(bool aIgnore) {
  if (IgnoringViewportScrolling() == aIgnore) {
    return;
  }
  RenderingState state(this);
  if (aIgnore) {
    state.mRenderingStateFlags |=
        RenderingStateFlags::IgnoringViewportScrolling;
  } else {
    state.mRenderingStateFlags &=
        ~RenderingStateFlags::IgnoringViewportScrolling;
  }
  SetRenderingState(state);
}

nsresult PresShell::SetResolutionAndScaleTo(float aResolution,
                                            ResolutionChangeOrigin aOrigin) {
  if (!(aResolution > 0.0)) {
    return NS_ERROR_ILLEGAL_VALUE;
  }
  if (aResolution == mResolution.valueOr(0.0)) {
    MOZ_ASSERT(mResolution.isSome());
    return NS_OK;
  }
  RenderingState state(this);
  state.mResolution = Some(aResolution);
  SetRenderingState(state);
  if (mMobileViewportManager) {
    mMobileViewportManager->ResolutionUpdated();
  }
  if (aOrigin == ResolutionChangeOrigin::Apz) {
    mResolutionUpdatedByApz = true;
  } else {
    mResolutionUpdated = true;
  }

  if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
    window->VisualViewport()->PostResizeEvent();
  }

  return NS_OK;
}

float PresShell::GetCumulativeResolution() {
  float resolution = GetResolution();
  nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
  if (parentCtx) {
    resolution *= parentCtx->PresShell()->GetCumulativeResolution();
  }
  return resolution;
}

float PresShell::GetCumulativeNonRootScaleResolution() {
  float resolution = 1.0;
  PresShell* currentPresShell = this;
  while (currentPresShell) {
    nsPresContext* currentCtx = currentPresShell->GetPresContext();
    if (currentCtx != currentCtx->GetRootPresContext()) {
      resolution *= currentPresShell->GetResolution();
    }
    nsPresContext* parentCtx = currentCtx->GetParentPresContext();
    if (parentCtx) {
      currentPresShell = parentCtx->PresShell();
    } else {
      currentPresShell = nullptr;
    }
  }
  return resolution;
}

void PresShell::SetRestoreResolution(float aResolution,
                                     LayoutDeviceIntSize aDisplaySize) {
  if (mMobileViewportManager) {
    mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
  }
}

void PresShell::SetRenderingState(const RenderingState& aState) {
  if (mRenderingStateFlags != aState.mRenderingStateFlags) {
    // Rendering state changed in a way that forces us to flush any
    // retained layers we might already have.
    LayerManager* manager = GetLayerManager();
    if (manager) {
      FrameLayerBuilder::InvalidateAllLayers(manager);
    }
  }

  // nsSubDocumentFrame uses a resolution different from 1.0 to determine if it
  // needs to build a nsDisplayResolution item. So if we are going from or
  // to 1.0 then we need to invalidate the subdoc frame so that item gets
  // created/removed.
  if (mResolution.valueOr(1.0) != aState.mResolution.valueOr(1.0) &&
      (mResolution.valueOr(1.0) == 1.0 ||
       aState.mResolution.valueOr(1.0) == 1.0)) {
    if (nsIFrame* frame = GetRootFrame()) {
      frame = nsLayoutUtils::GetCrossDocParentFrame(frame);
      if (frame) {
        frame->InvalidateFrame();
      }
    }
  }

  mRenderingStateFlags = aState.mRenderingStateFlags;
  mResolution = aState.mResolution;
}

void PresShell::SynthesizeMouseMove(bool aFromScroll) {
  if (!sSynthMouseMove) return;

  if (mPaintingSuppressed || !mIsActive || !mPresContext) {
    return;
  }

  if (!mPresContext->IsRoot()) {
    if (PresShell* rootPresShell = GetRootPresShell()) {
      rootPresShell->SynthesizeMouseMove(aFromScroll);
    }
    return;
  }

  if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
    return;

  if (!mSynthMouseMoveEvent.IsPending()) {
    RefPtr<nsSynthMouseMoveEvent> ev =
        new nsSynthMouseMoveEvent(this, aFromScroll);

    GetPresContext()->RefreshDriver()->AddRefreshObserver(ev,
                                                          FlushType::Display);
    mSynthMouseMoveEvent = std::move(ev);
  }
}

/**
 * Find the first floating view with a widget in a postorder traversal of the
 * view tree that contains the point. Thus more deeply nested floating views
 * are preferred over their ancestors, and floating views earlier in the
 * view hierarchy (i.e., added later) are preferred over their siblings.
 * This is adequate for finding the "topmost" floating view under a point,
 * given that floating views don't supporting having a specific z-index.
 *
 * We cannot exit early when aPt is outside the view bounds, because floating
 * views aren't necessarily included in their parent's bounds, so this could
 * traverse the entire view hierarchy --- use carefully.
 */
static nsView* FindFloatingViewContaining(nsView* aView, nsPoint aPt) {
  if (aView->GetVisibility() == nsViewVisibility_kHide)
    // No need to look into descendants.
    return nullptr;

  nsIFrame* frame = aView->GetFrame();
  if (frame) {
    if (!frame->IsVisibleConsideringAncestors(
            nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) ||
        !frame->PresShell()->IsActive()) {
      return nullptr;
    }
  }

  for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
    nsView* r = FindFloatingViewContaining(v, v->ConvertFromParentCoords(aPt));
    if (r) return r;
  }

  if (aView->GetFloating() && aView->HasWidget() &&
      aView->GetDimensions().Contains(aPt))
    return aView;

  return nullptr;
}

/*
 * This finds the first view containing the given point in a postorder
 * traversal of the view tree that contains the point, assuming that the
 * point is not in a floating view.  It assumes that only floating views
 * extend outside the bounds of their parents.
 *
 * This methods should only be called if FindFloatingViewContaining
 * returns null.
 */
static nsView* FindViewContaining(nsView* aView, nsPoint aPt) {
  if (!aView->GetDimensions().Contains(aPt) ||
      aView->GetVisibility() == nsViewVisibility_kHide) {
    return nullptr;
  }

  nsIFrame* frame = aView->GetFrame();
  if (frame) {
    if (!frame->IsVisibleConsideringAncestors(
            nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) ||
        !frame->PresShell()->IsActive()) {
      return nullptr;
    }
  }

  for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
    nsView* r = FindViewContaining(v, v->ConvertFromParentCoords(aPt));
    if (r) return r;
  }

  return aView;
}

static BrowserBridgeChild* GetChildBrowser(nsView* aView) {
  if (!aView) {
    return nullptr;
  }
  nsIFrame* frame = aView->GetFrame();
  if (!frame && aView->GetParent()) {
    // If frame is null then view is an anonymous inner view, and we want
    // the frame from the corresponding outer view.
    frame = aView->GetParent()->GetFrame();
  }
  if (!frame || !frame->GetContent()) {
    return nullptr;
  }
  return BrowserBridgeChild::GetFrom(frame->GetContent());
}

void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) {
  // If drag session has started, we shouldn't synthesize mousemove event.
  nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
  if (dragSession) {
    mSynthMouseMoveEvent.Forget();
    return;
  }

  // allow new event to be posted while handling this one only if the
  // source of the event is a scroll (to prevent infinite reflow loops)
  if (aFromScroll) {
    mSynthMouseMoveEvent.Forget();
  }

  nsView* rootView = mViewManager ? mViewManager->GetRootView() : nullptr;
  if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) ||
      !rootView || !rootView->HasWidget() || !mPresContext) {
    mSynthMouseMoveEvent.Forget();
    return;
  }

  NS_ASSERTION(mPresContext->IsRoot(), "Only a root pres shell should be here");

  // Hold a ref to ourselves so DispatchEvent won't destroy us (since
  // we need to access members after we call DispatchEvent).
  RefPtr<PresShell> kungFuDeathGrip(this);

#ifdef DEBUG_MOUSE_LOCATION
  printf("[ps=%p]synthesizing mouse move to (%d,%d)\n", this, mMouseLocation.x,
         mMouseLocation.y);
#endif

  int32_t APD = mPresContext->AppUnitsPerDevPixel();

  // We need a widget to put in the event we are going to dispatch so we look
  // for a view that has a widget and the mouse location is over. We first look
  // for floating views, if there isn't one we use the root view. |view| holds
  // that view.
  nsView* view = nullptr;

  // The appunits per devpixel ratio of |view|.
  int32_t viewAPD;

  // mRefPoint will be mMouseLocation relative to the widget of |view|, the
  // widget we will put in the event we dispatch, in viewAPD appunits
  nsPoint refpoint(0, 0);

  // We always dispatch the event to the pres shell that contains the view that
  // the mouse is over. pointVM is the VM of that pres shell.
  nsViewManager* pointVM = nullptr;

  // This could be a bit slow (traverses entire view hierarchy)
  // but it's OK to do it once per synthetic mouse event
  view = FindFloatingViewContaining(rootView, mMouseLocation);
  nsView* pointView = view;
  if (!view) {
    view = rootView;
    pointView = FindViewContaining(rootView, mMouseLocation);
    // pointView can be null in situations related to mouse capture
    pointVM = (pointView ? pointView : view)->GetViewManager();
    refpoint = mMouseLocation + rootView->ViewToWidgetOffset();
    viewAPD = APD;
  } else {
    pointVM = view->GetViewManager();
    nsIFrame* frame = view->GetFrame();
    NS_ASSERTION(frame, "floating views can't be anonymous");
    viewAPD = frame->PresContext()->AppUnitsPerDevPixel();
    refpoint = mMouseLocation.ScaleToOtherAppUnits(APD, viewAPD);
    refpoint -= view->GetOffsetTo(rootView);
    refpoint += view->ViewToWidgetOffset();
  }
  NS_ASSERTION(view->GetWidget(), "view should have a widget here");
  WidgetMouseEvent event(true, eMouseMove, view->GetWidget(),
                         WidgetMouseEvent::eSynthesized);
  event.mRefPoint =
      LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD);
  event.mTime = PR_IntervalNow();
  // XXX set event.mModifiers ?
  // XXX mnakano I think that we should get the latest information from widget.

  if (BrowserBridgeChild* bbc = GetChildBrowser(pointView)) {
    // If we have a BrowserBridgeChild, we're going to be dispatching this
    // mouse event into an OOP iframe of the current document.
    event.mLayersId = bbc->GetLayersId();
    bbc->SendDispatchSynthesizedMouseEvent(event);
  } else if (RefPtr<PresShell> presShell = pointVM->GetPresShell()) {
    // Since this gets run in a refresh tick there isn't an InputAPZContext on
    // the stack from the nsBaseWidget. We need to simulate one with at least
    // the correct target guid, so that the correct callback transform gets
    // applied if this event goes to a child process. The input block id is set
    // to 0 because this is a synthetic event which doesn't really belong to any
    // input block. Same for the APZ response field.
    InputAPZContext apzContext(mMouseEventTargetGuid, 0, nsEventStatus_eIgnore);
    presShell->DispatchSynthMouseMove(&event);
  }

  if (!aFromScroll) {
    mSynthMouseMoveEvent.Forget();
  }
}

/* static */
void PresShell::MarkFramesInListApproximatelyVisible(
    const nsDisplayList& aList) {
  for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) {
    nsDisplayList* sublist = item->GetChildren();
    if (sublist) {
      MarkFramesInListApproximatelyVisible(*sublist);
      continue;
    }

    nsIFrame* frame = item->Frame();
    MOZ_ASSERT(frame);

    if (!frame->TrackingVisibility()) {
      continue;
    }

    // Use the presshell containing the frame.
    PresShell* presShell = frame->PresShell();
    MOZ_ASSERT(!presShell->AssumeAllFramesVisible());
    if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) {
      // The frame was added to mApproximatelyVisibleFrames, so increment its
      // visible count.
      frame->IncApproximateVisibleCount();
    }
  }
}

/* static */
void PresShell::DecApproximateVisibleCount(
    VisibleFrames& aFrames, const Maybe<OnNonvisible>& aNonvisibleAction
    /* = Nothing() */) {
  for (auto iter = aFrames.Iter(); !iter.Done(); iter.Next()) {
    nsIFrame* frame = iter.Get()->GetKey();
    // Decrement the frame's visible count if we're still tracking its
    // visibility. (We may not be, if the frame disabled visibility tracking
    // after we added it to the visible frames list.)
    if (frame->TrackingVisibility()) {
      frame->DecApproximateVisibleCount(aNonvisibleAction);
    }
  }
}

void PresShell::RebuildApproximateFrameVisibilityDisplayList(
    const nsDisplayList& aList) {
  MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
  mApproximateFrameVisibilityVisited = true;

  // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
  // them in oldApproxVisibleFrames.
  VisibleFrames oldApproximatelyVisibleFrames;
  mApproximatelyVisibleFrames.SwapElements(oldApproximatelyVisibleFrames);

  MarkFramesInListApproximatelyVisible(aList);

  DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
}

/* static */
void PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView,
                                                       bool aClear) {
  nsViewManager* vm = aView->GetViewManager();
  if (aClear) {
    PresShell* presShell = vm->GetPresShell();
    if (!presShell->mApproximateFrameVisibilityVisited) {
      presShell->ClearApproximatelyVisibleFramesList();
    }
    presShell->mApproximateFrameVisibilityVisited = false;
  }
  for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
    ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm);
  }
}

void PresShell::ClearApproximatelyVisibleFramesList(
    const Maybe<OnNonvisible>& aNonvisibleAction
    /* = Nothing() */) {
  DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction);
  mApproximatelyVisibleFrames.Clear();
}

void PresShell::MarkFramesInSubtreeApproximatelyVisible(
    nsIFrame* aFrame, const nsRect& aRect, bool aRemoveOnly /* = false */) {
  MOZ_ASSERT(aFrame->PresShell() == this, "wrong presshell");

  if (aFrame->TrackingVisibility() && aFrame->StyleVisibility()->IsVisible() &&
      (!aRemoveOnly ||
       aFrame->GetVisibility() == Visibility::ApproximatelyVisible)) {
    MOZ_ASSERT(!AssumeAllFramesVisible());
    if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
      // The frame was added to mApproximatelyVisibleFrames, so increment its
      // visible count.
      aFrame->IncApproximateVisibleCount();
    }
  }

  nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame);
  if (subdocFrame) {
    PresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting(
        nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION);
    if (presShell && !presShell->AssumeAllFramesVisible()) {
      nsRect rect = aRect;
      nsIFrame* root = presShell->GetRootFrame();
      if (root) {
        rect.MoveBy(aFrame->GetOffsetToCrossDoc(root));
      } else {
        rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft());
      }
      rect = rect.ScaleToOtherAppUnitsRoundOut(
          aFrame->PresContext()->AppUnitsPerDevPixel(),
          presShell->GetPresContext()->AppUnitsPerDevPixel());

      presShell->RebuildApproximateFrameVisibility(&rect);
    }
    return;
  }

  nsRect rect = aRect;

  nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame);
  if (scrollFrame) {
    bool ignoreDisplayPort = false;
    if (nsLayoutUtils::IsMissingDisplayPortBaseRect(aFrame->GetContent())) {
      // We can properly set the base rect for root scroll frames on top level
      // and root content documents. Otherwise the base rect we compute might
      // be way too big without the limiting that
      // ScrollFrameHelper::DecideScrollableLayer does, so we just ignore the
      // displayport in that case.
      nsPresContext* pc = aFrame->PresContext();
      if (scrollFrame->IsRootScrollFrameOfDocument() &&
          (pc->IsRootContentDocument() || !pc->GetParentPresContext())) {
        nsRect baseRect =
            nsRect(nsPoint(0, 0),
                   nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame));
        nsLayoutUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect);
      } else {
        ignoreDisplayPort = true;
      }
    }

    nsRect displayPort;
    bool usingDisplayport =
        !ignoreDisplayPort &&
        nsLayoutUtils::GetDisplayPortForVisibilityTesting(
            aFrame->GetContent(), &displayPort, RelativeTo::ScrollFrame);

    scrollFrame->NotifyApproximateFrameVisibilityUpdate(!usingDisplayport);

    if (usingDisplayport) {
      rect = displayPort;
    } else {
      rect = rect.Intersect(scrollFrame->GetScrollPortRect());
    }
    rect = scrollFrame->ExpandRectToNearlyVisible(rect);
  }

  bool preserves3DChildren = aFrame->Extend3DContext();

  // We assume all frames in popups are visible, so we skip them here.
  const nsIFrame::ChildListIDs skip = {nsIFrame::kPopupList,
                                       nsIFrame::kSelectPopupList};
  for (nsIFrame::ChildListIterator childLists(aFrame); !childLists.IsDone();
       childLists.Next()) {
    if (skip.contains(childLists.CurrentID())) {
      continue;
    }

    for (nsIFrame* child : childLists.CurrentList()) {
      nsRect r = rect - child->GetPosition();
      if (!r.IntersectRect(r, child->GetVisualOverflowRect())) {
        continue;
      }
      if (child->IsTransformed()) {
        // for children of a preserve3d element we just pass down the same dirty
        // rect
        if (!preserves3DChildren ||
            !child->Combines3DTransformWithAncestors()) {
          const nsRect overflow = child->GetVisualOverflowRectRelativeToSelf();
          nsRect out;
          if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) {
            r = out;
          } else {
            r.SetEmpty();
          }
        }
      }
      MarkFramesInSubtreeApproximatelyVisible(child, r);
    }
  }
}

void PresShell::RebuildApproximateFrameVisibility(
    nsRect* aRect, bool aRemoveOnly /* = false */) {
  MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
  mApproximateFrameVisibilityVisited = true;

  nsIFrame* rootFrame = GetRootFrame();
  if (!rootFrame) {
    return;
  }

  // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
  // them in oldApproximatelyVisibleFrames.
  VisibleFrames oldApproximatelyVisibleFrames;
  mApproximatelyVisibleFrames.SwapElements(oldApproximatelyVisibleFrames);

  nsRect vis(nsPoint(0, 0), rootFrame->GetSize());
  if (aRect) {
    vis = *aRect;
  }

  MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, aRemoveOnly);

  DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
}

void PresShell::UpdateApproximateFrameVisibility() {
  DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false);
}

void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) {
  MOZ_ASSERT(
      !mPresContext || mPresContext->IsRootContentDocument(),
      "Updating approximate frame visibility on a non-root content document?");

  mUpdateApproximateFrameVisibilityEvent.Revoke();

  if (mHaveShutDown || mIsDestroying) {
    return;
  }

  // call update on that frame
  nsIFrame* rootFrame = GetRootFrame();
  if (!rootFrame) {
    ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
    return;
  }

  RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly);
  ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);

#ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST
  // This can be used to debug the frame walker by comparing beforeFrameList
  // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see
  // if they produce the same results (mApproximatelyVisibleFrames holds the
  // frames the display list thinks are visible, beforeFrameList holds the
  // frames the frame walker thinks are visible).
  nsDisplayListBuilder builder(
      rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false);
  nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize());
  nsIFrame* rootScroll = GetRootScrollFrame();
  if (rootScroll) {
    nsIContent* content = rootScroll->GetContent();
    if (content) {
      Unused << nsLayoutUtils::GetDisplayPortForVisibilityTesting(
          content, &updateRect, RelativeTo::ScrollFrame);
    }

    if (IgnoringViewportScrolling()) {
      builder.SetIgnoreScrollFrame(rootScroll);
    }
  }
  builder.IgnorePaintSuppression();
  builder.EnterPresShell(rootFrame);
  nsDisplayList list;
  rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list);
  builder.LeavePresShell(rootFrame, &list);

  RebuildApproximateFrameVisibilityDisplayList(list);

  ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);

  list.DeleteAll(&builder);
#endif
}

bool PresShell::AssumeAllFramesVisible() {
  static bool sFrameVisibilityEnabled = true;
  static bool sFrameVisibilityPrefCached = false;

  if (!sFrameVisibilityPrefCached) {
    Preferences::AddBoolVarCache(&sFrameVisibilityEnabled,
                                 "layout.framevisibility.enabled", true);
    sFrameVisibilityPrefCached = true;
  }

  if (!sFrameVisibilityEnabled || !mPresContext || !mDocument) {
    return true;
  }

  // We assume all frames are visible in print, print preview, chrome, and
  // resource docs and don't keep track of them.
  if (mPresContext->Type() == nsPresContext::eContext_PrintPreview ||
      mPresContext->Type() == nsPresContext::eContext_Print ||
      mPresContext->IsChrome() || mDocument->IsResourceDoc()) {
    return true;
  }

  // If we're assuming all frames are visible in the top level content
  // document, we need to in subdocuments as well. Otherwise we can get in a
  // situation where things like animations won't work in subdocuments because
  // their frames appear not to be visible, since we won't schedule an image
  // visibility update if the top level content document is assuming all
  // frames are visible.
  //
  // Note that it's not safe to call IsRootContentDocument() if we're
  // currently being destroyed, so we have to check that first.
  if (!mHaveShutDown && !mIsDestroying &&
      !mPresContext->IsRootContentDocumentInProcess()) {
    nsPresContext* presContext =
        mPresContext->GetToplevelContentDocumentPresContext();
    if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) {
      return true;
    }
  }

  return false;
}

void PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() {
  if (AssumeAllFramesVisible()) {
    return;
  }

  if (!mPresContext) {
    return;
  }

  nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver();
  if (!refreshDriver) {
    return;
  }

  // Ask the refresh driver to update frame visibility soon.
  refreshDriver->ScheduleFrameVisibilityUpdate();
}

void PresShell::ScheduleApproximateFrameVisibilityUpdateNow() {
  if (AssumeAllFramesVisible()) {
    return;
  }

  if (!mPresContext->IsRootContentDocument()) {
    nsPresContext* presContext =
        mPresContext->GetToplevelContentDocumentPresContext();
    if (!presContext) return;
    MOZ_ASSERT(presContext->IsRootContentDocument(),
               "Didn't get a root prescontext from "
               "GetToplevelContentDocumentPresContext?");
    presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
    return;
  }

  if (mHaveShutDown || mIsDestroying) {
    return;
  }

  if (mUpdateApproximateFrameVisibilityEvent.IsPending()) {
    return;
  }

  RefPtr<nsRunnableMethod<PresShell>> event =
      NewRunnableMethod("PresShell::UpdateApproximateFrameVisibility", this,
                        &PresShell::UpdateApproximateFrameVisibility);
  nsresult rv = mDocument->Dispatch(TaskCategory::Other, do_AddRef(event));

  if (NS_SUCCEEDED(rv)) {
    mUpdateApproximateFrameVisibilityEvent = std::move(event);
  }
}

void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) {
  if (!aFrame->TrackingVisibility()) {
    return;
  }

  if (AssumeAllFramesVisible()) {
    aFrame->IncApproximateVisibleCount();
    return;
  }

#ifdef DEBUG
  // Make sure it's in this pres shell.
  nsCOMPtr<nsIContent> content = aFrame->GetContent();
  if (content) {
    PresShell* presShell = content->OwnerDoc()->GetPresShell();
    MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
  }
#endif

  if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
    // We inserted a new entry.
    aFrame->IncApproximateVisibleCount();
  }
}

void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) {
#ifdef DEBUG
  // Make sure it's in this pres shell.
  nsCOMPtr<nsIContent> content = aFrame->GetContent();
  if (content) {
    PresShell* presShell = content->OwnerDoc()->GetPresShell();
    MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
  }
#endif

  if (AssumeAllFramesVisible()) {
    MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0,
               "Shouldn't have any frames in the table");
    return;
  }

  if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) &&
      aFrame->TrackingVisibility()) {
    // aFrame was in the hashtable, and we're still tracking its visibility,
    // so we need to decrement its visible count.
    aFrame->DecApproximateVisibleCount();
  }
}

class nsAutoNotifyDidPaint {
 public:
  nsAutoNotifyDidPaint(PresShell* aShell, PaintFlags aFlags)
      : mShell(aShell), mFlags(aFlags) {}
  ~nsAutoNotifyDidPaint() {
    if (!!(mFlags & PaintFlags::PaintComposite)) {
      mShell->GetPresContext()->NotifyDidPaintForSubtree();
    }
  }

 private:
  PresShell* mShell;
  PaintFlags mFlags;
};

void PresShell::Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
                      PaintFlags aFlags) {
  nsCString url;
  nsIURI* uri = mDocument->GetDocumentURI();
  Document* contentRoot = GetPrimaryContentDocument();
  if (contentRoot) {
    uri = contentRoot->GetDocumentURI();
  }
  url = uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A");
#ifdef MOZ_GECKO_PROFILER
  AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("PresShell::Paint", GRAPHICS, url);
#endif

  Maybe<js::AutoAssertNoContentJS> nojs;

  // On Android, Flash can call into content JS during painting, so we can't
  // assert there. However, we don't rely on this assertion on Android because
  // we don't paint while JS is running.
#if !defined(MOZ_WIDGET_ANDROID)
  if (!(aFlags & PaintFlags::PaintComposite)) {
    // We need to allow content JS when the flag is set since we may trigger
    // MozAfterPaint events in content in those cases.
    nojs.emplace(dom::danger::GetJSContext());
  }
#endif

  NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell");
  NS_ASSERTION(aViewToPaint, "null view");

  MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared");

  if (!mIsActive) {
    return;
  }

  if (StaticPrefs::APZKeyboardEnabled()) {
    // Update the focus target for async keyboard scrolling. This will be
    // forwarded to APZ by nsDisplayList::PaintRoot. We need to to do this
    // before we enter the paint phase because dispatching eVoid events can
    // cause layout to happen.
    mAPZFocusTarget = FocusTarget(this, mAPZFocusSequenceNumber);
  }

  nsPresContext* presContext = GetPresContext();
  AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint);

  nsIFrame* frame = aViewToPaint->GetFrame();

  LayerManager* layerManager = aViewToPaint->GetWidget()->GetLayerManager();
  NS_ASSERTION(layerManager, "Must be in paint event");
  bool shouldInvalidate = layerManager->NeedsWidgetInvalidation();

  nsAutoNotifyDidPaint notifyDidPaint(this, aFlags);

  // Whether or not we should set first paint when painting is suppressed
  // is debatable. For now we'll do it because B2G relied on first paint
  // to configure the viewport and we only want to do that when we have
  // real content to paint. See Bug 798245
  if (mIsFirstPaint && !mPaintingSuppressed) {
    layerManager->SetIsFirstPaint();
    mIsFirstPaint = false;
  }

  if (!layerManager->BeginTransaction(url