From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- layout/base/PresShell.cpp | 11994 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 11994 insertions(+) create mode 100644 layout/base/PresShell.cpp (limited to 'layout/base/PresShell.cpp') diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp new file mode 100644 index 0000000000..e57d773a27 --- /dev/null +++ b/layout/base/PresShell.cpp @@ -0,0 +1,11994 @@ +/* -*- 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 "Units.h" +#include "mozilla/dom/FontFaceSet.h" +#include "mozilla/dom/ElementBinding.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/ContentIterator.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/GeckoMVMContext.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/IntegerRange.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/PointerLockManager.h" +#include "mozilla/PresShellInlines.h" +#include "mozilla/RangeUtils.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticAnalysisFunctions.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_font.h" +#include "mozilla/StaticPrefs_image.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_toolkit.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/ViewportUtils.h" +#include "mozilla/gfx/Types.h" +#include + +#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/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.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 "mozilla/dom/UserActivation.h" +#include "nsAnimationManager.h" +#include "nsNameSpaceManager.h" // for Pref-related rule management (bugs 22963,20760,31816) +#include "nsFlexContainerFrame.h" +#include "nsIFrame.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 "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" +#include "AutoProfilerStyleMarker.h" +#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 "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "nsStyleSheetService.h" +#include "gfxUtils.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/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 "mozilla/a11y/DocAccessible.h" +# ifdef DEBUG +# include "mozilla/a11y/Logging.h" +# endif +#endif + +// For style data reconstruction +#include "nsStyleChangeList.h" +#include "nsCSSFrameConstructor.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" + +#include "mozilla/layers/CompositorBridgeChild.h" +#include "gfxPlatform.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 "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 "nsNetUtil.h" +#include "nsSubDocumentFrame.h" +#include "nsQueryObject.h" +#include "mozilla/GlobalStyleSheetCache.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/FocusTarget.h" +#include "mozilla/layers/ScrollingInteractionContext.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/layers/WebRenderUserData.h" +#include "mozilla/layout/ScrollAnchorContainer.h" +#include "mozilla/layers/APZPublicUtils.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ScrollTimelineAnimationTracker.h" +#include "mozilla/ScrollTypes.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/dom/ImageTracker.h" +#include "nsIDocShellTreeOwner.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "ScrollSnap.h" +#include "VisualViewport.h" +#include "ZoomConstraintsClient.h" + +// 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 mRange; + nsDisplayListBuilder mBuilder; + nsDisplayList mList; + + // offset of builder's reference frame to the root frame + nsPoint mRootOffset; + + // Resolution at which the items are normally painted. So if we're painting + // these items in a range separately from the "full display list", we may want + // to paint them at this resolution. + float mResolution = 1.0; + + RangePaintInfo(nsRange* aRange, nsIFrame* aFrame) + : mRange(aRange), + mBuilder(aFrame, nsDisplayListBuilderMode::Painting, false), + mList(&mBuilder) { + 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() = default; + + 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 mCounts; + nsClassHashtable 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: + MOZ_CAN_RUN_SCRIPT explicit nsAutoCauseReflowNotifier(PresShell* aPresShell) + : mPresShell(aPresShell) { + mPresShell->WillCauseReflow(); + } + MOZ_CAN_RUN_SCRIPT ~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) { + RefPtr presShell(mPresShell); + presShell->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 nsIFrame::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 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(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(), + &aVisitor.mEventStatus); + } + } + } + + RefPtr 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 observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(ToSupports(mDocument), + "before-first-paint", nullptr); + } + return NS_OK; + } + + private: + RefPtr 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()); + + mShell = aShell; + mWeakFrame = aFrame; + mTargetContent = aTargetContent; + aShell->mPointerEventTarget = aFrame->GetContent(); + } + + ~AutoPointerEventTargetUpdater() { + if (!mTargetContent || !mShell || mWeakFrame.IsAlive()) { + return; + } + mShell->mPointerEventTarget.swap(*mTargetContent); + } + + private: + RefPtr mShell; + AutoWeakFrame mWeakFrame; + nsIContent** mTargetContent; +}; + +bool PresShell::sDisableNonTestMouseEvents = false; + +LazyLogModule PresShell::gLog("PresShell"); + +TimeStamp PresShell::EventHandler::sLastInputCreated; +TimeStamp PresShell::EventHandler::sLastInputProcessed; +StaticRefPtr 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 = 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 (nsCRT::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.Contains(aWeakFrame)); + mWeakFrames.Insert(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.Contains(aWeakFrame)); + mWeakFrames.Remove(aWeakFrame); +} + +already_AddRefed PresShell::FrameSelection() { + RefPtr ret = mSelection; + return ret.forget(); +} + +//---------------------------------------------------------------------- + +static uint32_t sNextPresShellId = 0; + +/* 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(Document* aDocument) + : mDocument(aDocument), + mViewManager(nullptr), + mFrameManager(nullptr), + mAutoWeakFrames(nullptr), +#ifdef ACCESSIBILITY + mDocAccessible(nullptr), +#endif // ACCESSIBILITY + mCurrentEventFrame(nullptr), + mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), + mLastResolutionChangeOrigin(ResolutionChangeOrigin::Apz), + 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), + mIsInActiveTab(true), + mFrozen(false), + mIsFirstPaint(true), + mObservesMutationsForPrint(false), + mWasLastReflowInterrupted(false), + mObservingStyleFlushes(false), + mObservingLayoutFlushes(false), + mResizeEventPending(false), + mFontSizeInflationForceEnabled(false), + mFontSizeInflationDisabledInMasterProcess(false), + mFontSizeInflationEnabled(false), + mIsNeverPainting(false), + mResolutionUpdated(false), + mResolutionUpdatedByApz(false), + mUnderHiddenEmbedderElement(false), + mDocumentLoading(false), + mNoDelayedMouseEvents(false), + mNoDelayedKeyEvents(false), + mApproximateFrameVisibilityVisited(false), + mHasCSSBackgroundColor(true), + mIsLastChromeOnlyEscapeKeyConsumed(false), + mHasReceivedPaintMessage(false), + mIsLastKeyDownCanceled(false), + mHasHandledUserInput(false), + mForceDispatchKeyPressEventsForNonPrintableKeys(false), + mForceUseLegacyKeyCodeAndCharCodeValues(false), + mInitializedWithKeyPressEventDispatchingBlacklist(false), + mForceUseLegacyNonPrimaryDispatch(false), + mInitializedWithClickEventDispatchingBlacklist(false), + mMouseLocationWasSetBySynthesizedMouseEventForTests(false), + mHasTriedFastUnsuppress(false), + mProcessingReflowCommands(false), + mPendingDidDoReflow(false) { + MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this)); + MOZ_ASSERT(aDocument); + +#ifdef MOZ_REFLOW_PERF + mReflowCountMgr = MakeUnique(); + mReflowCountMgr->SetPresContext(mPresContext); + mReflowCountMgr->SetPresShell(this); +#endif + mLastOSWake = mLoadBegin = TimeStamp::Now(); +} + +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"); + + MOZ_ASSERT(!mAllocatedPointers || 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(nsPresContext* aPresContext, nsViewManager* aViewManager) { + MOZ_ASSERT(mDocument); + MOZ_ASSERT(aPresContext); + MOZ_ASSERT(aViewManager); + MOZ_ASSERT(!mViewManager, "already initialized"); + + 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(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. + // FYI: We cannot initialize mPresContext in the constructor because we + // cannot call AttachPresShell() in it and once we initialize + // mPresContext, other objects may refer refresh driver or restyle + // manager via mPresContext and that causes hitting MOZ_ASSERT in some + // places. Therefore, we should initialize mPresContext here with + // const_cast hack since we want to guarantee that mPresContext lives + // as long as the PresShell. + const_cast&>(mPresContext) = aPresContext; + mPresContext->AttachPresShell(this); + + mPresContext->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(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 = mPresContext->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 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); + os->AddObserver(this, "internal-look-and-feel-changed", 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. + ActivenessMaybeChanged(); + + // Setup our font inflation preferences. + mFontSizeInflationEmPerLine = StaticPrefs::font_size_inflation_emPerLine(); + mFontSizeInflationMinTwips = StaticPrefs::font_size_inflation_minTwips(); + mFontSizeInflationLineThreshold = + StaticPrefs::font_size_inflation_lineThreshold(); + mFontSizeInflationForceEnabled = + StaticPrefs::font_size_inflation_forceEnabled(); + mFontSizeInflationDisabledInMasterProcess = + StaticPrefs::font_size_inflation_disabledInMasterProcess(); + // We'll compute the font size inflation state in Initialize(), when we know + // the document type. + + mTouchManager.Init(this, mDocument); + + if (mPresContext->IsRootContentDocumentCrossProcess()) { + mZoomConstraintsClient = new ZoomConstraintsClient(); + mZoomConstraintsClient->Init(this, mDocument); + + // We call this to create mMobileViewportManager, if it is needed. + MaybeRecreateMobileViewportManager(false); + } + + if (nsCOMPtr docShell = mPresContext->GetDocShell()) { + if (BrowsingContext* bc = docShell->GetBrowsingContext()) { + mUnderHiddenEmbedderElement = bc->IsUnderHiddenEmbedderElement(); + } + } +} + +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)); + } +} + +bool PresShell::InRDMPane() { + if (Document* doc = GetDocument()) { + if (BrowsingContext* bc = doc->GetBrowsingContext()) { + return bc->InRDMPane(); + } + } + return false; +} + +#if defined(MOZ_WIDGET_ANDROID) +void PresShell::MaybeNotifyShowDynamicToolbar() { + const DynamicToolbarState dynToolbarState = GetDynamicToolbarState(); + if ((dynToolbarState == DynamicToolbarState::Collapsed || + dynToolbarState == DynamicToolbarState::InTransition)) { + MOZ_ASSERT(mPresContext && + mPresContext->IsRootContentDocumentCrossProcess()); + if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { + browserChild->SendShowDynamicToolbar(); + } + } +} +#endif // defined(MOZ_WIDGET_ANDROID) + +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"); + + [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI(); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( + "Layout tree destruction", LAYOUT_Destroy, + uri ? uri->GetSpecOrDefault() : "N/A"_ns); + + // Try to determine if the page is the user had a meaningful opportunity to + // zoom this page. This is not 100% accurate but should be "good enough" for + // telemetry purposes. + auto isUserZoomablePage = [&]() -> bool { + if (mIsFirstPaint) { + // Page was never painted, so it wasn't zoomable by the user. We get a + // handful of these "transient" presShells. + return false; + } + if (!mPresContext->IsRootContentDocumentCrossProcess()) { + // Not a root content document, so APZ doesn't support zooming it. + return false; + } + if (InRDMPane()) { + // Responsive design mode is a special case that we want to ignore here. + return false; + } + if (mDocument && mDocument->IsInitialDocument()) { + // Ignore initial about:blank page loads + return false; + } + if (XRE_IsContentProcess() && + IsExtensionRemoteType(ContentChild::GetSingleton()->GetRemoteType())) { + // Also omit presShells from the extension process because they sometimes + // can't be zoomed by the user. + return false; + } + // Otherwise assume the page is user-zoomable. + return true; + }; + if (isUserZoomablePage()) { + Telemetry::Accumulate(Telemetry::APZ_ZOOM_ACTIVITY, + IsResolutionUpdatedByApz()); + } + + // 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->RemoveProperty(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 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"); + os->RemoveObserver(this, "internal-look-and-feel-changed"); + } + } + + // If our paint suppression timer is still active, kill it. + CancelPaintSuppressionTimer(); + + // Same for our reflow continuation timer + if (mReflowContinueTimer) { + mReflowContinueTimer->Cancel(); + mReflowContinueTimer = nullptr; + } + + mSynthMouseMoveEvent.Revoke(); + + mUpdateApproximateFrameVisibilityEvent.Revoke(); + + ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages)); + + if (mCaret) { + mCaret->Terminate(); + mCaret = nullptr; + } + + mFocusedFrameSelection = nullptr; + + if (mSelection) { + RefPtr frameSelection = mSelection; + frameSelection->DisconnectFromPresShell(); + } + + // 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(); + mPendingScrollResnap.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(!). + // XXXmats is this still needed now that plugins are gone? + StopObservingRefreshDriver(); + + if (rd->GetPresContext() == GetPresContext()) { + rd->RevokeViewManagerFlush(); + rd->ClearHasScheduleFlush(); + } + + 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); + } + const nsTArray weakFrames = ToArray(mWeakFrames); + for (WeakFrame* weakFrame : weakFrames) { + weakFrame->Clear(this); + } + + // Terminate AccessibleCaretEventHub after tearing down the frame tree so that + // we don't need to remove caret element's frame in + // AccessibleCaret::RemoveCaretElement(). + if (mAccessibleCaretEventHub) { + mAccessibleCaretEventHub->Terminate(); + mAccessibleCaretEventHub = nullptr; + } + + 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(); + } + + 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 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 (mDocument->IsInChromeDocShell()) { + return; + } + + PreferenceSheet::EnsureInitialized(); + auto* cache = GlobalStyleSheetCache::Singleton(); + + RefPtr 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(*newPrefSheet); + mPrefStyleSheet = newPrefSheet; +} + +void PresShell::RemovePreferenceStyles() { + if (mPrefStyleSheet) { + StyleSet()->RemoveStyleSheet(*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>& 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(StyleSet()->SheetCount(StyleOrigin::User))) { + StyleSet()->AppendStyleSheet(*aSheet); + } else { + StyleSheet* ref = StyleSet()->SheetAt(StyleOrigin::User, index); + StyleSet()->InsertStyleSheetBefore(*aSheet, *ref); + } + + mDocument->ApplicableStylesChanged(); +} + +void PresShell::AddAgentSheet(StyleSheet* aSheet) { + // Make sure this does what nsDocumentViewer::CreateStyleSet does + // wrt ordering. + StyleSet()->AppendStyleSheet(*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(*aSheet, *firstAuthorSheet); + } else { + StyleSet()->AppendStyleSheet(*aSheet); + } + + mDocument->ApplicableStylesChanged(); +} + +bool PresShell::FixUpFocus() { + if (!StaticPrefs::dom_focus_fixup()) { + return false; + } + if (NS_WARN_IF(!mDocument)) { + return false; + } + + nsIContent* currentFocus = mDocument->GetUnretargetedFocusedContent( + Document::IncludeChromeOnly::Yes); + if (!currentFocus) { + return false; + } + + nsIFrame* f = currentFocus->GetPrimaryFrame(); + if (f && f->IsFocusable()) { + return false; + } + + if (currentFocus == mDocument->GetBody() || + currentFocus == mDocument->GetRootElement()) { + return false; + } + + RefPtr fm = nsFocusManager::GetFocusManager(); + nsCOMPtr window = mDocument->GetWindow(); + if (NS_WARN_IF(!window)) { + return false; + } + fm->ClearFocus(window); + return true; +} + +void PresShell::SelectionWillTakeFocus() { + if (mSelection) { + FrameSelectionWillTakeFocus(*mSelection); + } +} + +void PresShell::SelectionWillLoseFocus() { + // Do nothing, the main selection is the default focused selection. +} + +// Selection repainting code relies on selection offsets being properly +// adjusted (see bug 1626291), so we need to wait until the DOM is finished +// notifying. +static void RepaintNormalSelectionWhenSafe(nsFrameSelection& aFrameSelection) { + if (nsContentUtils::IsSafeToRunScript()) { + aFrameSelection.RepaintSelection(SelectionType::eNormal); + return; + } + + // Note that importantly we don't defer changing the DisplaySelection. That'd + // be potentially racy with other code that may change it. + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "RepaintNormalSelectionWhenSafe", + [sel = RefPtr(&aFrameSelection)] { + sel->RepaintSelection(SelectionType::eNormal); + })); +} + +void PresShell::FrameSelectionWillLoseFocus(nsFrameSelection& aFrameSelection) { + if (mFocusedFrameSelection != &aFrameSelection) { + return; + } + + // Do nothing, the main selection is the default focused selection. + if (&aFrameSelection == mSelection) { + return; + } + + RefPtr old = std::move(mFocusedFrameSelection); + MOZ_ASSERT(!mFocusedFrameSelection); + + if (old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) { + old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); + RepaintNormalSelectionWhenSafe(*old); + } + + if (mSelection) { + FrameSelectionWillTakeFocus(*mSelection); + } +} + +void PresShell::FrameSelectionWillTakeFocus(nsFrameSelection& aFrameSelection) { + if (mFocusedFrameSelection == &aFrameSelection) { +#ifdef XP_MACOSX + // FIXME: Mac needs to update the global selection cache, even if the + // document's focused selection doesn't change, and this is currently done + // from RepaintSelection. Maybe we should move part of the global selection + // handling here, or something of that sort, unclear. + RepaintNormalSelectionWhenSafe(aFrameSelection); +#endif + return; + } + + RefPtr old = std::move(mFocusedFrameSelection); + mFocusedFrameSelection = &aFrameSelection; + + if (old && + old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) { + old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); + RepaintNormalSelectionWhenSafe(*old); + } + + if (aFrameSelection.GetDisplaySelection() != + nsISelectionController::SELECTION_ON) { + aFrameSelection.SetDisplaySelection(nsISelectionController::SELECTION_ON); + RepaintNormalSelectionWhenSafe(aFrameSelection); + } +} + +NS_IMETHODIMP +PresShell::SetDisplaySelection(int16_t aToggle) { + RefPtr frameSelection = mSelection; + frameSelection->SetDisplaySelection(aToggle); + return NS_OK; +} + +NS_IMETHODIMP +PresShell::GetDisplaySelection(int16_t* aToggle) { + RefPtr 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 frameSelection = mSelection; + RefPtr 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 frameSelection = mSelection; + return frameSelection->GetSelection(ToSelectionType(aRawSelectionType)); +} + +Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) { + if (!mSelection) { + return nullptr; + } + + RefPtr frameSelection = mSelection; + return frameSelection->GetSelection(aSelectionType); +} + +nsFrameSelection* PresShell::GetLastFocusedFrameSelection() { + return mFocusedFrameSelection ? mFocusedFrameSelection : mSelection; +} + +NS_IMETHODIMP +PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType, + SelectionRegion aRegion, int16_t aFlags) { + if (!mSelection) return NS_ERROR_NULL_POINTER; + + RefPtr 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 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 + +void PresShell::InitPaintSuppressionTimer() { + // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value. + Document* doc = mDocument->GetDisplayDocument() + ? mDocument->GetDisplayDocument() + : mDocument.get(); + const bool inProcess = !doc->GetBrowsingContext() || + doc->GetBrowsingContext()->Top()->IsInProcess(); + int32_t delay = inProcess + ? StaticPrefs::nglayout_initialpaint_delay() + : StaticPrefs::nglayout_initialpaint_delay_in_oopif(); + mPaintSuppressionTimer->InitWithNamedFuncCallback( + [](nsITimer* aTimer, void* aPresShell) { + RefPtr self = static_cast(aPresShell); + self->UnsuppressPainting(); + }, + this, delay, nsITimer::TYPE_ONE_SHOT, + "PresShell::sPaintSuppressionCallback"); +} + +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 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, nsCSSFrameConstructor::InsertionKind::Sync); + } + // Something in mFrameConstructor->ContentInserted may have caused + // Destroy() to get called, bug 337586. Or, nsAutoCauseReflowNotifier + // (which sets up a script blocker) going out of scope may have killed us + // too + NS_ENSURE_STATE(!mHaveShutDown); + } + + if (mDocument->HasAutoFocusCandidates()) { + mDocument->ScheduleFlushAutoFocusCandidates(); + } + + 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->HasAnyStateBits(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::None, 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. + mPaintSuppressionTimer->SetTarget( + mDocument->EventTargetFor(TaskCategory::Other)); + InitPaintSuppressionTimer(); + if (mHasTriedFastUnsuppress) { + // Someone tried to unsuppress painting before Initialize was called so + // unsuppress painting rather soon. + mHasTriedFastUnsuppress = false; + TryUnsuppressPaintingSoon(); + MOZ_ASSERT(mHasTriedFastUnsuppress); + } + } + } + + // 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::TryUnsuppressPaintingSoon() { + if (mHasTriedFastUnsuppress) { + return; + } + mHasTriedFastUnsuppress = true; + + if (!mDidInitialize || !IsPaintingSuppressed() || !XRE_IsContentProcess()) { + return; + } + + if (!mDocument->IsInitialDocument() && + mDocument->DidHitCompleteSheetCache() && + mPresContext->IsRootContentDocumentCrossProcess()) { + // Try to unsuppress faster on a top level page if it uses stylesheet + // cache, since that hints that many resources can be painted sooner than + // in a cold page load case. + NS_DispatchToCurrentThreadQueue( + NS_NewRunnableFunction("PresShell::TryUnsuppressPaintingSoon", + [self = RefPtr{this}]() -> void { + if (self->IsPaintingSuppressed()) { + PROFILER_MARKER_UNTYPED( + "Fast paint unsuppression", GRAPHICS); + self->UnsuppressPainting(); + } + }), + EventQueuePriority::Control); + } +} + +void PresShell::RefreshZoomConstraintsForScreenSizeChange() { + if (mZoomConstraintsClient) { + mZoomConstraintsClient->ScreenSizeChanged(); + } +} + +void PresShell::ForceResizeReflowWithCurrentDimensions() { + nscoord currentWidth = 0; + nscoord currentHeight = 0; + mViewManager->GetWindowDimensions(¤tWidth, ¤tHeight); + ResizeReflow(currentWidth, currentHeight); +} + +void PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight, + 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 (UsesMobileViewportSizing()) { + // If we are using mobile viewport sizing, request a reflow from the MVM. + // 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. + MOZ_ASSERT(mMobileViewportManager); + mMobileViewportManager->RequestReflow(false); + return; + } + ResizeReflowIgnoreOverride(aWidth, aHeight, aOptions); +} + +bool PresShell::SimpleResizeReflow(nscoord aWidth, nscoord aHeight) { + MOZ_ASSERT(aWidth != NS_UNCONSTRAINEDSIZE); + MOZ_ASSERT(aHeight != NS_UNCONSTRAINEDSIZE); + nsSize oldSize = mPresContext->GetVisibleArea().Size(); + mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight)); + nsIFrame* rootFrame = GetRootFrame(); + if (!rootFrame) { + return false; + } + WritingMode wm = rootFrame->GetWritingMode(); + bool isBSizeChanging = + wm.IsVertical() ? oldSize.width != aWidth : oldSize.height != aHeight; + if (isBSizeChanging) { + nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame); + } + FrameNeedsReflow(rootFrame, IntrinsicDirty::None, + NS_FRAME_HAS_DIRTY_CHILDREN); + + if (mMobileViewportManager) { + mMobileViewportManager->UpdateSizesBeforeReflow(); + } + return true; +} + +bool PresShell::CanHandleUserInputEvents(WidgetGUIEvent* aGUIEvent) { + if (XRE_IsParentProcess()) { + return true; + } + + if (aGUIEvent->mFlags.mIsSynthesizedForTests && + !StaticPrefs::dom_input_events_security_isUserInputHandlingDelayTest()) { + return true; + } + + if (!aGUIEvent->IsUserAction()) { + return true; + } + + if (nsPresContext* rootPresContext = mPresContext->GetRootPresContext()) { + return rootPresContext->UserInputEventsAllowed(); + } + + return true; +} + +void PresShell::AddResizeEventFlushObserverIfNeeded() { + if (!mIsDestroying && !mResizeEventPending && + MOZ_LIKELY(!mDocument->GetBFCacheEntry())) { + mResizeEventPending = true; + mPresContext->RefreshDriver()->AddResizeEventFlushObserver(this); + } +} + +bool PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight, + ResizeReflowOptions aOptions) { + MOZ_ASSERT(!mIsReflowing, "Shouldn't be in reflow here!"); + + // Historically we never fired resize events if there was no root frame by the + // time this function got called. + const bool initialized = mDidInitialize; + RefPtr kungFuDeathGrip(this); + + auto postResizeEventIfNeeded = [this, initialized]() { + if (initialized) { + AddResizeEventFlushObserverIfNeeded(); + } + }; + + if (!(aOptions & ResizeReflowOptions::BSizeLimit)) { + nsSize oldSize = mPresContext->GetVisibleArea().Size(); + if (oldSize == nsSize(aWidth, aHeight)) { + return false; + } + + bool changed = SimpleResizeReflow(aWidth, aHeight); + postResizeEventIfNeeded(); + return changed; + } + + // Make sure that style is flushed before setting the pres context + // VisibleArea. + // + // 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); + + nsIFrame* rootFrame = GetRootFrame(); + if (mIsDestroying || !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 false; + } + + mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight)); + // There isn't anything useful we can do if the initial reflow hasn't + // happened. + return true; + } + + WritingMode wm = rootFrame->GetWritingMode(); + MOZ_ASSERT((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE, + "unconstrained isize not allowed"); + + nscoord targetWidth = aWidth; + nscoord targetHeight = aHeight; + if (wm.IsVertical()) { + targetWidth = NS_UNCONSTRAINEDSIZE; + } else { + targetHeight = NS_UNCONSTRAINEDSIZE; + } + + mPresContext->SetVisibleArea(nsRect(0, 0, targetWidth, targetHeight)); + // XXX Do a full invalidate at the beginning so that invalidates along + // the way don't have region accumulation issues? + + // 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(mViewManager); + + mDirtyRoots.Remove(rootFrame); + DoReflow(rootFrame, true, nullptr); + + 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); + } + } + + // Now, we may have been destroyed by the destructor of + // `nsAutoCauseReflowNotifier`. + + mPendingDidDoReflow = true; + DidDoReflow(true); + + // the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE, + // and the isize shouldn't be NS_UNCONSTRAINEDSIZE anyway. + MOZ_DIAGNOSTIC_ASSERT( + mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE, + "width should not be NS_UNCONSTRAINEDSIZE after reflow"); + MOZ_DIAGNOSTIC_ASSERT( + mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE, + "height should not be NS_UNCONSTRAINEDSIZE after reflow"); + + postResizeEventIfNeeded(); + return true; +} + +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; + FireResizeEventSync(); +} + +void PresShell::FireResizeEventSync() { + if (mIsDocumentGone) { + return; + } + + // Send resize event from here. + WidgetEvent event(true, mozilla::eResize); + nsEventStatus status = nsEventStatus_eIgnore; + + if (RefPtr window = mDocument->GetWindow()) { + EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status); + } +} + +static nsIContent* GetNativeAnonymousSubtreeRoot(nsIContent* aContent) { + if (!aContent) { + return nullptr; + } + return aContent->GetClosestNativeAnonymousSubtreeRoot(); +} + +void PresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent) { + MOZ_ASSERT(aAnonContent->IsRootOfNativeAnonymousSubtree()); + mPresContext->EventStateManager()->NativeAnonymousContentRemoved( + aAnonContent); +#ifdef ACCESSIBILITY + if (nsAccessibilityService* accService = GetAccService()) { + accService->ContentRemoved(this, aAnonContent); + } +#endif + if (mDocument->DevToolsAnonymousAndShadowEventsEnabled()) { + aAnonContent->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ true); + } + 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->RemoveAllProperties(); + + if (aFrame == mCurrentEventFrame) { + mCurrentEventContent = aFrame->GetContent(); + mCurrentEventFrame = nullptr; + } + + 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.Remove(aFrame); + + nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame); + if (scrollableFrame) { + mPendingScrollAnchorSelection.Remove(scrollableFrame); + mPendingScrollAnchorAdjustment.Remove(scrollableFrame); + mPendingScrollResnap.Remove(scrollableFrame); + } + + mContentVisibilityAutoFrames.Remove(aFrame); + } +} + +already_AddRefed PresShell::GetCaret() const { + RefPtr caret = mCaret; + return caret.forget(); +} + +already_AddRefed +PresShell::GetAccessibleCaretEventHub() const { + RefPtr 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 aFlags) { + mSelectionFlags = aFlags; + return NS_OK; +} + +NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t* aFlags) { + if (!aFlags) { + return NS_ERROR_INVALID_ARG; + } + + *aFlags = mSelectionFlags; + return NS_OK; +} + +// implementation of nsISelectionController + +NS_IMETHODIMP +PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) { + RefPtr frameSelection = mSelection; + return frameSelection->PhysicalMove(aDirection, aAmount, aExtend); +} + +NS_IMETHODIMP +PresShell::CharacterMove(bool aForward, bool aExtend) { + RefPtr frameSelection = mSelection; + return frameSelection->CharacterMove(aForward, aExtend); +} + +NS_IMETHODIMP +PresShell::WordMove(bool aForward, bool aExtend) { + RefPtr 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::LineMove(bool aForward, bool aExtend) { + RefPtr 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 frameSelection = mSelection; + return frameSelection->IntraLineMove(aForward, aExtend); +} + +NS_IMETHODIMP +PresShell::PageMove(bool aForward, bool aExtend) { + nsIFrame* frame = nullptr; + if (!aExtend) { + frame = do_QueryFrame(GetScrollableFrameToScroll(VerticalScrollDirection)); + // If there is no scrollable frame, get the frame to move caret instead. + } + if (!frame || frame->PresContext() != mPresContext) { + frame = mSelection->GetFrameToPageSelect(); + if (!frame) { + return NS_OK; + } + } + // We may scroll parent scrollable element of current selection limiter. + // In such case, we don't want to scroll selection into view unless + // selection is changed. + RefPtr frameSelection = mSelection; + return frameSelection->PageMove( + aForward, aExtend, frame, nsFrameSelection::SelectionIntoView::IfChanged); +} + +NS_IMETHODIMP +PresShell::ScrollPage(bool aForward) { + nsIScrollableFrame* scrollFrame = + GetScrollableFrameToScroll(VerticalScrollDirection); + ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Pages); + if (scrollFrame) { + scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES, + scrollMode, nullptr, + mozilla::ScrollOrigin::NotSpecified, + nsIScrollableFrame::NOT_MOMENTUM, + ScrollSnapFlags::IntendedDirection | + ScrollSnapFlags::IntendedEndPosition); + } + return NS_OK; +} + +NS_IMETHODIMP +PresShell::ScrollLine(bool aForward) { + nsIScrollableFrame* scrollFrame = + GetScrollableFrameToScroll(VerticalScrollDirection); + ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines); + if (scrollFrame) { + nsRect scrollPort = scrollFrame->GetScrollPortRect(); + nsSize lineSize = scrollFrame->GetLineScrollAmount(); + int32_t lineCount = StaticPrefs::toolkit_scrollbox_verticalScrollDistance(); + if (lineCount * lineSize.height > scrollPort.Height()) { + return ScrollPage(aForward); + } + scrollFrame->ScrollBy( + nsIntPoint(0, aForward ? lineCount : -lineCount), ScrollUnit::LINES, + scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified, + nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection); + } + return NS_OK; +} + +NS_IMETHODIMP +PresShell::ScrollCharacter(bool aRight) { + nsIScrollableFrame* scrollFrame = + GetScrollableFrameToScroll(HorizontalScrollDirection); + ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines); + if (scrollFrame) { + int32_t h = StaticPrefs::toolkit_scrollbox_horizontalScrollDistance(); + scrollFrame->ScrollBy( + nsIntPoint(aRight ? h : -h, 0), ScrollUnit::LINES, scrollMode, nullptr, + mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM, + ScrollSnapFlags::IntendedDirection); + } + return NS_OK; +} + +NS_IMETHODIMP +PresShell::CompleteScroll(bool aForward) { + nsIScrollableFrame* scrollFrame = + GetScrollableFrameToScroll(VerticalScrollDirection); + ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Other); + if (scrollFrame) { + scrollFrame->ScrollBy( + nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE, scrollMode, + nullptr, mozilla::ScrollOrigin::NotSpecified, + nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedEndPosition); + } + return NS_OK; +} + +NS_IMETHODIMP +PresShell::CompleteMove(bool aForward, bool aExtend) { + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + RefPtr 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); + + const nsFrameSelection::FocusMode focusMode = + aExtend ? nsFrameSelection::FocusMode::kExtendSelection + : nsFrameSelection::FocusMode::kCollapseToNewPoint; + frameSelection->HandleClick( + MOZ_KnownLive(pos.mResultContent) /* bug 1636889 */, pos.mContentOffset, + pos.mContentOffset, focusMode, + 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); +} + +// end implementations nsISelectionController + +nsIFrame* PresShell::GetRootScrollFrame() const { + if (!mFrameConstructor) { + return nullptr; + } + 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 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; +} + +bool PresShell::IsLayoutFlushObserver() { + return GetPresContext()->RefreshDriver()->IsLayoutFlushObserver(this); +} + +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... + // XXX Since bug 372769 is now fixed, the assertion is being enabled in bug + // 1758104. +# if 0 + // 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->HasAnyStateBits(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 +} +#endif + +void PresShell::PostPendingScrollAnchorSelection( + mozilla::layout::ScrollAnchorContainer* aContainer) { + mPendingScrollAnchorSelection.Insert(aContainer->ScrollableFrame()); +} + +void PresShell::FlushPendingScrollAnchorSelections() { + for (nsIScrollableFrame* scroll : mPendingScrollAnchorSelection) { + scroll->Anchor()->SelectAnchor(); + } + mPendingScrollAnchorSelection.Clear(); +} + +void PresShell::PostPendingScrollAnchorAdjustment( + ScrollAnchorContainer* aContainer) { + mPendingScrollAnchorAdjustment.Insert(aContainer->ScrollableFrame()); +} + +void PresShell::FlushPendingScrollAnchorAdjustments() { + for (nsIScrollableFrame* scroll : mPendingScrollAnchorAdjustment) { + scroll->Anchor()->ApplyAdjustments(); + } + mPendingScrollAnchorAdjustment.Clear(); +} + +void PresShell::PostPendingScrollResnap(nsIScrollableFrame* aScrollableFrame) { + mPendingScrollResnap.Insert(aScrollableFrame); +} + +void PresShell::FlushPendingScrollResnap() { + for (nsIScrollableFrame* scrollableFrame : mPendingScrollResnap) { + scrollableFrame->TryResnap(); + } + mPendingScrollResnap.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::FrameAncestorsAndDescendants || + 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 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 = subtreeRoot->IsSubtreeDirty(); + 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; + } + + auto FrameIsReflowRoot = [](const nsIFrame* aFrame) { + return aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT | + NS_FRAME_DYNAMIC_REFLOW_ROOT); + }; + + auto CanStopClearingAncestorIntrinsics = [&](const nsIFrame* aFrame) { + return FrameIsReflowRoot(aFrame) && aFrame != subtreeRoot; + }; + + auto IsReflowBoundary = [&](const nsIFrame* aFrame) { + return FrameIsReflowRoot(aFrame) && + (aFrame != 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::None) { + // Mark argument and all ancestors dirty. (Unless we hit a reflow root + // that should contain the reflow. + for (nsIFrame* a = subtreeRoot; + a && !CanStopClearingAncestorIntrinsics(a); a = a->GetParent()) { + a->MarkIntrinsicISizesDirty(); + if (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; + } + } + } + + const bool frameAncestorAndDescendantISizesDirty = + (aIntrinsicDirty == IntrinsicDirty::FrameAncestorsAndDescendants); + const bool dirty = (aBitToAdd == NS_FRAME_IS_DIRTY); + if (frameAncestorAndDescendantISizesDirty || dirty) { + // 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 stack; + stack.AppendElement(subtreeRoot); + + do { + nsIFrame* f = stack.PopLastElement(); + + if (frameAncestorAndDescendantISizesDirty && f->IsPlaceholderFrame()) { + // Call `GetOutOfFlowFrame` directly because we can get here from + // frame destruction and the placeholder might be already torn down. + if (nsIFrame* oof = + static_cast(f)->GetOutOfFlowFrame()) { + if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) { + // We have another distinct subtree we need to mark. + subtrees.AppendElement(oof); + } + } + } + + for (const auto& childList : f->ChildLists()) { + for (nsIFrame* kid : childList.mList) { + if (frameAncestorAndDescendantISizesDirty) { + kid->MarkIntrinsicISizesDirty(); + } + if (dirty) { + kid->AddStateBits(NS_FRAME_IS_DIRTY); + } + 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 (IsReflowBoundary(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 = f->IsSubtreeDirty(); + f->ChildIsDirty(child); + NS_ASSERTION(f->HasAnyStateBits(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->HasAnyStateBits(NS_FRAME_IN_REFLOW), + "Frame passed in not in reflow?"); + + mFramesToDirty.Insert(aFrame); +} + +already_AddRefed PresShell::GetContentForScrolling() const { + if (nsCOMPtr focused = GetFocusedContentInOurWindow()) { + return focused.forget(); + } + return GetSelectedContentForScrolling(); +} + +already_AddRefed PresShell::GetSelectedContentForScrolling() const { + nsCOMPtr selectedContent; + if (mSelection) { + Selection* domSelection = mSelection->GetSelection(SelectionType::eNormal); + if (domSelection) { + selectedContent = + nsIContent::FromNodeOrNull(domSelection->GetFocusNode()); + } + } + return selectedContent.forget(); +} + +nsIScrollableFrame* PresShell::GetScrollableFrameToScrollForContent( + nsIContent* aContent, ScrollDirections aDirections) { + nsIScrollableFrame* scrollFrame = nullptr; + if (aContent) { + nsIFrame* startFrame = aContent->GetPrimaryFrame(); + if (startFrame) { + scrollFrame = startFrame->GetScrollTargetFrame(); + if (scrollFrame) { + startFrame = scrollFrame->GetScrolledFrame(); + } + scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection( + startFrame, aDirections); + } + } + if (!scrollFrame) { + scrollFrame = GetRootScrollFrameAsScrollable(); + if (!scrollFrame || !scrollFrame->GetScrolledFrame()) { + return nullptr; + } + scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection( + scrollFrame->GetScrolledFrame(), aDirections); + } + return scrollFrame; +} + +nsIScrollableFrame* PresShell::GetScrollableFrameToScroll( + ScrollDirections aDirections) { + nsCOMPtr content = GetContentForScrolling(); + return GetScrollableFrameToScrollForContent(content.get(), aDirections); +} + +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 and show fallback, let layout know + // that it needs to do work. + if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1 && + aOldSlot->HasChildren()) { + 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() && aNewSlot->HasChildren()) { + 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 AssertNoFramesOrStyleDataInDescendants(Element& aElement) { + for (nsINode* node : ShadowIncludingTreeIterator(aElement)) { + nsIContent* c = nsIContent::FromNode(node); + if (c == &aElement) { + continue; + } + // FIXME(emilio): The check is needed because of bug 135040. + MOZ_ASSERT(!c->GetPrimaryFrame() || c->IsHTMLElement(nsGkAtoms::area)); + MOZ_ASSERT(!c->IsElement() || !c->AsElement()->HasServoData()); + } +} +#endif + +void PresShell::DestroyFramesForAndRestyle(Element* aElement) { +#ifdef DEBUG + auto postCondition = MakeScopeExit([&]() { + MOZ_ASSERT(!aElement->GetPrimaryFrame()); + AssertNoFramesOrStyleDataInDescendants(*aElement); + }); +#endif + + MOZ_ASSERT(aElement); + if (!aElement->HasServoData()) { + // Nothing to do here, the element already is out of the flat tree or is not + // styled. + return; + } + + // Mark ourselves as not safe to flush while we're doing frame destruction. + nsAutoScriptBlocker scriptBlocker; + ++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::ShadowRootWillBeAttached(Element& aElement) { +#ifdef DEBUG + auto postCondition = MakeScopeExit( + [&]() { AssertNoFramesOrStyleDataInDescendants(aElement); }); +#endif + + if (!aElement.HasServoData()) { + // Nothing to do here, the element already is out of the flat tree or is not + // styled. + return; + } + + if (!aElement.HasChildren()) { + // The element has no children, just avoid the work. + return; + } + + // Mark ourselves as not safe to flush while we're doing frame destruction. + nsAutoScriptBlocker scriptBlocker; + ++mChangeNestCount; + + // NOTE(emilio): We use FlattenedChildIterator intentionally here (rather than + // StyleChildrenIterator), since we don't want to remove ::before / ::after + // content. + FlattenedChildIterator iter(&aElement); + nsCSSFrameConstructor* fc = FrameConstructor(); + for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) { + fc->DestroyFramesFor(c); + if (c->IsElement()) { + RestyleManager::ClearServoDataFromSubtree(c->AsElement()); + } + } + +#ifdef ACCESSIBILITY + if (nsAccessibilityService* accService = GetAccService()) { + accService->ScheduleAccessibilitySubtreeUpdate(this, &aElement); + } +#endif + + --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 + // checkpoint for these restyles to go through. + mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint, + nsChangeHint(0)); +} + +void PresShell::SetForwardingContainer(const WeakPtr& 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 toRemove; + for (WeakFrame* weakFrame : mWeakFrames) { + if (weakFrame->GetFrame() == aFrame) { + toRemove.AppendElement(weakFrame); + } + } + for (WeakFrame* weakFrame : toRemove) { + weakFrame->Clear(this); + } +} + +UniquePtr PresShell::CreateReferenceRenderingContext() { + if (mPresContext->IsScreen()) { + return gfxContext::CreateOrNull( + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get()); + } + + // 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. + nsDeviceContext* devCtx = mPresContext->DeviceContext(); + return devCtx->CreateReferenceRenderingContext(); +} + +// https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier +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 esm = mPresContext->EventStateManager(); + + // 1. If there is no indicated part of the document, set the Document's target + // element to null. + // + // FIXME(emilio): Per spec empty fragment string should take the same + // code-path as "top"! + if (aAnchorName.IsEmpty()) { + NS_ASSERTION(!aScroll, "can't scroll to empty anchor name"); + esm->SetContentState(nullptr, ElementState::URLTARGET); + return NS_OK; + } + + // 2. If the indicated part of the document is the top of the document, + // then: + // (handled below when `target` is null, and anchor is `top`) + + // 3.1. Let target be element that is the indicated part of the document. + // + // https://html.spec.whatwg.org/#target-element + // https://html.spec.whatwg.org/#find-a-potential-indicated-element + RefPtr target = + nsContentUtils::GetTargetElement(mDocument, aAnchorName); + + // 1. If there is no indicated part of the document, set the Document's + // target element to null. + // 2.1. Set the Document's target element to null. + // 3.2. Set the Document's target element to target. + esm->SetContentState(target, ElementState::URLTARGET); + + // TODO: Spec probably needs a section to account for this. + if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) { + if (rootScroll->DidHistoryRestore()) { + // Scroll position restored from history trumps scrolling to anchor. + aScroll = false; + rootScroll->ClearDidHistoryRestore(); + } + } + + if (target) { + if (aScroll) { + // 3.3. TODO: Run the ancestor details revealing algorithm on target. + // 3.4. Scroll target into view, with behavior set to "auto", block set to + // "start", and inline set to "nearest". + // FIXME(emilio): Not all callers pass ScrollSmoothAuto (but we use auto + // smooth scroll for `top` regardless below, so maybe they should!). + ScrollingInteractionContext scrollToAnchorContext(true); + MOZ_TRY(ScrollContentIntoView( + target, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always), + ScrollAxis(), + ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags)); + + if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) { + mLastAnchorScrolledTo = target; + mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y; + } + } + + { + // 3.6. Move the sequential focus navigation starting point to target. + // + // Move the caret to the anchor. That way tabbing will start from the new + // location. + // + // TODO(emilio): Do we want to do this even if aScroll is false? + // + // NOTE: Intentionally out of order for now with the focus steps, see + // https://github.com/whatwg/html/issues/7759 + RefPtr jumpToRange = nsRange::Create(mDocument); + nsCOMPtr nodeToSelect = target.get(); + while (nodeToSelect->GetFirstChild()) { + nodeToSelect = nodeToSelect->GetFirstChild(); + } + jumpToRange->SelectNodeContents(*nodeToSelect, IgnoreErrors()); + if (RefPtr sel = mSelection->GetSelection(SelectionType::eNormal)) { + sel->RemoveAllRanges(IgnoreErrors()); + sel->AddRangeAndSelectFramesAndNotifyListeners(*jumpToRange, + IgnoreErrors()); + if (!StaticPrefs::layout_selectanchor()) { + // Use a caret (collapsed selection) at the start of the anchor. + sel->CollapseToStart(IgnoreErrors()); + } + } + } + + // 3.5. Run the focusing steps for target, with the Document's viewport as + // the fallback target. + // + // Note that ScrollContentIntoView flushes, so we don't need to do that + // again here. We also don't need to scroll again either. + // + // We intentionally focus the target only when aScroll is true, we need to + // sort out if the spec needs to differentiate these cases. When aScroll is + // false we still clear the focus unconditionally, that's legacy behavior, + // maybe we shouldn't do it. + // + // TODO(emilio): Do we really want to clear the focus even if aScroll is + // false? + const bool shouldFocusTarget = [&] { + if (!aScroll) { + return false; + } + nsIFrame* targetFrame = target->GetPrimaryFrame(); + return targetFrame && targetFrame->IsFocusable(); + }(); + + if (shouldFocusTarget) { + FocusOptions options; + options.mPreventScroll = true; + target->Focus(options, CallerType::NonSystem, IgnoreErrors()); + } else if (RefPtr fm = nsFocusManager::GetFocusManager()) { + if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) { + // Now focus the document itself if focus is on an element within it. + nsCOMPtr focusedWindow; + fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); + if (SameCOMIdentity(win, focusedWindow)) { + fm->ClearFocus(focusedWindow); + } + } + } + + // If the target is an animation element, activate the animation + if (auto* animationElement = SVGAnimationElement::FromNode(target.get())) { + animationElement->ActivateByHyperlink(); + } + +#ifdef ACCESSIBILITY + if (nsAccessibilityService* accService = GetAccService()) { + accService->NotifyOfAnchorJumpTo(target); + } +#endif + } else if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, u"top"_ns)) { + // 2.2. Scroll to the beginning of the document for the Document. + 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) { + ScrollMode scrollMode = + sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant; + // Scroll to the top of the page + sf->ScrollTo(nsPoint(0, 0), scrollMode); + } + } else { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult PresShell::ScrollToAnchor() { + nsCOMPtr lastAnchor = std::move(mLastAnchorScrolledTo); + if (!lastAnchor) { + 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; + } + return ScrollContentIntoView( + lastAnchor, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always), + ScrollAxis(), ScrollFlags::AnchorScrollFlags); +} + +/* + * 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, + nsILineIterator*& 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) { + auto line = aLines->GetLine(index).unwrap(); + frameBounds += frame->GetOffsetTo(f); + frame = f; + if (line.mLineBounds.y < frameBounds.y) { + frameBounds.height = frameBounds.YMost() - line.mLineBounds.y; + frameBounds.y = line.mLineBounds.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 = aRect.UnionEdges(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 in a given axis. + switch (aWhenToScroll) { + case WhenToScroll::Always: + // The caller wants the frame as visible as possible + return true; + case WhenToScroll::IfNotVisible: + // Scroll only if no part of the frame is visible in this view. + return aRectMax - aLineSize <= aViewMin || + aRectMin + aLineSize >= aViewMax; + case WhenToScroll::IfNotFullyVisible: + // 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 (!aWhereToScroll.mPercentage) { + // 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 { + float percent = aWhereToScroll.mPercentage.value() / 100.0f; + nscoord frameAlignCoord = + NSToCoordRound(aRectMin + (aRectMax - aRectMin) * percent); + resultCoord = NSToCoordRound(frameAlignCoord - scrollPortLength * percent); + } + // Force the scroll range to extend to include resultCoord. + *aRangeMin = std::min(resultCoord, aRectMax - scrollPortLength); + *aRangeMax = std::max(resultCoord, aRectMin); + return resultCoord; +} + +static WhereToScroll GetApplicableWhereToScroll( + const nsIScrollableFrame* aFrameAsScrollable, + const nsIFrame* aScrollableFrame, const nsIFrame* aTarget, + ScrollDirection aScrollDirection, WhereToScroll aOriginal) { + MOZ_ASSERT(do_QueryFrame(aFrameAsScrollable) == aScrollableFrame); + if (aTarget == aScrollableFrame) { + return aOriginal; + } + + StyleScrollSnapAlignKeyword align = + aScrollDirection == ScrollDirection::eHorizontal + ? aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).first + : aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).second; + + switch (align) { + case StyleScrollSnapAlignKeyword::None: + return aOriginal; + case StyleScrollSnapAlignKeyword::Start: + return WhereToScroll::Start; + case StyleScrollSnapAlignKeyword::Center: + return WhereToScroll::Center; + case StyleScrollSnapAlignKeyword::End: + return WhereToScroll::End; + } + return aOriginal; +} + +/** + * 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. + */ +static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable, + const nsIFrame* aScrollableFrame, + const nsIFrame* aTarget, const nsRect& aRect, + const Sides aScrollPaddingSkipSides, + const nsMargin& aMargin, ScrollAxis aVertical, + ScrollAxis aHorizontal, ScrollFlags aScrollFlags) { + nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset(); + const nsPoint originalScrollPt = scrollPt; + const nsRect visibleRect(scrollPt, + aFrameAsScrollable->GetVisualViewportSize()); + + const nsMargin padding = [&] { + nsMargin p = aFrameAsScrollable->GetScrollPadding(); + p.ApplySkipSides(aScrollPaddingSkipSides); + return p + aMargin; + }(); + + const nsRect rectToScrollIntoView = [&] { + nsRect r(aRect); + r.Inflate(padding); + return r.Intersect(aFrameAsScrollable->GetScrolledRect()); + }(); + + 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)); + ScrollDirections directions = + aFrameAsScrollable->GetAvailableScrollingDirections(); + + if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) || + ss.mVertical != StyleOverflow::Hidden) && + (!aVertical.mOnlyIfPerceivedScrollableDirection || + (directions.contains(ScrollDirection::eVertical)))) { + if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y, + aRect.YMost(), visibleRect.y + padding.top, + visibleRect.YMost() - padding.bottom)) { + // If the scroll-snap-align on the frame is valid, we need to respect it. + WhereToScroll whereToScroll = GetApplicableWhereToScroll( + aFrameAsScrollable, aScrollableFrame, aTarget, + ScrollDirection::eVertical, aVertical.mWhereToScroll); + + nscoord maxHeight; + scrollPt.y = ComputeWhereToScroll( + whereToScroll, scrollPt.y, rectToScrollIntoView.y, + rectToScrollIntoView.YMost(), visibleRect.y, visibleRect.YMost(), + &allowedRange.y, &maxHeight); + allowedRange.height = maxHeight - allowedRange.y; + } + } + + if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) || + ss.mHorizontal != StyleOverflow::Hidden) && + (!aHorizontal.mOnlyIfPerceivedScrollableDirection || + (directions.contains(ScrollDirection::eHorizontal)))) { + if (ComputeNeedToScroll(aHorizontal.mWhenToScroll, lineSize.width, aRect.x, + aRect.XMost(), visibleRect.x + padding.left, + visibleRect.XMost() - padding.right)) { + // If the scroll-snap-align on the frame is valid, we need to respect it. + WhereToScroll whereToScroll = GetApplicableWhereToScroll( + aFrameAsScrollable, aScrollableFrame, aTarget, + ScrollDirection::eHorizontal, aHorizontal.mWhereToScroll); + + nscoord maxWidth; + scrollPt.x = ComputeWhereToScroll( + whereToScroll, scrollPt.x, rectToScrollIntoView.x, + rectToScrollIntoView.XMost(), visibleRect.x, visibleRect.XMost(), + &allowedRange.x, &maxWidth); + allowedRange.width = maxWidth - allowedRange.x; + } + } + + // If we don't need to scroll, then don't try since it might cancel + // a current smooth scroll operation. + if (scrollPt == originalScrollPt) { + return; + } + + ScrollMode scrollMode = ScrollMode::Instant; + // Default to an instant scroll, but if the scroll behavior given is "auto" + // or "smooth", use that as the specified behavior. If the user has disabled + // smooth scrolls, a given mode of "auto" or "smooth" should not result in + // a smooth scroll. + ScrollBehavior behavior = ScrollBehavior::Instant; + if (aScrollFlags & ScrollFlags::ScrollSmooth) { + behavior = ScrollBehavior::Smooth; + } else if (aScrollFlags & ScrollFlags::ScrollSmoothAuto) { + behavior = ScrollBehavior::Auto; + } + bool smoothScroll = aFrameAsScrollable->IsSmoothScroll(behavior); + if (smoothScroll) { + scrollMode = ScrollMode::SmoothMsd; + } + nsIFrame* frame = do_QueryFrame(aFrameAsScrollable); + AutoWeakFrame weakFrame(frame); + aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange, + ScrollSnapFlags::IntendedEndPosition, + aScrollFlags & ScrollFlags::TriggeredByScript + ? ScrollTriggeredByScript::Yes + : ScrollTriggeredByScript::No); + 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() && + frame->PresContext()->IsRootContentDocumentCrossProcess()) { + frame->PresShell()->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 composedDoc = aContent->GetComposedDoc(); + NS_ENSURE_STATE(composedDoc); + + NS_ASSERTION(mDidInitialize, "should have done initial reflow by now"); + + if (mContentToScrollTo) { + mContentToScrollTo->RemoveProperty(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))) { + mContentToScrollTo = nullptr; + } + + // If the target frame is an ancestor of a `content-visibility: auto` + // element ensure that it is laid out, so that the boundary rectangle is + // correct. + if (mContentToScrollTo) { + if (nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame()) { + if (frame->IsHiddenByContentVisibilityOnAnyAncestor( + nsIFrame::IncludeContentVisibility::Auto)) { + frame->PresShell()->EnsureReflowIfFrameHasHiddenContent(frame); + } + } + } + + // 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; +} + +static nsMargin GetScrollMargin(const nsIFrame* aFrame) { + MOZ_ASSERT(aFrame); + // If we're focusing something that can't be targeted by content, allow + // content to customize the margin. + // + // TODO: This is also a bit of an issue for delegated focus, see + // https://github.com/whatwg/html/issues/7033. + if (aFrame->GetContent() && aFrame->GetContent()->ChromeOnlyAccess()) { + if (const nsIContent* userContent = + aFrame->GetContent()->GetChromeOnlyAccessSubtreeRootParent()) { + if (const nsIFrame* frame = userContent->GetPrimaryFrame()) { + return frame->StyleMargin()->GetScrollMargin(); + } + } + } + return aFrame->StyleMargin()->GetScrollMargin(); +} + +void PresShell::DoScrollContentIntoView() { + NS_ASSERTION(mDidInitialize, "should have done initial reflow by now"); + + nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame(); + + if (!frame || frame->IsHiddenByContentVisibilityOnAnyAncestor( + nsIFrame::IncludeContentVisibility::Hidden)) { + mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); + mContentToScrollTo = nullptr; + return; + } + + if (frame->HasAnyStateBits(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; + } + + auto* data = static_cast( + mContentToScrollTo->GetProperty(nsGkAtoms::scrolling)); + if (MOZ_UNLIKELY(!data)) { + mContentToScrollTo = nullptr; + return; + } + + ScrollFrameIntoView(frame, Nothing(), data->mContentScrollVAxis, + data->mContentScrollHAxis, data->mContentToScrollToFlags); +} + +bool PresShell::ScrollFrameIntoView( + nsIFrame* aTargetFrame, const Maybe& aKnownRectRelativeToTarget, + ScrollAxis aVertical, ScrollAxis aHorizontal, ScrollFlags aScrollFlags) { + // The scroll margin only applies to the whole bounds of the element, so don't + // apply it if we get an arbitrary rect / point to scroll to. + const nsMargin scrollMargin = + aKnownRectRelativeToTarget ? nsMargin() : GetScrollMargin(aTargetFrame); + + Sides skipPaddingSides; + const auto MaybeSkipPaddingSides = [&](nsIFrame* aFrame) { + if (!aFrame->IsStickyPositioned()) { + return; + } + const nsPoint pos = aFrame->GetPosition(); + const nsPoint normalPos = aFrame->GetNormalPosition(); + if (pos == normalPos) { + return; // Frame is not stuck. + } + // If we're targetting a sticky element, make sure not to apply + // scroll-padding on the direction we're stuck. + const auto& offsets = aFrame->StylePosition()->mOffset; + for (auto side : AllPhysicalSides()) { + if (offsets.Get(side).IsAuto()) { + continue; + } + // See if this axis is stuck. + const bool yAxis = side == eSideTop || side == eSideBottom; + const bool stuck = yAxis ? pos.y != normalPos.y : pos.x != normalPos.x; + if (!stuck) { + continue; + } + skipPaddingSides |= SideToSideBit(side); + } + }; + + nsIFrame* container = aTargetFrame; + + // This function needs to work even if rect has a width or height of 0. + nsRect rect = [&] { + if (aKnownRectRelativeToTarget) { + return *aKnownRectRelativeToTarget; + } + MaybeSkipPaddingSides(aTargetFrame); + while (nsIFrame* parent = container->GetParent()) { + container = parent; + if (static_cast(do_QueryFrame(container))) { + // We really just need a non-fragmented frame so that we can accumulate + // the bounds of all our continuations relative to it. We shouldn't jump + // out of our nearest scrollable frame, and that's an ok reference + // frame, so try to use that, or the root frame if there's nothing to + // scroll in this document. + break; + } + MaybeSkipPaddingSides(container); + } + MOZ_DIAGNOSTIC_ASSERT(container); + + nsRect targetFrameBounds; + { + bool haveRect = false; + const bool useWholeLineHeightForInlines = + aVertical.mWhenToScroll != WhenToScroll::IfNotFullyVisible; + AutoAssertNoDomMutations + guard; // Ensure use of nsILineIterators is safe. + nsIFrame* prevBlock = nullptr; + // Reuse the same line iterator across calls to AccumulateFrameBounds. + // We set it every time we detect a new block (stored in prevBlock). + nsILineIterator* lines = nullptr; + // The last line we found a continuation on in |lines|. We assume that + // later continuations cannot come on earlier lines. + int32_t curLine = 0; + nsIFrame* frame = aTargetFrame; + do { + AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines, + targetFrameBounds, haveRect, prevBlock, lines, + curLine); + } while ((frame = frame->GetNextContinuation())); + } + + return targetFrameBounds; + }(); + + bool didScroll = false; + const nsIFrame* target = aTargetFrame; + // Walk up the frame hierarchy scrolling the rect into view and + // keeping rect relative to container + do { + if (nsIScrollableFrame* sf = do_QueryFrame(container)) { + 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(); + + { + AutoWeakFrame wf(container); + ScrollToShowRect(sf, container, target, targetRect, skipPaddingSides, + scrollMargin, 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; + } + + // This scroll container will be the next target element in the nearest + // ancestor scroll container. + target = container; + // We found a sticky scroll container, we shouldn't skip that side + // anymore. + skipPaddingSides = {}; + } + + MaybeSkipPaddingSides(container); + + nsIFrame* parent; + if (container->IsTransformed()) { + container->GetTransformMatrix(ViewportType::Layout, RelativeTo{nullptr}, + &parent); + rect = + nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent); + } else { + rect += container->GetPosition(); + parent = container->GetParent(); + } + if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) { + nsPoint extraOffset(0, 0); + int32_t APD = container->PresContext()->AppUnitsPerDevPixel(); + parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(container, + &extraOffset); + if (parent) { + int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel(); + rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD); + rect += extraOffset; + } else { + nsCOMPtr docShell = + container->PresContext()->GetDocShell(); + if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { + // Defer to the parent document if this is an out-of-process iframe. + Unused << browserChild->SendScrollRectIntoView( + rect, aVertical, aHorizontal, aScrollFlags, APD); + } + } + } + container = parent; + } while (container); + + return didScroll; +} + +void PresShell::ScheduleViewManagerFlush() { + if (MOZ_UNLIKELY(mIsDestroying)) { + return; + } + + nsPresContext* presContext = GetPresContext(); + if (presContext) { + presContext->RefreshDriver()->ScheduleViewManagerFlush(); + } + SetNeedLayoutFlush(); +} + +void PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent) { + AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "DispatchSynthMouseMove", + GRAPHICS, mPresContext->GetDocShell()); + nsEventStatus status = nsEventStatus_eIgnore; + nsView* targetView = nsView::GetViewFor(aEvent->mWidget); + if (!targetView) return; + RefPtr viewManager = targetView->GetViewManager(); + viewManager->DispatchEvent(aEvent, targetView, &status); +} + +void PresShell::ClearMouseCaptureOnView(nsView* aView) { + if (nsIContent* capturingContent = GetCapturingContent()) { + if (aView) { + // if a view was specified, ensure that the captured content is within + // this view. + nsIFrame* frame = capturingContent->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) { + ReleaseCapturingContent(); + // the view containing the captured content likely disappeared so + // disable capture for now. + AllowMouseCapture(false); + break; + } + + view = view->GetParent(); + } while (view); + // return if the view wasn't found + return; + } + } + } + + ReleaseCapturingContent(); + } + + // 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. + AllowMouseCapture(false); +} + +void PresShell::ClearMouseCapture() { + ReleaseCapturingContent(); + AllowMouseCapture(false); +} + +void PresShell::ClearMouseCapture(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame); + + nsIContent* capturingContent = GetCapturingContent(); + if (!capturingContent) { + return; + } + + nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame(); + const bool shouldClear = + !capturingFrame || + nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aFrame, capturingFrame); + if (shouldClear) { + ClearMouseCapture(); + } +} + +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 docShell(mPresContext->GetDocShell()); + if (!docShell) return NS_ERROR_FAILURE; + + nsCOMPtr 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(); + + PROFILER_MARKER_UNTYPED("UnsuppressAndInvalidate", GRAPHICS); + + mPaintingSuppressed = false; + if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) { + // let's assume that outline on a root frame is not supported + rootFrame->InvalidateFrame(); + } + + if (mPresContext->IsRootContentDocumentCrossProcess()) { + if (auto* bc = BrowserChild::GetFrom(mDocument->GetDocShell())) { + if (mDocument->IsInitialDocument()) { + bc->SendDidUnsuppressPaintingNormalPriority(); + } else { + bc->SendDidUnsuppressPainting(); + } + } + } + + // 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::CancelPaintSuppressionTimer() { + if (mPaintSuppressionTimer) { + mPaintSuppressionTimer->Cancel(); + mPaintSuppressionTimer = nullptr; + } +} + +void PresShell::UnsuppressPainting() { + CancelPaintSuppressionTimer(); + + 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) { + while (true) { + // Call all our callbacks, tell us if we need to flush again. + 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 && callback->ReflowFinished()) { + shouldFlush = true; + } + } + + if (!shouldFlush || mIsDestroying) { + return; + } + + // The flush might cause us to have more callbacks. + const auto flushType = + aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout; + 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?"); + } + + for (const auto& childList : aRoot.ChildLists()) { + for (const nsIFrame* child : childList.mList) { + AssertFrameSubtreeIsSane(*child); + } + } +} +#endif + +static inline void AssertFrameTreeIsSane(const PresShell& aPresShell) { +#ifdef DEBUG + if (const nsIFrame* root = aPresShell.GetRootFrame()) { + AssertFrameSubtreeIsSane(*root); + } +#endif +} + +static void TriggerPendingScrollTimelineAnimations(Document* aDocument) { + auto* tracker = aDocument->GetScrollTimelineAnimationTracker(); + if (!tracker || !tracker->HasPendingAnimations()) { + return; + } + tracker->TriggerPendingAnimations(); +} + +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 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; + + // If this is a layout flush, first update the relevancy of any content + // of elements with `content-visibility: auto` so that the values + // returned from script queries are up-to-date. + if (flushType >= mozilla::FlushType::Layout) { + UpdateRelevancyOfContentVisibilityAutoFrames(); + } + + MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?"); + + AUTO_PROFILER_MARKER_TEXT( + "DoFlushPendingNotifications", LAYOUT, + MarkerOptions(MarkerStack::Capture(), MarkerInnerWindowIdFromDocShell( + mPresContext->GetDocShell())), + nsDependentCString(kFlushTypeNames[flushType])); + AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE( + "PresShell::DoFlushPendingNotifications", LAYOUT, + kFlushTypeNames[flushType]); + +#ifdef ACCESSIBILITY +# ifdef DEBUG + if (nsAccessibilityService* accService = GetAccService()) { + NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(), + "Flush during accessible tree update!"); + } +# endif +#endif + + NS_ASSERTION(flushType >= FlushType::Style, "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 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 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(); + + // Ensure our preference sheets are up-to-date. + UpdatePreferenceStyles(); + + // Process pending restyles, since any flush of the presshell wants + // up-to-date style data. + if (MOZ_LIKELY(!mIsDestroying)) { + viewManager->FlushDelayedResize(); + 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(); + + mPresContext->FlushFontPaletteValues(); + + // Flush any requested SMIL samples. + if (mDocument->HasAnimationController()) { + mDocument->GetAnimationController()->FlushResampleRequests(); + } + } + + // The FlushResampleRequests() above flushed style changes. + if (MOZ_LIKELY(!mIsDestroying) && aFlush.mFlushAnimations && + mPresContext->EffectCompositor()) { + mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations(); + } + + // The FlushResampleRequests() above flushed style changes. + if (MOZ_LIKELY(!mIsDestroying)) { + nsAutoScriptBlocker scriptBlocker; + Maybe innerWindowID; + if (auto* window = mDocument->GetInnerWindow()) { + innerWindowID = Some(window->WindowID()); + } + AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause), + innerWindowID); + PerfStats::AutoMetricRecording autoRecording; + LAYOUT_TELEMETRY_RECORD_BASE(Restyle); + + mPresContext->RestyleManager()->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; + if (DoFlushLayout(/* aInterruptible = */ flushType < FlushType::Layout)) { + if (mContentToScrollTo) { + DoScrollContentIntoView(); + if (mContentToScrollTo) { + mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); + mContentToScrollTo = nullptr; + } + } + } + } + + FlushPendingScrollResnap(); + + if (MOZ_LIKELY(!mIsDestroying)) { + // Try to trigger pending scroll-driven animations after we flush + // style and layout (if any). If we try to trigger them after flushing + // style but the frame tree is not ready, we will check them again after + // we flush layout because the requirement to trigger scroll-driven + // animations is that the associated scroll containers are ready (i.e. the + // scroll-timeline is active), and this depends on the readiness of the + // scrollable frame and the primary frame of the scroll container. + TriggerPendingScrollTimelineAnimations(mDocument); + } + + 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(); + } + + // Update flush counters + if (didStyleFlush) { + mLayoutTelemetry.IncReqsPerFlush(FlushType::Style); + } + + if (didLayoutFlush) { + mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout); + } + + // Record telemetry for the number of requests per each flush type. + // + // Flushes happen as style or style+layout. This depends upon the `flushType` + // where flushType >= InterruptibleLayout means flush layout and flushType >= + // Style means flush style. We only report if didLayoutFlush or didStyleFlush + // is true because we care if a flush really did take place. (Flush is guarded + // by `isSafeToFlush == true`.) + if (flushType >= FlushType::InterruptibleLayout && didLayoutFlush) { + MOZ_ASSERT(didLayoutFlush == didStyleFlush); + mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Layout); + } else if (flushType >= FlushType::Style && didStyleFlush) { + MOZ_ASSERT(!didLayoutFlush); + mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Style); + } +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CharacterDataChanged( + nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); + MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged"); + MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document"); + + nsAutoCauseReflowNotifier crNotifier(this); + + mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo); + mFrameConstructor->CharacterDataChanged(aContent, aInfo); +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ElementStateChanged( + Document* aDocument, Element* aElement, ElementState aStateMask) { + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); + MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentStateChanged"); + MOZ_ASSERT(aDocument == mDocument, "Unexpected aDocument"); + + if (mDidInitialize) { + nsAutoCauseReflowNotifier crNotifier(this); + mPresContext->RestyleManager()->ElementStateChanged(aElement, aStateMask); + } +} + +void PresShell::DocumentStatesChanged(DocumentState aStateMask) { + MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged"); + MOZ_ASSERT(mDocument); + MOZ_ASSERT(!aStateMask.IsEmpty()); + + if (mDidInitialize) { + StyleSet()->InvalidateStyleForDocumentStateChanges(aStateMask); + } + + if (aStateMask.HasState(DocumentState::WINDOW_INACTIVE)) { + if (nsIFrame* root = mFrameConstructor->GetRootFrame()) { + root->SchedulePaint(); + } + } +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeWillChange( + Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) { + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); + 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); + } +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeChanged( + Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType, const nsAttrValue* aOldValue) { + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); + 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); + } +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentAppended( + nsIContent* aFirstNewContent) { + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); + 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); +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentInserted( + nsIContent* aChild) { + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); + 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, nsCSSFrameConstructor::InsertionKind::Async); +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentRemoved( + nsIContent* aChild, nsIContent* aPreviousSibling) { + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); + 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->IsRootOfNativeAnonymousSubtree())) { + oldNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling() + : container->GetFirstChild(); + } + + // After removing aChild from tree we should save information about live + // ancestor + if (mPointerEventTarget && + mPointerEventTarget->IsInclusiveDescendantOf(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() { + // TODO: Looks like that nsFrameConstructor::NotifyCounterStylesAreDirty() + // does not run script. If so, we don't need to block script with + // nsAutoCauseReflowNotifier here. Instead, there should be methods + // and stack only class which manages only mChangeNestCount for + // avoiding unnecessary `MOZ_CAN_RUN_SCRIPT` marking. + nsAutoCauseReflowNotifier reflowNotifier(this); + mFrameConstructor->NotifyCounterStylesAreDirty(); +} + +bool PresShell::FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const { + return mDirtyRoots.FrameIsAncestorOfAnyElement(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; + } + + if (Element* root = mDocument->GetRootElement()) { + PostRecreateFramesFor(root); + } + + mDocument->FlushPendingNotifications(FlushType::Frames); +} + +nsresult PresShell::RenderDocument(const nsRect& aRect, + RenderDocumentFlags aFlags, + nscolor aBackgroundColor, + gfxContext* aThebesContext) { + NS_ENSURE_TRUE(!(aFlags & RenderDocumentFlags::IsUntrusted), + NS_ERROR_NOT_IMPLEMENTED); + + nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext(); + if (rootPresContext) { + rootPresContext->FlushWillPaintObservers(); + if (mIsDestroying) return NS_OK; + } + + 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(sRGBColor::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::UseHighQualityScaling) { + flags |= PaintFrameFlags::UseHighQualityScaling; + } + 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) { + WindowRenderer* renderer = view->GetWidget()->GetWindowRenderer(); + // WebRenderLayerManagers in content processes + // don't support taking snapshots. + if (renderer && + (!renderer->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::ResetViewportScrolling) { + wouldFlushRetainedLayers = true; + flags |= PaintFrameFlags::ResetViewportScrolling; + } + 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; + + for (nsDisplayItem* i : aList->TakeItems()) { + if (i->GetType() == DisplayItemType::TYPE_CONTAINER) { + aList->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()) { + auto [frameStartOffset, frameEndOffset] = frame->GetOffsets(); + + int32_t hilightStart = + atStart ? std::max(static_cast(aRange->StartOffset()), + frameStartOffset) + : frameStartOffset; + int32_t hilightEnd = + atEnd ? std::min(static_cast(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 = + RangeUtils::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) { + aList->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); + } + } + + return surfaceRect; +} + +#ifdef DEBUG +# include + +static bool gDumpRangePaintList = false; +#endif + +UniquePtr PresShell::CreateRangePaintInfo( + nsRange* aRange, nsRect& aSurfaceRect, bool aForPrimarySelection) { + nsIFrame* ancestorFrame = nullptr; + 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::GetClosestCommonInclusiveAncestor( + 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->GetParentOrShadowHostNode(); + } + + // 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(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->InkOverflowRect()); + info->mBuilder.SetDirtyRect(frame->InkOverflowRect()); + frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList); + } + }; + if (startContainer->NodeType() == nsINode::TEXT_NODE) { + BuildDisplayListForNode(startContainer); + } + for (; !subtreeIter.IsDone(); subtreeIter.Next()) { + nsCOMPtr node = subtreeIter.GetCurrentNode(); + BuildDisplayListForNode(node); + } + if (endContainer != startContainer && + endContainer->NodeType() == nsINode::TEXT_NODE) { + BuildDisplayListForNode(endContainer); + } + + // If one of the ancestor presShells (including this one) has a resolution + // set, we may have some APZ zoom applied. That means we may want to rasterize + // the nodes at that zoom level. Populate `info` with the relevant information + // so that the caller can decide what to do. Also wrap the display list in + // appropriate nsDisplayAsyncZoom display items. This code handles the general + // case with nested async zooms (even though that never actually happens), + // because it fell out of the implementation for free. + // + // TODO: Do we need to do the same for ancestor transforms? + for (nsPresContext* ctx = GetPresContext(); ctx; + ctx = ctx->GetParentPresContext()) { + PresShell* shell = ctx->PresShell(); + float resolution = shell->GetResolution(); + + // If we are at the root document in the process, try to see if documents + // in enclosing processes have a resolution and include that as well. + if (!ctx->GetParentPresContext()) { + // xScale is an arbitrary choice. Outside of edge cases involving CSS + // transforms, xScale == yScale so it doesn't matter. + resolution *= ViewportUtils::TryInferEnclosingResolution(shell).xScale; + } + + if (resolution == 1.0) { + continue; + } + + info->mResolution *= resolution; + nsIFrame* rootScrollFrame = shell->GetRootScrollFrame(); + ViewID zoomedId = + nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent()); + + nsDisplayList wrapped(&info->mBuilder); + wrapped.AppendNewToTop(&info->mBuilder, rootScrollFrame, + &info->mList, nullptr, zoomedId); + info->mList.AppendToTop(&wrapped); + } + +#ifdef DEBUG + if (gDumpRangePaintList) { + fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n"); + nsIFrame::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"); + nsIFrame::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->GetBoundingClientRect().TopLeft(); + rangeRect.MoveBy(info->mRootOffset); + aSurfaceRect.UnionRect(aSurfaceRect, rangeRect); + + return info; +} + +already_AddRefed PresShell::PaintRangePaintInfo( + const nsTArray>& aItems, Selection* aSelection, + const Maybe& 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 + LayoutDeviceIntRect pixelArea = LayoutDeviceIntRect::FromAppUnitsToOutside( + aArea, pc->AppUnitsPerDevPixel()); + + // if the image should not be resized, scale must be 1 + float scale = 1.0; + + 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 + int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width); + int32_t 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 + int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1); + int32_t 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); + } + } + + // Pick a resolution scale factor that is the highest we need for any of + // the items. This means some items may get rendered at a higher-than-needed + // resolution but at least nothing will be avoidably blurry. + float resolutionScale = 1.0; + for (const UniquePtr& rangeInfo : aItems) { + resolutionScale = std::max(resolutionScale, rangeInfo->mResolution); + } + float unclampedResolution = resolutionScale; + // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and + // `resolutionScale` isn't bigger than `maxSize`. This prevents creating + // giant/unbounded images. + resolutionScale = + std::min(resolutionScale, maxSize.width / (scale * pixelArea.width)); + resolutionScale = + std::min(resolutionScale, maxSize.height / (scale * pixelArea.height)); + // The following assert should only get hit if pixelArea scaled by `scale` + // alone would already have been bigger than `maxSize`, which should never + // be the case. For release builds we handle gracefully by reverting + // resolutionScale to 1.0 to avoid unexpected consequences. + MOZ_ASSERT(resolutionScale >= 1.0); + resolutionScale = std::max(1.0f, resolutionScale); + + scale *= resolutionScale; + + // Now we need adjust the output screen position of the surface based on the + // scaling factor and any APZ zoom that may be in effect. The goal is here + // to set `aScreenRect`'s top-left corner (in screen-relative LD pixels) + // such that the scaling effect on the surface appears anchored at `aPoint` + // ("anchor" here is like "transform-origin"). When this code is e.g. used + // to generate a drag image for dragging operations, `aPoint` refers to the + // position of the mouse cursor (also in screen-relative LD pixels), and the + // user-visible effect of doing this is that the point at which the user + // clicked to start the drag remains under the mouse during the drag. + + // In order to do this we first compute the top-left corner of the + // pixelArea is screen-relative LD pixels. + LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual( + LayoutDevicePoint(pixelArea.TopLeft()), pc); + // And then adjust the output screen position based on that, which we can do + // since everything here is screen-relative LD pixels. Note that the scale + // factor we use here is the effective "transform" scale applied to the + // content we're painting, relative to the scale at which it would normally + // get painted at as part of page rendering (`unclampedResolution`). + float scaleRelativeToNormalContent = scale / unclampedResolution; + aScreenRect->x = + NSToIntFloor(aPoint.x - float(aPoint.x.value - visualPoint.x.value) * + scaleRelativeToNormalContent); + aScreenRect->y = + NSToIntFloor(aPoint.y - float(aPoint.y.value - visualPoint.y.value) * + scaleRelativeToNormalContent); + + pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale); + pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale); + if (!pixelArea.width || !pixelArea.height) { + return nullptr; + } + } else { + // move aScreenRect to the position of the surface in screen coordinates + LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual( + LayoutDevicePoint(pixelArea.TopLeft()), pc); + aScreenRect->MoveTo(RoundedToInt(visualPoint)); + } + aScreenRect->width = pixelArea.width; + aScreenRect->height = pixelArea.height; + + RefPtr dt = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8); + if (!dt || !dt->IsValid()) { + return nullptr; + } + + gfxContext ctx(dt); + + if (aRegion) { + RefPtr 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 = 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 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& 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); + rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &ctx, + nsDisplayList::PAINT_DEFAULT, Nothing()); + aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y); + } + + // restore the old selection display state + frameSelection->SetDisplaySelection(oldDisplaySelection); + + return dt->Snapshot(); +} + +already_AddRefed PresShell::RenderNode( + nsINode* aNode, const Maybe& 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> rangeItems; + + // nothing to draw if the node isn't in a document + if (!aNode->IsInComposedDoc()) { + return nullptr; + } + + RefPtr range = nsRange::Create(aNode); + IgnoredErrorResult rv; + range->SelectNode(*aNode, rv); + if (rv.Failed()) { + return nullptr; + } + + UniquePtr info = CreateRangePaintInfo(range, area, false); + if (info) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + rangeItems.AppendElement(std::move(info)); + } + + Maybe 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 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> 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 + const uint32_t rangeCount = aSelection->RangeCount(); + NS_ASSERTION(rangeCount > 0, "RenderSelection called with no selection"); + for (const uint32_t r : IntegerRange(rangeCount)) { + MOZ_ASSERT(aSelection->RangeCount() == rangeCount); + RefPtr range = aSelection->GetRangeAt(r); + + UniquePtr info = CreateRangePaintInfo(range, area, true); + if (info) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + rangeItems.AppendElement(std::move(info)); + } + } + + return PaintRangePaintInfo(rangeItems, aSelection, Nothing(), area, aPoint, + aScreenRect, aFlags); +} + +void AddDisplayItemToBottom(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, nsDisplayItem* aItem) { + if (!aItem) { + return; + } + + nsDisplayList list(aBuilder); + list.AppendToTop(aItem); + list.AppendToTop(aList); + aList->AppendToTop(&list); +} + +static bool AddCanvasBackgroundColor(const nsDisplayList* aList, + nsIFrame* aCanvasFrame, nscolor aColor, + bool aCSSBackgroundColor) { + for (nsDisplayItem* i : *aList) { + const DisplayItemType type = i->GetType(); + + if (i->Frame() == aCanvasFrame && + type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR) { + auto* bg = static_cast(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) && + !aFrame->IsViewportFrame() && !aFrame->IsPageContentFrame()) { + 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. + bool addedScrollingBackgroundColor = false; + if (!aFrame->GetParent()) { + nsIScrollableFrame* sf = + aFrame->PresShell()->GetRootScrollFrameAsScrollable(); + if (sf) { + nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); + if (canvasFrame && canvasFrame->IsVisibleForPainting()) { + // TODO: We should be able to set canvas background color during display + // list building to avoid calling this function. + 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 and the dynamic toolbar + // movement. + // We can only do that if the color is opaque. + bool forceUnscrolledItem = + nsLayoutUtils::UsesAsyncScrolling(aFrame) && NS_GET_A(bgcolor) == 255; + + if (!addedScrollingBackgroundColor || forceUnscrolledItem) { + const bool isRootContentDocumentCrossProcess = + mPresContext->IsRootContentDocumentCrossProcess(); + MOZ_ASSERT_IF( + !aFrame->GetParent() && isRootContentDocumentCrossProcess && + mPresContext->HasDynamicToolbar(), + aBounds.Size() == + nsLayoutUtils::ExpandHeightForDynamicToolbar( + mPresContext, aFrame->InkOverflowRectRelativeToSelf().Size())); + + nsDisplaySolidColor* item = MakeDisplayItem( + aBuilder, aFrame, aBounds, bgcolor); + if (addedScrollingBackgroundColor && isRootContentDocumentCrossProcess) { + item->SetIsCheckerboardBackground(); + } + AddDisplayItemToBottom(aBuilder, aList, item); + } +} + +bool PresShell::IsTransparentContainerElement() const { + nsPresContext* pc = GetPresContext(); + if (!pc->IsRootContentDocumentCrossProcess()) { + if (pc->IsChrome()) { + return true; + } + // Frames are transparent except if their used embedder color-scheme is + // mismatched, in which case we use an opaque background to avoid + // black-on-black or white-on-white text, see + // https://github.com/w3c/csswg-drafts/issues/4772 + if (BrowsingContext* bc = pc->Document()->GetBrowsingContext()) { + switch (bc->GetEmbedderColorSchemes().mUsed) { + case dom::PrefersColorSchemeOverride::Light: + return pc->DefaultBackgroundColorScheme() == ColorScheme::Light; + case dom::PrefersColorSchemeOverride::Dark: + return pc->DefaultBackgroundColorScheme() == ColorScheme::Dark; + case dom::PrefersColorSchemeOverride::None: + case dom::PrefersColorSchemeOverride::EndGuard_: + break; + } + } + return true; + } + + nsIDocShell* docShell = pc->GetDocShell(); + if (!docShell) { + return false; + } + nsPIDOMWindowOuter* pwin = docShell->GetWindow(); + if (!pwin) { + return false; + } + if (Element* containerElement = pwin->GetFrameElementInternal()) { + return containerElement->HasAttr(nsGkAtoms::transparent); + } + if (BrowserChild* tab = BrowserChild::GetFrom(docShell)) { + // Check if presShell is the top PresShell. Only the top can influence the + // canvas background color. + return this == tab->GetTopLevelPresShell() && tab->IsTransparent(); + } + return false; +} + +nscolor PresShell::GetDefaultBackgroundColorToDraw() const { + if (!mPresContext) { + return NS_RGB(255, 255, 255); + } + return mPresContext->DefaultBackgroundColor(); +} + +void PresShell::UpdateCanvasBackground() { + auto canvasBg = ComputeCanvasBackground(); + mCanvasBackgroundColor = canvasBg.mColor; + mHasCSSBackgroundColor = canvasBg.mCSSSpecified; +} + +PresShell::CanvasBackground PresShell::ComputeCanvasBackground() const { + // 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* canvas = GetCanvasFrame(); + if (!canvas) { + // If the root element of the document (ie html) has style 'display: none' + // then the document's background color does not get drawn; return the color + // we actually draw. + return {GetDefaultBackgroundColorToDraw(), false}; + } + + const nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(canvas); + nscolor color = NS_RGBA(0, 0, 0, 0); + bool drawBackgroundImage = false; + bool drawBackgroundColor = false; + const nsStyleDisplay* disp = bgFrame->StyleDisplay(); + StyleAppearance appearance = disp->EffectiveAppearance(); + if (bgFrame->IsThemed(disp) && + appearance != StyleAppearance::MozWinBorderlessGlass) { + // Ignore the CSS background-color if -moz-appearance is used and it is + // not one of the glass values. (Windows 7 Glass has traditionally not + // overridden background colors, so we preserve that behavior for now.) + } else { + color = nsCSSRendering::DetermineBackgroundColor( + mPresContext, bgFrame->Style(), canvas, drawBackgroundImage, + drawBackgroundColor); + } + if (!IsTransparentContainerElement()) { + color = NS_ComposeColors(GetDefaultBackgroundColorToDraw(), color); + } + return {color, drawBackgroundColor}; +} + +nscolor PresShell::ComputeBackstopColor(nsView* aDisplayRoot) { + nsIWidget* widget = aDisplayRoot->GetWidget(); + if (widget && + (widget->GetTransparencyMode() != widget::TransparencyMode::Opaque || + 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; +}; + +WindowRenderer* PresShell::GetWindowRenderer() { + NS_ASSERTION(mViewManager, "Should have view manager"); + + nsView* rootView = mViewManager->GetRootView(); + if (rootView) { + if (nsIWidget* widget = rootView->GetWidget()) { + return widget->GetWindowRenderer(); + } + } + 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(); +} + +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; + } + + // GetResolution handles mResolution being nothing by returning 1 so this + // is checking that the resolution is actually changing. + bool resolutionUpdated = (aResolution != GetResolution()); + + mLastResolutionChangeOrigin = aOrigin; + + RenderingState state(this); + state.mResolution = Some(aResolution); + SetRenderingState(state); + if (mMobileViewportManager) { + mMobileViewportManager->ResolutionUpdated(aOrigin); + } + // Changing the resolution changes the visual viewport size which may + // make the current visual viewport offset out-of-bounds (if the size + // increased). APZ will reconcile this by sending a clamped visual + // viewport offset on the next repaint, but to avoid main-thread code + // observing an out-of-bounds offset until then, reclamp it here. + if (IsVisualViewportOffsetSet()) { + SetVisualViewportOffset(GetVisualViewportOffset(), + GetLayoutViewportOffset()); + } + if (aOrigin == ResolutionChangeOrigin::Apz) { + mResolutionUpdatedByApz = true; + } else if (resolutionUpdated) { + mResolutionUpdated = true; + } + + if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) { + window->VisualViewport()->PostResizeEvent(); + } + + return NS_OK; +} + +float PresShell::GetCumulativeResolution() const { + float resolution = GetResolution(); + nsPresContext* parentCtx = GetPresContext()->GetParentPresContext(); + if (parentCtx) { + resolution *= parentCtx->PresShell()->GetCumulativeResolution(); + } + return resolution; +} + +void PresShell::SetRestoreResolution(float aResolution, + LayoutDeviceIntSize aDisplaySize) { + if (mMobileViewportManager) { + mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize); + } +} + +void PresShell::SetRenderingState(const RenderingState& aState) { + if (GetResolution() != aState.mResolution.valueOr(1.f)) { + if (nsIFrame* frame = GetRootFrame()) { + frame->SchedulePaint(); + } + } + + mRenderingStateFlags = aState.mRenderingStateFlags; + mResolution = aState.mResolution; +#ifdef ACCESSIBILITY + if (nsAccessibilityService* accService = GetAccService()) { + accService->NotifyOfResolutionChange(this, GetResolution()); + } +#endif +} + +void PresShell::SynthesizeMouseMove(bool aFromScroll) { + if (!StaticPrefs::layout_reflow_synthMouseMove()) 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 ev = + new nsSynthMouseMoveEvent(this, aFromScroll); + + GetPresContext()->RefreshDriver()->AddRefreshObserver( + ev, FlushType::Display, "Synthetic mouse move event"); + mSynthMouseMoveEvent = std::move(ev); + } +} + +static nsView* FindFloatingViewContaining(nsPresContext* aRootPresContext, + nsIWidget* aRootWidget, + const LayoutDeviceIntPoint& aPt) { + nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint( + aRootPresContext, aRootWidget, aPt, + nsLayoutUtils::GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets); + return popupFrame ? popupFrame->GetView() : nullptr; +} + +/* + * This finds the first view with a frame that contains the given point in a + * postorder traversal of the view tree, 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. + * + * aPt is relative aRelativeToView with the viewport type + * aRelativeToViewportType. aRelativeToView will always have a frame. If aView + * has a frame then aRelativeToView will be aView. (The reason aRelativeToView + * and aView are separate is because we need to traverse into views without + * frames (ie the inner view of a subdocument frame) but we can only easily + * transform between views using TransformPoint which takes frames.) + */ +static nsView* FindViewContaining(nsView* aRelativeToView, + ViewportType aRelativeToViewportType, + nsView* aView, nsPoint aPt) { + MOZ_ASSERT(aRelativeToView->GetFrame()); + + if (aView->GetVisibility() == ViewVisibility::Hide) { + return nullptr; + } + + nsIFrame* frame = aView->GetFrame(); + if (frame) { + if (!frame->PresShell()->IsActive() || + !frame->IsVisibleConsideringAncestors( + nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) { + return nullptr; + } + + // We start out in visual coords and then if we cross the zoom boundary we + // become in layout coords. The zoom boundary always occurs in a document + // with IsRootContentDocumentCrossProcess. The root view of such a document + // is outside the zoom boundary and any child view must be inside the zoom + // boundary because we only create views for certain kinds of frames and + // none of them can be between the root frame and the zoom boundary. + bool crossingZoomBoundary = false; + if (aRelativeToViewportType == ViewportType::Visual) { + if (!aRelativeToView->GetParent() || + aRelativeToView->GetViewManager() != + aRelativeToView->GetParent()->GetViewManager()) { + if (aRelativeToView->GetFrame() + ->PresContext() + ->IsRootContentDocumentCrossProcess()) { + crossingZoomBoundary = true; + } + } + } + + ViewportType nextRelativeToViewportType = aRelativeToViewportType; + if (crossingZoomBoundary) { + nextRelativeToViewportType = ViewportType::Layout; + } + + nsLayoutUtils::TransformResult result = nsLayoutUtils::TransformPoint( + RelativeTo{aRelativeToView->GetFrame(), aRelativeToViewportType}, + RelativeTo{frame, nextRelativeToViewportType}, aPt); + if (result != nsLayoutUtils::TRANSFORM_SUCCEEDED) { + return nullptr; + } + + // Even though aPt is in visual coordinates until we cross the zoom boundary + // it is valid to compare it to view coords (which are in layout coords) + // because visual coords are the same as layout coords for every view + // outside of the zoom boundary except for the root view of the root content + // document. + // For the root view of the root content document, its bounds don't + // actually correspond to what is visible when we have a + // MobileViewportManager. So we skip the hit test. This is okay because the + // point has already been hit test: 1) if we are the root view in the + // process then the point comes from a real mouse event so it must have been + // over our widget, or 2) if we are the root of a subdocument then + // hittesting against the view of the subdocument frame that contains us + // already happened and succeeded before getting here. + if (!crossingZoomBoundary) { + if (!aView->GetDimensions().Contains(aPt)) { + return nullptr; + } + } + + aRelativeToView = aView; + aRelativeToViewportType = nextRelativeToViewportType; + } + + for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) { + nsView* r = + FindViewContaining(aRelativeToView, aRelativeToViewportType, v, aPt); + if (r) return r; + } + + return frame ? aView : nullptr; +} + +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 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 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; + + if (rootView->GetFrame()) { + view = FindFloatingViewContaining( + mPresContext, rootView->GetWidget(), + LayoutDeviceIntPoint::FromAppUnitsToNearest( + mMouseLocation + rootView->ViewToWidgetOffset(), APD)); + } + + nsView* pointView = view; + if (!view) { + view = rootView; + if (rootView->GetFrame()) { + pointView = FindViewContaining(rootView, ViewportType::Visual, rootView, + mMouseLocation); + } else { + pointView = rootView; + } + // 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; + DebugOnly result = + nsLayoutUtils::TransformPoint( + RelativeTo{rootView->GetFrame(), ViewportType::Visual}, + RelativeTo{frame, ViewportType::Layout}, refpoint); + MOZ_ASSERT(result == nsLayoutUtils::TRANSFORM_SUCCEEDED); + 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); + // 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 = 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) { + 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& aNonvisibleAction + /* = Nothing() */) { + for (nsIFrame* frame : aFrames) { + // 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 = + std::move(mApproximatelyVisibleFrames); + + 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& aNonvisibleAction + /* = Nothing() */) { + DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction); + mApproximatelyVisibleFrames.Clear(); +} + +void PresShell::MarkFramesInSubtreeApproximatelyVisible( + nsIFrame* aFrame, const nsRect& aRect, bool aRemoveOnly /* = false */) { + MOZ_DIAGNOSTIC_ASSERT(aFrame, "aFrame arg should be a valid frame pointer"); + 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 (DisplayPortUtils::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 + // nsHTMLScrollFrame::DecideScrollableLayer does, so we just ignore the + // displayport in that case. + nsPresContext* pc = aFrame->PresContext(); + if (scrollFrame->IsRootScrollFrameOfDocument() && + (pc->IsRootContentDocumentCrossProcess() || + (pc->IsChrome() && !pc->GetParentPresContext()))) { + nsRect baseRect( + nsPoint(), nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame)); + DisplayPortUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect); + } else { + ignoreDisplayPort = true; + } + } + + nsRect displayPort; + bool usingDisplayport = + !ignoreDisplayPort && + DisplayPortUtils::GetDisplayPortForVisibilityTesting( + aFrame->GetContent(), &displayPort); + + scrollFrame->NotifyApproximateFrameVisibilityUpdate(!usingDisplayport); + + if (usingDisplayport) { + rect = displayPort; + } else { + rect = rect.Intersect(scrollFrame->GetScrollPortRect()); + } + rect = scrollFrame->ExpandRectToNearlyVisible(rect); + } + + bool preserves3DChildren = aFrame->Extend3DContext(); + + for (const auto& [list, listID] : aFrame->ChildLists()) { + if (listID == FrameChildListID::Popup) { + // We assume all frames in popups are visible, so we skip them here. + continue; + } + + for (nsIFrame* child : list) { + // Note: This assert should be trivially satisfied, just by virtue of how + // nsFrameList and its iterator works (with nullptr being an end-of-list + // sentinel which should terminate the loop). But we do somehow get + // crash reports inside this loop that suggest `child` is null... + MOZ_DIAGNOSTIC_ASSERT(child, "shouldn't have null values in child lists"); + nsRect r = rect - child->GetPosition(); + if (!r.IntersectRect(r, child->InkOverflowRect())) { + 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->InkOverflowRectRelativeToSelf(); + nsRect out; + if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) { + r = out; + } else { + r.SetEmpty(); + } + } + } + MarkFramesInSubtreeApproximatelyVisible(child, r, aRemoveOnly); + } + } +} + +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 = + std::move(mApproximatelyVisibleFrames); + + nsRect vis(nsPoint(0, 0), rootFrame->GetSize()); + if (aRect) { + vis = *aRect; + } + + // If we are in-process root but not the top level content, we need to take + // the intersection with the iframe visible rect. + if (mPresContext->IsRootContentDocumentInProcess() && + !mPresContext->IsRootContentDocumentCrossProcess()) { + // There are two possibilities that we can't get the iframe's visible + // rect other than the iframe is out side of ancestors' display ports. + // a) the BrowserChild is being torn down + // b) the visible rect hasn't been delivered the BrowserChild + // In both cases we consider the visible rect is empty. + Maybe visibleRect; + if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { + visibleRect = browserChild->GetVisibleRect(); + } + vis = vis.Intersect(visibleRect.valueOr(nsRect())); + } + + MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, aRemoveOnly); + + DecApproximateVisibleCount(oldApproximatelyVisibleFrames); +} + +void PresShell::UpdateApproximateFrameVisibility() { + DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false); +} + +void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) { + MOZ_ASSERT( + !mPresContext || mPresContext->IsRootContentDocumentInProcess(), + "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() { + if (!StaticPrefs::layout_framevisibility_enabled() || !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 IsRootContentDocumentInProcess() if we're + // currently being destroyed, so we have to check that first. + if (!mHaveShutDown && !mIsDestroying && + !mPresContext->IsRootContentDocumentInProcess()) { + nsPresContext* presContext = + mPresContext->GetInProcessRootContentDocumentPresContext(); + 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->IsRootContentDocumentInProcess()) { + nsPresContext* presContext = + mPresContext->GetInProcessRootContentDocumentPresContext(); + if (!presContext) return; + MOZ_ASSERT(presContext->IsRootContentDocumentInProcess(), + "Didn't get a root prescontext from " + "GetInProcessRootContentDocumentPresContext?"); + presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow(); + return; + } + + if (mHaveShutDown || mIsDestroying) { + return; + } + + if (mUpdateApproximateFrameVisibilityEvent.IsPending()) { + return; + } + + RefPtr> 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 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 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(); + } +} + +void PresShell::PaintAndRequestComposite(nsView* aView, PaintFlags aFlags) { + if (!mIsActive) { + return; + } + + WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer(); + NS_ASSERTION(renderer, "Must be in paint event"); + if (renderer->AsFallback()) { + // The fallback renderer doesn't do any retaining, so we + // just need to notify the view and widget that we're invalid, and + // we'll do a paint+composite from the PaintWindow callback. + GetViewManager()->InvalidateView(aView); + return; + } + + // Otherwise we're a retained WebRenderLayerManager, so we want to call + // Paint to update with any changes and push those to WR. + PaintInternalFlags flags = PaintInternalFlags::None; + if (aFlags & PaintFlags::PaintSyncDecodeImages) { + flags |= PaintInternalFlags::PaintSyncDecodeImages; + } + PaintInternal(aView, flags); +} + +void PresShell::SyncPaintFallback(nsView* aView) { + if (!mIsActive) { + return; + } + + WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer(); + NS_ASSERTION(renderer->AsFallback(), + "Can't do Sync paint for remote renderers"); + if (!renderer->AsFallback()) { + return; + } + + PaintInternal(aView, PaintInternalFlags::PaintComposite); + GetPresContext()->NotifyDidPaintForSubtree(); +} + +void PresShell::PaintInternal(nsView* aViewToPaint, PaintInternalFlags aFlags) { + nsCString url; + nsIURI* uri = mDocument->GetDocumentURI(); + Document* contentRoot = GetPrimaryContentDocument(); + if (contentRoot) { + uri = contentRoot->GetDocumentURI(); + } + url = uri ? uri->GetSpecOrDefault() : "N/A"_ns; + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( + "Paint", GRAPHICS, Substring(url, std::min(size_t(128), url.Length()))); + + Maybe 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 & PaintInternalFlags::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::apz_keyboard_enabled_AtStartup()) { + // 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(); + + WindowRenderer* renderer = aViewToPaint->GetWidget()->GetWindowRenderer(); + NS_ASSERTION(renderer, "Must be in paint event"); + WebRenderLayerManager* layerManager = renderer->AsWebRender(); + + // 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) { + MOZ_LOG(gLog, LogLevel::Debug, + ("PresShell::Paint, first paint, this=%p", this)); + + if (layerManager) { + layerManager->SetIsFirstPaint(); + } + mIsFirstPaint = false; + } + + if (!renderer->BeginTransaction(url)) { + return; + } + + // Send an updated focus target with this transaction. Be sure to do this + // before we paint in the case this is an empty transaction. + if (layerManager) { + layerManager->SetFocusTarget(mAPZFocusTarget); + } + + if (frame) { + if (!(aFlags & PaintInternalFlags::PaintSyncDecodeImages) && + !frame->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) { + if (layerManager) { + layerManager->SetTransactionIdAllocator(presContext->RefreshDriver()); + } + + if (renderer->EndEmptyTransaction( + (aFlags & PaintInternalFlags::PaintComposite) + ? WindowRenderer::END_DEFAULT + : WindowRenderer::END_NO_COMPOSITE)) { + return; + } + } + frame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE); + } + + nscolor bgcolor = ComputeBackstopColor(aViewToPaint); + PaintFrameFlags flags = + PaintFrameFlags::WidgetLayers | PaintFrameFlags::ExistingTransaction; + + // We force sync-decode for printing / print-preview (printing already does + // this from nsPageSequenceFrame::PrintNextSheet). + // We also force sync-decoding via pref for reftests. + if (aFlags & PaintInternalFlags::PaintSyncDecodeImages || + mDocument->IsStaticDocument() || + StaticPrefs::image_decode_sync_enabled()) { + flags |= PaintFrameFlags::SyncDecodeImages; + } + if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + flags |= PaintFrameFlags::ForWebRender; + } + + if (frame) { + // We can paint directly into the widget using its layer manager. + nsLayoutUtils::PaintFrame(nullptr, frame, nsRegion(), bgcolor, + nsDisplayListBuilderMode::Painting, flags); + return; + } + + bgcolor = NS_ComposeColors(bgcolor, mCanvasBackgroundColor); + + if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + presContext->GetVisibleArea(), presContext->AppUnitsPerDevPixel()); + WebRenderBackgroundData data(wr::ToLayoutRect(bounds), + wr::ToColorF(ToDeviceColor(bgcolor))); + WrFiltersHolder wrFilters; + + layerManager->SetTransactionIdAllocator(presContext->RefreshDriver()); + layerManager->EndTransactionWithoutLayer(nullptr, nullptr, + std::move(wrFilters), &data, 0); + return; + } + + FallbackRenderer* fallback = renderer->AsFallback(); + MOZ_ASSERT(fallback); + + if (aFlags & PaintInternalFlags::PaintComposite) { + nsIntRect bounds = presContext->GetVisibleArea().ToOutsidePixels( + presContext->AppUnitsPerDevPixel()); + fallback->EndTransactionWithColor(bounds, ToDeviceColor(bgcolor)); + } +} + +// static +void PresShell::SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags, + WidgetEvent* aEvent) { + // If capture was set for pointer lock, don't unlock unless we are coming + // out of pointer lock explicitly. + if (!aContent && sCapturingContentInfo.mPointerLock && + !(aFlags & CaptureFlags::PointerLock)) { + return; + } + + sCapturingContentInfo.mContent = nullptr; + sCapturingContentInfo.mRemoteTarget = nullptr; + + // only set capturing content if allowed or the + // CaptureFlags::IgnoreAllowedState or CaptureFlags::PointerLock are used. + if ((aFlags & CaptureFlags::IgnoreAllowedState) || + sCapturingContentInfo.mAllowed || (aFlags & CaptureFlags::PointerLock)) { + if (aContent) { + sCapturingContentInfo.mContent = aContent; + } + if (aEvent) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(aEvent->mMessage == eMouseDown); + MOZ_ASSERT(aEvent->HasBeenPostedToRemoteProcess()); + sCapturingContentInfo.mRemoteTarget = + BrowserParent::GetLastMouseRemoteTarget(); + MOZ_ASSERT(sCapturingContentInfo.mRemoteTarget); + } + // CaptureFlags::PointerLock is the same as + // CaptureFlags::RetargetToElement & CaptureFlags::IgnoreAllowedState. + sCapturingContentInfo.mRetargetToElement = + !!(aFlags & CaptureFlags::RetargetToElement) || + !!(aFlags & CaptureFlags::PointerLock); + sCapturingContentInfo.mPreventDrag = + !!(aFlags & CaptureFlags::PreventDragStart); + sCapturingContentInfo.mPointerLock = !!(aFlags & CaptureFlags::PointerLock); + } +} + +nsIContent* PresShell::GetCurrentEventContent() { + if (mCurrentEventContent && + mCurrentEventContent->GetComposedDoc() != mDocument) { + mCurrentEventContent = nullptr; + mCurrentEventFrame = nullptr; + } + return mCurrentEventContent; +} + +nsIFrame* PresShell::GetCurrentEventFrame() { + if (MOZ_UNLIKELY(mIsDestroying)) { + return nullptr; + } + + // GetCurrentEventContent() makes sure the content is still in the + // same document that this pres shell belongs to. If not, then the + // frame shouldn't get an event, nor should we even assume its safe + // to try and find the frame. + nsIContent* content = GetCurrentEventContent(); + if (!mCurrentEventFrame && content) { + mCurrentEventFrame = content->GetPrimaryFrame(); + MOZ_ASSERT(!mCurrentEventFrame || + mCurrentEventFrame->PresContext()->GetPresShell() == this); + } + return mCurrentEventFrame; +} + +already_AddRefed PresShell::GetEventTargetContent( + WidgetEvent* aEvent) { + nsCOMPtr content = GetCurrentEventContent(); + if (!content) { + nsIFrame* currentEventFrame = GetCurrentEventFrame(); + if (currentEventFrame) { + currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content)); + NS_ASSERTION(!content || content->GetComposedDoc() == mDocument, + "handing out content from a different doc"); + } + } + return content.forget(); +} + +void PresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent) { + if (mCurrentEventFrame || mCurrentEventContent) { + mCurrentEventFrameStack.InsertElementAt(0, mCurrentEventFrame); + mCurrentEventContentStack.InsertObjectAt(mCurrentEventContent, 0); + } + mCurrentEventFrame = aFrame; + mCurrentEventContent = aContent; +} + +void PresShell::PopCurrentEventInfo() { + mCurrentEventFrame = nullptr; + mCurrentEventContent = nullptr; + + if (0 != mCurrentEventFrameStack.Length()) { + mCurrentEventFrame = mCurrentEventFrameStack.ElementAt(0); + mCurrentEventFrameStack.RemoveElementAt(0); + mCurrentEventContent = mCurrentEventContentStack.ObjectAt(0); + mCurrentEventContentStack.RemoveObjectAt(0); + + // Don't use it if it has moved to a different document. + if (mCurrentEventContent && + mCurrentEventContent->GetComposedDoc() != mDocument) { + mCurrentEventContent = nullptr; + mCurrentEventFrame = nullptr; + } + } +} + +// static +bool PresShell::EventHandler::InZombieDocument(nsIContent* aContent) { + // If a content node points to a null document, or the document is not + // attached to a window, then it is possibly in a zombie document, + // about to be replaced by a newly loading document. + // Such documents cannot handle DOM events. + // It might actually be in a node not attached to any document, + // in which case there is not parent presshell to retarget it to. + Document* doc = aContent->GetComposedDoc(); + return !doc || !doc->GetWindow(); +} + +already_AddRefed PresShell::GetRootWindow() { + nsCOMPtr window = mDocument->GetWindow(); + if (window) { + nsCOMPtr rootWindow = window->GetPrivateRoot(); + NS_ASSERTION(rootWindow, "nsPIDOMWindow::GetPrivateRoot() returns NULL"); + return rootWindow.forget(); + } + + // If we don't have DOM window, we're zombie, we should find the root window + // with our parent shell. + RefPtr parentPresShell = GetParentPresShellForEventHandling(); + NS_ENSURE_TRUE(parentPresShell, nullptr); + return parentPresShell->GetRootWindow(); +} + +already_AddRefed +PresShell::GetFocusedDOMWindowInOurWindow() { + nsCOMPtr rootWindow = GetRootWindow(); + NS_ENSURE_TRUE(rootWindow, nullptr); + nsCOMPtr focusedWindow; + nsFocusManager::GetFocusedDescendant(rootWindow, + nsFocusManager::eIncludeAllDescendants, + getter_AddRefs(focusedWindow)); + return focusedWindow.forget(); +} + +already_AddRefed PresShell::GetFocusedContentInOurWindow() const { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && mDocument) { + RefPtr focusedElement; + fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr, + getter_AddRefs(focusedElement)); + return focusedElement.forget(); + } + return nullptr; +} + +already_AddRefed PresShell::GetParentPresShellForEventHandling() { + if (!mPresContext) { + return nullptr; + } + + // Now, find the parent pres shell and send the event there + RefPtr docShell = mPresContext->GetDocShell(); + if (!docShell) { + docShell = mForwardingContainer.get(); + } + + // Might have gone away, or never been around to start with + if (!docShell) { + return nullptr; + } + + BrowsingContext* bc = docShell->GetBrowsingContext(); + if (!bc) { + return nullptr; + } + + RefPtr parentBC; + if (XRE_IsParentProcess()) { + parentBC = bc->Canonical()->GetParentCrossChromeBoundary(); + } else { + parentBC = bc->GetParent(); + } + + RefPtr parentDocShell = + parentBC ? parentBC->GetDocShell() : nullptr; + if (!parentDocShell) { + return nullptr; + } + + RefPtr parentPresShell = parentDocShell->GetPresShell(); + return parentPresShell.forget(); +} + +nsresult PresShell::EventHandler::RetargetEventToParent( + WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { + // Send this events straight up to the parent pres shell. + // We do this for keystroke events in zombie documents or if either a frame + // or a root content is not present. + // That way at least the UI key bindings can work. + + RefPtr parentPresShell = GetParentPresShellForEventHandling(); + NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE); + + // Fake the event as though it's from the parent pres shell's root frame. + return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(), + aGUIEvent, true, aEventStatus); +} + +void PresShell::DisableNonTestMouseEvents(bool aDisable) { + sDisableNonTestMouseEvents = aDisable; +} + +bool PresShell::MouseLocationWasSetBySynthesizedMouseEventForTests() const { + if (!mPresContext) { + return false; + } + if (mPresContext->IsRoot()) { + return mMouseLocationWasSetBySynthesizedMouseEventForTests; + } + PresShell* rootPresShell = GetRootPresShell(); + return rootPresShell && + rootPresShell->mMouseLocationWasSetBySynthesizedMouseEventForTests; +} + +nsPoint PresShell::GetEventLocation(const WidgetMouseEvent& aEvent) const { + nsIFrame* rootFrame = GetRootFrame(); + if (rootFrame) { + RelativeTo relativeTo{rootFrame}; + if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) { + relativeTo.mViewportType = ViewportType::Visual; + } + return nsLayoutUtils::GetEventCoordinatesRelativeTo(&aEvent, relativeTo); + } + + nsView* rootView = mViewManager->GetRootView(); + return nsLayoutUtils::TranslateWidgetToView(mPresContext, aEvent.mWidget, + aEvent.mRefPoint, rootView); +} + +void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) { + if (!mPresContext) { + return; + } + + if (!mPresContext->IsRoot()) { + PresShell* rootPresShell = GetRootPresShell(); + if (rootPresShell) { + rootPresShell->RecordPointerLocation(aEvent); + } + return; + } + + if ((aEvent->mMessage == eMouseMove && + aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) || + aEvent->mMessage == eMouseEnterIntoWidget || + aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp) { + mMouseLocation = GetEventLocation(*aEvent->AsMouseEvent()); + mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid(); + mMouseLocationWasSetBySynthesizedMouseEventForTests = + aEvent->mFlags.mIsSynthesizedForTests; +#ifdef DEBUG_MOUSE_LOCATION + if (aEvent->mMessage == eMouseEnterIntoWidget) { + printf("[ps=%p]got mouse enter for %p\n", this, aEvent->mWidget); + } + printf("[ps=%p]setting mouse location to (%d,%d)\n", this, mMouseLocation.x, + mMouseLocation.y); +#endif + if (aEvent->mMessage == eMouseEnterIntoWidget) { + SynthesizeMouseMove(false); + } + } else if (aEvent->mMessage == eMouseExitFromWidget) { + // Although we only care about the mouse moving into an area for which this + // pres shell doesn't receive mouse move events, we don't check which widget + // the mouse exit was for since this seems to vary by platform. Hopefully + // this won't matter at all since we'll get the mouse move or enter after + // the mouse exit when the mouse moves from one of our widgets into another. + mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid(); + mMouseLocationWasSetBySynthesizedMouseEventForTests = + aEvent->mFlags.mIsSynthesizedForTests; +#ifdef DEBUG_MOUSE_LOCATION + printf("[ps=%p]got mouse exit for %p\n", this, aEvent->mWidget); + printf("[ps=%p]clearing mouse location\n", this); +#endif + } else if ((aEvent->mMessage == ePointerMove && + aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) || + aEvent->mMessage == ePointerDown || + aEvent->mMessage == ePointerUp) { + // TODO: instead, encapsulate `mMouseLocation` and + // `mLastOverWindowPointerLocation` in a struct. + mLastOverWindowPointerLocation = GetEventLocation(*aEvent->AsMouseEvent()); + } +} + +void PresShell::nsSynthMouseMoveEvent::Revoke() { + if (mPresShell) { + mPresShell->GetPresContext()->RefreshDriver()->RemoveRefreshObserver( + this, FlushType::Display); + mPresShell = nullptr; + } +} + +// static +nsIFrame* PresShell::EventHandler::GetNearestFrameContainingPresShell( + PresShell* aPresShell) { + nsViewManager* vm = aPresShell->GetViewManager(); + if (!vm) { + return nullptr; + } + nsView* view = vm->GetRootView(); + while (view && !view->GetFrame()) { + view = view->GetParent(); + } + + nsIFrame* frame = nullptr; + if (view) { + frame = view->GetFrame(); + } + + return frame; +} + +static CallState FlushThrottledStyles(Document& aDocument) { + PresShell* presShell = aDocument.GetPresShell(); + if (presShell && presShell->IsVisible()) { + if (nsPresContext* presContext = presShell->GetPresContext()) { + presContext->RestyleManager()->UpdateOnlyAnimationStyles(); + } + } + + aDocument.EnumerateSubDocuments(FlushThrottledStyles); + return CallState::Continue; +} + +bool PresShell::CanDispatchEvent(const WidgetGUIEvent* aEvent) const { + bool rv = + mPresContext && !mHaveShutDown && nsContentUtils::IsSafeToRunScript(); + if (aEvent) { + rv &= (aEvent && aEvent->mWidget && !aEvent->mWidget->Destroyed()); + } + return rv; +} + +/* static */ +PresShell* PresShell::GetShellForEventTarget(nsIFrame* aFrame, + nsIContent* aContent) { + if (aFrame) { + return aFrame->PresShell(); + } + if (aContent) { + Document* doc = aContent->GetComposedDoc(); + if (!doc) { + return nullptr; + } + return doc->GetPresShell(); + } + return nullptr; +} + +/* static */ +PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) { + switch (aEvent->mMessage) { + case eTouchMove: + case eTouchCancel: + case eTouchEnd: { + // get the correct shell to dispatch to + WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); + for (dom::Touch* touch : touchEvent->mTouches) { + if (!touch) { + return nullptr; + } + + RefPtr oldTouch = + TouchManager::GetCapturedTouch(touch->Identifier()); + if (!oldTouch) { + return nullptr; + } + + nsCOMPtr content = do_QueryInterface(oldTouch->GetTarget()); + if (!content) { + return nullptr; + } + + nsIFrame* contentFrame = content->GetPrimaryFrame(); + if (!contentFrame) { + return nullptr; + } + + PresShell* presShell = contentFrame->PresContext()->GetPresShell(); + if (presShell) { + return presShell; + } + } + return nullptr; + } + default: + return nullptr; + } +} + +nsresult PresShell::HandleEvent(nsIFrame* aFrameForPresShell, + WidgetGUIEvent* aGUIEvent, + bool aDontRetargetEvents, + nsEventStatus* aEventStatus) { + MOZ_ASSERT(aGUIEvent); + // If it's synthesized in the parent process and our mouse location was set + // by a mouse event which was synthesized for tests because the test does not + // want to change `:hover` state with the synthesized mouse event for native + // mouse cursor position. + if (aGUIEvent->mMessage == eMouseMove && + aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() && + !aGUIEvent->mFlags.mIsSynthesizedForTests && + MouseLocationWasSetBySynthesizedMouseEventForTests() && + aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) { + return NS_OK; + } + + // Here we are granting some delays to ensure that user input events are + // created while the page content may not be visible to the user are not + // processed. + // The main purpose of this is to avoid user inputs are handled in the + // new document where as the user inputs were originally targeting some + // content in the old document. + if (!CanHandleUserInputEvents(aGUIEvent)) { + return NS_OK; + } + + EventHandler eventHandler(*this); + return eventHandler.HandleEvent(aFrameForPresShell, aGUIEvent, + aDontRetargetEvents, aEventStatus); +} + +nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrameForPresShell, + WidgetGUIEvent* aGUIEvent, + bool aDontRetargetEvents, + nsEventStatus* aEventStatus) { + MOZ_ASSERT(aGUIEvent); + MOZ_DIAGNOSTIC_ASSERT(aGUIEvent->IsTrusted()); + MOZ_ASSERT(aEventStatus); + + NS_ASSERTION(aFrameForPresShell, "aFrameForPresShell should be not null"); + + // Update the latest focus sequence number with this new sequence number; + // the next transasction that gets sent to the compositor will carry this over + if (mPresShell->mAPZFocusSequenceNumber < aGUIEvent->mFocusSequenceNumber) { + mPresShell->mAPZFocusSequenceNumber = aGUIEvent->mFocusSequenceNumber; + } + + if (mPresShell->IsDestroying() || + (PresShell::sDisableNonTestMouseEvents && + !aGUIEvent->mFlags.mIsSynthesizedForTests && + aGUIEvent->HasMouseEventMessage())) { + return NS_OK; + } + + mPresShell->RecordPointerLocation(aGUIEvent); + + if (MaybeHandleEventWithAccessibleCaret(aFrameForPresShell, aGUIEvent, + aEventStatus)) { + // Handled by AccessibleCaretEventHub. + return NS_OK; + } + + if (MaybeDiscardEvent(aGUIEvent)) { + // Cannot handle the event for now. + return NS_OK; + } + + if (!aDontRetargetEvents) { + // If aGUIEvent should be handled in another PresShell, we should call its + // HandleEvent() and do nothing here. + nsresult rv = NS_OK; + if (MaybeHandleEventWithAnotherPresShell(aFrameForPresShell, aGUIEvent, + aEventStatus, &rv)) { + // Handled by another PresShell or nobody can handle the event. + return rv; + } + } + + if (MaybeDiscardOrDelayKeyboardEvent(aGUIEvent)) { + // The event is discarded or put into the delayed event queue. + return NS_OK; + } + + if (aGUIEvent->IsUsingCoordinates()) { + return HandleEventUsingCoordinates(aFrameForPresShell, aGUIEvent, + aEventStatus, aDontRetargetEvents); + } + + // Activation events need to be dispatched even if no frame was found, since + // we don't want the focus to be out of sync. + if (!aFrameForPresShell) { + if (!NS_EVENT_NEEDS_FRAME(aGUIEvent)) { + // Push nullptr for both current event target content and frame since + // there is no frame but the event does not require a frame. + AutoCurrentEventInfoSetter eventInfoSetter(*this); + return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, + nullptr); + } + + if (aGUIEvent->HasKeyEventMessage()) { + // Keypress events in new blank tabs should not be completely thrown away. + // Retarget them -- the parent chrome shell might make use of them. + return RetargetEventToParent(aGUIEvent, aEventStatus); + } + + return NS_OK; + } + + if (aGUIEvent->IsTargetedAtFocusedContent()) { + return HandleEventAtFocusedContent(aGUIEvent, aEventStatus); + } + + return HandleEventWithFrameForPresShell(aFrameForPresShell, aGUIEvent, + aEventStatus); +} + +nsresult PresShell::EventHandler::HandleEventUsingCoordinates( + nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, + nsEventStatus* aEventStatus, bool aDontRetargetEvents) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aGUIEvent->IsUsingCoordinates()); + MOZ_ASSERT(aEventStatus); + + // Flush pending notifications to handle the event with the latest layout. + // But if it causes destroying the frame for mPresShell, stop handling the + // event. (why?) + AutoWeakFrame weakFrame(aFrameForPresShell); + MaybeFlushPendingNotifications(aGUIEvent); + if (!weakFrame.IsAlive()) { + *aEventStatus = nsEventStatus_eIgnore; + return NS_OK; + } + + // XXX Retrieving capturing content here. However, some of the following + // methods allow to run script. So, isn't it possible the capturing + // content outdated? + nsCOMPtr capturingContent = + EventHandler::GetCapturingContentFor(aGUIEvent); + + if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) { + PointerLockManager::Unlock(); + } + + nsIFrame* frameForPresShell = MaybeFlushThrottledStyles(aFrameForPresShell); + if (NS_WARN_IF(!frameForPresShell)) { + return NS_OK; + } + + bool isCapturingContentIgnored = false; + bool isCaptureRetargeted = false; + nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEvent( + frameForPresShell, aGUIEvent, capturingContent, + &isCapturingContentIgnored, &isCaptureRetargeted); + if (isCapturingContentIgnored) { + capturingContent = nullptr; + } + + // The order to generate pointer event is + // 1. check pending pointer capture. + // 2. check if there is a capturing content. + // 3. hit test + // 4. dispatch pointer events + // 5. check whether the targets of all Touch instances are in the same + // document and suppress invalid instances. + // 6. dispatch mouse or touch events. + + // Try to keep frame for following check, because frame can be damaged + // during MaybeProcessPointerCapture. + { + AutoWeakFrame frameKeeper(rootFrameToHandleEvent); + PointerEventHandler::MaybeProcessPointerCapture(aGUIEvent); + // Prevent application crashes, in case damaged frame. + if (!frameKeeper.IsAlive()) { + NS_WARNING("Nothing to handle this event!"); + return NS_OK; + } + } + + // Only capture mouse events and pointer events. + RefPtr pointerCapturingElement = + PointerEventHandler::GetPointerCapturingElement(aGUIEvent); + + if (pointerCapturingElement) { + rootFrameToHandleEvent = pointerCapturingElement->GetPrimaryFrame(); + if (!rootFrameToHandleEvent) { + return HandleEventWithPointerCapturingContentWithoutItsFrame( + aFrameForPresShell, aGUIEvent, pointerCapturingElement, aEventStatus); + } + } + + WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); + bool isWindowLevelMouseExit = + (aGUIEvent->mMessage == eMouseExitFromWidget) && + (mouseEvent && + (mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePlatformTopLevel || + mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet)); + + // Get the frame at the event point. However, don't do this if we're + // capturing and retargeting the event because the captured frame will + // be used instead below. Also keep using the root frame if we're dealing + // with a window-level mouse exit event since we want to start sending + // mouse out events at the root EventStateManager. + EventTargetData eventTargetData(rootFrameToHandleEvent); + if (!isCaptureRetargeted && !isWindowLevelMouseExit && + !pointerCapturingElement) { + if (!ComputeEventTargetFrameAndPresShellAtEventPoint( + rootFrameToHandleEvent, aGUIEvent, &eventTargetData)) { + *aEventStatus = nsEventStatus_eIgnore; + return NS_OK; + } + } + + // if a node is capturing the mouse, check if the event needs to be + // retargeted at the capturing content instead. This will be the case when + // capture retargeting is being used, no frame was found or the frame's + // content is not a descendant of the capturing content. + if (capturingContent && !pointerCapturingElement && + (PresShell::sCapturingContentInfo.mRetargetToElement || + !eventTargetData.mFrame->GetContent() || + !nsContentUtils::ContentIsCrossDocDescendantOf( + eventTargetData.mFrame->GetContent(), capturingContent))) { + // A check was already done above to ensure that capturingContent is + // in this presshell. + NS_ASSERTION(capturingContent->OwnerDoc() == GetDocument(), + "Unexpected document"); + nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame(); + if (capturingFrame) { + eventTargetData.SetFrameAndComputePresShell(capturingFrame); + } + } + + if (NS_WARN_IF(!eventTargetData.mFrame)) { + return NS_OK; + } + + // Suppress mouse event if it's being targeted at an element inside + // a document which needs events suppressed + if (MaybeDiscardOrDelayMouseEvent(eventTargetData.mFrame, aGUIEvent)) { + return NS_OK; + } + + // Check if we have an active EventStateManager which isn't the + // EventStateManager of the current PresContext. If that is the case, and + // mouse is over some ancestor document, forward event handling to the + // active document. This way content can get mouse events even when mouse + // is over the chrome or outside the window. + if (eventTargetData.MaybeRetargetToActiveDocument(aGUIEvent) && + NS_WARN_IF(!eventTargetData.mFrame)) { + return NS_OK; + } + + // Wheel events only apply to elements. If this is a wheel event, attempt to + // update the event target from the current wheel transaction before we + // compute the element from the target frame. + eventTargetData.UpdateWheelEventTarget(aGUIEvent); + + if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) { + return NS_OK; + } + // Note that even if ComputeElementFromFrame() returns true, + // eventTargetData.mContent can be nullptr here. + + // Dispatch a pointer event if Pointer Events is enabled. Note that if + // pointer event listeners change the layout, eventTargetData is + // automatically updated. + if (!DispatchPrecedingPointerEvent( + aFrameForPresShell, aGUIEvent, pointerCapturingElement, + aDontRetargetEvents, &eventTargetData, aEventStatus)) { + return NS_OK; + } + + // frame could be null after dispatching pointer events. + // XXX Despite of this comment, we update the event target data outside + // DispatchPrecedingPointerEvent(). Can we make it call + // UpdateTouchEventTarget()? + eventTargetData.UpdateTouchEventTarget(aGUIEvent); + + // Handle the event in the correct shell. + // We pass the subshell's root frame as the frame to start from. This is + // the only correct alternative; if the event was captured then it + // must have been captured by us or some ancestor shell and we + // now ask the subshell to dispatch it normally. + EventHandler eventHandler(*eventTargetData.mPresShell); + AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, eventTargetData); + // eventTargetData is on the stack and is guaranteed to keep its + // mOverrideClickTarget alive, so we can just use MOZ_KnownLive here. + nsresult rv = eventHandler.HandleEventWithCurrentEventInfo( + aGUIEvent, aEventStatus, true, + MOZ_KnownLive(eventTargetData.mOverrideClickTarget)); + return rv; +} + +bool PresShell::EventHandler::MaybeFlushPendingNotifications( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + + switch (aGUIEvent->mMessage) { + case eMouseDown: + case eMouseUp: { + RefPtr presContext = mPresShell->GetPresContext(); + if (NS_WARN_IF(!presContext)) { + return false; + } + uint64_t framesConstructedCount = presContext->FramesConstructedCount(); + uint64_t framesReflowedCount = presContext->FramesReflowedCount(); + + MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout); + return framesConstructedCount != presContext->FramesConstructedCount() || + framesReflowedCount != presContext->FramesReflowedCount(); + } + default: + return false; + } +} + +// The type of coordinates to use for hit-testing input events +// that are relative to the RCD's viewport frame. +// On most platforms, use visual coordinates so that scrollbars +// can be targeted. +// On mobile, use layout coordinates because hit-testing in +// visual coordinates clashes with mobile viewport sizing, where +// the ViewportFrame is sized to the initial containing block +// (ICB) size, which is in layout coordinates. This is fine +// because we don't need to be able to target scrollbars on mobile +// (scrollbar dragging isn't supported). +static ViewportType ViewportTypeForInputEventsRelativeToRoot() { +#ifdef MOZ_WIDGET_ANDROID + return ViewportType::Layout; +#else + return ViewportType::Visual; +#endif +} + +nsIFrame* PresShell::EventHandler::GetFrameToHandleNonTouchEvent( + nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aGUIEvent->mClass != eTouchEventClass); + + ViewportType viewportType = ViewportType::Layout; + if (aRootFrameToHandleEvent->Type() == LayoutFrameType::Viewport) { + nsPresContext* pc = aRootFrameToHandleEvent->PresContext(); + if (pc->IsChrome()) { + viewportType = ViewportType::Visual; + } else if (pc->IsRootContentDocumentCrossProcess()) { + viewportType = ViewportTypeForInputEventsRelativeToRoot(); + } + } + RelativeTo relativeTo{aRootFrameToHandleEvent, viewportType}; + nsPoint eventPoint = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo); + + uint32_t flags = 0; + if (aGUIEvent->mClass == eMouseEventClass) { + WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); + if (mouseEvent && mouseEvent->mIgnoreRootScrollFrame) { + flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME; + } + } + + nsIFrame* targetFrame = + FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags); + if (!targetFrame) { + return aRootFrameToHandleEvent; + } + + if (targetFrame->PresShell() == mPresShell) { + // If found target is in mPresShell, we've already found it in the latest + // layout so that we can use it. + return targetFrame; + } + + // If target is in a child document, we've not flushed its layout yet. + PresShell* childPresShell = targetFrame->PresShell(); + EventHandler childEventHandler(*childPresShell); + AutoWeakFrame weakFrame(aRootFrameToHandleEvent); + bool layoutChanged = + childEventHandler.MaybeFlushPendingNotifications(aGUIEvent); + if (!weakFrame.IsAlive()) { + // Stop handling the event if the root frame to handle event is destroyed + // by the reflow. (but why?) + return nullptr; + } + if (!layoutChanged) { + // If the layout in the child PresShell hasn't been changed, we don't + // need to recompute the target. + return targetFrame; + } + + // Finally, we need to recompute the target with the latest layout. + targetFrame = + FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags); + + return targetFrame ? targetFrame : aRootFrameToHandleEvent; +} + +bool PresShell::EventHandler::ComputeEventTargetFrameAndPresShellAtEventPoint( + nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent, + EventTargetData* aEventTargetData) { + MOZ_ASSERT(aRootFrameToHandleEvent); + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aEventTargetData); + + if (aGUIEvent->mClass == eTouchEventClass) { + nsIFrame* targetFrameAtTouchEvent = TouchManager::SetupTarget( + aGUIEvent->AsTouchEvent(), aRootFrameToHandleEvent); + aEventTargetData->SetFrameAndComputePresShell(targetFrameAtTouchEvent); + return true; + } + + nsIFrame* targetFrame = + GetFrameToHandleNonTouchEvent(aRootFrameToHandleEvent, aGUIEvent); + aEventTargetData->SetFrameAndComputePresShell(targetFrame); + return !!aEventTargetData->mFrame; +} + +bool PresShell::EventHandler::DispatchPrecedingPointerEvent( + nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, + nsIContent* aPointerCapturingContent, bool aDontRetargetEvents, + EventTargetData* aEventTargetData, nsEventStatus* aEventStatus) { + MOZ_ASSERT(aFrameForPresShell); + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aEventTargetData); + MOZ_ASSERT(aEventStatus); + + // Dispatch pointer events from the mouse or touch events. Regarding + // pointer events from mouse, we should dispatch those pointer events to + // the same target as the source mouse events. We pass the frame found + // in hit test to PointerEventHandler and dispatch pointer events to it. + // + // Regarding pointer events from touch, the behavior is different. Touch + // events are dispatched to the same target as the target of touchstart. + // Multiple touch points must be dispatched to the same document. Pointer + // events from touch can be dispatched to different documents. We Pass the + // original frame to PointerEventHandler, reentry PresShell::HandleEvent, + // and do hit test for each point. + nsIFrame* targetFrame = aGUIEvent->mClass == eTouchEventClass + ? aFrameForPresShell + : aEventTargetData->mFrame; + + if (aPointerCapturingContent) { + aEventTargetData->mOverrideClickTarget = + GetOverrideClickTarget(aGUIEvent, aFrameForPresShell); + aEventTargetData->mPresShell = + PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent); + if (!aEventTargetData->mPresShell) { + // If we can't process event for the capturing content, release + // the capture. + PointerEventHandler::ReleaseIfCaptureByDescendant( + aPointerCapturingContent); + return false; + } + + targetFrame = aPointerCapturingContent->GetPrimaryFrame(); + aEventTargetData->mFrame = targetFrame; + } + + AutoWeakFrame weakTargetFrame(targetFrame); + AutoWeakFrame weakFrame(aEventTargetData->mFrame); + nsCOMPtr content(aEventTargetData->mContent); + RefPtr presShell(aEventTargetData->mPresShell); + nsCOMPtr targetContent; + PointerEventHandler::DispatchPointerFromMouseOrTouch( + presShell, aEventTargetData->mFrame, content, aGUIEvent, + aDontRetargetEvents, aEventStatus, getter_AddRefs(targetContent)); + + // If the target frame is alive, the caller should keep handling the event + // unless event target frame is destroyed. + if (weakTargetFrame.IsAlive()) { + return weakFrame.IsAlive(); + } + + // If the event is not a mouse event, the caller should keep handling the + // event unless event target frame is destroyed. Note that this case is + // not defined by the spec. + if (aGUIEvent->mClass != eMouseEventClass) { + return weakFrame.IsAlive(); + } + + // Spec defines that mouse events must be dispatched to the same target as + // the pointer event. If the target is no longer participating in its + // ownerDocument's tree, fire the event at the original target's nearest + // ancestor node + if (!targetContent) { + return false; + } + + // XXX Why don't we reset aEventTargetData->mContent here? + aEventTargetData->mFrame = targetContent->GetPrimaryFrame(); + aEventTargetData->mPresShell = PresShell::GetShellForEventTarget( + aEventTargetData->mFrame, targetContent); + + // If new target PresShel is not found, we cannot keep handling the event. + return !!aEventTargetData->mPresShell; +} + +bool PresShell::EventHandler::MaybeHandleEventWithAccessibleCaret( + nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, + nsEventStatus* aEventStatus) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aEventStatus); + + // Don't dispatch event to AccessibleCaretEventHub when the event status + // is nsEventStatus_eConsumeNoDefault. This might be happened when content + // preventDefault on the pointer events. In such case, we also call + // preventDefault on mouse events to stop default behaviors. + if (*aEventStatus == nsEventStatus_eConsumeNoDefault) { + return false; + } + + if (!AccessibleCaretEnabled(GetDocument()->GetDocShell())) { + return false; + } + + // AccessibleCaretEventHub handles only mouse, touch, and keyboard events. + if (aGUIEvent->mClass != eMouseEventClass && + aGUIEvent->mClass != eTouchEventClass && + aGUIEvent->mClass != eKeyboardEventClass) { + return false; + } + + // First, try the event hub at the event point to handle a long press to + // select a word in an unfocused window. + do { + EventTargetData eventTargetData(nullptr); + if (!ComputeEventTargetFrameAndPresShellAtEventPoint( + aFrameForPresShell, aGUIEvent, &eventTargetData)) { + break; + } + + if (!eventTargetData.mPresShell) { + break; + } + + RefPtr eventHub = + eventTargetData.mPresShell->GetAccessibleCaretEventHub(); + if (!eventHub) { + break; + } + + *aEventStatus = eventHub->HandleEvent(aGUIEvent); + if (*aEventStatus != nsEventStatus_eConsumeNoDefault) { + break; + } + + // If the event is consumed, cancel APZC panning by setting + // mMultipleActionsPrevented. + aGUIEvent->mFlags.mMultipleActionsPrevented = true; + return true; + } while (false); + + // Then, we target the event to the event hub at the focused window. + nsCOMPtr window = GetFocusedDOMWindowInOurWindow(); + if (!window) { + return false; + } + RefPtr retargetEventDoc = window->GetExtantDoc(); + if (!retargetEventDoc) { + return false; + } + RefPtr presShell = retargetEventDoc->GetPresShell(); + if (!presShell) { + return false; + } + + RefPtr eventHub = + presShell->GetAccessibleCaretEventHub(); + if (!eventHub) { + return false; + } + *aEventStatus = eventHub->HandleEvent(aGUIEvent); + if (*aEventStatus != nsEventStatus_eConsumeNoDefault) { + return false; + } + // If the event is consumed, cancel APZC panning by setting + // mMultipleActionsPrevented. + aGUIEvent->mFlags.mMultipleActionsPrevented = true; + return true; +} + +bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + + // If it is safe to dispatch events now, don't discard the event. + if (nsContentUtils::IsSafeToRunScript()) { + return false; + } + + // If the event does not cause dispatching DOM event (i.e., internal event), + // we can keep handling it even when it's not safe to run script. + if (!aGUIEvent->IsAllowedToDispatchDOMEvent()) { + return false; + } + + // If the event is a composition event, we need to let IMEStateManager know + // it's discarded because it needs to listen all composition events to manage + // TextComposition instance. + if (aGUIEvent->mClass == eCompositionEventClass) { + IMEStateManager::OnCompositionEventDiscarded( + aGUIEvent->AsCompositionEvent()); + } + +#ifdef DEBUG + if (aGUIEvent->IsIMERelatedEvent()) { + nsPrintfCString warning("%s event is discarded", + ToChar(aGUIEvent->mMessage)); + NS_WARNING(warning.get()); + } +#endif // #ifdef DEBUG + + nsContentUtils::WarnScriptWasIgnored(GetDocument()); + return true; +} + +// static +nsIContent* PresShell::EventHandler::GetCapturingContentFor( + WidgetGUIEvent* aGUIEvent) { + return (aGUIEvent->mClass == ePointerEventClass || + aGUIEvent->mClass == eWheelEventClass || + aGUIEvent->HasMouseEventMessage()) + ? PresShell::GetCapturingContent() + : nullptr; +} + +bool PresShell::EventHandler::GetRetargetEventDocument( + WidgetGUIEvent* aGUIEvent, Document** aRetargetEventDocument) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aRetargetEventDocument); + + *aRetargetEventDocument = nullptr; + + // key and IME related events should not cross top level window boundary. + // Basically, such input events should be fired only on focused widget. + // However, some IMEs might need to clean up composition after focused + // window is deactivated. And also some tests on MozMill want to test key + // handling on deactivated window because MozMill window can be activated + // during tests. So, there is no merit the events should be redirected to + // active window. So, the events should be handled on the last focused + // content in the last focused DOM window in same top level window. + // Note, if no DOM window has been focused yet, we can discard the events. + if (aGUIEvent->IsTargetedAtFocusedWindow()) { + nsCOMPtr window = GetFocusedDOMWindowInOurWindow(); + // No DOM window in same top level window has not been focused yet, + // discard the events. + if (!window) { + return false; + } + + RefPtr retargetEventDoc = window->GetExtantDoc(); + if (!retargetEventDoc) { + return false; + } + retargetEventDoc.forget(aRetargetEventDocument); + return true; + } + + nsIContent* capturingContent = + EventHandler::GetCapturingContentFor(aGUIEvent); + if (capturingContent) { + // if the mouse is being captured then retarget the mouse event at the + // document that is being captured. + RefPtr retargetEventDoc = capturingContent->GetComposedDoc(); + retargetEventDoc.forget(aRetargetEventDocument); + return true; + } + +#ifdef ANDROID + if (aGUIEvent->mClass == eTouchEventClass || + aGUIEvent->mClass == eMouseEventClass || + aGUIEvent->mClass == eWheelEventClass) { + RefPtr retargetEventDoc = mPresShell->GetPrimaryContentDocument(); + retargetEventDoc.forget(aRetargetEventDocument); + return true; + } +#endif // #ifdef ANDROID + + // When we don't find another document to handle the event, we need to keep + // handling the event by ourselves. + return true; +} + +nsIFrame* PresShell::EventHandler::GetFrameForHandlingEventWith( + WidgetGUIEvent* aGUIEvent, Document* aRetargetDocument, + nsIFrame* aFrameForPresShell) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aRetargetDocument); + + RefPtr retargetPresShell = aRetargetDocument->GetPresShell(); + // Even if the document doesn't have PresShell, i.e., it's invisible, we + // need to dispatch only KeyboardEvent in its nearest visible document + // because key focus shouldn't be caught by invisible document. + if (!retargetPresShell) { + if (!aGUIEvent->HasKeyEventMessage()) { + return nullptr; + } + Document* retargetEventDoc = aRetargetDocument; + while (!retargetPresShell) { + retargetEventDoc = retargetEventDoc->GetInProcessParentDocument(); + if (!retargetEventDoc) { + return nullptr; + } + retargetPresShell = retargetEventDoc->GetPresShell(); + } + } + + // If the found PresShell is this instance, caller needs to keep handling + // aGUIEvent by itself. Therefore, return the given frame which was set + // to aFrame of HandleEvent(). + if (retargetPresShell == mPresShell) { + return aFrameForPresShell; + } + + // Use root frame of the new PresShell if there is. + nsIFrame* rootFrame = retargetPresShell->GetRootFrame(); + if (rootFrame) { + return rootFrame; + } + + // Otherwise, and if aGUIEvent requires content of PresShell, caller should + // stop handling the event. + if (aGUIEvent->mMessage == eQueryTextContent || + aGUIEvent->IsContentCommandEvent()) { + return nullptr; + } + + // Otherwise, use nearest ancestor frame which includes the PresShell. + return GetNearestFrameContainingPresShell(retargetPresShell); +} + +bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell( + nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, + nsEventStatus* aEventStatus, nsresult* aRv) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aEventStatus); + MOZ_ASSERT(aRv); + + *aRv = NS_OK; + + RefPtr retargetEventDoc; + if (!GetRetargetEventDocument(aGUIEvent, getter_AddRefs(retargetEventDoc))) { + // Nobody can handle this event. So, treat as handled by somebody to make + // caller do nothing anymore. + return true; + } + + // If there is no proper retarget document, the caller should handle the + // event by itself. + if (!retargetEventDoc) { + return false; + } + + nsIFrame* frame = GetFrameForHandlingEventWith(aGUIEvent, retargetEventDoc, + aFrameForPresShell); + if (!frame) { + // Nobody can handle this event. So, treat as handled by somebody to make + // caller do nothing anymore. + return true; + } + + // If we reached same frame as set to HandleEvent(), the caller should handle + // the event by itself. + if (frame == aFrameForPresShell) { + return false; + } + + // We need to handle aGUIEvent with another PresShell. + RefPtr presShell = frame->PresContext()->PresShell(); + *aRv = presShell->HandleEvent(frame, aGUIEvent, true, aEventStatus); + return true; +} + +bool PresShell::EventHandler::MaybeDiscardOrDelayKeyboardEvent( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + + if (aGUIEvent->mClass != eKeyboardEventClass) { + return false; + } + + Document* document = GetDocument(); + if (!document || !document->EventHandlingSuppressed()) { + return false; + } + + MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent(), + !InputTaskManager::Get()->IsSuspended()); + + if (aGUIEvent->mMessage == eKeyDown) { + mPresShell->mNoDelayedKeyEvents = true; + } else if (!mPresShell->mNoDelayedKeyEvents) { + UniquePtr delayedKeyEvent = + MakeUnique(aGUIEvent->AsKeyboardEvent()); + mPresShell->mDelayedEvents.AppendElement(std::move(delayedKeyEvent)); + } + aGUIEvent->mFlags.mIsSuppressedOrDelayed = true; + return true; +} + +bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent( + nsIFrame* aFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aFrameToHandleEvent); + MOZ_ASSERT(aGUIEvent); + + if (aGUIEvent->mClass != eMouseEventClass) { + return false; + } + + if (!aFrameToHandleEvent->PresContext() + ->Document() + ->EventHandlingSuppressed()) { + return false; + } + + MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() && + aGUIEvent->mMessage != eMouseMove, + !InputTaskManager::Get()->IsSuspended()); + + RefPtr ps = aFrameToHandleEvent->PresShell(); + + if (aGUIEvent->mMessage == eMouseDown) { + ps->mNoDelayedMouseEvents = true; + } else if (!ps->mNoDelayedMouseEvents && + (aGUIEvent->mMessage == eMouseUp || + // contextmenu is triggered after right mouseup on Windows and + // right mousedown on other platforms. + aGUIEvent->mMessage == eContextMenu || + aGUIEvent->mMessage == eMouseExitFromWidget)) { + UniquePtr delayedMouseEvent = + MakeUnique(aGUIEvent->AsMouseEvent()); + ps->mDelayedEvents.AppendElement(std::move(delayedMouseEvent)); + } + + // If there is a suppressed event listener associated with the document, + // notify it about the suppressed mouse event. This allows devtools + // features to continue receiving mouse events even when the devtools + // debugger has paused execution in a page. + RefPtr suppressedListener = aFrameToHandleEvent->PresContext() + ->Document() + ->GetSuppressedEventListener(); + if (!suppressedListener || + aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) { + return true; + } + + nsCOMPtr targetContent; + aFrameToHandleEvent->GetContentForEvent(aGUIEvent, + getter_AddRefs(targetContent)); + if (targetContent) { + aGUIEvent->mTarget = targetContent; + } + + nsCOMPtr eventTarget = aGUIEvent->mTarget; + RefPtr event = EventDispatcher::CreateEvent( + eventTarget, aFrameToHandleEvent->PresContext(), aGUIEvent, u""_ns); + + suppressedListener->HandleEvent(*event); + return true; +} + +nsIFrame* PresShell::EventHandler::MaybeFlushThrottledStyles( + nsIFrame* aFrameForPresShell) { + if (!GetDocument()) { + // XXX Only when mPresShell has document, we'll try to look for a frame + // containing mPresShell even if given frame is nullptr. Does this + // make sense? + return aFrameForPresShell; + } + + PresShell* rootPresShell = mPresShell->GetRootPresShell(); + if (NS_WARN_IF(!rootPresShell)) { + return nullptr; + } + Document* rootDocument = rootPresShell->GetDocument(); + if (NS_WARN_IF(!rootDocument)) { + return nullptr; + } + + AutoWeakFrame weakFrameForPresShell(aFrameForPresShell); + { // scope for scriptBlocker. + nsAutoScriptBlocker scriptBlocker; + FlushThrottledStyles(*rootDocument); + } + + if (weakFrameForPresShell.IsAlive()) { + return aFrameForPresShell; + } + + return GetNearestFrameContainingPresShell(mPresShell); +} + +nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEvent( + nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, + nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored, + bool* aIsCaptureRetargeted) { + MOZ_ASSERT(aFrameForPresShell); + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aIsCapturingContentIgnored); + MOZ_ASSERT(aIsCaptureRetargeted); + + nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEventWithPopup( + aFrameForPresShell, aGUIEvent, aCapturingContent, + aIsCapturingContentIgnored); + if (*aIsCapturingContentIgnored) { + // If the capturing content is ignored, we don't need to respect it. + return rootFrameToHandleEvent; + } + + if (!aCapturingContent) { + return rootFrameToHandleEvent; + } + + // If we have capturing content, let's compute root frame with it again. + return ComputeRootFrameToHandleEventWithCapturingContent( + rootFrameToHandleEvent, aCapturingContent, aIsCapturingContentIgnored, + aIsCaptureRetargeted); +} + +nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEventWithPopup( + nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent, + nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored) { + MOZ_ASSERT(aRootFrameToHandleEvent); + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aIsCapturingContentIgnored); + + *aIsCapturingContentIgnored = false; + + nsPresContext* framePresContext = aRootFrameToHandleEvent->PresContext(); + nsPresContext* rootPresContext = framePresContext->GetRootPresContext(); + NS_ASSERTION(rootPresContext == GetPresContext()->GetRootPresContext(), + "How did we end up outside the connected " + "prescontext/viewmanager hierarchy?"); + nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates( + rootPresContext, aGUIEvent); + if (!popupFrame) { + return aRootFrameToHandleEvent; + } + + // If a remote browser is currently capturing input break out if we + // detect a chrome generated popup. + // XXXedgar, do we need to check fission OOP iframe? + if (aCapturingContent && + EventStateManager::IsTopLevelRemoteTarget(aCapturingContent)) { + *aIsCapturingContentIgnored = true; + } + + // If the popupFrame is an ancestor of the 'frame', the frame should + // handle the event, otherwise, the popup should handle it. + if (nsContentUtils::ContentIsCrossDocDescendantOf( + framePresContext->GetPresShell()->GetDocument(), + popupFrame->GetContent())) { + return aRootFrameToHandleEvent; + } + + // If we aren't starting our event dispatch from the root frame of the + // root prescontext, then someone must be capturing the mouse. In that + // case we only want to use the popup list if the capture is + // inside the popup. + if (framePresContext == rootPresContext && + aRootFrameToHandleEvent == FrameConstructor()->GetRootFrame()) { + return popupFrame; + } + + if (aCapturingContent && !*aIsCapturingContentIgnored && + aCapturingContent->IsInclusiveDescendantOf(popupFrame->GetContent())) { + return popupFrame; + } + + return aRootFrameToHandleEvent; +} + +nsIFrame* +PresShell::EventHandler::ComputeRootFrameToHandleEventWithCapturingContent( + nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent, + bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted) { + MOZ_ASSERT(aRootFrameToHandleEvent); + MOZ_ASSERT(aCapturingContent); + MOZ_ASSERT(aIsCapturingContentIgnored); + MOZ_ASSERT(aIsCaptureRetargeted); + + *aIsCapturingContentIgnored = false; + *aIsCaptureRetargeted = false; + + // If a capture is active, determine if the BrowsingContext is active. If + // not, clear the capture and target the mouse event normally instead. This + // would occur if the mouse button is held down while a tab change occurs. + // If the BrowsingContext is active, look for a scrolling container. + BrowsingContext* bc = GetPresContext()->Document()->GetBrowsingContext(); + if (!bc || !bc->IsActive()) { + ClearMouseCapture(); + *aIsCapturingContentIgnored = true; + return aRootFrameToHandleEvent; + } + + if (PresShell::sCapturingContentInfo.mRetargetToElement) { + *aIsCaptureRetargeted = true; + return aRootFrameToHandleEvent; + } + + // A check was already done above to ensure that aCapturingContent is + // in this presshell. + NS_ASSERTION(aCapturingContent->OwnerDoc() == GetDocument(), + "Unexpected document"); + nsIFrame* captureFrame = aCapturingContent->GetPrimaryFrame(); + if (!captureFrame) { + return aRootFrameToHandleEvent; + } + + // scrollable frames should use the scrolling container as the root instead + // of the document + nsIScrollableFrame* scrollFrame = do_QueryFrame(captureFrame); + return scrollFrame ? scrollFrame->GetScrolledFrame() + : aRootFrameToHandleEvent; +} + +nsresult +PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame( + nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, + nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aPointerCapturingContent); + MOZ_ASSERT(!aPointerCapturingContent->GetPrimaryFrame(), + "Handle the event with frame rather than only with the content"); + MOZ_ASSERT(aEventStatus); + + RefPtr presShellForCapturingContent = + PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent); + if (!presShellForCapturingContent) { + // If we can't process event for the capturing content, release + // the capture. + PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent); + return NS_OK; + } + + nsCOMPtr overrideClickTarget = + GetOverrideClickTarget(aGUIEvent, aFrameForPresShell); + + // Dispatch events to the capturing content even it's frame is + // destroyed. + PointerEventHandler::DispatchPointerFromMouseOrTouch( + presShellForCapturingContent, nullptr, aPointerCapturingContent, + aGUIEvent, false, aEventStatus, nullptr); + + if (presShellForCapturingContent == mPresShell) { + return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingContent, + aEventStatus, true, nullptr, + overrideClickTarget); + } + + EventHandler eventHandlerForCapturingContent( + std::move(presShellForCapturingContent)); + return eventHandlerForCapturingContent.HandleEventWithTarget( + aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr, + overrideClickTarget); +} + +nsresult PresShell::EventHandler::HandleEventAtFocusedContent( + WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent()); + MOZ_ASSERT(aEventStatus); + + AutoCurrentEventInfoSetter eventInfoSetter(*this); + + RefPtr eventTargetElement = + ComputeFocusedEventTargetElement(aGUIEvent); + + mPresShell->mCurrentEventFrame = nullptr; + if (eventTargetElement) { + nsresult rv = NS_OK; + if (MaybeHandleEventWithAnotherPresShell(eventTargetElement, aGUIEvent, + aEventStatus, &rv)) { + return rv; + } + } + + // If we cannot handle the event with mPresShell, let's try to handle it + // with parent PresShell. + mPresShell->mCurrentEventContent = eventTargetElement; + if (!mPresShell->GetCurrentEventContent() || + !mPresShell->GetCurrentEventFrame() || + InZombieDocument(mPresShell->mCurrentEventContent)) { + return RetargetEventToParent(aGUIEvent, aEventStatus); + } + + nsresult rv = + HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr); + return rv; +} + +Element* PresShell::EventHandler::ComputeFocusedEventTargetElement( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent()); + + // key and IME related events go to the focused frame in this DOM window. + nsPIDOMWindowOuter* window = GetDocument()->GetWindow(); + nsCOMPtr focusedWindow; + Element* eventTargetElement = nsFocusManager::GetFocusedDescendant( + window, nsFocusManager::eOnlyCurrentWindow, + getter_AddRefs(focusedWindow)); + + // otherwise, if there is no focused content or the focused content has + // no frame, just use the root content. This ensures that key events + // still get sent to the window properly if nothing is focused or if a + // frame goes away while it is focused. + if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) { + eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget(); + } + + switch (aGUIEvent->mMessage) { + case eKeyDown: + sLastKeyDownEventTargetElement = eventTargetElement; + return eventTargetElement; + case eKeyPress: + case eKeyUp: + if (!sLastKeyDownEventTargetElement) { + return eventTargetElement; + } + // If a different element is now focused for the keypress/keyup event + // than what was focused during the keydown event, check if the new + // focused element is not in a chrome document any more, and if so, + // retarget the event back at the keydown target. This prevents a + // content area from grabbing the focus from chrome in-between key + // events. + if (eventTargetElement) { + bool keyDownIsChrome = nsContentUtils::IsChromeDoc( + sLastKeyDownEventTargetElement->GetComposedDoc()); + if (keyDownIsChrome != nsContentUtils::IsChromeDoc( + eventTargetElement->GetComposedDoc()) || + (keyDownIsChrome && BrowserParent::GetFrom(eventTargetElement))) { + eventTargetElement = sLastKeyDownEventTargetElement; + } + } + + if (aGUIEvent->mMessage == eKeyUp) { + sLastKeyDownEventTargetElement = nullptr; + } + [[fallthrough]]; + default: + return eventTargetElement; + } +} + +bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell( + Element* aEventTargetElement, WidgetGUIEvent* aGUIEvent, + nsEventStatus* aEventStatus, nsresult* aRv) { + MOZ_ASSERT(aEventTargetElement); + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates()); + MOZ_ASSERT(aEventStatus); + MOZ_ASSERT(aRv); + + Document* eventTargetDocument = aEventTargetElement->OwnerDoc(); + if (!eventTargetDocument || eventTargetDocument == GetDocument()) { + *aRv = NS_OK; + return false; + } + + RefPtr eventTargetPresShell = eventTargetDocument->GetPresShell(); + if (!eventTargetPresShell) { + *aRv = NS_OK; + return true; // No PresShell can handle the event. + } + + EventHandler eventHandler(std::move(eventTargetPresShell)); + *aRv = eventHandler.HandleRetargetedEvent(aGUIEvent, aEventStatus, + aEventTargetElement); + return true; +} + +nsresult PresShell::EventHandler::HandleEventWithFrameForPresShell( + nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, + nsEventStatus* aEventStatus) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates()); + MOZ_ASSERT(!aGUIEvent->IsTargetedAtFocusedContent()); + MOZ_ASSERT(aEventStatus); + + AutoCurrentEventInfoSetter eventInfoSetter(*this, aFrameForPresShell, + nullptr); + + nsresult rv = NS_OK; + if (mPresShell->GetCurrentEventFrame()) { + rv = + HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr); + } + + return rv; +} + +Document* PresShell::GetPrimaryContentDocument() { + nsPresContext* context = GetPresContext(); + if (!context || !context->IsRoot()) { + return nullptr; + } + + nsCOMPtr shellAsTreeItem = context->GetDocShell(); + if (!shellAsTreeItem) { + return nullptr; + } + + nsCOMPtr owner; + shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner)); + if (!owner) { + return nullptr; + } + + // now get the primary content shell (active tab) + nsCOMPtr item; + owner->GetPrimaryContentShell(getter_AddRefs(item)); + nsCOMPtr childDocShell = do_QueryInterface(item); + if (!childDocShell) { + return nullptr; + } + + return childDocShell->GetExtantDocument(); +} + +nsresult PresShell::EventHandler::HandleEventWithTarget( + WidgetEvent* aEvent, nsIFrame* aNewEventFrame, nsIContent* aNewEventContent, + nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent, + nsIContent** aTargetContent, nsIContent* aOverrideClickTarget) { + MOZ_ASSERT(aEvent); + MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); + +#if DEBUG + MOZ_ASSERT(!aNewEventFrame || + aNewEventFrame->PresContext()->GetPresShell() == mPresShell, + "wrong shell"); + if (aNewEventContent) { + Document* doc = aNewEventContent->GetComposedDoc(); + NS_ASSERTION(doc, "event for content that isn't in a document"); + // NOTE: We don't require that the document still have a PresShell. + // See bug 1375940. + } +#endif + NS_ENSURE_STATE(!aNewEventContent || + aNewEventContent->GetComposedDoc() == GetDocument()); + if (aEvent->mClass == ePointerEventClass) { + mPresShell->RecordPointerLocation(aEvent->AsMouseEvent()); + } + AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame, + aTargetContent); + AutoCurrentEventInfoSetter eventInfoSetter(*this, aNewEventFrame, + aNewEventContent); + nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false, + aOverrideClickTarget); + return rv; +} + +namespace { + +class MOZ_RAII AutoEventHandler final { + public: + AutoEventHandler(WidgetEvent* aEvent, Document* aDocument) : mEvent(aEvent) { + MOZ_ASSERT(mEvent); + MOZ_ASSERT(mEvent->IsTrusted()); + + if (mEvent->mMessage == eMouseDown) { + PresShell::ReleaseCapturingContent(); + PresShell::AllowMouseCapture(true); + } + if (NeedsToUpdateCurrentMouseBtnState()) { + WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent(); + if (mouseEvent) { + EventStateManager::sCurrentMouseBtn = mouseEvent->mButton; + } + } + } + + ~AutoEventHandler() { + if (mEvent->mMessage == eMouseDown) { + PresShell::AllowMouseCapture(false); + } + if (NeedsToUpdateCurrentMouseBtnState()) { + EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed; + } + } + + protected: + bool NeedsToUpdateCurrentMouseBtnState() const { + return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp || + mEvent->mMessage == ePointerDown || mEvent->mMessage == ePointerUp; + } + + WidgetEvent* mEvent; +}; + +} // anonymous namespace + +nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo( + WidgetEvent* aEvent, nsEventStatus* aEventStatus, + bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) { + MOZ_ASSERT(aEvent); + MOZ_ASSERT(aEventStatus); + + RefPtr manager = GetPresContext()->EventStateManager(); + + // If we cannot handle the event with mPresShell because of no target, + // just record the response time. + // XXX Is this intentional? In such case, the score is really good because + // of nothing to do. So, it may make average and median better. + if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() && + !mPresShell->GetCurrentEventContent()) { + RecordEventHandlingResponsePerformance(aEvent); + return NS_OK; + } + + if (mPresShell->mCurrentEventContent && aEvent->IsTargetedAtFocusedWindow()) { + if (RefPtr fm = nsFocusManager::GetFocusManager()) { + // This may run script now. So, mPresShell might be destroyed after here. + nsCOMPtr currentEventContent = + mPresShell->mCurrentEventContent; + fm->FlushBeforeEventHandlingIfNeeded(currentEventContent); + } + } + + bool touchIsNew = false; + if (!PrepareToDispatchEvent(aEvent, aEventStatus, &touchIsNew)) { + return NS_OK; + } + + // We finished preparing to dispatch the event. So, let's record the + // performance. + RecordEventPreparationPerformance(aEvent); + + AutoHandlingUserInputStatePusher userInpStatePusher( + UserActivation::IsUserInteractionEvent(aEvent), aEvent); + AutoEventHandler eventHandler(aEvent, GetDocument()); + AutoPopupStatePusher popupStatePusher( + PopupBlocker::GetEventPopupControlState(aEvent)); + + // FIXME. If the event was reused, we need to clear the old target, + // bug 329430 + aEvent->mTarget = nullptr; + + HandlingTimeAccumulator handlingTimeAccumulator(*this, aEvent); + + nsresult rv = DispatchEvent(manager, aEvent, touchIsNew, aEventStatus, + aOverrideClickTarget); + + if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent) { + // Ensure that notifications to IME should be sent before getting next + // native event from the event queue. + // XXX Should we check the event message or event class instead of + // using aIsHandlingNativeEvent? + manager->TryToFlushPendingNotificationsToIME(); + } + + FinalizeHandlingEvent(aEvent); + + RecordEventHandlingResponsePerformance(aEvent); + + return rv; // Result of DispatchEvent() +} + +nsresult PresShell::EventHandler::DispatchEvent( + EventStateManager* aEventStateManager, WidgetEvent* aEvent, + bool aTouchIsNew, nsEventStatus* aEventStatus, + nsIContent* aOverrideClickTarget) { + MOZ_ASSERT(aEventStateManager); + MOZ_ASSERT(aEvent); + MOZ_ASSERT(aEventStatus); + + // 1. Give event to event manager for pre event state changes and + // generation of synthetic events. + { // Scope for presContext + RefPtr presContext = GetPresContext(); + nsCOMPtr eventContent = mPresShell->mCurrentEventContent; + nsresult rv = aEventStateManager->PreHandleEvent( + presContext, aEvent, mPresShell->mCurrentEventFrame, eventContent, + aEventStatus, aOverrideClickTarget); + if (NS_FAILED(rv)) { + return rv; + } + } + + // 2. Give event to the DOM for third party and JS use. + bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent(); + if (aEvent->mClass == eKeyboardEventClass) { + nsContentUtils::SetIsHandlingKeyBoardEvent(true); + } + // If EventStateManager or something wants reply from remote process and + // needs to win any other event listeners in chrome, the event is both + // stopped its propagation and marked as "waiting reply from remote + // process". In this case, PresShell shouldn't dispatch the event into + // the DOM tree because they don't have a chance to stop propagation in + // the system event group. On the other hand, if its propagation is not + // stopped, that means that the event may be reserved by chrome. If it's + // reserved by chrome, the event shouldn't be sent to any remote + // processes. In this case, PresShell needs to dispatch the event to + // the DOM tree for checking if it's reserved. + if (aEvent->IsAllowedToDispatchDOMEvent() && + !(aEvent->PropagationStopped() && + aEvent->IsWaitingReplyFromRemoteProcess())) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(), + "Somebody changed aEvent to cause a DOM event!"); + nsPresShellEventCB eventCB(mPresShell); + if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) { + if (target->OnlySystemGroupDispatch(aEvent->mMessage)) { + aEvent->StopPropagation(); + } + } + if (aEvent->mClass == eTouchEventClass) { + DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew); + } else { + DispatchEventToDOM(aEvent, aEventStatus, &eventCB); + } + } + + nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent); + + if (mPresShell->IsDestroying()) { + return NS_OK; + } + + // 3. Give event to event manager for post event state changes and + // generation of synthetic events. + // Refetch the prescontext, in case it changed. + RefPtr presContext = GetPresContext(); + return aEventStateManager->PostHandleEvent( + presContext, aEvent, mPresShell->GetCurrentEventFrame(), aEventStatus, + aOverrideClickTarget); +} + +bool PresShell::EventHandler::PrepareToDispatchEvent( + WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aTouchIsNew) { + MOZ_ASSERT(aEvent->IsTrusted()); + MOZ_ASSERT(aEventStatus); + MOZ_ASSERT(aTouchIsNew); + + *aTouchIsNew = false; + if (aEvent->IsUserAction()) { + mPresShell->mHasHandledUserInput = true; + } + + switch (aEvent->mMessage) { + case eKeyPress: + case eKeyDown: + case eKeyUp: { + WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent(); + MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent); + return true; + } + case eMouseMove: { + bool allowCapture = EventStateManager::GetActiveEventStateManager() && + GetPresContext() && + GetPresContext()->EventStateManager() == + EventStateManager::GetActiveEventStateManager(); + PresShell::AllowMouseCapture(allowCapture); + return true; + } + case eDrop: { + nsCOMPtr session = nsContentUtils::GetDragSession(); + if (session) { + bool onlyChromeDrop = false; + session->GetOnlyChromeDrop(&onlyChromeDrop); + if (onlyChromeDrop) { + aEvent->mFlags.mOnlyChromeDispatch = true; + } + } + return true; + } + case eDragExit: { + if (!StaticPrefs::dom_event_dragexit_enabled()) { + aEvent->mFlags.mOnlyChromeDispatch = true; + } + return true; + } + case eContextMenu: { + // If we cannot open context menu even though eContextMenu is fired, we + // should stop dispatching it into the DOM. + WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (mouseEvent->IsContextMenuKeyEvent() && + !AdjustContextMenuKeyEvent(mouseEvent)) { + return false; + } + + // If "Shift" state is active, context menu should be forcibly opened even + // if web apps want to prevent it since we respect our users' intention. + // In this case, we don't fire "contextmenu" event on web content because + // of not cancelable. + if (mouseEvent->IsShift()) { + aEvent->mFlags.mOnlyChromeDispatch = true; + aEvent->mFlags.mRetargetToNonNativeAnonymous = true; + } + return true; + } + case eTouchStart: + case eTouchMove: + case eTouchEnd: + case eTouchCancel: + case eTouchPointerCancel: + return mPresShell->mTouchManager.PreHandleEvent( + aEvent, aEventStatus, *aTouchIsNew, mPresShell->mCurrentEventContent); + default: + return true; + } +} + +void PresShell::EventHandler::FinalizeHandlingEvent(WidgetEvent* aEvent) { + switch (aEvent->mMessage) { + case eKeyPress: + case eKeyDown: + case eKeyUp: { + if (aEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) { + if (aEvent->mMessage == eKeyUp) { + // Reset this flag after key up is handled. + mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = false; + } else { + if (aEvent->mFlags.mOnlyChromeDispatch && + aEvent->mFlags.mDefaultPreventedByChrome) { + mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = true; + } + if (aEvent->mMessage == eKeyDown && + !aEvent->mFlags.mDefaultPrevented) { + if (Document* doc = GetDocument()) { + doc->TryCancelDialog(); + } + } + } + } + if (aEvent->mMessage == eKeyDown) { + mPresShell->mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented; + } + return; + } + case eMouseUp: + // reset the capturing content now that the mouse button is up + PresShell::ReleaseCapturingContent(); + return; + case eMouseMove: + PresShell::AllowMouseCapture(false); + return; + case eDrag: + case eDragEnd: + case eDragEnter: + case eDragExit: + case eDragLeave: + case eDragOver: + case eDrop: { + // After any drag event other than dragstart (which is handled + // separately, as we need to collect the data first), the DataTransfer + // needs to be made protected, and then disconnected. + DataTransfer* dataTransfer = aEvent->AsDragEvent()->mDataTransfer; + if (dataTransfer) { + dataTransfer->Disconnect(); + } + return; + } + default: + return; + } +} + +void PresShell::EventHandler::MaybeHandleKeyboardEventBeforeDispatch( + WidgetKeyboardEvent* aKeyboardEvent) { + MOZ_ASSERT(aKeyboardEvent); + + if (aKeyboardEvent->mKeyCode != NS_VK_ESCAPE) { + return; + } + + // If we're in fullscreen mode, exit from it forcibly when Escape key is + // pressed. + Document* doc = mPresShell->GetCurrentEventContent() + ? mPresShell->mCurrentEventContent->OwnerDoc() + : nullptr; + Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc); + if (root && root->GetFullscreenElement()) { + // Prevent default action on ESC key press when exiting + // DOM fullscreen mode. This prevents the browser ESC key + // handler from stopping all loads in the document, which + // would cause