diff options
Diffstat (limited to 'layout/base/PresShell.cpp')
-rw-r--r-- | layout/base/PresShell.cpp | 12203 |
1 files changed, 12203 insertions, 0 deletions
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp new file mode 100644 index 0000000000..90a9eee411 --- /dev/null +++ b/layout/base/PresShell.cpp @@ -0,0 +1,12203 @@ +/* -*- 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/RefPtr.h" +#include "mozilla/dom/AncestorIterator.h" +#include "mozilla/dom/FontFaceSet.h" +#include "mozilla/dom/ElementBinding.h" +#include "mozilla/dom/LargestContentfulPaint.h" +#include "mozilla/dom/PerformanceMainThread.h" +#include "mozilla/dom/HTMLAreaElement.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CaretAssociationHint.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/Try.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 <algorithm> + +#ifdef XP_WIN +# include "winuser.h" +#endif + +#include "gfxContext.h" +#include "gfxUserFontSet.h" +#include "nsContentList.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "mozilla/dom/BrowserBridgeChild.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/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/DOMIntersectionObserver.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 "nsGlobalWindowOuter.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<nsRange> 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<nsCharPtrHashKey, ReflowCounter> mCounts; + nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter> mIndiFrameCounts; + FILE* mFD; + + bool mDumpFrameCounts; + bool mDumpFrameByFrameCounts; + bool mPaintFrameByFrameCounts; + + bool mCycledOnce; + + // Root Frame for Individual Tracking + nsPresContext* mPresContext; + PresShell* mPresShell; + + // ReflowCountMgr gReflowCountMgr; +}; +#endif +//======================================================================== + +// comment out to hide caret +#define SHOW_CARET + +// The upper bound on the amount of time to spend reflowing, in +// microseconds. When this bound is exceeded and reflow commands are +// still queued up, a reflow event is posted. The idea is for reflow +// to not hog the processor beyond the time specifed in +// gMaxRCProcessingTime. This data member is initialized from the +// layout.reflow.timeslice pref. +#define NS_MAX_REFLOW_TIME 1000000 +static int32_t gMaxRCProcessingTime = -1; + +struct nsCallbackEventRequest { + nsIReflowCallback* callback; + nsCallbackEventRequest* next; +}; + +// ---------------------------------------------------------------------------- +// +// NOTE(emilio): It'd be nice for this to assert that our document isn't in the +// bfcache, but font pref changes don't care about that, and maybe / probably +// shouldn't. +#ifdef DEBUG +# define ASSERT_REFLOW_SCHEDULED_STATE() \ + { \ + if (ObservingLayoutFlushes()) { \ + MOZ_ASSERT( \ + mDocument->GetBFCacheEntry() || \ + mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \ + "Unexpected state"); \ + } else { \ + MOZ_ASSERT( \ + !mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \ + "Unexpected state"); \ + } \ + } +#else +# define ASSERT_REFLOW_SCHEDULED_STATE() /* nothing */ +#endif + +class nsAutoCauseReflowNotifier { + public: + 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> 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<EventStateManager> esm = + aVisitor.mPresContext->EventStateManager(); + esm->DispatchLegacyMouseScrollEvents( + frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus); + } + } + nsIFrame* frame = mPresShell->GetCurrentEventFrame(); + if (!frame && (aVisitor.mEvent->mMessage == eMouseUp || + aVisitor.mEvent->mMessage == eTouchEnd)) { + // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure + // that capturing is released. + frame = mPresShell->GetRootFrame(); + } + if (frame) { + frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(), + &aVisitor.mEventStatus); + } + } + } + + RefPtr<PresShell> mPresShell; +}; + +class nsBeforeFirstPaintDispatcher : public Runnable { + public: + explicit nsBeforeFirstPaintDispatcher(Document* aDocument) + : mozilla::Runnable("nsBeforeFirstPaintDispatcher"), + mDocument(aDocument) {} + + // Fires the "before-first-paint" event so that interested parties (right now, + // the mobile browser) are aware of it. + NS_IMETHOD Run() override { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(ToSupports(mDocument), + "before-first-paint", nullptr); + } + return NS_OK; + } + + private: + RefPtr<Document> mDocument; +}; + +// This is a helper class to track whether the targeted frame is destroyed after +// dispatching pointer events. In that case, we need the original targeted +// content so that we can dispatch the mouse events to it. +class MOZ_STACK_CLASS AutoPointerEventTargetUpdater final { + public: + AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent, + nsIFrame* aFrame, nsIContent** aTargetContent) { + MOZ_ASSERT(aEvent); + if (!aTargetContent || aEvent->mClass != ePointerEventClass) { + // Make the destructor happy. + mTargetContent = nullptr; + return; + } + MOZ_ASSERT(aShell); + MOZ_ASSERT(aFrame); + MOZ_ASSERT(!aFrame->GetContent() || + aShell->GetDocument() == aFrame->GetContent()->OwnerDoc()); + + mShell = aShell; + mWeakFrame = aFrame; + mTargetContent = aTargetContent; + aShell->mPointerEventTarget = aFrame->GetContent(); + } + + ~AutoPointerEventTargetUpdater() { + if (!mTargetContent || !mShell || mWeakFrame.IsAlive()) { + return; + } + mShell->mPointerEventTarget.swap(*mTargetContent); + } + + private: + RefPtr<PresShell> mShell; + AutoWeakFrame mWeakFrame; + nsIContent** mTargetContent; +}; + +bool PresShell::sDisableNonTestMouseEvents = false; +int16_t PresShell::sMouseButtons = MouseButtonsFlag::eNoButtons; + +LazyLogModule PresShell::gLog("PresShell"); + +TimeStamp PresShell::EventHandler::sLastInputCreated; +TimeStamp PresShell::EventHandler::sLastInputProcessed; +StaticRefPtr<Element> PresShell::EventHandler::sLastKeyDownEventTargetElement; + +bool PresShell::sProcessInteractable = false; + +static bool gVerifyReflowEnabled; + +bool PresShell::GetVerifyReflowEnable() { +#ifdef DEBUG + static bool firstTime = true; + if (firstTime) { + firstTime = false; + char* flags = PR_GetEnv("GECKO_VERIFY_REFLOW_FLAGS"); + if (flags) { + bool error = false; + + for (;;) { + char* comma = 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<nsFrameSelection> PresShell::FrameSelection() { + RefPtr<nsFrameSelection> 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), + mActiveSuppressDisplayport(0), + mPresShellId(++sNextPresShellId), + mFontSizeInflationEmPerLine(0), + mFontSizeInflationMinTwips(0), + mFontSizeInflationLineThreshold(0), + mSelectionFlags(nsISelectionDisplay::DISPLAY_TEXT | + nsISelectionDisplay::DISPLAY_IMAGES), + mChangeNestCount(0), + mRenderingStateFlags(RenderingStateFlags::None), + mInFlush(false), + mCaretEnabled(false), + mNeedLayoutFlush(true), + mNeedStyleFlush(true), + mNeedThrottledAnimationFlush(true), + mVisualViewportSizeSet(false), + mDidInitialize(false), + mIsDestroying(false), + mIsReflowing(false), + mIsObservingDocument(false), + mForbiddenToFlush(false), + mIsDocumentGone(false), + mHaveShutDown(false), + mPaintingSuppressed(false), + mLastRootReflowHadUnconstrainedBSize(false), + mShouldUnsuppressPainting(false), + mIgnoreFrameDestruction(false), + mIsActive(true), + mFrozen(false), + mIsFirstPaint(true), + 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), + mIsLastChromeOnlyEscapeKeyConsumed(false), + mHasReceivedPaintMessage(false), + mIsLastKeyDownCanceled(false), + mHasHandledUserInput(false), + mForceDispatchKeyPressEventsForNonPrintableKeys(false), + mForceUseLegacyKeyCodeAndCharCodeValues(false), + mInitializedWithKeyPressEventDispatchingBlacklist(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<ReflowCountMgr>(); + 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<nsCSSFrameConstructor>(mDocument, this); + + mFrameManager = mFrameConstructor.get(); + + // The document viewer owns both view manager and pres shell. + mViewManager->SetPresShell(this); + + // Bind the context to the presentation shell. + // 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<RefPtr<nsPresContext>&>(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(); + + const bool accessibleCaretEnabled = + AccessibleCaretEnabled(mDocument->GetDocShell()); + if (accessibleCaretEnabled) { + // Need to happen before nsFrameSelection has been set up. + mAccessibleCaretEventHub = new AccessibleCaretEventHub(this); + mAccessibleCaretEventHub->Init(); + } + + 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<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(this, "memory-pressure", false); + os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false); + if (XRE_IsParentProcess() && !sProcessInteractable) { + os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false); + } + os->AddObserver(this, "font-info-updated", false); + 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<nsIDocShell> 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<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "memory-pressure"); + os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC); + if (XRE_IsParentProcess()) { + os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored"); + } + os->RemoveObserver(this, "font-info-updated"); + 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<nsFrameSelection> frameSelection = mSelection; + frameSelection->DisconnectFromPresShell(); + } + + 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<WeakFrame*> 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<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers( + ToSupports(mDocument), "author-style-disabled-changed", nullptr); + } + } +} + +bool PresShell::GetAuthorStyleDisabled() const { + return StyleSet()->GetAuthorStyleDisabled(); +} + +void PresShell::AddUserSheet(StyleSheet* aSheet) { + // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt + // ordering. We want this new sheet to come after all the existing stylesheet + // service sheets (which are at the start), but before other user sheets; see + // nsIStyleSheetService.idl for the ordering. + + nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); + nsTArray<RefPtr<StyleSheet>>& userSheets = *sheetService->UserStyleSheets(); + + // Search for the place to insert the new user sheet. Since all of the + // stylesheet service provided user sheets should be at the start of the style + // set's list, and aSheet should be at the end of userSheets. Given that, we + // can find the right place to insert the new sheet based on the length of + // userSheets. + MOZ_ASSERT(aSheet); + MOZ_ASSERT(userSheets.LastElement() == aSheet); + + size_t index = userSheets.Length() - 1; + + // Assert that all of userSheets (except for the last, new element) matches up + // with what's in the style set. + for (size_t i = 0; i < index; ++i) { + MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]); + } + + if (index == static_cast<size_t>(StyleSet()->SheetCount(StyleOrigin::User))) { + StyleSet()->AppendStyleSheet(*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 (NS_WARN_IF(!mDocument)) { + return false; + } + + nsIContent* currentFocus = mDocument->GetUnretargetedFocusedContent( + Document::IncludeChromeOnly::Yes); + if (!currentFocus) { + return false; + } + + // If focus target is an area element with one or more shapes that are + // focusable areas. + if (auto* area = HTMLAreaElement::FromNode(currentFocus)) { + if (nsFocusManager::IsAreaElementFocusable(*area)) { + 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<nsPIDOMWindowOuter> 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<nsFrameSelection>(&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<nsFrameSelection> 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<nsFrameSelection> 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<nsFrameSelection> frameSelection = mSelection; + frameSelection->SetDisplaySelection(aToggle); + return NS_OK; +} + +NS_IMETHODIMP +PresShell::GetDisplaySelection(int16_t* aToggle) { + RefPtr<nsFrameSelection> frameSelection = mSelection; + *aToggle = frameSelection->GetDisplaySelection(); + return NS_OK; +} + +NS_IMETHODIMP +PresShell::GetSelectionFromScript(RawSelectionType aRawSelectionType, + Selection** aSelection) { + if (!aSelection || !mSelection) return NS_ERROR_NULL_POINTER; + + RefPtr<nsFrameSelection> frameSelection = mSelection; + RefPtr<Selection> selection = + frameSelection->GetSelection(ToSelectionType(aRawSelectionType)); + + if (!selection) { + return NS_ERROR_INVALID_ARG; + } + + selection.forget(aSelection); + return NS_OK; +} + +Selection* PresShell::GetSelection(RawSelectionType aRawSelectionType) { + if (!mSelection) { + return nullptr; + } + + RefPtr<nsFrameSelection> frameSelection = mSelection; + return frameSelection->GetSelection(ToSelectionType(aRawSelectionType)); +} + +Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) { + if (!mSelection) { + return nullptr; + } + + RefPtr<nsFrameSelection> frameSelection = mSelection; + return frameSelection->GetSelection(aSelectionType); +} + +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<nsFrameSelection> frameSelection = mSelection; + return frameSelection->ScrollSelectionIntoView( + ToSelectionType(aRawSelectionType), aRegion, aFlags); +} + +NS_IMETHODIMP +PresShell::RepaintSelection(RawSelectionType aRawSelectionType) { + if (!mSelection) { + return NS_ERROR_NULL_POINTER; + } + + if (MOZ_UNLIKELY(mIsDestroying)) { + return NS_OK; + } + + RefPtr<nsFrameSelection> frameSelection = mSelection; + return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType)); +} + +// Make shell be a document observer +void PresShell::BeginObservingDocument() { + if (mDocument && !mIsDestroying) { + mIsObservingDocument = true; + if (mIsDocumentGone) { + NS_WARNING( + "Adding a presshell that was disconnected from the document " + "as a document observer? Sounds wrong..."); + mIsDocumentGone = false; + } + } +} + +// Make shell stop being a document observer +void PresShell::EndObservingDocument() { + // XXXbz do we need to tell the frame constructor that the document + // is gone, perhaps? Except for printing it's NOT gone, sometimes. + mIsDocumentGone = true; + mIsObservingDocument = false; +} + +#ifdef DEBUG_kipp +char* nsPresShell_ReflowStackPointerTop; +#endif + +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<PresShell> self = static_cast<PresShell*>(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<PresShell> kungFuDeathGrip(this); + + RecomputeFontSizeInflationEnabled(); + MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying); + + // Ensure the pres context doesn't think it has changed, since we haven't even + // started layout. This avoids spurious restyles / reflows afterwards. + // + // Note that this is very intentionally before setting mDidInitialize so it + // doesn't notify the document, or run media query change events. + mPresContext->FlushPendingMediaFeatureValuesChanged(); + MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying); + + mDidInitialize = true; + +#ifdef DEBUG + if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) { + if (mDocument) { + nsIURI* uri = mDocument->GetDocumentURI(); + if (uri) { + printf("*** PresShell::Initialize (this=%p, url='%s')\n", (void*)this, + uri->GetSpecOrDefault().get()); + } + } + } +#endif + + // Get the root frame from the frame manager + // XXXbz it would be nice to move this somewhere else... like frame manager + // Init(), say. But we need to make sure our views are all set up by the + // time we do this! + nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); + NS_ASSERTION(!rootFrame, "How did that happen, exactly?"); + + if (!rootFrame) { + nsAutoScriptBlocker scriptBlocker; + rootFrame = mFrameConstructor->ConstructRootFrame(); + mFrameConstructor->SetRootFrame(rootFrame); + } + + NS_ENSURE_STATE(!mHaveShutDown); + + if (!rootFrame) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (Element* root = mDocument->GetRootElement()) { + { + nsAutoCauseReflowNotifier reflowNotifier(this); + // Have the style sheet processor construct frame for the root + // content object down + mFrameConstructor->ContentInserted( + root, 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(GetMainThreadSerialEventTarget()); + 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<PresShell> 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<nsPIDOMWindowOuter> window = mDocument->GetWindow()) { + // MOZ_KnownLive due to bug 1506441 + EventDispatcher::Dispatch(MOZ_KnownLive(nsGlobalWindowOuter::Cast(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); + } + } +} + +already_AddRefed<nsCaret> PresShell::GetCaret() const { + RefPtr<nsCaret> caret = mCaret; + return caret.forget(); +} + +already_AddRefed<AccessibleCaretEventHub> +PresShell::GetAccessibleCaretEventHub() const { + RefPtr<AccessibleCaretEventHub> eventHub = mAccessibleCaretEventHub; + return eventHub.forget(); +} + +void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; } + +void PresShell::RestoreCaret() { mCaret = mOriginalCaret; } + +NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) { + bool oldEnabled = mCaretEnabled; + + mCaretEnabled = aInEnable; + + if (mCaretEnabled != oldEnabled) { + MOZ_ASSERT(mCaret); + if (mCaret) { + mCaret->SetVisible(mCaretEnabled); + } + } + + return NS_OK; +} + +NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly) { + if (mCaret) mCaret->SetCaretReadOnly(aReadOnly); + return NS_OK; +} + +NS_IMETHODIMP PresShell::GetCaretEnabled(bool* aOutEnabled) { + NS_ENSURE_ARG_POINTER(aOutEnabled); + *aOutEnabled = mCaretEnabled; + return NS_OK; +} + +NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility) { + if (mCaret) mCaret->SetVisibilityDuringSelection(aVisibility); + return NS_OK; +} + +NS_IMETHODIMP PresShell::GetCaretVisible(bool* aOutIsVisible) { + *aOutIsVisible = false; + if (mCaret) { + *aOutIsVisible = mCaret->IsVisible(); + } + return NS_OK; +} + +NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t 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<nsFrameSelection> frameSelection = mSelection; + return frameSelection->PhysicalMove(aDirection, aAmount, aExtend); +} + +NS_IMETHODIMP +PresShell::CharacterMove(bool aForward, bool aExtend) { + RefPtr<nsFrameSelection> frameSelection = mSelection; + return frameSelection->CharacterMove(aForward, aExtend); +} + +NS_IMETHODIMP +PresShell::WordMove(bool aForward, bool aExtend) { + RefPtr<nsFrameSelection> frameSelection = mSelection; + nsresult result = frameSelection->WordMove(aForward, aExtend); + // if we can't go down/up any more we must then move caret completely to + // end/beginning respectively. + if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend); + return result; +} + +NS_IMETHODIMP +PresShell::LineMove(bool aForward, bool aExtend) { + RefPtr<nsFrameSelection> frameSelection = mSelection; + nsresult result = frameSelection->LineMove(aForward, aExtend); + // if we can't go down/up any more we must then move caret completely to + // end/beginning respectively. + if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend); + return result; +} + +NS_IMETHODIMP +PresShell::IntraLineMove(bool aForward, bool aExtend) { + RefPtr<nsFrameSelection> frameSelection = mSelection; + return frameSelection->IntraLineMove(aForward, aExtend); +} + +NS_IMETHODIMP +PresShell::PageMove(bool aForward, bool aExtend) { + nsIFrame* frame = nullptr; + if (!aExtend) { + frame = do_QueryFrame(GetScrollableFrameToScroll(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<nsFrameSelection> 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<nsFrameSelection> frameSelection = mSelection; + nsIContent* limiter = frameSelection->GetAncestorLimiter(); + nsIFrame* frame = limiter ? limiter->GetPrimaryFrame() + : FrameConstructor()->GetRootElementFrame(); + if (!frame) return NS_ERROR_FAILURE; + nsIFrame::CaretPosition pos = frame->GetExtremeCaretPosition(!aForward); + + 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 ? CaretAssociationHint::After : CaretAssociationHint::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 { + return mFrameConstructor->GetCanvasFrame(); +} + +void PresShell::RestoreRootScrollPosition() { + nsIScrollableFrame* scrollableFrame = GetRootScrollFrameAsScrollable(); + if (scrollableFrame) { + scrollableFrame->ScrollToRestoredPosition(); + } +} + +void PresShell::MaybeReleaseCapturingContent() { + RefPtr<nsFrameSelection> frameSelection = FrameSelection(); + if (frameSelection) { + frameSelection->SetDragState(false); + } + if (sCapturingContentInfo.mContent && + sCapturingContentInfo.mContent->OwnerDoc() == mDocument) { + PresShell::ReleaseCapturingContent(); + } +} + +void PresShell::BeginLoad(Document* aDocument) { + mDocumentLoading = true; + + gfxTextPerfMetrics* tp = nullptr; + if (mPresContext) { + tp = mPresContext->GetTextPerfMetrics(); + } + + bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug); + if (shouldLog || tp) { + mLoadBegin = TimeStamp::Now(); + } + + if (shouldLog) { + nsIURI* uri = mDocument->GetDocumentURI(); + MOZ_LOG(gLog, LogLevel::Debug, + ("(presshell) %p load begin [%s]\n", this, + uri ? uri->GetSpecOrDefault().get() : "")); + } +} + +void PresShell::EndLoad(Document* aDocument) { + MOZ_ASSERT(aDocument == mDocument, "Wrong document"); + + RestoreRootScrollPosition(); + + mDocumentLoading = false; +} + +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<nsIFrame*, 4> subtrees; + subtrees.AppendElement(aFrame); + + do { + nsIFrame* subtreeRoot = subtrees.PopLastElement(); + + // Grab |wasDirty| now so we can go ahead and update the bits on + // subtreeRoot. + bool wasDirty = 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<nsIFrame*, 32> 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<nsPlaceholderFrame*>(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<nsIContent> PresShell::GetContentForScrolling() const { + if (nsCOMPtr<nsIContent> focused = GetFocusedContentInOurWindow()) { + return focused.forget(); + } + return GetSelectedContentForScrolling(); +} + +already_AddRefed<nsIContent> PresShell::GetSelectedContentForScrolling() const { + nsCOMPtr<nsIContent> selectedContent; + if (mSelection) { + Selection* domSelection = mSelection->GetSelection(SelectionType::eNormal); + if (domSelection) { + selectedContent = + nsIContent::FromNodeOrNull(domSelection->GetFocusNode()); + } + } + return selectedContent.forget(); +} + +nsIScrollableFrame* PresShell::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<nsIContent> 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 <area> 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<nsDocShell>& aContainer) { + mForwardingContainer = aContainer; +} + +void PresShell::ClearFrameRefs(nsIFrame* aFrame) { + mPresContext->EventStateManager()->ClearFrameRefs(aFrame); + + AutoWeakFrame* weakFrame = mAutoWeakFrames; + while (weakFrame) { + AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame(); + if (weakFrame->GetFrame() == aFrame) { + // This removes weakFrame from mAutoWeakFrames. + weakFrame->Clear(this); + } + weakFrame = prev; + } + + AutoTArray<WeakFrame*, 4> toRemove; + for (WeakFrame* weakFrame : mWeakFrames) { + if (weakFrame->GetFrame() == aFrame) { + toRemove.AppendElement(weakFrame); + } + } + for (WeakFrame* weakFrame : toRemove) { + weakFrame->Clear(this); + } +} + +UniquePtr<gfxContext> 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<EventStateManager> 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<Element> 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<nsRange> jumpToRange = nsRange::Create(mDocument); + nsCOMPtr<nsIContent> 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<nsIFocusManager> fm = nsFocusManager::GetFocusManager()) { + if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) { + // Now focus the document itself if focus is on an element within it. + nsCOMPtr<mozIDOMWindowProxy> focusedWindow; + fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); + if (SameCOMIdentity(win, focusedWindow)) { + fm->ClearFocus(focusedWindow); + } + } + } + + // If the target is an animation element, activate the animation + if (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<nsIContent> 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->IsLineParticipant() && !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: + if (aLineSize > (aRectMax - aRectMin)) { + // If the line size is greater than the size of the rect + // to scroll into view, do not use the line size to determine + // if we need to scroll. + aLineSize = 0; + } + + // 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<Document> 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<PresShell::ScrollIntoViewData>))) { + mContentToScrollTo = nullptr; + } + + // If the target frame has an ancestor of a `content-visibility: auto` + // element ensure that it is laid out, so that the boundary rectangle is + // correct. + // Additionally, ensure that all ancestor elements with 'content-visibility: + // auto' are set to 'visible'. so that they are laid out as visible before + // scrolling, improving the accuracy of the scroll position, especially when + // the scroll target is within the overflow area. And here invoking + // 'SetTemporarilyVisibleForScrolledIntoViewDescendant' would make the + // intersection observer knows that it should generate entries for these + // c-v:auto ancestors, so that the content relevancy could be checked again + // after scrolling. https://drafts.csswg.org/css-contain-2/#cv-notes + bool reflowedForHiddenContent = false; + if (mContentToScrollTo) { + if (nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame()) { + bool hasContentVisibilityAutoAncestor = false; + auto* ancestor = frame->GetClosestContentVisibilityAncestor( + nsIFrame::IncludeContentVisibility::Auto); + while (ancestor) { + if (auto* element = Element::FromNodeOrNull(ancestor->GetContent())) { + hasContentVisibilityAutoAncestor = true; + element->SetTemporarilyVisibleForScrolledIntoViewDescendant(true); + element->SetVisibleForContentVisibility(true); + } + ancestor = ancestor->GetClosestContentVisibilityAncestor( + nsIFrame::IncludeContentVisibility::Auto); + } + if (hasContentVisibilityAutoAncestor) { + UpdateHiddenContentInForcedLayout(frame); + // TODO: There might be the other already scheduled relevancy updates, + // other than caused be scrollIntoView. + UpdateContentRelevancyImmediately(ContentRelevancyReason::Visible); + reflowedForHiddenContent = ReflowForHiddenContentIfNeeded(); + } + } + } + + if (!reflowedForHiddenContent) { + // 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<ScrollIntoViewData*>( + 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<nsRect>& 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<nsIScrollableFrame*>(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<nsIDocShell> 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<nsViewManager> 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<nsIDocShell> docShell(mPresContext->GetDocShell()); + if (!docShell) return NS_ERROR_FAILURE; + + nsCOMPtr<nsILayoutHistoryState> historyState; + docShell->GetLayoutHistoryState(getter_AddRefs(historyState)); + if (!historyState) { + // Create the document state object + historyState = NS_NewLayoutHistoryState(); + docShell->SetLayoutHistoryState(historyState); + } + + *aState = historyState; + NS_IF_ADDREF(*aState); + + // Capture frame state for the entire frame hierarchy + nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); + if (!rootFrame) return NS_OK; + + mFrameConstructor->CaptureFrameState(rootFrame, historyState); + + return NS_OK; +} + +void PresShell::ScheduleBeforeFirstPaint() { + if (!mDocument->IsResourceDoc()) { + // Notify observers that a new page is about to be drawn. Execute this + // as soon as it is safe to run JS, which is guaranteed to be before we + // go back to the event loop and actually draw the page. + MOZ_LOG(gLog, LogLevel::Debug, + ("PresShell::ScheduleBeforeFirstPaint this=%p", this)); + + nsContentUtils::AddScriptRunner( + new nsBeforeFirstPaintDispatcher(mDocument)); + } +} + +void PresShell::UnsuppressAndInvalidate() { + // Note: We ignore the EnsureVisible check for resource documents, because + // they won't have a docshell, so they'll always fail EnsureVisible. + if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) || + mHaveShutDown) { + // No point; we're about to be torn down anyway. + return; + } + + ScheduleBeforeFirstPaint(); + + 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<PresShell> kungFuDeathGrip = this; + + /** + * VERY IMPORTANT: If you add some sort of new flushing to this + * method, make sure to add the relevant SetNeedLayoutFlush or + * SetNeedStyleFlush calls on the shell. + */ + FlushType flushType = aFlush.mFlushType; + + // 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<nsViewManager> viewManager = mViewManager; + bool didStyleFlush = false; + bool didLayoutFlush = false; + if (isSafeToFlush) { + // Record that we are in a flush, so that our optimization in + // Document::FlushPendingNotifications doesn't skip any re-entrant + // calls to us. Otherwise, we might miss some needed flushes, since + // we clear mNeedStyleFlush / mNeedLayoutFlush here at the top of + // the function but we might not have done the work yet. + AutoRestore<bool> guard(mInFlush); + mInFlush = true; + + // We need to make sure external resource documents are flushed too (for + // example, svg filters that reference a filter in an external document + // need the frames in the external document to be constructed for the + // filter to work). We only need external resources to be flushed when the + // main document is flushing >= FlushType::Frames, so we flush external + // resources here instead of Document::FlushPendingNotifications. + mDocument->FlushExternalResources(flushType); + + // Force flushing of any pending content notifications that might have + // queued up while our event was pending. That will ensure that we don't + // construct frames for content right now that's still waiting to be + // notified on, + mDocument->FlushPendingNotifications(FlushType::ContentAndNotify); + + mDocument->UpdateSVGUseElementShadowTrees(); + + // Process pending restyles, since any flush of the presshell wants + // up-to-date style data. + if (MOZ_LIKELY(!mIsDestroying)) { + viewManager->FlushDelayedResize(); + 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<uint64_t> innerWindowID; + if (auto* window = mDocument->GetInnerWindow()) { + innerWindowID = Some(window->WindowID()); + } + AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause), + innerWindowID); + PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> 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<int32_t>(aRange->StartOffset()), + frameStartOffset) + : frameStartOffset; + int32_t hilightEnd = + atEnd ? std::min(static_cast<int32_t>(aRange->EndOffset()), + frameEndOffset) + : frameEndOffset; + if (hilightStart < hilightEnd) { + // determine the location of the start and end edges of the range. + nsPoint startPoint, endPoint; + frame->GetPointFromOffset(hilightStart, &startPoint); + frame->GetPointFromOffset(hilightEnd, &endPoint); + + // The clip rectangle is determined by taking the the start and + // end points of the range, offset from the reference frame. + // Because of rtl, the end point may be to the left of (or above, + // in vertical mode) the start point, so x (or y) is set to the + // lower of the values. + nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize()); + if (frame->GetWritingMode().IsVertical()) { + nscoord y = std::min(startPoint.y, endPoint.y); + textRect.y += y; + textRect.height = std::max(startPoint.y, endPoint.y) - y; + } else { + nscoord x = std::min(startPoint.x, endPoint.x); + textRect.x += x; + textRect.width = std::max(startPoint.x, endPoint.x) - x; + } + surfaceRect.UnionRect(surfaceRect, textRect); + + const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot(); + + DisplayItemClip newClip; + newClip.SetTo(textRect); + + const DisplayItemClipChain* newClipChain = + aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr); + + i->IntersectClip(aBuilder, newClipChain, true); + itemToInsert = i; + } + } + // Don't try to descend into subdocuments. + // If this ever changes we'd need to add handling for subdocuments with + // different zoom levels. + else if (content->GetUncomposedDoc() == + aRange->GetStartContainer()->GetUncomposedDoc()) { + // if the node is within the range, append it to the temporary list + bool before, after; + nsresult rv = + 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 <stdio.h> + +static bool gDumpRangePaintList = false; +#endif + +UniquePtr<RangePaintInfo> 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<RangePaintInfo>(aRange, ancestorFrame); + info->mBuilder.SetIncludeAllOutOfFlows(); + if (aForPrimarySelection) { + info->mBuilder.SetSelectedFramesOnly(); + } + info->mBuilder.EnterPresShell(ancestorFrame); + + ContentSubtreeIterator subtreeIter; + nsresult rv = subtreeIter.Init(aRange); + if (NS_FAILED(rv)) { + return nullptr; + } + + auto BuildDisplayListForNode = [&](nsINode* aNode) { + if (MOZ_UNLIKELY(!aNode->IsContent())) { + return; + } + nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); + // XXX deal with frame being null due to display:contents + for (; frame; + frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) { + info->mBuilder.SetVisibleRect(frame->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<nsINode> 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<nsDisplayAsyncZoom>(&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<SourceSurface> PresShell::PaintRangePaintInfo( + const nsTArray<UniquePtr<RangePaintInfo>>& aItems, Selection* aSelection, + const Maybe<CSSIntRegion>& aRegion, nsRect aArea, + const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect, + RenderImageFlags aFlags) { + nsPresContext* pc = GetPresContext(); + if (!pc || aArea.width == 0 || aArea.height == 0) return nullptr; + + // use the rectangle to create the surface + 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<RangePaintInfo>& 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<DrawTarget> dt = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8); + if (!dt || !dt->IsValid()) { + return nullptr; + } + + gfxContext ctx(dt); + + if (aRegion) { + RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING); + + // Convert aRegion from CSS pixels to dev pixels + nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel()) + .ToOutsidePixels(pc->AppUnitsPerDevPixel()); + for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + + builder->MoveTo(rect.TopLeft()); + builder->LineTo(rect.TopRight()); + builder->LineTo(rect.BottomRight()); + builder->LineTo(rect.BottomLeft()); + builder->LineTo(rect.TopLeft()); + } + + RefPtr<Path> path = builder->Finish(); + ctx.Clip(path); + } + + gfxMatrix initialTM = ctx.CurrentMatrixDouble(); + + if (resize) { + initialTM.PreScale(scale, scale); + } + + // translate so that points are relative to the surface area + gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint( + -aArea.TopLeft(), pc->AppUnitsPerDevPixel()); + initialTM.PreTranslate(surfaceOffset); + + // temporarily hide the selection so that text is drawn normally. If a + // selection is being rendered, use that, otherwise use the presshell's + // selection. + RefPtr<nsFrameSelection> frameSelection; + if (aSelection) { + frameSelection = aSelection->GetFrameSelection(); + } else { + frameSelection = FrameSelection(); + } + int16_t oldDisplaySelection = frameSelection->GetDisplaySelection(); + frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); + + // next, paint each range in the selection + for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) { + // the display lists paint relative to the offset from the reference + // frame, so account for that translation too: + gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint( + rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel()); + ctx.SetMatrixDouble(initialTM.PreTranslate(rootOffset)); + aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y); + nsRegion visible(aArea); + 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<SourceSurface> PresShell::RenderNode( + nsINode* aNode, const Maybe<CSSIntRegion>& aRegion, + const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect, + RenderImageFlags aFlags) { + // area will hold the size of the surface needed to draw the node, measured + // from the root frame. + nsRect area; + nsTArray<UniquePtr<RangePaintInfo>> rangeItems; + + // nothing to draw if the node isn't in a document + if (!aNode->IsInComposedDoc()) { + return nullptr; + } + + RefPtr<nsRange> range = nsRange::Create(aNode); + IgnoredErrorResult rv; + range->SelectNode(*aNode, rv); + if (rv.Failed()) { + return nullptr; + } + + UniquePtr<RangePaintInfo> 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<CSSIntRegion> region = aRegion; + if (region) { + // combine the area with the supplied region + CSSIntRect rrectPixels = region->GetBounds(); + + nsRect rrect = ToAppUnits(rrectPixels, AppUnitsPerCSSPixel()); + area.IntersectRect(area, rrect); + + nsPresContext* pc = GetPresContext(); + if (!pc) return nullptr; + + // move the region so that it is offset from the topleft corner of the + // surface + region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x), + -nsPresContext::AppUnitsToIntCSSPixels(area.y)); + } + + return PaintRangePaintInfo(rangeItems, nullptr, region, area, aPoint, + aScreenRect, aFlags); +} + +already_AddRefed<SourceSurface> PresShell::RenderSelection( + Selection* aSelection, const LayoutDeviceIntPoint aPoint, + LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) { + // area will hold the size of the surface needed to draw the selection, + // measured from the root frame. + nsRect area; + nsTArray<UniquePtr<RangePaintInfo>> rangeItems; + + // iterate over each range and collect them into the rangeItems array. + // This is done so that the size of selection can be determined so as + // to allocate a surface area + 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<nsRange> range = aSelection->GetRangeAt(r); + + UniquePtr<RangePaintInfo> 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<nsDisplayCanvasBackgroundColor*>(i); + bg->SetExtraBackgroundColor(aColor); + return true; + } + + const bool isBlendContainer = + type == DisplayItemType::TYPE_BLEND_CONTAINER || + type == DisplayItemType::TYPE_TABLE_BLEND_CONTAINER; + + nsDisplayList* sublist = i->GetSameCoordinateSystemChildren(); + if (sublist && !(isBlendContainer && !aCSSBackgroundColor) && + AddCanvasBackgroundColor(sublist, aCanvasFrame, aColor, + aCSSBackgroundColor)) { + return true; + } + } + return false; +} + +void PresShell::AddCanvasBackgroundColorItem(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + nsIFrame* aFrame, + const nsRect& aBounds, + nscolor aBackstopColor) { + if (aBounds.IsEmpty()) { + return; + } + const bool isViewport = aFrame->IsViewportFrame(); + nscolor canvasColor; + if (isViewport) { + canvasColor = mCanvasBackground.mViewportColor; + } else if (aFrame->IsPageContentFrame()) { + canvasColor = mCanvasBackground.mPageColor; + } else { + // 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. + return; + } + const nscolor bgcolor = NS_ComposeColors(aBackstopColor, canvasColor); + 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 (isViewport) { + if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) { + 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, mCanvasBackground.mCSSSpecified); + } + } + } + + // 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<nsDisplaySolidColor>( + aBuilder, aFrame, aBounds, bgcolor); + if (addedScrollingBackgroundColor && isRootContentDocumentCrossProcess) { + item->SetIsCheckerboardBackground(); + } + AddDisplayItemToBottom(aBuilder, aList, item); + } +} + +bool PresShell::IsTransparentContainerElement() const { + if (mDocument->IsInitialDocument()) { + switch (StaticPrefs::layout_css_initial_document_transparency()) { + case 3: + return true; + case 2: + if (!mDocument->IsTopLevelContentDocument()) { + return true; + } + [[fallthrough]]; + case 1: + if (mDocument->IsLikelyContentInaccessibleTopLevelAboutBlank()) { + return true; + } + [[fallthrough]]; + default: + break; + } + } + + nsPresContext* pc = GetPresContext(); + if (!pc->IsRootContentDocumentCrossProcess()) { + if (mDocument->IsInChromeDocShell()) { + 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 = mDocument->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() { + mCanvasBackground = ComputeCanvasBackground(); +} + +struct SingleCanvasBackground { + nscolor mColor = 0; + bool mCSSSpecified = false; +}; + +static SingleCanvasBackground ComputeSingleCanvasBackground(nsIFrame* aCanvas) { + MOZ_ASSERT(aCanvas->IsCanvasFrame()); + const nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aCanvas); + nscolor color = NS_RGBA(0, 0, 0, 0); + bool drawBackgroundImage = false; + bool drawBackgroundColor = false; + if (!bgFrame->IsThemed()) { + // Ignore the CSS background-color if -moz-appearance is used. + color = nsCSSRendering::DetermineBackgroundColor( + aCanvas->PresContext(), bgFrame->Style(), aCanvas, drawBackgroundImage, + drawBackgroundColor); + } + return {color, drawBackgroundColor}; +} + +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) { + nscolor color = GetDefaultBackgroundColorToDraw(); + // 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 {color, color, false}; + } + + auto viewportBg = ComputeSingleCanvasBackground(canvas); + if (!IsTransparentContainerElement()) { + viewportBg.mColor = + NS_ComposeColors(GetDefaultBackgroundColorToDraw(), viewportBg.mColor); + } + nscolor pageColor = viewportBg.mColor; + nsCanvasFrame* docElementCb = + mFrameConstructor->GetDocElementContainingBlock(); + if (canvas != docElementCb) { + // We're in paged mode / print / print-preview, and just computed the "root" + // canvas background. Compute the doc element containing block background + // too. + MOZ_ASSERT(mPresContext->IsRootPaginatedDocument()); + pageColor = ComputeSingleCanvasBackground(docElementCb).mColor; + } + return {viewportBg.mColor, pageColor, viewportBg.mCSSSpecified}; +} + +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<nsSynthMouseMoveEvent> 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<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (dragSession) { + mSynthMouseMoveEvent.Forget(); + return; + } + + // allow new event to be posted while handling this one only if the + // source of the event is a scroll (to prevent infinite reflow loops) + if (aFromScroll) { + mSynthMouseMoveEvent.Forget(); + } + + nsView* rootView = mViewManager ? mViewManager->GetRootView() : nullptr; + if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) || + !rootView || !rootView->HasWidget() || !mPresContext) { + mSynthMouseMoveEvent.Forget(); + return; + } + + NS_ASSERTION(mPresContext->IsRoot(), "Only a root pres shell should be here"); + + // Hold a ref to ourselves so DispatchEvent won't destroy us (since + // we need to access members after we call DispatchEvent). + RefPtr<PresShell> kungFuDeathGrip(this); + +#ifdef DEBUG_MOUSE_LOCATION + printf("[ps=%p]synthesizing mouse move to (%d,%d)\n", this, mMouseLocation.x, + mMouseLocation.y); +#endif + + int32_t APD = mPresContext->AppUnitsPerDevPixel(); + + // We need a widget to put in the event we are going to dispatch so we look + // for a view that has a widget and the mouse location is over. We first look + // for floating views, if there isn't one we use the root view. |view| holds + // that view. + nsView* view = nullptr; + + // The appunits per devpixel ratio of |view|. + int32_t viewAPD; + + // mRefPoint will be mMouseLocation relative to the widget of |view|, the + // widget we will put in the event we dispatch, in viewAPD appunits + nsPoint refpoint(0, 0); + + // We always dispatch the event to the pres shell that contains the view that + // the mouse is over. pointVM is the VM of that pres shell. + nsViewManager* pointVM = nullptr; + + 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<nsLayoutUtils::TransformResult> 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); + + // If the last cursor location was set by a synthesized mouse event for tests, + // running test should expect a restyle or a DOM mutation under the cursor may + // cause mouse boundary events in a remote process if the cursor is over a + // remote content. Therefore, the events should not be ignored by + // PresShell::HandleEvent in the remote process. So we need to mark the + // synthesized eMouseMove as "synthesized for tests". + event.mFlags.mIsSynthesizedForTests = + mMouseLocationWasSetBySynthesizedMouseEventForTests; + + event.mRefPoint = + LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD); + event.mButtons = PresShell::sMouseButtons; + // XXX set event.mModifiers ? + // XXX mnakano I think that we should get the latest information from widget. + + if (BrowserBridgeChild* bbc = GetChildBrowser(pointView)) { + // If we have a BrowserBridgeChild, we're going to be dispatching this + // mouse event into an OOP iframe of the current document. + event.mLayersId = bbc->GetLayersId(); + bbc->SendDispatchSynthesizedMouseEvent(event); + } else if (RefPtr<PresShell> presShell = pointVM->GetPresShell()) { + // Since this gets run in a refresh tick there isn't an InputAPZContext on + // the stack from the nsBaseWidget. We need to simulate one with at least + // the correct target guid, so that the correct callback transform gets + // applied if this event goes to a child process. The input block id is set + // to 0 because this is a synthetic event which doesn't really belong to any + // input block. Same for the APZ response field. + InputAPZContext apzContext(mMouseEventTargetGuid, 0, nsEventStatus_eIgnore); + presShell->DispatchSynthMouseMove(&event); + } + + if (!aFromScroll) { + mSynthMouseMoveEvent.Forget(); + } +} + +/* static */ +void PresShell::MarkFramesInListApproximatelyVisible( + const nsDisplayList& aList) { + for (nsDisplayItem* item : aList) { + nsDisplayList* sublist = item->GetChildren(); + if (sublist) { + MarkFramesInListApproximatelyVisible(*sublist); + continue; + } + + nsIFrame* frame = item->Frame(); + MOZ_ASSERT(frame); + + if (!frame->TrackingVisibility()) { + continue; + } + + // Use the presshell containing the frame. + PresShell* presShell = frame->PresShell(); + MOZ_ASSERT(!presShell->AssumeAllFramesVisible()); + if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) { + // The frame was added to mApproximatelyVisibleFrames, so increment its + // visible count. + frame->IncApproximateVisibleCount(); + } + } +} + +/* static */ +void PresShell::DecApproximateVisibleCount( + VisibleFrames& aFrames, const Maybe<OnNonvisible>& aNonvisibleAction + /* = Nothing() */) { + for (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<OnNonvisible>& 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<nsRect> 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<nsRunnableMethod<PresShell>> event = + NewRunnableMethod("PresShell::UpdateApproximateFrameVisibility", this, + &PresShell::UpdateApproximateFrameVisibility); + nsresult rv = mDocument->Dispatch(do_AddRef(event)); + + if (NS_SUCCEEDED(rv)) { + mUpdateApproximateFrameVisibilityEvent = std::move(event); + } +} + +void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) { + if (!aFrame->TrackingVisibility()) { + return; + } + + if (AssumeAllFramesVisible()) { + aFrame->IncApproximateVisibleCount(); + return; + } + +#ifdef DEBUG + // Make sure it's in this pres shell. + nsCOMPtr<nsIContent> content = aFrame->GetContent(); + if (content) { + PresShell* presShell = content->OwnerDoc()->GetPresShell(); + MOZ_ASSERT(!presShell || presShell == this, "wrong shell"); + } +#endif + + if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) { + // We inserted a new entry. + aFrame->IncApproximateVisibleCount(); + } +} + +void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) { +#ifdef DEBUG + // Make sure it's in this pres shell. + nsCOMPtr<nsIContent> content = aFrame->GetContent(); + if (content) { + PresShell* presShell = content->OwnerDoc()->GetPresShell(); + MOZ_ASSERT(!presShell || presShell == this, "wrong shell"); + } +#endif + + if (AssumeAllFramesVisible()) { + MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0, + "Shouldn't have any frames in the table"); + return; + } + + if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) && + aFrame->TrackingVisibility()) { + // aFrame was in the hashtable, and we're still tracking its visibility, + // so we need to decrement its visible count. + aFrame->DecApproximateVisibleCount(); + } +} + +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<js::AutoAssertNoContentJS> nojs; + + // On Android, Flash can call into content JS during painting, so we can't + // assert there. However, we don't rely on this assertion on Android because + // we don't paint while JS is running. +#if !defined(MOZ_WIDGET_ANDROID) + if (!(aFlags & 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, mCanvasBackground.mViewportColor); + + 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<nsIContent> PresShell::GetEventTargetContent( + WidgetEvent* aEvent) { + nsCOMPtr<nsIContent> 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<nsPIDOMWindowOuter> PresShell::GetRootWindow() { + nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow(); + if (window) { + nsCOMPtr<nsPIDOMWindowOuter> 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<PresShell> parentPresShell = GetParentPresShellForEventHandling(); + NS_ENSURE_TRUE(parentPresShell, nullptr); + return parentPresShell->GetRootWindow(); +} + +already_AddRefed<nsPIDOMWindowOuter> +PresShell::GetFocusedDOMWindowInOurWindow() { + nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow(); + NS_ENSURE_TRUE(rootWindow, nullptr); + nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; + nsFocusManager::GetFocusedDescendant(rootWindow, + nsFocusManager::eIncludeAllDescendants, + getter_AddRefs(focusedWindow)); + return focusedWindow.forget(); +} + +already_AddRefed<nsIContent> PresShell::GetFocusedContentInOurWindow() const { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && mDocument) { + RefPtr<Element> focusedElement; + fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr, + getter_AddRefs(focusedElement)); + return focusedElement.forget(); + } + return nullptr; +} + +already_AddRefed<PresShell> PresShell::GetParentPresShellForEventHandling() { + if (!mPresContext) { + return nullptr; + } + + // Now, find the parent pres shell and send the event there + RefPtr<nsDocShell> 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<BrowsingContext> parentBC; + if (XRE_IsParentProcess()) { + parentBC = bc->Canonical()->GetParentCrossChromeBoundary(); + } else { + parentBC = bc->GetParent(); + } + + RefPtr<nsIDocShell> parentDocShell = + parentBC ? parentBC->GetDocShell() : nullptr; + if (!parentDocShell) { + return nullptr; + } + + RefPtr<PresShell> 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<PresShell> 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<dom::Touch> oldTouch = + TouchManager::GetCapturedTouch(touch->Identifier()); + if (!oldTouch) { + return nullptr; + } + + nsCOMPtr<nsIContent> 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); + // Running tests must not expect that some mouse boundary events are fired + // when something occurs in the parent process, e.g., when a popup is + // opened/closed at the last mouse cursor position in the parent process (the + // position may be different from the position which stored in this process). + // Therefore, let's ignore synthesized mouse events coming form another + // process if and only if they are not caused by the API. + if (aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() && + !aGUIEvent->mFlags.mIsSynthesizedForTests && + MouseLocationWasSetBySynthesizedMouseEventForTests()) { + switch (aGUIEvent->mMessage) { + // Synthesized eMouseMove will case mouse boundary events like mouseover, + // mouseout, and :hover state is changed at dispatching the events. + case eMouseMove: + // eMouseExitFromWidget comes from the parent process if the cursor + // crosses a puppet widget boundary. Then, the event will be handled as a + // synthesized eMouseMove in this process and may cause unexpected + // `mouseout` and `mouseleave`. + case eMouseExitFromWidget: + // eMouseEnterIntoWidget causes updating the hover state under the event + // position which may be different from the last cursor position + // synthesized in this process. + case eMouseEnterIntoWidget: + if (!aGUIEvent->AsMouseEvent()->IsReal()) { + return NS_OK; + } + break; + default: + break; + } + } + + // 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; + } + + if (mPresContext) { + switch (aGUIEvent->mMessage) { + case eMouseMove: + if (!aGUIEvent->AsMouseEvent()->IsReal()) { + break; + } + [[fallthrough]]; + case eMouseDown: + case eMouseUp: { + // We should flush pending mousemove event now because some mouse + // boundary events which should've already been dispatched before a user + // input may have not been dispatched. E.g., if a mousedown event + // listener removed or appended an element under the cursor and mouseup + // event comes immediately after that, mouseover or mouseout may have + // not been dispatched on the new element yet. + // XXX If eMouseMove is not propery dispatched before eMouseDown and + // a `mousedown` event listener removes the event target or its + // ancestor, eMouseOver will be dispatched between eMouseDown and + // eMouseUp. That could cause unexpected behavior if a `mouseover` + // event listener assumes it's always disptached before `mousedown`. + // However, we're not sure whether it could happen with users' input. + // FIXME: Perhaps, we need to do this for all events which are directly + // caused by user input, e.g., eKeyDown, etc. + RefPtr<PresShell> rootPresShell = + mPresContext->IsRoot() ? this : GetRootPresShell(); + if (rootPresShell && rootPresShell->mSynthMouseMoveEvent.IsPending()) { + AutoWeakFrame frameForPresShellWeak(aFrameForPresShell); + RefPtr<nsSynthMouseMoveEvent> synthMouseMoveEvent = + rootPresShell->mSynthMouseMoveEvent.get(); + synthMouseMoveEvent->Run(); + if (IsDestroying()) { + return NS_OK; + } + // XXX If the frame or "this" is reframed, it might be better to + // recompute the frame. However, it could treat the user input on + // unexpected element. Therefore, we should not do that until we'd + // get a bug report caused by that. + if (MOZ_UNLIKELY(!frameForPresShellWeak.IsAlive())) { + return NS_OK; + } + } + break; + } + default: + break; + } + } + + 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<nsIContent> 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<Element> 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.GetFrameContent() || + !nsContentUtils::ContentIsCrossDocDescendantOf( + eventTargetData.GetFrameContent(), 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.GetFrame())) { + return NS_OK; + } + + // Suppress mouse event if it's being targeted at an element inside + // a document which needs events suppressed + if (MaybeDiscardOrDelayMouseEvent(eventTargetData.GetFrame(), 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.GetFrame())) { + 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<nsPresContext> 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->GetFrame(); +} + +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->GetFrame(); + + 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->SetFrameAndContent(targetFrame, aPointerCapturingContent); + } + + AutoWeakFrame weakTargetFrame(targetFrame); + AutoWeakFrame weakFrame(aEventTargetData->GetFrame()); + nsCOMPtr<nsIContent> content(aEventTargetData->GetContent()); + RefPtr<PresShell> presShell(aEventTargetData->mPresShell); + nsCOMPtr<nsIContent> targetContent; + PointerEventHandler::DispatchPointerFromMouseOrTouch( + presShell, aEventTargetData->GetFrame(), 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; + } + + aEventTargetData->SetFrameAndContent(targetContent->GetPrimaryFrame(), + targetContent); + aEventTargetData->mPresShell = PresShell::GetShellForEventTarget( + aEventTargetData->GetFrame(), aEventTargetData->GetContent()); + + // If new target PresShel is not found, we cannot keep handling the event. + return !!aEventTargetData->mPresShell; +} + +/** + * Event retargetting may retarget a mouse event and change the reference point. + * If event retargetting changes the reference point of a event that accessible + * caret will not handle, restore the original reference point. + */ +class AutoEventTargetPointResetter { + public: + explicit AutoEventTargetPointResetter(WidgetGUIEvent* aGUIEvent) + : mGUIEvent(aGUIEvent), + mRefPoint(aGUIEvent->mRefPoint), + mHandledByAccessibleCaret(false) {} + + void SetHandledByAccessibleCaret() { mHandledByAccessibleCaret = true; } + + ~AutoEventTargetPointResetter() { + if (!mHandledByAccessibleCaret) { + mGUIEvent->mRefPoint = mRefPoint; + } + } + + private: + WidgetGUIEvent* mGUIEvent; + LayoutDeviceIntPoint mRefPoint; + bool mHandledByAccessibleCaret; +}; + +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; + } + + AutoEventTargetPointResetter autoEventTargetPointResetter(aGUIEvent); + // 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<AccessibleCaretEventHub> 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; + autoEventTargetPointResetter.SetHandledByAccessibleCaret(); + return true; + } while (false); + + // Then, we target the event to the event hub at the focused window. + nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow(); + if (!window) { + return false; + } + RefPtr<Document> retargetEventDoc = window->GetExtantDoc(); + if (!retargetEventDoc) { + return false; + } + RefPtr<PresShell> presShell = retargetEventDoc->GetPresShell(); + if (!presShell) { + return false; + } + + RefPtr<AccessibleCaretEventHub> 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; + autoEventTargetPointResetter.SetHandledByAccessibleCaret(); + 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<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow(); + // No DOM window in same top level window has not been focused yet, + // discard the events. + if (!window) { + return false; + } + + RefPtr<Document> 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<Document> retargetEventDoc = capturingContent->GetComposedDoc(); + retargetEventDoc.forget(aRetargetEventDocument); + return true; + } + +#ifdef ANDROID + if (aGUIEvent->mClass == eTouchEventClass || + aGUIEvent->mClass == eMouseEventClass || + aGUIEvent->mClass == eWheelEventClass) { + RefPtr<Document> 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<PresShell> 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<Document> 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> 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> delayedKeyEvent = + MakeUnique<DelayedKeyEvent>(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<PresShell> 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> delayedMouseEvent = + MakeUnique<DelayedMouseEvent>(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<EventListener> suppressedListener = aFrameToHandleEvent->PresContext() + ->Document() + ->GetSuppressedEventListener(); + if (!suppressedListener || + aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) { + return true; + } + + nsCOMPtr<nsIContent> targetContent; + aFrameToHandleEvent->GetContentForEvent(aGUIEvent, + getter_AddRefs(targetContent)); + if (targetContent) { + aGUIEvent->mTarget = targetContent; + } + + nsCOMPtr<EventTarget> eventTarget = aGUIEvent->mTarget; + RefPtr<Event> 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<PresShell> presShellForCapturingContent = + PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent); + if (!presShellForCapturingContent) { + // If we can't process event for the capturing content, release + // the capture. + PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent); + // Since we don't dispatch ePointeUp nor ePointerCancel in this case, + // EventStateManager::PostHandleEvent does not have a chance to dispatch + // ePointerLostCapture event. Therefore, we need to dispatch it here. + PointerEventHandler::MaybeImplicitlyReleasePointerCapture(aGUIEvent); + return NS_OK; + } + + nsCOMPtr<nsIContent> 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<Element> 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<nsPIDOMWindowOuter> 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<PresShell> 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<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell(); + if (!shellAsTreeItem) { + return nullptr; + } + + nsCOMPtr<nsIDocShellTreeOwner> owner; + shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner)); + if (!owner) { + return nullptr; + } + + // now get the primary content shell (active tab) + nsCOMPtr<nsIDocShellTreeItem> item; + owner->GetPrimaryContentShell(getter_AddRefs(item)); + nsCOMPtr<nsIDocShell> 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<EventStateManager> 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() && + aEvent->AllowFlushingPendingNotifications()) { + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + // This may run script now. So, mPresShell might be destroyed after here. + nsCOMPtr<nsIContent> 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<nsPresContext> presContext = GetPresContext(); + nsCOMPtr<nsIContent> 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<nsPresContext> 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<nsIDragSession> 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() && + StaticPrefs::dom_event_contextmenu_shift_suppresses_event()) { + 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 (RefPtr<Document> doc = GetDocument()) { + doc->HandleEscKey(); + } + } + } + } + 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 <video> loads to stop. + // XXX We need to claim the Escape key event which will be + // dispatched only into chrome is already consumed by + // content because we need to prevent its default here + // for some reasons (not sure) but we need to detect + // if a chrome event handler will call PreventDefault() + // again and check it later. + aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop); + aKeyboardEvent->mFlags.mOnlyChromeDispatch = true; + + // The event listeners in chrome can prevent this ESC behavior by + // calling prevent default on the preceding keydown/press events. + if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed && + aKeyboardEvent->mMessage == eKeyUp) { + // ESC key released while in DOM fullscreen mode. + // Fully exit all browser windows and documents from + // fullscreen mode. + Document::AsyncExitFullscreen(nullptr); + } + } + + nsCOMPtr<Document> pointerLockedDoc = PointerLockManager::GetLockedDocument(); + if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) { + // XXX See above comment to understand the reason why this needs + // to claim that the Escape key event is consumed by content + // even though it will be dispatched only into chrome. + aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop); + aKeyboardEvent->mFlags.mOnlyChromeDispatch = true; + if (aKeyboardEvent->mMessage == eKeyUp) { + PointerLockManager::Unlock(); + } + } +} + +void PresShell::EventHandler::RecordEventPreparationPerformance( + const WidgetEvent* aEvent) { + MOZ_ASSERT(aEvent); + + switch (aEvent->mMessage) { + case eKeyPress: + case eKeyDown: + case eKeyUp: + if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) { + GetPresContext()->RecordInteractionTime( + nsPresContext::InteractionType::KeyInteraction, aEvent->mTimeStamp); + } + Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_KEYBOARD_MS, + aEvent->mTimeStamp); + return; + + case eMouseDown: + case eMouseUp: + Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_CLICK_MS, + aEvent->mTimeStamp); + [[fallthrough]]; + case ePointerDown: + case ePointerUp: + GetPresContext()->RecordInteractionTime( + nsPresContext::InteractionType::ClickInteraction, aEvent->mTimeStamp); + return; + + case eMouseMove: + if (aEvent->mFlags.mHandledByAPZ) { + Telemetry::AccumulateTimeDelta( + Telemetry::INPUT_EVENT_QUEUED_APZ_MOUSE_MOVE_MS, + aEvent->mTimeStamp); + } + GetPresContext()->RecordInteractionTime( + nsPresContext::InteractionType::MouseMoveInteraction, + aEvent->mTimeStamp); + return; + + case eWheel: + if (aEvent->mFlags.mHandledByAPZ) { + Telemetry::AccumulateTimeDelta( + Telemetry::INPUT_EVENT_QUEUED_APZ_WHEEL_MS, aEvent->mTimeStamp); + } + return; + + case eTouchMove: + if (aEvent->mFlags.mHandledByAPZ) { + Telemetry::AccumulateTimeDelta( + Telemetry::INPUT_EVENT_QUEUED_APZ_TOUCH_MOVE_MS, + aEvent->mTimeStamp); + } + return; + + default: + return; + } +} + +void PresShell::EventHandler::RecordEventHandlingResponsePerformance( + const WidgetEvent* aEvent) { + if (!Telemetry::CanRecordBase() || aEvent->mTimeStamp.IsNull() || + aEvent->mTimeStamp <= mPresShell->mLastOSWake || + !aEvent->AsInputEvent()) { + return; + } + + TimeStamp now = TimeStamp::Now(); + double millis = (now - aEvent->mTimeStamp).ToMilliseconds(); + Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_MS, millis); + if (GetDocument() && + GetDocument()->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) { + Telemetry::Accumulate(Telemetry::LOAD_INPUT_EVENT_RESPONSE_MS, millis); + } + + if (!sLastInputProcessed || sLastInputProcessed < aEvent->mTimeStamp) { + if (sLastInputProcessed) { + // This input event was created after we handled the last one. + // Accumulate the previous events' coalesced duration. + double lastMillis = + (sLastInputProcessed - sLastInputCreated).ToMilliseconds(); + Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_COALESCED_MS, + lastMillis); + + if (MOZ_UNLIKELY(!PresShell::sProcessInteractable)) { + // For content process, we use the ready state of + // top-level-content-document to know if the process has finished the + // start-up. + // For parent process, see the topic + // 'sessionstore-one-or-no-tab-restored' in PresShell::Observe. + if (XRE_IsContentProcess() && GetDocument() && + GetDocument()->IsTopLevelContentDocument()) { + switch (GetDocument()->GetReadyStateEnum()) { + case Document::READYSTATE_INTERACTIVE: + case Document::READYSTATE_COMPLETE: + PresShell::sProcessInteractable = true; + break; + default: + break; + } + } + } + if (MOZ_LIKELY(PresShell::sProcessInteractable)) { + Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_POST_STARTUP_MS, + lastMillis); + } else { + Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_STARTUP_MS, + lastMillis); + } + } + sLastInputCreated = aEvent->mTimeStamp; + } else if (aEvent->mTimeStamp < sLastInputCreated) { + // This event was created before the last input. May be processing out + // of order, so coalesce backwards, too. + sLastInputCreated = aEvent->mTimeStamp; + } + sLastInputProcessed = now; +} + +// static +nsIPrincipal* +PresShell::EventHandler::GetDocumentPrincipalToCompareWithBlacklist( + PresShell& aPresShell) { + nsPresContext* presContext = aPresShell.GetPresContext(); + if (NS_WARN_IF(!presContext)) { + return nullptr; + } + return presContext->Document()->GetPrincipalForPrefBasedHacks(); +} + +nsresult PresShell::EventHandler::DispatchEventToDOM( + WidgetEvent* aEvent, nsEventStatus* aEventStatus, + nsPresShellEventCB* aEventCB) { + nsresult rv = NS_OK; + nsCOMPtr<nsINode> eventTarget = mPresShell->mCurrentEventContent; + nsPresShellEventCB* eventCBPtr = aEventCB; + if (!eventTarget) { + nsCOMPtr<nsIContent> targetContent; + if (mPresShell->mCurrentEventFrame) { + rv = mPresShell->mCurrentEventFrame->GetContentForEvent( + aEvent, getter_AddRefs(targetContent)); + } + if (NS_SUCCEEDED(rv) && targetContent) { + eventTarget = targetContent; + } else if (GetDocument()) { + eventTarget = GetDocument(); + // If we don't have any content, the callback wouldn't probably + // do nothing. + eventCBPtr = nullptr; + } + } + if (eventTarget) { + if (eventTarget->OwnerDoc()->ShouldResistFingerprinting( + RFPTarget::WidgetEvents) && + aEvent->IsBlockedForFingerprintingResistance()) { + aEvent->mFlags.mOnlySystemGroupDispatchInContent = true; + } else if (aEvent->mMessage == eKeyPress) { + // If eKeyPress event is marked as not dispatched in the default event + // group in web content, it's caused by non-printable key or key + // combination. In this case, UI Events declares that browsers + // shouldn't dispatch keypress event. However, some web apps may be + // broken with this strict behavior due to historical issue. + // Therefore, we need to keep dispatching keypress event for such keys + // even with breaking the standard. + // Similarly, the other browsers sets non-zero value of keyCode or + // charCode of keypress event to the other. Therefore, we should + // behave so, however, some web apps may be broken. On such web apps, + // we should keep using legacy our behavior. + if (!mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist) { + mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist = true; + nsCOMPtr<nsIPrincipal> principal = + GetDocumentPrincipalToCompareWithBlacklist(*mPresShell); + if (principal) { + mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys = + principal->IsURIInPrefList( + "dom.keyboardevent.keypress.hack.dispatch_non_printable_" + "keys") || + principal->IsURIInPrefList( + "dom.keyboardevent.keypress.hack." + "dispatch_non_printable_keys.addl"); + + mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues |= + principal->IsURIInPrefList( + "dom.keyboardevent.keypress.hack." + "use_legacy_keycode_and_charcode") || + principal->IsURIInPrefList( + "dom.keyboardevent.keypress.hack." + "use_legacy_keycode_and_charcode.addl"); + } + } + if (mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys) { + aEvent->mFlags.mOnlySystemGroupDispatchInContent = false; + } + if (mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues) { + aEvent->AsKeyboardEvent()->mUseLegacyKeyCodeAndCharCodeValues = true; + } + } + + if (aEvent->mClass == eCompositionEventClass) { + RefPtr<nsPresContext> presContext = GetPresContext(); + RefPtr<BrowserParent> browserParent = + IMEStateManager::GetActiveBrowserParent(); + IMEStateManager::DispatchCompositionEvent( + eventTarget, presContext, browserParent, aEvent->AsCompositionEvent(), + aEventStatus, eventCBPtr); + } else { + if (aEvent->mClass == eMouseEventClass) { + PresShell::sMouseButtons = aEvent->AsMouseEvent()->mButtons; + } + RefPtr<nsPresContext> presContext = GetPresContext(); + EventDispatcher::Dispatch(eventTarget, presContext, aEvent, nullptr, + aEventStatus, eventCBPtr); + } + } + return rv; +} + +void PresShell::EventHandler::DispatchTouchEventToDOM( + WidgetEvent* aEvent, nsEventStatus* aEventStatus, + nsPresShellEventCB* aEventCB, bool aTouchIsNew) { + // calling preventDefault on touchstart or the first touchmove for a + // point prevents mouse events. calling it on the touchend should + // prevent click dispatching. + bool canPrevent = (aEvent->mMessage == eTouchStart) || + (aEvent->mMessage == eTouchMove && aTouchIsNew) || + (aEvent->mMessage == eTouchEnd); + bool preventDefault = false; + nsEventStatus tmpStatus = nsEventStatus_eIgnore; + WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); + + // loop over all touches and dispatch events on any that have changed + for (dom::Touch* touch : touchEvent->mTouches) { + // We should remove all suppressed touch instances in + // TouchManager::PreHandleEvent. + MOZ_ASSERT(!touch->mIsTouchEventSuppressed); + + if (!touch || !touch->mChanged) { + continue; + } + + nsCOMPtr<EventTarget> targetPtr = touch->mTarget; + nsCOMPtr<nsIContent> content = do_QueryInterface(targetPtr); + if (!content) { + continue; + } + + Document* doc = content->OwnerDoc(); + nsIContent* capturingContent = PresShell::GetCapturingContent(); + if (capturingContent) { + if (capturingContent->OwnerDoc() != doc) { + // Wrong document, don't dispatch anything. + continue; + } + content = capturingContent; + } + // copy the event + MOZ_ASSERT(touchEvent->IsTrusted()); + WidgetTouchEvent newEvent(true, touchEvent->mMessage, touchEvent->mWidget); + newEvent.AssignTouchEventData(*touchEvent, false); + newEvent.mTarget = targetPtr; + newEvent.mFlags.mHandledByAPZ = touchEvent->mFlags.mHandledByAPZ; + + RefPtr<PresShell> contentPresShell; + if (doc == GetDocument()) { + contentPresShell = doc->GetPresShell(); + if (contentPresShell) { + // XXXsmaug huge hack. Pushing possibly capturing content, + // even though event target is something else. + contentPresShell->PushCurrentEventInfo(content->GetPrimaryFrame(), + content); + } + } + + RefPtr<nsPresContext> presContext = doc->GetPresContext(); + if (!presContext) { + if (contentPresShell) { + contentPresShell->PopCurrentEventInfo(); + } + continue; + } + + tmpStatus = nsEventStatus_eIgnore; + EventDispatcher::Dispatch(targetPtr, presContext, &newEvent, nullptr, + &tmpStatus, aEventCB); + if (nsEventStatus_eConsumeNoDefault == tmpStatus || + newEvent.mFlags.mMultipleActionsPrevented) { + preventDefault = true; + } + + if (newEvent.mFlags.mMultipleActionsPrevented) { + touchEvent->mFlags.mMultipleActionsPrevented = true; + } + + if (contentPresShell) { + contentPresShell->PopCurrentEventInfo(); + } + } + + if (preventDefault && canPrevent) { + *aEventStatus = nsEventStatus_eConsumeNoDefault; + } else { + *aEventStatus = nsEventStatus_eIgnore; + } +} + +// Dispatch event to content only (NOT full processing) +// See also HandleEventWithTarget which does full event processing. +nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent, + WidgetEvent* aEvent, + nsEventStatus* aStatus) { + nsresult rv = NS_OK; + + PushCurrentEventInfo(nullptr, aTargetContent); + + // Bug 41013: Check if the event should be dispatched to content. + // It's possible that we are in the middle of destroying the window + // and the js context is out of date. This check detects the case + // that caused a crash in bug 41013, but there may be a better way + // to handle this situation! + nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak(); + if (container) { + // Dispatch event to content + rv = EventDispatcher::Dispatch(aTargetContent, mPresContext, aEvent, + nullptr, aStatus); + } + + PopCurrentEventInfo(); + return rv; +} + +// See the method above. +nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent, + Event* aEvent, + nsEventStatus* aStatus) { + nsresult rv = NS_OK; + + PushCurrentEventInfo(nullptr, aTargetContent); + nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak(); + if (container) { + rv = EventDispatcher::DispatchDOMEvent(aTargetContent, nullptr, aEvent, + mPresContext, aStatus); + } + + PopCurrentEventInfo(); + return rv; +} + +bool PresShell::EventHandler::AdjustContextMenuKeyEvent( + WidgetMouseEvent* aMouseEvent) { + // if a menu is open, open the context menu relative to the active item on the + // menu. + if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { + nsIFrame* popupFrame = pm->GetTopPopup(widget::PopupType::Menu); + if (popupFrame) { + nsIFrame* itemFrame = (static_cast<nsMenuPopupFrame*>(popupFrame)) + ->GetCurrentMenuItemFrame(); + if (!itemFrame) itemFrame = popupFrame; + + nsCOMPtr<nsIWidget> widget = popupFrame->GetNearestWidget(); + aMouseEvent->mWidget = widget; + LayoutDeviceIntPoint widgetPoint = widget->WidgetToScreenOffset(); + aMouseEvent->mRefPoint = + LayoutDeviceIntPoint::FromAppUnitsToNearest( + itemFrame->GetScreenRectInAppUnits().BottomLeft(), + itemFrame->PresContext()->AppUnitsPerDevPixel()) - + widgetPoint; + + mPresShell->mCurrentEventContent = itemFrame->GetContent(); + mPresShell->mCurrentEventFrame = itemFrame; + + return true; + } + } + + // If we're here because of the key-equiv for showing context menus, we + // have to twiddle with the NS event to make sure the context menu comes + // up in the upper left of the relevant content area before we create + // the DOM event. Since we never call InitMouseEvent() on the event, + // the client X/Y will be 0,0. We can make use of that if the widget is null. + // Use the root view manager's widget since it's most likely to have one, + // and the coordinates returned by GetCurrentItemAndPositionForElement + // are relative to the widget of the root of the root view manager. + nsRootPresContext* rootPC = GetPresContext()->GetRootPresContext(); + aMouseEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); + if (rootPC) { + aMouseEvent->mWidget = + rootPC->PresShell()->GetViewManager()->GetRootWidget(); + if (aMouseEvent->mWidget) { + // default the refpoint to the topleft of our document + nsPoint offset(0, 0); + nsIFrame* rootFrame = FrameConstructor()->GetRootFrame(); + if (rootFrame) { + nsView* view = rootFrame->GetClosestView(&offset); + offset += view->GetOffsetToWidget(aMouseEvent->mWidget); + aMouseEvent->mRefPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest( + offset, GetPresContext()->AppUnitsPerDevPixel()); + } + } + } else { + aMouseEvent->mWidget = nullptr; + } + + // see if we should use the caret position for the popup + LayoutDeviceIntPoint caretPoint; + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + if (PrepareToUseCaretPosition(MOZ_KnownLive(aMouseEvent->mWidget), + caretPoint)) { + // caret position is good + int32_t devPixelRatio = GetPresContext()->AppUnitsPerDevPixel(); + caretPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest( + ViewportUtils::LayoutToVisual( + LayoutDeviceIntPoint::ToAppUnits(caretPoint, devPixelRatio), + GetPresContext()->PresShell()), + devPixelRatio); + aMouseEvent->mRefPoint = caretPoint; + return true; + } + + // If we're here because of the key-equiv for showing context menus, we + // have to reset the event target to the currently focused element. Get it + // from the focus controller. + RefPtr<Element> currentFocus; + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + currentFocus = fm->GetFocusedElement(); + } + + // Reset event coordinates relative to focused frame in view + if (currentFocus) { + nsCOMPtr<nsIContent> currentPointElement; + GetCurrentItemAndPositionForElement( + currentFocus, getter_AddRefs(currentPointElement), + aMouseEvent->mRefPoint, MOZ_KnownLive(aMouseEvent->mWidget)); + if (currentPointElement) { + mPresShell->mCurrentEventContent = currentPointElement; + mPresShell->mCurrentEventFrame = nullptr; + mPresShell->GetCurrentEventFrame(); + } + } + + return true; +} + +// PresShell::EventHandler::PrepareToUseCaretPosition +// +// This checks to see if we should use the caret position for popup context +// menus. Returns true if the caret position should be used, and the +// coordinates of that position is returned in aTargetPt. This function +// will also scroll the window as needed to make the caret visible. +// +// The event widget should be the widget that generated the event, and +// whose coordinate system the resulting event's mRefPoint should be +// relative to. The returned point is in device pixels realtive to the +// widget passed in. +bool PresShell::EventHandler::PrepareToUseCaretPosition( + nsIWidget* aEventWidget, LayoutDeviceIntPoint& aTargetPt) { + nsresult rv; + + // check caret visibility + RefPtr<nsCaret> caret = mPresShell->GetCaret(); + NS_ENSURE_TRUE(caret, false); + + bool caretVisible = caret->IsVisible(); + if (!caretVisible) return false; + + // caret selection, this is a temporary weak reference, so no refcounting is + // needed + Selection* domSelection = caret->GetSelection(); + NS_ENSURE_TRUE(domSelection, false); + + // since the match could be an anonymous textnode inside a + // <textarea> or text <input>, we need to get the outer frame + // note: frames are not refcounted + nsIFrame* frame = nullptr; // may be nullptr + nsINode* node = domSelection->GetFocusNode(); + NS_ENSURE_TRUE(node, false); + nsCOMPtr<nsIContent> content = nsIContent::FromNode(node); + if (content) { + nsIContent* nonNative = content->FindFirstNonChromeOnlyAccessContent(); + content = nonNative; + } + + if (content) { + // It seems like ScrollSelectionIntoView should be enough, but it's + // not. The problem is that scrolling the selection into view when it is + // below the current viewport will align the top line of the frame exactly + // with the bottom of the window. This is fine, BUT, the popup event causes + // the control to be re-focused which does this exact call to + // ScrollContentIntoView, which has a one-pixel disagreement of whether the + // frame is actually in view. The result is that the frame is aligned with + // the top of the window, but the menu is still at the bottom. + // + // Doing this call first forces the frame to be in view, eliminating the + // problem. The only difference in the result is that if your cursor is in + // an edit box below the current view, you'll get the edit box aligned with + // the top of the window. This is arguably better behavior anyway. + rv = MOZ_KnownLive(mPresShell) + ->ScrollContentIntoView( + content, + ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible), + ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible), + ScrollFlags::ScrollOverflowHidden); + NS_ENSURE_SUCCESS(rv, false); + frame = content->GetPrimaryFrame(); + NS_WARNING_ASSERTION(frame, "No frame for focused content?"); + } + + // Actually scroll the selection (ie caret) into view. Note that this must + // be synchronous since we will be checking the caret position on the screen. + // + // Be easy about errors, and just don't scroll in those cases. Better to have + // the correct menu at a weird place than the wrong menu. + // After ScrollSelectionIntoView(), the pending notifications might be + // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + nsCOMPtr<nsISelectionController> selCon; + if (frame) + frame->GetSelectionController(GetPresContext(), getter_AddRefs(selCon)); + else + selCon = static_cast<nsISelectionController*>(mPresShell); + if (selCon) { + rv = selCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_FOCUS_REGION, + nsISelectionController::SCROLL_SYNCHRONOUS); + NS_ENSURE_SUCCESS(rv, false); + } + + nsPresContext* presContext = GetPresContext(); + + // get caret position relative to the closest view + nsRect caretCoords; + nsIFrame* caretFrame = caret->GetGeometry(&caretCoords); + if (!caretFrame) return false; + nsPoint viewOffset; + nsView* view = caretFrame->GetClosestView(&viewOffset); + if (!view) return false; + // and then get the caret coords relative to the event widget + if (aEventWidget) { + viewOffset += view->GetOffsetToWidget(aEventWidget); + } + caretCoords.MoveBy(viewOffset); + + // caret coordinates are in app units, convert to pixels + aTargetPt.x = + presContext->AppUnitsToDevPixels(caretCoords.x + caretCoords.width); + aTargetPt.y = + presContext->AppUnitsToDevPixels(caretCoords.y + caretCoords.height); + + // make sure rounding doesn't return a pixel which is outside the caret + // (e.g. one line lower) + aTargetPt.y -= 1; + + return true; +} + +void PresShell::EventHandler::GetCurrentItemAndPositionForElement( + Element* aFocusedElement, nsIContent** aTargetToUse, + LayoutDeviceIntPoint& aTargetPt, nsIWidget* aRootWidget) { + nsCOMPtr<nsIContent> focusedContent = aFocusedElement; + MOZ_KnownLive(mPresShell) + ->ScrollContentIntoView(focusedContent, ScrollAxis(), ScrollAxis(), + ScrollFlags::ScrollOverflowHidden); + + nsPresContext* presContext = GetPresContext(); + + bool istree = false, checkLineHeight = true; + nscoord extraTreeY = 0; + + // Set the position to just underneath the current item for multi-select + // lists or just underneath the selected item for single-select lists. If + // the element is not a list, or there is no selection, leave the position + // as is. + nsCOMPtr<Element> item; + nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect = + aFocusedElement->AsXULMultiSelectControl(); + if (multiSelect) { + checkLineHeight = false; + + int32_t currentIndex; + multiSelect->GetCurrentIndex(¤tIndex); + if (currentIndex >= 0) { + RefPtr<XULTreeElement> tree = XULTreeElement::FromNode(focusedContent); + // Tree view special case (tree items have no frames) + // Get the focused row and add its coordinates, which are already in + // pixels + // XXX Boris, should we create a new interface so that this doesn't + // need to know about trees? Something like nsINodelessChildCreator + // which could provide the current focus coordinates? + if (tree) { + tree->EnsureRowIsVisible(currentIndex); + int32_t firstVisibleRow = tree->GetFirstVisibleRow(); + int32_t rowHeight = tree->RowHeight(); + + extraTreeY += nsPresContext::CSSPixelsToAppUnits( + (currentIndex - firstVisibleRow + 1) * rowHeight); + istree = true; + + RefPtr<nsTreeColumns> cols = tree->GetColumns(); + if (cols) { + nsTreeColumn* col = cols->GetFirstColumn(); + if (col) { + RefPtr<Element> colElement = col->Element(); + nsIFrame* frame = colElement->GetPrimaryFrame(); + if (frame) { + extraTreeY += frame->GetSize().height; + } + } + } + } else { + multiSelect->GetCurrentItem(getter_AddRefs(item)); + } + } + } else { + // don't check menulists as the selected item will be inside a popup. + nsCOMPtr<nsIDOMXULMenuListElement> menulist = + aFocusedElement->AsXULMenuList(); + if (!menulist) { + nsCOMPtr<nsIDOMXULSelectControlElement> select = + aFocusedElement->AsXULSelectControl(); + if (select) { + checkLineHeight = false; + select->GetSelectedItem(getter_AddRefs(item)); + } + } + } + + if (item) { + focusedContent = item; + } + + nsIFrame* frame = focusedContent->GetPrimaryFrame(); + if (frame) { + NS_ASSERTION( + frame->PresContext() == GetPresContext(), + "handling event for focused content that is not in our document?"); + + nsPoint frameOrigin(0, 0); + + // Get the frame's origin within its view + nsView* view = frame->GetClosestView(&frameOrigin); + NS_ASSERTION(view, "No view for frame"); + + // View's origin relative the widget + if (aRootWidget) { + frameOrigin += view->GetOffsetToWidget(aRootWidget); + } + + // Start context menu down and to the right from top left of frame + // use the lineheight. This is a good distance to move the context + // menu away from the top left corner of the frame. If we always + // used the frame height, the context menu could end up far away, + // for example when we're focused on linked images. + // On the other hand, we want to use the frame height if it's less + // than the current line height, so that the context menu appears + // associated with the correct frame. + nscoord extra = 0; + if (!istree) { + extra = frame->GetSize().height; + if (checkLineHeight) { + nsIScrollableFrame* scrollFrame = + nsLayoutUtils::GetNearestScrollableFrame( + frame, nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN | + nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT); + if (scrollFrame) { + nsSize scrollAmount = scrollFrame->GetLineScrollAmount(); + nsIFrame* f = do_QueryFrame(scrollFrame); + int32_t APD = presContext->AppUnitsPerDevPixel(); + int32_t scrollAPD = f->PresContext()->AppUnitsPerDevPixel(); + scrollAmount = scrollAmount.ScaleToOtherAppUnits(scrollAPD, APD); + if (extra > scrollAmount.height) { + extra = scrollAmount.height; + } + } + } + } + + aTargetPt.x = presContext->AppUnitsToDevPixels(frameOrigin.x); + aTargetPt.y = + presContext->AppUnitsToDevPixels(frameOrigin.y + extra + extraTreeY); + } + + NS_IF_ADDREF(*aTargetToUse = focusedContent); +} + +bool PresShell::ShouldIgnoreInvalidation() { + return mPaintingSuppressed || !mIsActive || mIsNeverPainting; +} + +void PresShell::WillPaint() { + // Check the simplest things first. In particular, it's important to + // check mIsActive before making any of the more expensive calls such + // as GetRootPresContext, for the case of a browser with a large + // number of tabs. + // Don't bother doing anything if some viewmanager in our tree is painting + // while we still have painting suppressed or we are not active. + if (!mIsActive || mPaintingSuppressed || !IsVisible()) { + return; + } + + nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext(); + if (!rootPresContext) { + // In some edge cases, such as when we don't have a root frame yet, + // we can't find the root prescontext. There's nothing to do in that + // case. + return; + } + + rootPresContext->FlushWillPaintObservers(); + if (mIsDestroying) return; + + // Process reflows, if we have them, to reduce flicker due to invalidates and + // reflow being interspersed. Note that we _do_ allow this to be + // interruptible; if we can't do all the reflows it's better to flicker a bit + // than to freeze up. + FlushPendingNotifications( + ChangesToFlush(FlushType::InterruptibleLayout, false)); +} + +void PresShell::DidPaintWindow() { + nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext(); + if (rootPresContext != mPresContext) { + // This could be a popup's presshell. No point in notifying XPConnect + // about compositing of popups. + return; + } + + if (!mHasReceivedPaintMessage) { + mHasReceivedPaintMessage = true; + + nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService(); + if (obsvc && mDocument) { + nsPIDOMWindowOuter* window = mDocument->GetWindow(); + if (window && nsGlobalWindowOuter::Cast(window)->IsChromeWindow()) { + obsvc->NotifyObservers(window, "widget-first-paint", nullptr); + } + } + } +} + +bool PresShell::IsVisible() const { + if (!mIsActive || !mViewManager) return false; + + nsView* view = mViewManager->GetRootView(); + if (!view) return true; + + // inner view of subdoc frame + view = view->GetParent(); + if (!view) return true; + + // subdoc view + view = view->GetParent(); + if (!view) return true; + + nsIFrame* frame = view->GetFrame(); + if (!frame) return true; + + return frame->IsVisibleConsideringAncestors( + nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY); +} + +void PresShell::SuppressDisplayport(bool aEnabled) { + if (aEnabled) { + mActiveSuppressDisplayport++; + } else if (mActiveSuppressDisplayport > 0) { + bool isSuppressed = IsDisplayportSuppressed(); + mActiveSuppressDisplayport--; + if (isSuppressed && !IsDisplayportSuppressed()) { + // We unsuppressed the displayport, trigger a paint + if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) { + rootFrame->SchedulePaint(); + } + } + } +} + +static bool sDisplayPortSuppressionRespected = true; + +void PresShell::RespectDisplayportSuppression(bool aEnabled) { + bool isSuppressed = IsDisplayportSuppressed(); + sDisplayPortSuppressionRespected = aEnabled; + if (isSuppressed && !IsDisplayportSuppressed()) { + // We unsuppressed the displayport, trigger a paint + if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) { + rootFrame->SchedulePaint(); + } + } +} + +bool PresShell::IsDisplayportSuppressed() { + return sDisplayPortSuppressionRespected && mActiveSuppressDisplayport > 0; +} + +static CallState FreezeSubDocument(Document& aDocument) { + if (PresShell* presShell = aDocument.GetPresShell()) { + presShell->Freeze(); + } + return CallState::Continue; +} + +void PresShell::Freeze(bool aIncludeSubDocuments) { + mUpdateApproximateFrameVisibilityEvent.Revoke(); + + MaybeReleaseCapturingContent(); + + if (mCaret) { + SetCaretEnabled(false); + } + + mPaintingSuppressed = true; + + if (aIncludeSubDocuments && mDocument) { + mDocument->EnumerateSubDocuments(FreezeSubDocument); + } + + nsPresContext* presContext = GetPresContext(); + if (presContext) { + presContext->DisableInteractionTimeRecording(); + if (presContext->RefreshDriver()->GetPresContext() == presContext) { + presContext->RefreshDriver()->Freeze(); + } + + if (nsPresContext* rootPresContext = presContext->GetRootPresContext()) { + rootPresContext->ResetUserInputEventsAllowed(); + } + } + + mFrozen = true; + if (mDocument) { + UpdateImageLockingState(); + } +} + +void PresShell::FireOrClearDelayedEvents(bool aFireEvents) { + mNoDelayedMouseEvents = false; + mNoDelayedKeyEvents = false; + if (!aFireEvents) { + mDelayedEvents.Clear(); + return; + } + + if (mDocument) { + RefPtr<Document> doc = mDocument; + while (!mIsDestroying && mDelayedEvents.Length() && + !doc->EventHandlingSuppressed()) { + UniquePtr<DelayedEvent> ev = std::move(mDelayedEvents[0]); + mDelayedEvents.RemoveElementAt(0); + if (ev->IsKeyPressEvent() && mIsLastKeyDownCanceled) { + continue; + } + ev->Dispatch(); + } + if (!doc->EventHandlingSuppressed()) { + mDelayedEvents.Clear(); + } + } +} + +void PresShell::Thaw(bool aIncludeSubDocuments) { + nsPresContext* presContext = GetPresContext(); + if (presContext && + presContext->RefreshDriver()->GetPresContext() == presContext) { + presContext->RefreshDriver()->Thaw(); + } + + if (aIncludeSubDocuments && mDocument) { + mDocument->EnumerateSubDocuments([](Document& aSubDoc) { + if (PresShell* presShell = aSubDoc.GetPresShell()) { + presShell->Thaw(); + } + return CallState::Continue; + }); + } + + // Get the activeness of our presshell, as this might have changed + // while we were in the bfcache + ActivenessMaybeChanged(); + + // We're now unfrozen + mFrozen = false; + UpdateImageLockingState(); + + UnsuppressPainting(); + + // In case the above UnsuppressPainting call didn't start the + // refresh driver, we manually start the refresh driver to + // ensure nsPresContext::MaybeIncreaseMeasuredTicksSinceLoading + // can be called for user input events handling. + if (presContext && presContext->IsRoot()) { + if (!presContext->RefreshDriver()->HasPendingTick()) { + presContext->RefreshDriver()->InitializeTimer(); + } + } +} + +//-------------------------------------------------------- +// Start of protected and private methods on the PresShell +//-------------------------------------------------------- + +void PresShell::MaybeScheduleReflow() { + ASSERT_REFLOW_SCHEDULED_STATE(); + if (mObservingLayoutFlushes || mIsDestroying || mIsReflowing || + mDirtyRoots.IsEmpty()) + return; + + if (!mPresContext->HasPendingInterrupt() || !ScheduleReflowOffTimer()) { + ScheduleReflow(); + } + + ASSERT_REFLOW_SCHEDULED_STATE(); +} + +void PresShell::ScheduleReflow() { + ASSERT_REFLOW_SCHEDULED_STATE(); + DoObserveLayoutFlushes(); + ASSERT_REFLOW_SCHEDULED_STATE(); +} + +void PresShell::WillCauseReflow() { + nsContentUtils::AddScriptBlocker(); + ++mChangeNestCount; +} + +void PresShell::DidCauseReflow() { + NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()"); + --mChangeNestCount; + nsContentUtils::RemoveScriptBlocker(); +} + +void PresShell::WillDoReflow() { + mDocument->FlushUserFontSet(); + + mPresContext->FlushCounterStyles(); + + mPresContext->FlushFontFeatureValues(); + + mPresContext->FlushFontPaletteValues(); + + mLastReflowStart = GetPerformanceNowUnclamped(); +} + +void PresShell::DidDoReflow(bool aInterruptible) { + MOZ_ASSERT(mPendingDidDoReflow); + if (!nsContentUtils::IsSafeToRunScript()) { + // If we're reflowing while script-blocked (e.g. from container query + // updates), defer our reflow callbacks until the end of our next layout + // flush. + SetNeedLayoutFlush(); + return; + } + + auto clearPendingDidDoReflow = + MakeScopeExit([&] { mPendingDidDoReflow = false; }); + + mHiddenContentInForcedLayout.Clear(); + + HandlePostedReflowCallbacks(aInterruptible); + + if (mIsDestroying) { + return; + } + + { + nsAutoScriptBlocker scriptBlocker; + AutoAssertNoFlush noReentrantFlush(*this); + if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) { + DOMHighResTimeStamp now = GetPerformanceNowUnclamped(); + docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now); + } + + if (StaticPrefs::layout_reflow_synthMouseMove()) { + SynthesizeMouseMove(false); + } + + mPresContext->NotifyMissingFonts(); + } + + if (mIsDestroying) { + return; + } + + if (mDirtyRoots.IsEmpty()) { + // We only unsuppress painting if we're out of reflows. It's pointless to + // do so if reflows are still pending, since reflows are just going to + // thrash the frames around some more. By waiting we avoid an overeager + // "jitter" effect. + if (mShouldUnsuppressPainting) { + mShouldUnsuppressPainting = false; + UnsuppressAndInvalidate(); + } + } else { + // If any new reflow commands were enqueued during the reflow, schedule + // another reflow event to process them. Note that we want to do this + // after DidDoReflow(), since that method can change whether there are + // dirty roots around by flushing, and there's no point in posting a + // reflow event just to have the flush revoke it. + MaybeScheduleReflow(); + // And record that we might need flushing + SetNeedLayoutFlush(); + } +} + +DOMHighResTimeStamp PresShell::GetPerformanceNowUnclamped() { + DOMHighResTimeStamp now = 0; + + if (nsPIDOMWindowInner* window = mDocument->GetInnerWindow()) { + Performance* perf = window->GetPerformance(); + + if (perf) { + now = perf->NowUnclamped(); + } + } + + return now; +} + +void PresShell::sReflowContinueCallback(nsITimer* aTimer, void* aPresShell) { + RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell); + + MOZ_ASSERT(aTimer == self->mReflowContinueTimer, "Unexpected timer"); + self->mReflowContinueTimer = nullptr; + self->ScheduleReflow(); +} + +bool PresShell::ScheduleReflowOffTimer() { + MOZ_ASSERT(!mObservingLayoutFlushes, "Shouldn't get here"); + ASSERT_REFLOW_SCHEDULED_STATE(); + + if (!mReflowContinueTimer) { + nsresult rv = NS_NewTimerWithFuncCallback( + getter_AddRefs(mReflowContinueTimer), sReflowContinueCallback, this, 30, + nsITimer::TYPE_ONE_SHOT, "sReflowContinueCallback", + GetMainThreadSerialEventTarget()); + return NS_SUCCEEDED(rv); + } + return true; +} + +bool PresShell::DoReflow(nsIFrame* target, bool aInterruptible, + OverflowChangedTracker* aOverflowTracker) { + [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI(); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( + "Reflow", LAYOUT_Reflow, uri ? uri->GetSpecOrDefault() : "N/A"_ns); + + LAYOUT_TELEMETRY_RECORD_BASE(Reflow); + + PerfStats::AutoMetricRecording<PerfStats::Metric::Reflowing> autoRecording; + + gfxTextPerfMetrics* tp = mPresContext->GetTextPerfMetrics(); + TimeStamp timeStart; + if (tp) { + tp->Accumulate(); + tp->reflowCount++; + timeStart = TimeStamp::Now(); + } + + // Schedule a paint, but don't actually mark this frame as changed for + // retained DL building purposes. If any child frames get moved, then + // they will schedule paint again. We could probaby skip this, and just + // schedule a similar paint when a frame is deleted. + target->SchedulePaint(nsIFrame::PAINT_DEFAULT, false); + + Maybe<uint64_t> innerWindowID; + if (auto* window = mDocument->GetInnerWindow()) { + innerWindowID = Some(window->WindowID()); + } + AutoProfilerTracing tracingLayoutFlush( + "Paint", aInterruptible ? "Reflow (interruptible)" : "Reflow (sync)", + geckoprofiler::category::LAYOUT, std::move(mReflowCause), innerWindowID); + mReflowCause = nullptr; + + FlushPendingScrollAnchorSelections(); + + if (mReflowContinueTimer) { + mReflowContinueTimer->Cancel(); + mReflowContinueTimer = nullptr; + } + + const bool isRoot = target == mFrameConstructor->GetRootFrame(); + + MOZ_ASSERT(isRoot || aOverflowTracker, + "caller must provide overflow tracker when reflowing " + "non-root frames"); + + // CreateReferenceRenderingContext can return nullptr + UniquePtr<gfxContext> rcx(CreateReferenceRenderingContext()); + +#ifdef DEBUG + mCurrentReflowRoot = target; +#endif + + // If the target frame is the root of the frame hierarchy, then + // use all the available space. If it's simply a `reflow root', + // then use the target frame's size as the available space. + WritingMode wm = target->GetWritingMode(); + LogicalSize size(wm); + if (isRoot) { + size = LogicalSize(wm, mPresContext->GetVisibleArea().Size()); + } else { + size = target->GetLogicalSize(); + } + + OverflowAreas oldOverflow; // initialized and used only when !isRoot + if (!isRoot) { + oldOverflow = target->GetOverflowAreas(); + } + + NS_ASSERTION(!target->GetNextInFlow() && !target->GetPrevInFlow(), + "reflow roots should never split"); + + // Don't pass size directly to the reflow input, since a + // constrained height implies page/column breaking. + LogicalSize reflowSize(wm, size.ISize(wm), NS_UNCONSTRAINEDSIZE); + ReflowInput reflowInput(mPresContext, target, rcx.get(), reflowSize, + ReflowInput::InitFlag::CallerWillInit); + reflowInput.mOrthogonalLimit = size.BSize(wm); + + if (isRoot) { + reflowInput.Init(mPresContext); + + // When the root frame is being reflowed with unconstrained block-size + // (which happens when we're called from + // nsDocumentViewer::SizeToContent), we're effectively doing a + // resize in the block direction, since it changes the meaning of + // percentage block-sizes even if no block-sizes actually changed. + // The same applies when we reflow again after that computation. This is + // an unusual case, and isn't caught by ReflowInput::InitResizeFlags. + bool hasUnconstrainedBSize = size.BSize(wm) == NS_UNCONSTRAINEDSIZE; + + if (hasUnconstrainedBSize || mLastRootReflowHadUnconstrainedBSize) { + reflowInput.SetBResize(true); + } + + mLastRootReflowHadUnconstrainedBSize = hasUnconstrainedBSize; + } else { + // Initialize reflow input with current used border and padding, + // in case this was set specially by the parent frame when the reflow root + // was reflowed by its parent. + reflowInput.Init(mPresContext, Nothing(), + Some(target->GetLogicalUsedBorder(wm)), + Some(target->GetLogicalUsedPadding(wm))); + } + + // fix the computed height + NS_ASSERTION(reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), + "reflow input should not set margin for reflow roots"); + if (size.BSize(wm) != NS_UNCONSTRAINEDSIZE) { + nscoord computedBSize = + size.BSize(wm) - + reflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm); + computedBSize = std::max(computedBSize, 0); + reflowInput.SetComputedBSize(computedBSize); + } + NS_ASSERTION( + reflowInput.ComputedISize() == + size.ISize(wm) - + reflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm), + "reflow input computed incorrect inline size"); + + mPresContext->ReflowStarted(aInterruptible); + mIsReflowing = true; + + nsReflowStatus status; + ReflowOutput desiredSize(reflowInput); + target->Reflow(mPresContext, desiredSize, reflowInput, status); + + // If an incremental reflow is initiated at a frame other than the + // root frame, then its desired size had better not change! If it's + // initiated at the root, then the size better not change unless its + // height was unconstrained to start with. + nsRect boundsRelativeToTarget = + nsRect(0, 0, desiredSize.Width(), desiredSize.Height()); + const bool isBSizeLimitReflow = + isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE; + NS_ASSERTION(isBSizeLimitReflow || desiredSize.Size(wm) == size, + "non-root frame's desired size changed during an " + "incremental reflow"); + NS_ASSERTION(status.IsEmpty(), "reflow roots should never split"); + + target->SetSize(boundsRelativeToTarget.Size()); + + // Always use boundsRelativeToTarget here, not desiredSize.InkOverflowRect(), + // because for root frames (where they could be different, since root frames + // are allowed to have overflow) the root view bounds need to match the + // viewport bounds; the view manager "window dimensions" code depends on it. + if (target->HasView()) { + nsContainerFrame::SyncFrameViewAfterReflow( + mPresContext, target, target->GetView(), boundsRelativeToTarget); + if (target->IsViewportFrame()) { + SyncWindowProperties(/* aSync = */ false); + } + } + + target->DidReflow(mPresContext, nullptr); + if (target->IsInScrollAnchorChain()) { + ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(target); + PostPendingScrollAnchorAdjustment(container); + } + if (MOZ_UNLIKELY(isBSizeLimitReflow)) { + mPresContext->SetVisibleArea(boundsRelativeToTarget); + } + +#ifdef DEBUG + mCurrentReflowRoot = nullptr; +#endif + + if (!isRoot && oldOverflow != target->GetOverflowAreas()) { + // The overflow area changed. Propagate this change to ancestors. + aOverflowTracker->AddFrame(target->GetParent(), + OverflowChangedTracker::CHILDREN_CHANGED); + } + + NS_ASSERTION( + mPresContext->HasPendingInterrupt() || mFramesToDirty.Count() == 0, + "Why do we need to dirty anything if not interrupted?"); + + mIsReflowing = false; + bool interrupted = mPresContext->HasPendingInterrupt(); + if (interrupted) { + // Make sure target gets reflowed again. + for (const auto& key : mFramesToDirty) { + // Mark frames dirty until target frame. + for (nsIFrame* f = key; f && !f->IsSubtreeDirty(); f = f->GetParent()) { + f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); + if (f->IsFlexItem()) { + nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(f); + } + + if (f == target) { + break; + } + } + } + + NS_ASSERTION(target->IsSubtreeDirty(), "Why is the target not dirty?"); + mDirtyRoots.Add(target); + SetNeedLayoutFlush(); + + // Clear mFramesToDirty after we've done the target->IsSubtreeDirty() + // assertion so that if it fails it's easier to see what's going on. +#ifdef NOISY_INTERRUPTIBLE_REFLOW + printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count()); +#endif /* NOISY_INTERRUPTIBLE_REFLOW */ + mFramesToDirty.Clear(); + + // Any FlushPendingNotifications with interruptible reflows + // should be suppressed now. We don't want to do extra reflow work + // before our reflow event happens. + mWasLastReflowInterrupted = true; + MaybeScheduleReflow(); + } + + // dump text perf metrics for reflows with significant text processing + if (tp) { + if (tp->current.numChars > 100) { + TimeDuration reflowTime = TimeStamp::Now() - timeStart; + LogTextPerfStats(tp, this, tp->current, reflowTime.ToMilliseconds(), + eLog_reflow, nullptr); + } + tp->Accumulate(); + } + + return !interrupted; +} + +#ifdef DEBUG +void PresShell::DoVerifyReflow() { + if (GetVerifyReflowEnable()) { + // First synchronously render what we have so far so that we can + // see it. + nsView* rootView = mViewManager->GetRootView(); + mViewManager->InvalidateView(rootView); + + FlushPendingNotifications(FlushType::Layout); + mInVerifyReflow = true; + bool ok = VerifyIncrementalReflow(); + mInVerifyReflow = false; + if (VerifyReflowFlags::All & gVerifyReflowFlags) { + printf("ProcessReflowCommands: finished (%s)\n", ok ? "ok" : "failed"); + } + + if (!mDirtyRoots.IsEmpty()) { + printf("XXX yikes! reflow commands queued during verify-reflow\n"); + } + } +} +#endif + +// used with Telemetry metrics +#define NS_LONG_REFLOW_TIME_MS 5000 + +bool PresShell::ProcessReflowCommands(bool aInterruptible) { + if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting && + !mPendingDidDoReflow) { + // Nothing to do; bail out + return true; + } + + const bool wasProcessingReflowCommands = mProcessingReflowCommands; + auto restoreProcessingReflowCommands = MakeScopeExit( + [&] { mProcessingReflowCommands = wasProcessingReflowCommands; }); + mProcessingReflowCommands = true; + + auto timerStart = mozilla::TimeStamp::Now(); + bool interrupted = false; + if (!mDirtyRoots.IsEmpty()) { +#ifdef DEBUG + if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) { + printf("ProcessReflowCommands: begin incremental reflow\n"); + } +#endif + + // If reflow is interruptible, then make a note of our deadline. + const PRIntervalTime deadline = + aInterruptible + ? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime) + : (PRIntervalTime)0; + + // Scope for the reflow entry point + nsAutoScriptBlocker scriptBlocker; + WillDoReflow(); + AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow); + nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager); + + OverflowChangedTracker overflowTracker; + + do { + // Send an incremental reflow notification to the target frame. + nsIFrame* target = mDirtyRoots.PopShallowestRoot(); + + if (!target->IsSubtreeDirty()) { + // It's not dirty anymore, which probably means the notification + // was posted in the middle of a reflow (perhaps with a reflow + // root in the middle). Don't do anything. + continue; + } + + interrupted = !DoReflow(target, aInterruptible, &overflowTracker); + + // Keep going until we're out of reflow commands, or we've run + // past our deadline, or we're interrupted. + } while (!interrupted && !mDirtyRoots.IsEmpty() && + (!aInterruptible || PR_IntervalNow() < deadline)); + + interrupted = !mDirtyRoots.IsEmpty(); + + overflowTracker.Flush(); + + if (!interrupted) { + // We didn't get interrupted. Go ahead and perform scroll anchor + // adjustments. + FlushPendingScrollAnchorAdjustments(); + } + mPendingDidDoReflow = true; + } + + // Exiting the scriptblocker might have killed us. If we were processing + // scroll commands, let the outermost call deal with it. + if (!mIsDestroying && mPendingDidDoReflow && !wasProcessingReflowCommands) { + DidDoReflow(aInterruptible); + } + +#ifdef DEBUG + if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) { + printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n", + (void*)this); + } + DoVerifyReflow(); +#endif + + { + TimeDuration elapsed = TimeStamp::Now() - timerStart; + int32_t intElapsed = int32_t(elapsed.ToMilliseconds()); + if (intElapsed > NS_LONG_REFLOW_TIME_MS) { + Telemetry::Accumulate(Telemetry::LONG_REFLOW_INTERRUPTIBLE, + aInterruptible ? 1 : 0); + } + } + + return !interrupted; +} + +bool PresShell::DoFlushLayout(bool aInterruptible) { + mFrameConstructor->RecalcQuotesAndCounters(); + return ProcessReflowCommands(aInterruptible); +} + +void PresShell::WindowSizeMoveDone() { + if (mPresContext) { + EventStateManager::ClearGlobalActiveContent(nullptr); + ClearMouseCapture(); + } +} + +NS_IMETHODIMP +PresShell::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (mIsDestroying) { + NS_WARNING("our observers should have been unregistered by now"); + return NS_OK; + } + + if (!nsCRT::strcmp(aTopic, "memory-pressure")) { + if (!AssumeAllFramesVisible() && + mPresContext->IsRootContentDocumentInProcess()) { + DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true); + } + return NS_OK; + } + + if (!nsCRT::strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) { + mLastOSWake = TimeStamp::Now(); + return NS_OK; + } + + // For parent process, user may expect the UI is interactable after a + // tab (previously opened page or home page) has restored. + if (!nsCRT::strcmp(aTopic, "sessionstore-one-or-no-tab-restored")) { + MOZ_ASSERT(XRE_IsParentProcess()); + sProcessInteractable = true; + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored"); + } + return NS_OK; + } + + if (!nsCRT::strcmp(aTopic, "font-info-updated")) { + // See how gfxPlatform::ForceGlobalReflow encodes this. + bool needsReframe = aData && !!aData[0]; + mPresContext->ForceReflowForFontInfoUpdate(needsReframe); + return NS_OK; + } + + // The "look-and-feel-changed" notification for JS observers will be + // dispatched HandleGlobalThemeChange once LookAndFeel caches are cleared. + if (!nsCRT::strcmp(aTopic, "internal-look-and-feel-changed")) { + // See how LookAndFeel::NotifyChangedAllWindows encodes this. + auto kind = widget::ThemeChangeKind(aData[0]); + mPresContext->ThemeChanged(kind); + return NS_OK; + } + + NS_WARNING("unrecognized topic in PresShell::Observe"); + return NS_ERROR_FAILURE; +} + +bool PresShell::AddRefreshObserver(nsARefreshObserver* aObserver, + FlushType aFlushType, + const char* aObserverDescription) { + nsPresContext* presContext = GetPresContext(); + if (MOZ_UNLIKELY(!presContext)) { + return false; + } + presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType, + aObserverDescription); + return true; +} + +bool PresShell::RemoveRefreshObserver(nsARefreshObserver* aObserver, + FlushType aFlushType) { + nsPresContext* presContext = GetPresContext(); + return presContext && presContext->RefreshDriver()->RemoveRefreshObserver( + aObserver, aFlushType); +} + +bool PresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) { + nsPresContext* presContext = GetPresContext(); + if (!presContext) { + return false; + } + presContext->RefreshDriver()->AddPostRefreshObserver(aObserver); + return true; +} + +bool PresShell::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) { + nsPresContext* presContext = GetPresContext(); + if (!presContext) { + return false; + } + presContext->RefreshDriver()->RemovePostRefreshObserver(aObserver); + return true; +} + +void PresShell::DoObserveStyleFlushes() { + MOZ_ASSERT(!ObservingStyleFlushes()); + mObservingStyleFlushes = true; + + if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) { + mPresContext->RefreshDriver()->AddStyleFlushObserver(this); + } +} + +void PresShell::DoObserveLayoutFlushes() { + MOZ_ASSERT(!ObservingLayoutFlushes()); + mObservingLayoutFlushes = true; + + if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) { + mPresContext->RefreshDriver()->AddLayoutFlushObserver(this); + } +} + +//------------------------------------------------------ +// End of protected and private methods on the PresShell +//------------------------------------------------------ + +//------------------------------------------------------------------ +//-- Delayed event Classes Impls +//------------------------------------------------------------------ + +PresShell::DelayedInputEvent::DelayedInputEvent() + : DelayedEvent(), mEvent(nullptr) {} + +PresShell::DelayedInputEvent::~DelayedInputEvent() { delete mEvent; } + +void PresShell::DelayedInputEvent::Dispatch() { + if (!mEvent || !mEvent->mWidget) { + return; + } + nsCOMPtr<nsIWidget> widget = mEvent->mWidget; + nsEventStatus status; + widget->DispatchEvent(mEvent, status); +} + +PresShell::DelayedMouseEvent::DelayedMouseEvent(WidgetMouseEvent* aEvent) { + MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); + WidgetMouseEvent* mouseEvent = + new WidgetMouseEvent(true, aEvent->mMessage, aEvent->mWidget, + aEvent->mReason, aEvent->mContextMenuTrigger); + mouseEvent->AssignMouseEventData(*aEvent, false); + mEvent = mouseEvent; +} + +PresShell::DelayedKeyEvent::DelayedKeyEvent(WidgetKeyboardEvent* aEvent) { + MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); + WidgetKeyboardEvent* keyEvent = + new WidgetKeyboardEvent(true, aEvent->mMessage, aEvent->mWidget); + keyEvent->AssignKeyEventData(*aEvent, false); + keyEvent->mFlags.mIsSynthesizedForTests = + aEvent->mFlags.mIsSynthesizedForTests; + keyEvent->mFlags.mIsSuppressedOrDelayed = true; + mEvent = keyEvent; +} + +bool PresShell::DelayedKeyEvent::IsKeyPressEvent() { + return mEvent->mMessage == eKeyPress; +} + +// Start of DEBUG only code + +#ifdef DEBUG + +static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg) { + nsAutoString n1, n2; + if (k1) { + k1->GetFrameName(n1); + } else { + n1.AssignLiteral(u"(null)"); + } + + if (k2) { + k2->GetFrameName(n2); + } else { + n2.AssignLiteral(u"(null)"); + } + + printf("verifyreflow: %s %p != %s %p %s\n", + NS_LossyConvertUTF16toASCII(n1).get(), (void*)k1, + NS_LossyConvertUTF16toASCII(n2).get(), (void*)k2, aMsg); +} + +static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg, + const nsRect& r1, const nsRect& r2) { + printf("VerifyReflow Error:\n"); + nsAutoString name; + + if (k1) { + k1->GetFrameName(name); + printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1); + } + printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height); + + if (k2) { + k2->GetFrameName(name); + printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2); + } + printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg); +} + +static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg, + const nsIntRect& r1, const nsIntRect& r2) { + printf("VerifyReflow Error:\n"); + nsAutoString name; + + if (k1) { + k1->GetFrameName(name); + printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1); + } + printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height); + + if (k2) { + k2->GetFrameName(name); + printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2); + } + printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg); +} + +static bool CompareTrees(nsPresContext* aFirstPresContext, + nsIFrame* aFirstFrame, + nsPresContext* aSecondPresContext, + nsIFrame* aSecondFrame) { + if (!aFirstPresContext || !aFirstFrame || !aSecondPresContext || + !aSecondFrame) + return true; + // XXX Evil hack to reduce false positives; I can't seem to figure + // out how to flush scrollbar changes correctly + // if (aFirstFrame->IsScrollbarFrame()) + // return true; + bool ok = true; + const auto& childLists1 = aFirstFrame->ChildLists(); + const auto& childLists2 = aSecondFrame->ChildLists(); + auto iterLists1 = childLists1.begin(); + auto iterLists2 = childLists2.begin(); + do { + const nsFrameList& kids1 = iterLists1 != childLists1.end() + ? iterLists1->mList + : nsFrameList::EmptyList(); + const nsFrameList& kids2 = iterLists2 != childLists2.end() + ? iterLists2->mList + : nsFrameList::EmptyList(); + int32_t l1 = kids1.GetLength(); + int32_t l2 = kids2.GetLength(); + if (l1 != l2) { + ok = false; + LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(), + "child counts don't match: "); + printf("%d != %d\n", l1, l2); + if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) { + break; + } + } + + LayoutDeviceIntRect r1, r2; + nsView* v1; + nsView* v2; + for (auto kids1Iter = kids1.begin(), kids2Iter = kids2.begin();; + ++kids1Iter, ++kids2Iter) { + nsIFrame* k1 = *kids1Iter; + nsIFrame* k2 = *kids2Iter; + if (((nullptr == k1) && (nullptr != k2)) || + ((nullptr != k1) && (nullptr == k2))) { + ok = false; + LogVerifyMessage(k1, k2, "child lists are different\n"); + break; + } else if (nullptr != k1) { + // Verify that the frames are the same size + if (!k1->GetRect().IsEqualInterior(k2->GetRect())) { + ok = false; + LogVerifyMessage(k1, k2, "(frame rects)", k1->GetRect(), + k2->GetRect()); + } + + // Make sure either both have views or neither have views; if they + // do have views, make sure the views are the same size. If the + // views have widgets, make sure they both do or neither does. If + // they do, make sure the widgets are the same size. + v1 = k1->GetView(); + v2 = k2->GetView(); + if (((nullptr == v1) && (nullptr != v2)) || + ((nullptr != v1) && (nullptr == v2))) { + ok = false; + LogVerifyMessage(k1, k2, "child views are not matched\n"); + } else if (nullptr != v1) { + if (!v1->GetBounds().IsEqualInterior(v2->GetBounds())) { + LogVerifyMessage(k1, k2, "(view rects)", v1->GetBounds(), + v2->GetBounds()); + } + + nsIWidget* w1 = v1->GetWidget(); + nsIWidget* w2 = v2->GetWidget(); + if (((nullptr == w1) && (nullptr != w2)) || + ((nullptr != w1) && (nullptr == w2))) { + ok = false; + LogVerifyMessage(k1, k2, "child widgets are not matched\n"); + } else if (nullptr != w1) { + r1 = w1->GetBounds(); + r2 = w2->GetBounds(); + if (!r1.IsEqualEdges(r2)) { + LogVerifyMessage(k1, k2, "(widget rects)", r1.ToUnknownRect(), + r2.ToUnknownRect()); + } + } + } + if (!ok && !(VerifyReflowFlags::All & gVerifyReflowFlags)) { + break; + } + + // XXX Should perhaps compare their float managers. + + // Compare the sub-trees too + if (!CompareTrees(aFirstPresContext, k1, aSecondPresContext, k2)) { + ok = false; + if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) { + break; + } + } + } else { + break; + } + } + if (!ok && (!(VerifyReflowFlags::All & gVerifyReflowFlags))) { + break; + } + + ++iterLists1; + ++iterLists2; + const bool lists1Done = iterLists1 == childLists1.end(); + const bool lists2Done = iterLists2 == childLists2.end(); + if (lists1Done != lists2Done || + (!lists1Done && iterLists1->mID != iterLists2->mID)) { + if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) { + ok = false; + } + LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(), + "child list names are not matched: "); + fprintf(stdout, "%s != %s\n", + !lists1Done ? ChildListName(iterLists1->mID) : "(null)", + !lists2Done ? ChildListName(iterLists2->mID) : "(null)"); + break; + } + } while (ok && iterLists1 != childLists1.end()); + + return ok; +} +#endif + +#if 0 +static nsIFrame* +FindTopFrame(nsIFrame* aRoot) +{ + if (aRoot) { + nsIContent* content = aRoot->GetContent(); + if (content) { + nsAtom* tag; + content->GetTag(tag); + if (nullptr != tag) { + NS_RELEASE(tag); + return aRoot; + } + } + + // Try one of the children + for (nsIFrame* kid : aRoot->PrincipalChildList()) { + nsIFrame* result = FindTopFrame(kid); + if (nullptr != result) { + return result; + } + } + } + return nullptr; +} +#endif + +#ifdef DEBUG + +// After an incremental reflow, we verify the correctness by doing a +// full reflow into a fresh frame tree. +bool PresShell::VerifyIncrementalReflow() { + if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) { + printf("Building Verification Tree...\n"); + } + + // Create a presentation context to view the new frame tree + RefPtr<nsPresContext> cx = new nsRootPresContext( + mDocument, mPresContext->IsPaginated() + ? nsPresContext::eContext_PrintPreview + : nsPresContext::eContext_Galley); + NS_ENSURE_TRUE(cx, false); + + nsDeviceContext* dc = mPresContext->DeviceContext(); + nsresult rv = cx->Init(dc); + NS_ENSURE_SUCCESS(rv, false); + + // Get our scrolling preference + nsView* rootView = mViewManager->GetRootView(); + NS_ENSURE_TRUE(rootView->HasWidget(), false); + nsIWidget* parentWidget = rootView->GetWidget(); + + // Create a new view manager. + RefPtr<nsViewManager> vm = new nsViewManager(); + NS_ENSURE_TRUE(vm, false); + rv = vm->Init(dc); + NS_ENSURE_SUCCESS(rv, false); + + // Create a child window of the parent that is our "root view/window" + // Create a view + nsRect tbounds = mPresContext->GetVisibleArea(); + nsView* view = vm->CreateView(tbounds, nullptr); + NS_ENSURE_TRUE(view, false); + + // now create the widget for the view + rv = view->CreateWidgetForParent(parentWidget, nullptr, true); + NS_ENSURE_SUCCESS(rv, false); + + // Setup hierarchical relationship in view manager + vm->SetRootView(view); + + // Make the new presentation context the same size as our + // presentation context. + cx->SetVisibleArea(mPresContext->GetVisibleArea()); + + RefPtr<PresShell> presShell = mDocument->CreatePresShell(cx, vm); + NS_ENSURE_TRUE(presShell, false); + + // Note that after we create the shell, we must make sure to destroy it + presShell->SetVerifyReflowEnable( + false); // turn off verify reflow while we're + // reflowing the test frame tree + vm->SetPresShell(presShell); + { + nsAutoCauseReflowNotifier crNotifier(this); + presShell->Initialize(); + } + presShell->FlushPendingNotifications(FlushType::Layout); + presShell->SetVerifyReflowEnable( + true); // turn on verify reflow again now that + // we're done reflowing the test frame tree + // Force the non-primary presshell to unsuppress; it doesn't want to normally + // because it thinks it's hidden + presShell->mPaintingSuppressed = false; + if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) { + printf("Verification Tree built, comparing...\n"); + } + + // Now that the document has been reflowed, use its frame tree to + // compare against our frame tree. + nsIFrame* root1 = mFrameConstructor->GetRootFrame(); + nsIFrame* root2 = presShell->GetRootFrame(); + bool ok = CompareTrees(mPresContext, root1, cx, root2); + if (!ok && (VerifyReflowFlags::Noisy & gVerifyReflowFlags)) { + printf("Verify reflow failed, primary tree:\n"); + root1->List(stdout); + printf("Verification tree:\n"); + root2->List(stdout); + } + +# if 0 + // Sample code for dumping page to png + // XXX Needs to be made more flexible + if (!ok) { + nsString stra; + static int num = 0; + stra.AppendLiteral("C:\\mozilla\\mozilla\\debug\\filea"); + stra.AppendInt(num); + stra.AppendLiteral(".png"); + gfxUtils::WriteAsPNG(presShell, stra); + nsString strb; + strb.AppendLiteral("C:\\mozilla\\mozilla\\debug\\fileb"); + strb.AppendInt(num); + strb.AppendLiteral(".png"); + gfxUtils::WriteAsPNG(presShell, strb); + ++num; + } +# endif + + presShell->EndObservingDocument(); + presShell->Destroy(); + if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) { + printf("Finished Verifying Reflow...\n"); + } + + return ok; +} + +// Layout debugging hooks +void PresShell::ListComputedStyles(FILE* out, int32_t aIndent) { + nsIFrame* rootFrame = GetRootFrame(); + if (rootFrame) { + rootFrame->Style()->List(out, aIndent); + } + + // The root element's frame's ComputedStyle is the root of a separate tree. + Element* rootElement = mDocument->GetRootElement(); + if (rootElement) { + nsIFrame* rootElementFrame = rootElement->GetPrimaryFrame(); + if (rootElementFrame) { + rootElementFrame->Style()->List(out, aIndent); + } + } +} +#endif + +#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER) +void PresShell::ListStyleSheets(FILE* out, int32_t aIndent) { + auto ListStyleSheetsAtOrigin = [this, out, aIndent](StyleOrigin origin) { + int32_t sheetCount = StyleSet()->SheetCount(origin); + for (int32_t i = 0; i < sheetCount; ++i) { + StyleSet()->SheetAt(origin, i)->List(out, aIndent); + } + }; + + ListStyleSheetsAtOrigin(StyleOrigin::UserAgent); + ListStyleSheetsAtOrigin(StyleOrigin::User); + ListStyleSheetsAtOrigin(StyleOrigin::Author); +} +#endif + +//============================================================= +//============================================================= +//-- Debug Reflow Counts +//============================================================= +//============================================================= +#ifdef MOZ_REFLOW_PERF +//------------------------------------------------------------- +void PresShell::DumpReflows() { + if (mReflowCountMgr) { + nsAutoCString uriStr; + if (mDocument) { + nsIURI* uri = mDocument->GetDocumentURI(); + if (uri) { + uri->GetPathQueryRef(uriStr); + } + } + mReflowCountMgr->DisplayTotals(uriStr.get()); + mReflowCountMgr->DisplayHTMLTotals(uriStr.get()); + mReflowCountMgr->DisplayDiffsInTotals(); + } +} + +//------------------------------------------------------------- +void PresShell::CountReflows(const char* aName, nsIFrame* aFrame) { + if (mReflowCountMgr) { + mReflowCountMgr->Add(aName, aFrame); + } +} + +//------------------------------------------------------------- +void PresShell::PaintCount(const char* aName, gfxContext* aRenderingContext, + nsPresContext* aPresContext, nsIFrame* aFrame, + const nsPoint& aOffset, uint32_t aColor) { + if (mReflowCountMgr) { + mReflowCountMgr->PaintCount(aName, aRenderingContext, aPresContext, aFrame, + aOffset, aColor); + } +} + +//------------------------------------------------------------- +void PresShell::SetPaintFrameCount(bool aPaintFrameCounts) { + if (mReflowCountMgr) { + mReflowCountMgr->SetPaintFrameCounts(aPaintFrameCounts); + } +} + +bool PresShell::IsPaintingFrameCounts() { + if (mReflowCountMgr) return mReflowCountMgr->IsPaintingFrameCounts(); + return false; +} + +//------------------------------------------------------------------ +//-- Reflow Counter Classes Impls +//------------------------------------------------------------------ + +//------------------------------------------------------------------ +ReflowCounter::ReflowCounter(ReflowCountMgr* aMgr) : mMgr(aMgr) { + ClearTotals(); + SetTotalsCache(); +} + +//------------------------------------------------------------------ +ReflowCounter::~ReflowCounter() = default; + +//------------------------------------------------------------------ +void ReflowCounter::ClearTotals() { mTotal = 0; } + +//------------------------------------------------------------------ +void ReflowCounter::SetTotalsCache() { mCacheTotal = mTotal; } + +//------------------------------------------------------------------ +void ReflowCounter::CalcDiffInTotals() { mCacheTotal = mTotal - mCacheTotal; } + +//------------------------------------------------------------------ +void ReflowCounter::DisplayTotals(const char* aStr) { + DisplayTotals(mTotal, aStr ? aStr : "Totals"); +} + +//------------------------------------------------------------------ +void ReflowCounter::DisplayDiffTotals(const char* aStr) { + DisplayTotals(mCacheTotal, aStr ? aStr : "Diff Totals"); +} + +//------------------------------------------------------------------ +void ReflowCounter::DisplayHTMLTotals(const char* aStr) { + DisplayHTMLTotals(mTotal, aStr ? aStr : "Totals"); +} + +//------------------------------------------------------------------ +void ReflowCounter::DisplayTotals(uint32_t aTotal, const char* aTitle) { + // figure total + if (aTotal == 0) { + return; + } + ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr); + + printf("%25s\t", aTitle); + printf("%d\t", aTotal); + if (gTots != this && aTotal > 0) { + gTots->Add(aTotal); + } +} + +//------------------------------------------------------------------ +void ReflowCounter::DisplayHTMLTotals(uint32_t aTotal, const char* aTitle) { + if (aTotal == 0) { + return; + } + + ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr); + FILE* fd = mMgr->GetOutFile(); + if (!fd) { + return; + } + + fprintf(fd, "<tr><td><center>%s</center></td>", aTitle); + fprintf(fd, "<td><center>%d</center></td></tr>\n", aTotal); + + if (gTots != this && aTotal > 0) { + gTots->Add(aTotal); + } +} + +//------------------------------------------------------------------ +//-- ReflowCountMgr +//------------------------------------------------------------------ + +# define KEY_BUF_SIZE_FOR_PTR \ + 24 // adequate char[] buffer to sprintf a pointer + +ReflowCountMgr::ReflowCountMgr() : mCounts(10), mIndiFrameCounts(10) { + mCycledOnce = false; + mDumpFrameCounts = false; + mDumpFrameByFrameCounts = false; + mPaintFrameByFrameCounts = false; +} + +//------------------------------------------------------------------ +ReflowCountMgr::~ReflowCountMgr() = default; + +//------------------------------------------------------------------ +ReflowCounter* ReflowCountMgr::LookUp(const char* aName) { + return mCounts.Get(aName); +} + +//------------------------------------------------------------------ +void ReflowCountMgr::Add(const char* aName, nsIFrame* aFrame) { + NS_ASSERTION(aName != nullptr, "Name shouldn't be null!"); + + if (mDumpFrameCounts) { + auto* const counter = mCounts.GetOrInsertNew(aName, this); + counter->Add(); + } + + if ((mDumpFrameByFrameCounts || mPaintFrameByFrameCounts) && + aFrame != nullptr) { + char key[KEY_BUF_SIZE_FOR_PTR]; + SprintfLiteral(key, "%p", (void*)aFrame); + auto* const counter = + mIndiFrameCounts + .LookupOrInsertWith(key, + [&aName, &aFrame, this]() { + auto counter = + MakeUnique<IndiReflowCounter>(this); + counter->mFrame = aFrame; + counter->mName.AssignASCII(aName); + return counter; + }) + .get(); + // this eliminates extra counts from super classes + if (counter && counter->mName.EqualsASCII(aName)) { + counter->mCount++; + counter->mCounter.Add(1); + } + } +} + +//------------------------------------------------------------------ +void ReflowCountMgr::PaintCount(const char* aName, + gfxContext* aRenderingContext, + nsPresContext* aPresContext, nsIFrame* aFrame, + const nsPoint& aOffset, uint32_t aColor) { + if (mPaintFrameByFrameCounts && aFrame != nullptr) { + char key[KEY_BUF_SIZE_FOR_PTR]; + SprintfLiteral(key, "%p", (void*)aFrame); + IndiReflowCounter* counter = mIndiFrameCounts.Get(key); + if (counter != nullptr && counter->mName.EqualsASCII(aName)) { + DrawTarget* drawTarget = aRenderingContext->GetDrawTarget(); + int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel(); + + aRenderingContext->Save(); + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(aOffset, appUnitsPerDevPixel); + aRenderingContext->SetMatrixDouble( + aRenderingContext->CurrentMatrixDouble().PreTranslate( + devPixelOffset)); + + // We don't care about the document language or user fonts here; + // just get a default Latin font. + nsFont font(StyleGenericFontFamily::Serif, Length::FromPixels(11)); + nsFontMetrics::Params params; + params.language = nsGkAtoms::x_western; + params.textPerf = aPresContext->GetTextPerfMetrics(); + params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup(); + RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params); + + char buf[16]; + int len = SprintfLiteral(buf, "%d", counter->mCount); + nscoord x = 0, y = fm->MaxAscent(); + nscoord width, height = fm->MaxHeight(); + fm->SetTextRunRTL(false); + width = fm->GetWidth(buf, len, drawTarget); + + sRGBColor color; + sRGBColor color2; + if (aColor != 0) { + color = sRGBColor::FromABGR(aColor); + color2 = sRGBColor(0.f, 0.f, 0.f); + } else { + gfx::Float rc = 0.f, gc = 0.f, bc = 0.f; + if (counter->mCount < 5) { + rc = 1.f; + gc = 1.f; + } else if (counter->mCount < 11) { + gc = 1.f; + } else { + rc = 1.f; + } + color = sRGBColor(rc, gc, bc); + color2 = sRGBColor(rc / 2, gc / 2, bc / 2); + } + + nsRect rect(0, 0, width + 15, height + 15); + Rect devPxRect = + NSRectToSnappedRect(rect, appUnitsPerDevPixel, *drawTarget); + ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack())); + drawTarget->FillRect(devPxRect, black); + + aRenderingContext->SetColor(color2); + fm->DrawString(buf, len, x + 15, y + 15, aRenderingContext); + aRenderingContext->SetColor(color); + fm->DrawString(buf, len, x, y, aRenderingContext); + + aRenderingContext->Restore(); + } + } +} + +//------------------------------------------------------------------ +void ReflowCountMgr::DoGrandTotals() { + mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) { + if (!entry) { + entry.Insert(MakeUnique<ReflowCounter>(this)); + } else { + entry.Data()->ClearTotals(); + } + }); + + printf("\t\t\t\tTotal\n"); + for (uint32_t i = 0; i < 78; i++) { + printf("-"); + } + printf("\n"); + for (const auto& entry : mCounts) { + entry.GetData()->DisplayTotals(entry.GetKey()); + } +} + +static void RecurseIndiTotals( + nsPresContext* aPresContext, + nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter>& aHT, + nsIFrame* aParentFrame, int32_t aLevel) { + if (aParentFrame == nullptr) { + return; + } + + char key[KEY_BUF_SIZE_FOR_PTR]; + SprintfLiteral(key, "%p", (void*)aParentFrame); + IndiReflowCounter* counter = aHT.Get(key); + if (counter) { + counter->mHasBeenOutput = true; + char* name = ToNewCString(counter->mName); + for (int32_t i = 0; i < aLevel; i++) printf(" "); + printf("%s - %p [%d][", name, (void*)aParentFrame, counter->mCount); + printf("%d", counter->mCounter.GetTotal()); + printf("]\n"); + free(name); + } + + for (nsIFrame* child : aParentFrame->PrincipalChildList()) { + RecurseIndiTotals(aPresContext, aHT, child, aLevel + 1); + } +} + +//------------------------------------------------------------------ +void ReflowCountMgr::DoIndiTotalsTree() { + printf("\n------------------------------------------------\n"); + printf("-- Individual Frame Counts\n"); + printf("------------------------------------------------\n"); + + if (mPresShell) { + nsIFrame* rootFrame = mPresShell->GetRootFrame(); + RecurseIndiTotals(mPresContext, mIndiFrameCounts, rootFrame, 0); + printf("------------------------------------------------\n"); + printf("-- Individual Counts of Frames not in Root Tree\n"); + printf("------------------------------------------------\n"); + for (const auto& counter : mIndiFrameCounts.Values()) { + if (!counter->mHasBeenOutput) { + char* name = ToNewCString(counter->mName); + printf("%s - %p [%d][", name, (void*)counter->mFrame, + counter->mCount); + printf("%d", counter->mCounter.GetTotal()); + printf("]\n"); + free(name); + } + } + } +} + +//------------------------------------------------------------------ +void ReflowCountMgr::DoGrandHTMLTotals() { + mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) { + if (!entry) { + entry.Insert(MakeUnique<ReflowCounter>(this)); + } else { + entry.Data()->ClearTotals(); + } + }); + + static const char* title[] = {"Class", "Reflows"}; + fprintf(mFD, "<tr>"); + for (uint32_t i = 0; i < ArrayLength(title); i++) { + fprintf(mFD, "<td><center><b>%s<b></center></td>", title[i]); + } + fprintf(mFD, "</tr>\n"); + + for (const auto& entry : mCounts) { + entry.GetData()->DisplayHTMLTotals(entry.GetKey()); + } +} + +//------------------------------------ +void ReflowCountMgr::DisplayTotals(const char* aStr) { +# ifdef DEBUG_rods + printf("%s\n", aStr ? aStr : "No name"); +# endif + if (mDumpFrameCounts) { + DoGrandTotals(); + } + if (mDumpFrameByFrameCounts) { + DoIndiTotalsTree(); + } +} +//------------------------------------ +void ReflowCountMgr::DisplayHTMLTotals(const char* aStr) { +# ifdef WIN32x // XXX NOT XP! + char name[1024]; + + char* sptr = strrchr(aStr, '/'); + if (sptr) { + sptr++; + strcpy(name, sptr); + char* eptr = strrchr(name, '.'); + if (eptr) { + *eptr = 0; + } + strcat(name, "_stats.html"); + } + mFD = fopen(name, "w"); + if (mFD) { + fprintf(mFD, "<html><head><title>Reflow Stats</title></head><body>\n"); + const char* title = aStr ? aStr : "No name"; + fprintf(mFD, + "<center><b>%s</b><br><table border=1 " + "style=\"background-color:#e0e0e0\">", + title); + DoGrandHTMLTotals(); + fprintf(mFD, "</center></table>\n"); + fprintf(mFD, "</body></html>\n"); + fclose(mFD); + mFD = nullptr; + } +# endif // not XP! +} + +//------------------------------------------------------------------ +void ReflowCountMgr::ClearTotals() { + for (const auto& data : mCounts.Values()) { + data->ClearTotals(); + } +} + +//------------------------------------------------------------------ +void ReflowCountMgr::ClearGrandTotals() { + mCounts.WithEntryHandle(kGrandTotalsStr, [&](auto&& entry) { + if (!entry) { + entry.Insert(MakeUnique<ReflowCounter>(this)); + } else { + entry.Data()->ClearTotals(); + entry.Data()->SetTotalsCache(); + } + }); +} + +//------------------------------------------------------------------ +void ReflowCountMgr::DisplayDiffsInTotals() { + if (mCycledOnce) { + printf("Differences\n"); + for (int32_t i = 0; i < 78; i++) { + printf("-"); + } + printf("\n"); + ClearGrandTotals(); + } + + for (const auto& entry : mCounts) { + if (mCycledOnce) { + entry.GetData()->CalcDiffInTotals(); + entry.GetData()->DisplayDiffTotals(entry.GetKey()); + } + entry.GetData()->SetTotalsCache(); + } + + mCycledOnce = true; +} + +#endif // MOZ_REFLOW_PERF + +nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) { + return FrameConstructor()->GetAbsoluteContainingBlock( + aFrame, nsCSSFrameConstructor::ABS_POS); +} + +void PresShell::ActivenessMaybeChanged() { + if (!mDocument) { + return; + } + SetIsActive(ComputeActiveness()); +} + +// A PresShell being active means that it is visible (or close to be visible, if +// the front-end is warming it). That means that when it is active we always +// tick its refresh driver at full speed if needed. +// +// Image documents behave specially in the sense that they are always "active" +// and never "in the active tab". However these documents tick manually so +// there's not much to worry about there. +bool PresShell::ComputeActiveness() const { + MOZ_LOG(gLog, LogLevel::Debug, + ("PresShell::ComputeActiveness(%s, %d)\n", + mDocument->GetDocumentURI() + ? mDocument->GetDocumentURI()->GetSpecOrDefault().get() + : "(no uri)", + mIsActive)); + + Document* doc = mDocument; + + if (doc->IsBeingUsedAsImage()) { + // Documents used as an image can remain active. They do not tick their + // refresh driver if not painted, and they can't run script or such so they + // can't really observe much else. + // + // Image docs can be displayed in multiple docs at the same time so the "in + // active tab" bool doesn't make much sense for them. + return true; + } + + if (Document* displayDoc = doc->GetDisplayDocument()) { + // Ok, we're an external resource document -- we need to use our display + // document's docshell to determine "IsActive" status, since we lack + // a browsing context of our own. + MOZ_ASSERT(!doc->GetBrowsingContext(), + "external resource doc shouldn't have its own BC"); + doc = displayDoc; + } + + BrowsingContext* bc = doc->GetBrowsingContext(); + const bool inActiveTab = bc && bc->IsActive(); + + MOZ_LOG(gLog, LogLevel::Debug, + (" > BrowsingContext %p active: %d", bc, inActiveTab)); + + Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc); + if (auto* browserChild = BrowserChild::GetFrom(root->GetDocShell())) { + // We might want to activate a tab even though the browsing-context is not + // active if the BrowserChild is considered visible. This serves two + // purposes: + // + // * For top-level tabs, we use this for tab warming. The browsing-context + // might still be inactive, but we want to activate the pres shell and + // the refresh driver. + // + // * For oop iframes, we do want to throttle them if they're not visible. + // + // TODO(emilio): Consider unifying the in-process vs. fission iframe + // throttling code (in-process throttling for non-visible iframes lives + // right now in Document::ShouldThrottleFrameRequests(), but that only + // throttles rAF). + if (!browserChild->IsVisible()) { + MOZ_LOG(gLog, LogLevel::Debug, + (" > BrowserChild %p is not visible", browserChild)); + return false; + } + + // If the browser is visible but just due to be preserving layers + // artificially, we do want to fall back to the browsing context activeness + // instead. Otherwise we do want to be active for the use cases above. + if (!browserChild->IsPreservingLayers()) { + MOZ_LOG(gLog, LogLevel::Debug, + (" > BrowserChild %p is visible and not preserving layers", + browserChild)); + return true; + } + MOZ_LOG( + gLog, LogLevel::Debug, + (" > BrowserChild %p is visible and preserving layers", browserChild)); + } + return inActiveTab; +} + +void PresShell::SetIsActive(bool aIsActive) { + MOZ_ASSERT(mDocument, "should only be called with a document"); + + const bool activityChanged = mIsActive != aIsActive; + + mIsActive = aIsActive; + + nsPresContext* presContext = GetPresContext(); + if (presContext && + presContext->RefreshDriver()->GetPresContext() == presContext) { + presContext->RefreshDriver()->SetActivity(aIsActive); + } + + if (activityChanged) { + // Propagate state-change to my resource documents' PresShells and other + // subdocuments. + // + // Note that it is fine to not propagate to fission iframes. Those will + // become active / inactive as needed as a result of they getting painted / + // not painted eventually. + auto recurse = [aIsActive](Document& aSubDoc) { + if (PresShell* presShell = aSubDoc.GetPresShell()) { + presShell->SetIsActive(aIsActive); + } + return CallState::Continue; + }; + mDocument->EnumerateExternalResources(recurse); + mDocument->EnumerateSubDocuments(recurse); + } + + UpdateImageLockingState(); + + if (activityChanged) { +#if defined(MOZ_WIDGET_ANDROID) + if (!aIsActive && presContext && + presContext->IsRootContentDocumentCrossProcess()) { + if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { + // Reset the dynamic toolbar offset state. + presContext->UpdateDynamicToolbarOffset(0); + } + } +#endif + } + + if (aIsActive) { +#ifdef ACCESSIBILITY + if (nsAccessibilityService* accService = GetAccService()) { + accService->PresShellActivated(this); + } +#endif + if (nsIFrame* rootFrame = GetRootFrame()) { + rootFrame->SchedulePaint(); + } + } +} + +RefPtr<MobileViewportManager> PresShell::GetMobileViewportManager() const { + return mMobileViewportManager; +} + +Maybe<MobileViewportManager::ManagerType> UseMobileViewportManager( + PresShell* aPresShell, Document* aDocument) { + // If we're not using APZ, we won't be able to zoom, so there is no + // point in having an MVM. + if (nsPresContext* presContext = aPresShell->GetPresContext()) { + if (nsIWidget* widget = presContext->GetNearestWidget()) { + if (!widget->AsyncPanZoomEnabled()) { + return Nothing(); + } + } + } + if (nsLayoutUtils::ShouldHandleMetaViewport(aDocument)) { + return Some(MobileViewportManager::ManagerType::VisualAndMetaViewport); + } + if (StaticPrefs::apz_mvm_force_enabled() || + nsLayoutUtils::AllowZoomingForDocument(aDocument)) { + return Some(MobileViewportManager::ManagerType::VisualViewportOnly); + } + return Nothing(); +} + +void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) { + // Determine if we require a MobileViewportManager, and what kind if so. We + // need one any time we allow resolution zooming for a document, and any time + // we want to obey <meta name="viewport"> tags for it. + Maybe<MobileViewportManager::ManagerType> mvmType = + UseMobileViewportManager(this, mDocument); + + if (mvmType.isNothing() && !mMobileViewportManager) { + // We don't need one and don't have it. So we're done. + return; + } + if (mvmType && mMobileViewportManager && + *mvmType == mMobileViewportManager->GetManagerType()) { + // We need one and we have one of the correct type, so we're done. + return; + } + + if (mMobileViewportManager) { + // We have one, but we need to either destroy it completely to replace it + // with another one of the correct type. So either way, let's destroy the + // one we have. + mMobileViewportManager->Destroy(); + mMobileViewportManager = nullptr; + mMVMContext = nullptr; + + ResetVisualViewportSize(); + + // After we clear out the MVM and the MVMContext, also reset the + // resolution to its pre-MVM value. + SetResolutionAndScaleTo(mDocument->GetSavedResolutionBeforeMVM(), + ResolutionChangeOrigin::MainThreadRestore); + + if (aAfterInitialization) { + // Force a reflow to our correct view manager size. + ForceResizeReflowWithCurrentDimensions(); + } + } + + if (mvmType) { + // Let's create the MVM of the type that we need. At this point we shouldn't + // have one. + MOZ_ASSERT(!mMobileViewportManager); + + if (mPresContext->IsRootContentDocumentCrossProcess()) { + // Store the resolution so we can restore to this resolution when + // the MVM is destroyed. + mDocument->SetSavedResolutionBeforeMVM(mResolution.valueOr(1.0f)); + + mMVMContext = new GeckoMVMContext(mDocument, this); + mMobileViewportManager = new MobileViewportManager(mMVMContext, *mvmType); + if (MOZ_UNLIKELY( + MOZ_LOG_TEST(MobileViewportManager::gLog, LogLevel::Debug))) { + nsIURI* uri = mDocument->GetDocumentURI(); + MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug, + ("Created MVM %p (type %d) for URI %s", + mMobileViewportManager.get(), (int)*mvmType, + uri ? uri->GetSpecOrDefault().get() : "(null)")); + } + + if (aAfterInitialization) { + // Setting the initial viewport will trigger a reflow. + mMobileViewportManager->SetInitialViewport(); + } + } + } +} + +bool PresShell::UsesMobileViewportSizing() const { + return mMobileViewportManager != nullptr && + nsLayoutUtils::ShouldHandleMetaViewport(mDocument); +} + +/* + * Determines the current image locking state. Called when one of the + * dependent factors changes. + */ +void PresShell::UpdateImageLockingState() { + // We're locked if we're both thawed and active. + const bool locked = !mFrozen && mIsActive; + auto* tracker = mDocument->ImageTracker(); + if (locked == tracker->GetLockingState()) { + return; + } + + tracker->SetLockingState(locked); + if (locked) { + // Request decodes for visible image frames; we want to start decoding as + // quickly as possible when we get foregrounded to minimize flashing. + for (const auto& key : mApproximatelyVisibleFrames) { + if (nsImageFrame* imageFrame = do_QueryFrame(key)) { + imageFrame->MaybeDecodeForPredictedSize(); + } + } + } +} + +PresShell* PresShell::GetRootPresShell() const { + if (mPresContext) { + nsPresContext* rootPresContext = mPresContext->GetRootPresContext(); + if (rootPresContext) { + return rootPresContext->PresShell(); + } + } + return nullptr; +} + +void PresShell::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const { + MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf; + mFrameArena.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::PresShell); + aSizes.mLayoutPresShellSize += mallocSizeOf(this); + if (mCaret) { + aSizes.mLayoutPresShellSize += mCaret->SizeOfIncludingThis(mallocSizeOf); + } + aSizes.mLayoutPresShellSize += + mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(mallocSizeOf) + + mFramesToDirty.ShallowSizeOfExcludingThis(mallocSizeOf) + + mPendingScrollAnchorSelection.ShallowSizeOfExcludingThis(mallocSizeOf) + + mPendingScrollAnchorAdjustment.ShallowSizeOfExcludingThis(mallocSizeOf); + + aSizes.mLayoutTextRunsSize += SizeOfTextRuns(mallocSizeOf); + + aSizes.mLayoutPresContextSize += + mPresContext->SizeOfIncludingThis(mallocSizeOf); + + mFrameConstructor->AddSizeOfIncludingThis(aSizes); +} + +size_t PresShell::SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const { + nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); + if (!rootFrame) { + return 0; + } + + // clear the TEXT_RUN_MEMORY_ACCOUNTED flags + nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, nullptr, + /* clear = */ true); + + // collect the total memory in use for textruns + return nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, aMallocSizeOf, + /* clear = */ false); +} + +void PresShell::MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty) { + nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); + if (rootFrame) { + const nsFrameList& childList = + rootFrame->GetChildList(FrameChildListID::Fixed); + for (nsIFrame* childFrame : childList) { + FrameNeedsReflow(childFrame, aIntrinsicDirty, NS_FRAME_IS_DIRTY); + } + } +} + +static void AppendSubtree(nsIDocShell* aDocShell, + nsTArray<nsCOMPtr<nsIDocumentViewer>>& aArray) { + if (nsCOMPtr<nsIDocumentViewer> viewer = aDocShell->GetDocViewer()) { + aArray.AppendElement(viewer); + } + + int32_t n = aDocShell->GetInProcessChildCount(); + for (int32_t i = 0; i < n; i++) { + nsCOMPtr<nsIDocShellTreeItem> childItem; + aDocShell->GetInProcessChildAt(i, getter_AddRefs(childItem)); + if (childItem) { + nsCOMPtr<nsIDocShell> child(do_QueryInterface(childItem)); + AppendSubtree(child, aArray); + } + } +} + +void PresShell::MaybeReflowForInflationScreenSizeChange() { + nsPresContext* pc = GetPresContext(); + const bool fontInflationWasEnabled = FontSizeInflationEnabled(); + RecomputeFontSizeInflationEnabled(); + bool changed = false; + if (FontSizeInflationEnabled() && FontSizeInflationMinTwips() != 0) { + pc->ScreenSizeInchesForFontInflation(&changed); + } + + changed = changed || fontInflationWasEnabled != FontSizeInflationEnabled(); + if (!changed) { + return; + } + if (nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell()) { + nsTArray<nsCOMPtr<nsIDocumentViewer>> array; + AppendSubtree(docShell, array); + for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) { + nsCOMPtr<nsIDocumentViewer> viewer = array[i]; + if (RefPtr<PresShell> descendantPresShell = viewer->GetPresShell()) { + nsIFrame* rootFrame = descendantPresShell->GetRootFrame(); + if (rootFrame) { + descendantPresShell->FrameNeedsReflow( + rootFrame, IntrinsicDirty::FrameAncestorsAndDescendants, + NS_FRAME_IS_DIRTY); + } + } + } + } +} + +void PresShell::CompleteChangeToVisualViewportSize() { + // This can get called during reflow, if the caller wants to get the latest + // visual viewport size after scrollbars have been added/removed. In such a + // case, we don't need to mark things as dirty because the things that we + // would mark dirty either just got updated (the root scrollframe's + // scrollbars), or will be laid out later during this reflow cycle (fixed-pos + // items). Callers that update the visual viewport during a reflow are + // responsible for maintaining these invariants. + if (!mIsReflowing) { + if (nsIScrollableFrame* rootScrollFrame = + GetRootScrollFrameAsScrollable()) { + rootScrollFrame->MarkScrollbarsDirtyForReflow(); + } + MarkFixedFramesForReflow(IntrinsicDirty::None); + } + + MaybeReflowForInflationScreenSizeChange(); + + if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) { + window->VisualViewport()->PostResizeEvent(); + } +} + +void PresShell::SetVisualViewportSize(nscoord aWidth, nscoord aHeight) { + MOZ_ASSERT(aWidth >= 0.0 && aHeight >= 0.0); + + if (!mVisualViewportSizeSet || mVisualViewportSize.width != aWidth || + mVisualViewportSize.height != aHeight) { + mVisualViewportSizeSet = true; + mVisualViewportSize.width = aWidth; + mVisualViewportSize.height = aHeight; + + CompleteChangeToVisualViewportSize(); + } +} + +void PresShell::ResetVisualViewportSize() { + if (mVisualViewportSizeSet) { + mVisualViewportSizeSet = false; + mVisualViewportSize.width = 0; + mVisualViewportSize.height = 0; + + CompleteChangeToVisualViewportSize(); + } +} + +bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset, + const nsPoint& aPrevLayoutScrollPos) { + nsPoint newOffset = aScrollOffset; + nsIScrollableFrame* rootScrollFrame = GetRootScrollFrameAsScrollable(); + if (rootScrollFrame) { + // See the comment in nsHTMLScrollFrame::Reflow above the call to + // SetVisualViewportOffset for why we need to do this. + nsRect scrollRange = rootScrollFrame->GetScrollRangeForUserInputEvents(); + if (!scrollRange.Contains(newOffset)) { + newOffset.x = std::min(newOffset.x, scrollRange.XMost()); + newOffset.x = std::max(newOffset.x, scrollRange.x); + newOffset.y = std::min(newOffset.y, scrollRange.YMost()); + newOffset.y = std::max(newOffset.y, scrollRange.y); + } + } + + // Careful here not to call GetVisualViewportOffset to get the previous visual + // viewport offset because if mVisualViewportOffset is nothing then we'll get + // the layout scroll position directly from the scroll frame and it has likely + // already been updated. + nsPoint prevOffset = aPrevLayoutScrollPos; + if (mVisualViewportOffset.isSome()) { + prevOffset = *mVisualViewportOffset; + } + if (prevOffset == newOffset) { + return false; + } + + mVisualViewportOffset = Some(newOffset); + + if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) { + window->VisualViewport()->PostScrollEvent(prevOffset, aPrevLayoutScrollPos); + } + + if (IsVisualViewportSizeSet() && rootScrollFrame) { + rootScrollFrame->Anchor()->UserScrolled(); + } + + if (gfxPlatform::UseDesktopZoomingScrollbars()) { + if (nsIScrollableFrame* rootScrollFrame = + GetRootScrollFrameAsScrollable()) { + rootScrollFrame->UpdateScrollbarPosition(); + } + } + + return true; +} + +void PresShell::ResetVisualViewportOffset() { mVisualViewportOffset.reset(); } + +void PresShell::ScrollToVisual(const nsPoint& aVisualViewportOffset, + FrameMetrics::ScrollOffsetUpdateType aUpdateType, + ScrollMode aMode) { + MOZ_ASSERT(aMode == ScrollMode::Instant || aMode == ScrollMode::SmoothMsd); + + if (aMode == ScrollMode::SmoothMsd) { + if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) { + if (sf->SmoothScrollVisual(aVisualViewportOffset, aUpdateType)) { + return; + } + } + } + + // If the caller asked for instant scroll, or if we failed + // to do a smooth scroll, do an instant scroll. + SetPendingVisualScrollUpdate(aVisualViewportOffset, aUpdateType); +} + +void PresShell::SetPendingVisualScrollUpdate( + const nsPoint& aVisualViewportOffset, + FrameMetrics::ScrollOffsetUpdateType aUpdateType) { + mPendingVisualScrollUpdate = + Some(VisualScrollUpdate{aVisualViewportOffset, aUpdateType}); + + // The pending update is picked up during the next paint. + // Schedule a paint to make sure one will happen. + if (nsIFrame* rootFrame = GetRootFrame()) { + rootFrame->SchedulePaint(); + } +} + +void PresShell::ClearPendingVisualScrollUpdate() { + if (mPendingVisualScrollUpdate && mPendingVisualScrollUpdate->mAcknowledged) { + mPendingVisualScrollUpdate = mozilla::Nothing(); + } +} + +void PresShell::AcknowledgePendingVisualScrollUpdate() { + MOZ_ASSERT(mPendingVisualScrollUpdate); + mPendingVisualScrollUpdate->mAcknowledged = true; +} + +nsPoint PresShell::GetVisualViewportOffsetRelativeToLayoutViewport() const { + return GetVisualViewportOffset() - GetLayoutViewportOffset(); +} + +nsPoint PresShell::GetLayoutViewportOffset() const { + nsPoint result; + if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) { + result = sf->GetScrollPosition(); + } + return result; +} + +nsSize PresShell::GetLayoutViewportSize() const { + nsSize result; + if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) { + result = sf->GetScrollPortRect().Size(); + } + return result; +} + +nsSize PresShell::GetVisualViewportSizeUpdatedByDynamicToolbar() const { + NS_ASSERTION(mVisualViewportSizeSet, + "asking for visual viewport size when its not set?"); + if (!mMobileViewportManager) { + return mVisualViewportSize; + } + + MOZ_ASSERT(GetDynamicToolbarState() == DynamicToolbarState::InTransition || + GetDynamicToolbarState() == DynamicToolbarState::Collapsed); + + nsSize sizeUpdatedByDynamicToolbar = + mMobileViewportManager->GetVisualViewportSizeUpdatedByDynamicToolbar(); + return sizeUpdatedByDynamicToolbar == nsSize() ? mVisualViewportSize + : sizeUpdatedByDynamicToolbar; +} + +void PresShell::RecomputeFontSizeInflationEnabled() { + mFontSizeInflationEnabled = DetermineFontSizeInflationState(); +} + +bool PresShell::DetermineFontSizeInflationState() { + MOZ_ASSERT(mPresContext, "our pres context should not be null"); + if (mPresContext->IsChrome()) { + return false; + } + + if (FontSizeInflationEmPerLine() == 0 && FontSizeInflationMinTwips() == 0) { + return false; + } + + // Force-enabling font inflation always trumps the heuristics here. + if (!FontSizeInflationForceEnabled()) { + if (BrowserChild* tab = BrowserChild::GetFrom(this)) { + // We're in a child process. Cancel inflation if we're not + // async-pan zoomed. + if (!tab->AsyncPanZoomEnabled()) { + return false; + } + } else if (XRE_IsParentProcess()) { + // We're in the master process. Cancel inflation if it's been + // explicitly disabled. + if (FontSizeInflationDisabledInMasterProcess()) { + return false; + } + } + } + + Maybe<LayoutDeviceIntSize> displaySize; + // The MVM already caches the top-level content viewer size and is therefore + // the fastest way of getting that data. + if (mPresContext->IsRootContentDocumentCrossProcess()) { + if (mMobileViewportManager) { + displaySize = Some(mMobileViewportManager->DisplaySize()); + } + } else if (PresShell* rootPresShell = GetRootPresShell()) { + // With any luck, we can get at the root content document without any cross- + // process shenanigans. + if (auto mvm = rootPresShell->GetMobileViewportManager()) { + displaySize = Some(mvm->DisplaySize()); + } + } + + if (!displaySize) { + // Unfortunately, it looks like the root content document lives in a + // different process. For consistency's sake it would be best to always use + // the content viewer size of the root content document, but it's not worth + // the effort, because this only makes a difference in the case of pages + // with an explicitly sized viewport (neither "width=device-width" nor a + // completely missing viewport tag) being loaded within a frame, which is + // hopefully a relatively exotic case. + // More to the point, these viewport size and zoom-based calculations don't + // really make sense for frames anyway, so instead of creating a way to + // access the content viewer size of the top level document cross-process, + // we probably rather want frames to simply inherit the font inflation state + // of their top-level parent and should therefore invest any time spent on + // getting things to work cross-process into that (bug 1724311). + + // Until we get around to that though, we just use the content viewer size + // of however high we can get within the same process. + + // (This also serves as a fallback code path if the MVM isn't available, + // e.g. when debugging in non-e10s mode on Desktop.) + nsPresContext* topContext = + mPresContext->GetInProcessRootContentDocumentPresContext(); + LayoutDeviceIntSize result; + if (!nsLayoutUtils::GetDocumentViewerSize(topContext, result)) { + return false; + } + displaySize = Some(result); + } + + ScreenIntSize screenSize = ViewAs<ScreenPixel>( + displaySize.value(), + PixelCastJustification::LayoutDeviceIsScreenForBounds); + nsViewportInfo vInf = GetDocument()->GetViewportInfo(screenSize); + + CSSToScreenScale defaultScale = + mPresContext->CSSToDevPixelScale() * LayoutDeviceToScreenScale(1.0); + + if (vInf.GetDefaultZoom() >= defaultScale || vInf.IsAutoSizeEnabled()) { + return false; + } + + return true; +} + +static nsIWidget* GetPresContextContainerWidget(nsPresContext* aPresContext) { + nsCOMPtr<nsISupports> container = aPresContext->Document()->GetContainer(); + nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container); + if (!baseWindow) { + return nullptr; + } + + nsCOMPtr<nsIWidget> mainWidget; + baseWindow->GetMainWidget(getter_AddRefs(mainWidget)); + return mainWidget; +} + +static bool IsTopLevelWidget(nsIWidget* aWidget) { + using WindowType = mozilla::widget::WindowType; + + auto windowType = aWidget->GetWindowType(); + return windowType == WindowType::TopLevel || + windowType == WindowType::Dialog || windowType == WindowType::Popup || + windowType == WindowType::Sheet; +} + +PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() { + nsSize minSize(0, 0); + nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame(); + if (!rootFrame || !mPresContext) { + return {minSize, maxSize}; + } + const auto* pos = rootFrame->StylePosition(); + if (pos->mMinWidth.ConvertsToLength()) { + minSize.width = pos->mMinWidth.ToLength(); + } + if (pos->mMinHeight.ConvertsToLength()) { + minSize.height = pos->mMinHeight.ToLength(); + } + if (pos->mMaxWidth.ConvertsToLength()) { + maxSize.width = pos->mMaxWidth.ToLength(); + } + if (pos->mMaxHeight.ConvertsToLength()) { + maxSize.height = pos->mMaxHeight.ToLength(); + } + return {minSize, maxSize}; +} + +void PresShell::SyncWindowProperties(bool aSync) { + nsView* view = mViewManager->GetRootView(); + if (!view || !view->HasWidget()) { + return; + } + RefPtr pc = mPresContext; + if (!pc) { + return; + } + + nsCOMPtr<nsIWidget> windowWidget = GetPresContextContainerWidget(pc); + if (!windowWidget || !IsTopLevelWidget(windowWidget)) { + return; + } + + nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame(); + if (!rootFrame) { + return; + } + + if (!aSync) { + view->SetNeedsWindowPropertiesSync(); + return; + } + + AutoWeakFrame weak(rootFrame); + if (!GetRootScrollFrame()) { + // Scrollframes use native widgets which don't work well with + // translucent windows, at least in Windows XP. So if the document + // has a root scrollrame it's useless to try to make it transparent, + // we'll just get something broken. + // We can change this to allow translucent toplevel HTML documents + // (e.g. to do something like Dashboard widgets), once we + // have broad support for translucent scrolled documents, but be + // careful because apparently some Firefox extensions expect + // openDialog("something.html") to produce an opaque window + // even if the HTML doesn't have a background-color set. + auto* canvas = GetCanvasFrame(); + widget::TransparencyMode mode = nsLayoutUtils::GetFrameTransparency( + canvas ? canvas : rootFrame, rootFrame); + windowWidget->SetTransparencyMode(mode); + + // For macOS, apply color scheme overrides to the top level window widget. + if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) { + windowWidget->SetColorScheme(scheme); + } + } + + if (!weak.IsAlive()) { + return; + } + + const auto& constraints = GetWindowSizeConstraints(); + nsContainerFrame::SetSizeConstraints(pc, windowWidget, constraints.mMinSize, + constraints.mMaxSize); +} + +nsresult PresShell::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType, + bool* aRetVal) { + *aRetVal = false; + return NS_OK; +} + +void PresShell::NotifyStyleSheetServiceSheetAdded(StyleSheet* aSheet, + uint32_t aSheetType) { + switch (aSheetType) { + case nsIStyleSheetService::AGENT_SHEET: + AddAgentSheet(aSheet); + break; + case nsIStyleSheetService::USER_SHEET: + AddUserSheet(aSheet); + break; + case nsIStyleSheetService::AUTHOR_SHEET: + AddAuthorSheet(aSheet); + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected aSheetType value"); + break; + } +} + +void PresShell::NotifyStyleSheetServiceSheetRemoved(StyleSheet* aSheet, + uint32_t aSheetType) { + StyleSet()->RemoveStyleSheet(*aSheet); + mDocument->ApplicableStylesChanged(); +} + +nsIContent* PresShell::EventHandler::GetOverrideClickTarget( + WidgetGUIEvent* aGUIEvent, nsIFrame* aFrame) { + if (aGUIEvent->mMessage != eMouseUp) { + return nullptr; + } + + MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass); + WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); + + uint32_t flags = 0; + RelativeTo relativeTo{aFrame}; + nsPoint eventPoint = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo); + if (mouseEvent->mIgnoreRootScrollFrame) { + flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME; + } + + nsIFrame* target = + FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags); + if (!target) { + return nullptr; + } + + nsIContent* overrideClickTarget = target->GetContent(); + while (overrideClickTarget && !overrideClickTarget->IsElement()) { + overrideClickTarget = overrideClickTarget->GetFlattenedTreeParent(); + } + return overrideClickTarget; +} + +/****************************************************************************** + * PresShell::EventHandler::EventTargetData + ******************************************************************************/ + +void PresShell::EventHandler::EventTargetData::SetFrameAndComputePresShell( + nsIFrame* aFrameToHandleEvent) { + if (aFrameToHandleEvent) { + mFrame = aFrameToHandleEvent; + mPresShell = aFrameToHandleEvent->PresShell(); + } else { + mFrame = nullptr; + mPresShell = nullptr; + } +} + +void PresShell::EventHandler::EventTargetData:: + SetFrameAndComputePresShellAndContent(nsIFrame* aFrameToHandleEvent, + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aFrameToHandleEvent); + MOZ_ASSERT(aGUIEvent); + + SetFrameAndComputePresShell(aFrameToHandleEvent); + SetContentForEventFromFrame(aGUIEvent); +} + +void PresShell::EventHandler::EventTargetData::SetContentForEventFromFrame( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(mFrame); + mContent = nullptr; + mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(mContent)); + AssertIfEventTargetContentAndFrameContentMismatch(aGUIEvent); +} + +nsIContent* PresShell::EventHandler::EventTargetData::GetFrameContent() const { + return mFrame ? mFrame->GetContent() : nullptr; +} + +void PresShell::EventHandler::EventTargetData:: + AssertIfEventTargetContentAndFrameContentMismatch( + const WidgetGUIEvent* aGUIEvent) const { +#ifdef DEBUG + if (!mContent || !mFrame || !mFrame->GetContent()) { + return; + } + + // If we know the event, we can compute the target correctly. + if (aGUIEvent) { + nsCOMPtr<nsIContent> content; + mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(content)); + MOZ_ASSERT(mContent == content); + return; + } + + // Otherwise, we can check only whether mContent is an inclusive ancestor + // element or not. + if (!mContent->IsElement()) { + MOZ_ASSERT(mContent == mFrame->GetContent()); + return; + } + const Element* const closestInclusiveAncestorElement = + [&]() -> const Element* { + for (const nsIContent* const content : + mFrame->GetContent()->InclusiveFlatTreeAncestorsOfType<nsIContent>()) { + if (content->IsElement()) { + return content->AsElement(); + } + } + return nullptr; + }(); + if (closestInclusiveAncestorElement == mContent) { + return; + } + if (closestInclusiveAncestorElement->IsInNativeAnonymousSubtree() && + (mContent == closestInclusiveAncestorElement + ->FindFirstNonChromeOnlyAccessContent())) { + return; + } + NS_WARNING(nsPrintfCString("mContent=%s", ToString(*mContent).c_str()).get()); + NS_WARNING(nsPrintfCString("mFrame->GetContent()=%s", + ToString(*mFrame->GetContent()).c_str()) + .get()); + MOZ_ASSERT(mContent == mFrame->GetContent()); +#endif // #ifdef DEBUG +} + +bool PresShell::EventHandler::EventTargetData::MaybeRetargetToActiveDocument( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(mFrame); + MOZ_ASSERT(mPresShell); + MOZ_ASSERT(!mContent, "Doesn't support to retarget the content"); + + EventStateManager* activeESM = + EventStateManager::GetActiveEventStateManager(); + if (!activeESM) { + return false; + } + + if (aGUIEvent->mClass != ePointerEventClass && + !aGUIEvent->HasMouseEventMessage()) { + return false; + } + + if (activeESM == GetEventStateManager()) { + return false; + } + + nsPresContext* activePresContext = activeESM->GetPresContext(); + if (!activePresContext) { + return false; + } + + PresShell* activePresShell = activePresContext->GetPresShell(); + if (!activePresShell) { + return false; + } + + // Note, currently for backwards compatibility we don't forward mouse events + // to the active document when mouse is over some subdocument. + if (!nsContentUtils::ContentIsCrossDocDescendantOf( + activePresShell->GetDocument(), GetDocument())) { + return false; + } + + SetFrameAndComputePresShell(activePresShell->GetRootFrame()); + return true; +} + +bool PresShell::EventHandler::EventTargetData::ComputeElementFromFrame( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + MOZ_ASSERT(aGUIEvent->IsUsingCoordinates()); + MOZ_ASSERT(mPresShell); + MOZ_ASSERT(mFrame); + + SetContentForEventFromFrame(aGUIEvent); + + // If there is no content for this frame, target it anyway. Some frames can + // be targeted but do not have content, particularly windows with scrolling + // off. + if (!mContent) { + return true; + } + + // Bug 103055, bug 185889: mouse events apply to *elements*, not all nodes. + // Thus we get the nearest element parent here. + // XXX we leave the frame the same even if we find an element parent, so that + // the text frame will receive the event (selection and friends are the ones + // who care about that anyway) + // + // We use weak pointers because during this tight loop, the node + // will *not* go away. And this happens on every mousemove. + nsIContent* content = mContent; + while (content && !content->IsElement()) { + content = content->GetFlattenedTreeParent(); + } + mContent = content; + + // If we found an element, target it. Otherwise, target *nothing*. + return !!mContent; +} + +void PresShell::EventHandler::EventTargetData::UpdateWheelEventTarget( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + + if (aGUIEvent->mMessage != eWheel) { + return; + } + + // If dom.event.wheel-event-groups.enabled is not set or the stored + // event target is removed, we will not get a event target frame from the + // wheel transaction here. + nsIFrame* groupFrame = WheelTransaction::GetEventTargetFrame(); + if (!groupFrame) { + return; + } + + // If the browsing context is no longer the same as the context of the + // current wheel transaction, do not override the event target. + if (!groupFrame->PresContext() || !groupFrame->PresShell() || + groupFrame->PresContext() != GetPresContext()) { + return; + } + + // If dom.event.wheel-event-groups.enabled is set and whe have a stored + // event target from the wheel transaction, override the event target. + SetFrameAndComputePresShellAndContent(groupFrame, aGUIEvent); +} + +void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget( + WidgetGUIEvent* aGUIEvent) { + MOZ_ASSERT(aGUIEvent); + + if (aGUIEvent->mClass != eTouchEventClass) { + return; + } + + if (aGUIEvent->mMessage == eTouchStart) { + WidgetTouchEvent* touchEvent = aGUIEvent->AsTouchEvent(); + nsIFrame* newFrame = + TouchManager::SuppressInvalidPointsAndGetTargetedFrame(touchEvent); + if (!newFrame) { + return; // XXX Why don't we stop handling the event in this case? + } + SetFrameAndComputePresShellAndContent(newFrame, aGUIEvent); + return; + } + + PresShell* newPresShell = PresShell::GetShellForTouchEvent(aGUIEvent); + if (!newPresShell) { + return; // XXX Why don't we stop handling the event in this case? + } + + // Touch events (except touchstart) are dispatching to the captured + // element. Get correct shell from it. + mPresShell = newPresShell; +} + +/****************************************************************************** + * PresShell::EventHandler::HandlingTimeAccumulator + ******************************************************************************/ + +PresShell::EventHandler::HandlingTimeAccumulator::HandlingTimeAccumulator( + const PresShell::EventHandler& aEventHandler, const WidgetEvent* aEvent) + : mEventHandler(aEventHandler), + mEvent(aEvent), + mHandlingStartTime(TimeStamp::Now()) { + MOZ_ASSERT(mEvent); + MOZ_ASSERT(mEvent->IsTrusted()); +} + +PresShell::EventHandler::HandlingTimeAccumulator::~HandlingTimeAccumulator() { + if (mEvent->mTimeStamp <= mEventHandler.mPresShell->mLastOSWake) { + return; + } + + switch (mEvent->mMessage) { + case eKeyPress: + case eKeyDown: + case eKeyUp: + Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_KEYBOARD_MS, + mHandlingStartTime); + return; + case eMouseDown: + Telemetry::AccumulateTimeDelta( + Telemetry::INPUT_EVENT_HANDLED_MOUSE_DOWN_MS, mHandlingStartTime); + return; + case eMouseUp: + Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_MOUSE_UP_MS, + mHandlingStartTime); + return; + case eMouseMove: + if (mEvent->mFlags.mHandledByAPZ) { + Telemetry::AccumulateTimeDelta( + Telemetry::INPUT_EVENT_HANDLED_APZ_MOUSE_MOVE_MS, + mHandlingStartTime); + } + return; + case eWheel: + if (mEvent->mFlags.mHandledByAPZ) { + Telemetry::AccumulateTimeDelta( + Telemetry::INPUT_EVENT_HANDLED_APZ_WHEEL_MS, mHandlingStartTime); + } + return; + case eTouchMove: + if (mEvent->mFlags.mHandledByAPZ) { + Telemetry::AccumulateTimeDelta( + Telemetry::INPUT_EVENT_HANDLED_APZ_TOUCH_MOVE_MS, + mHandlingStartTime); + } + return; + default: + return; + } +} + +void PresShell::EndPaint() { + ClearPendingVisualScrollUpdate(); + + if (mDocument) { + mDocument->EnumerateSubDocuments([](Document& aSubDoc) { + if (PresShell* presShell = aSubDoc.GetPresShell()) { + presShell->EndPaint(); + } + return CallState::Continue; + }); + + if (nsPresContext* presContext = GetPresContext()) { + if (PerformanceMainThread* perf = + presContext->GetPerformanceMainThread()) { + perf->FinalizeLCPEntriesForText(); + } + } + } +} + +void PresShell::PingPerTickTelemetry(FlushType aFlushType) { + mLayoutTelemetry.PingPerTickTelemetry(aFlushType); +} + +bool PresShell::GetZoomableByAPZ() const { + return mZoomConstraintsClient && mZoomConstraintsClient->GetAllowZoom(); +} + +bool PresShell::ReflowForHiddenContentIfNeeded() { + if (mHiddenContentInForcedLayout.IsEmpty()) { + return false; + } + mDocument->FlushPendingNotifications(FlushType::Layout); + mHiddenContentInForcedLayout.Clear(); + return true; +} + +void PresShell::UpdateHiddenContentInForcedLayout(nsIFrame* aFrame) { + if (!aFrame || !aFrame->IsSubtreeDirty() || + !StaticPrefs::layout_css_content_visibility_enabled()) { + return; + } + + nsIFrame* topmostFrameWithContentHidden = nullptr; + for (nsIFrame* cur = aFrame->GetInFlowParent(); cur; + cur = cur->GetInFlowParent()) { + if (cur->HidesContent()) { + topmostFrameWithContentHidden = cur; + mHiddenContentInForcedLayout.Insert(cur->GetContent()); + } + } + + if (mHiddenContentInForcedLayout.IsEmpty()) { + return; + } + + // Queue and immediately flush a reflow for this node. + MOZ_ASSERT(topmostFrameWithContentHidden); + FrameNeedsReflow(topmostFrameWithContentHidden, IntrinsicDirty::None, + NS_FRAME_IS_DIRTY); +} + +void PresShell::EnsureReflowIfFrameHasHiddenContent(nsIFrame* aFrame) { + MOZ_ASSERT(mHiddenContentInForcedLayout.IsEmpty()); + + UpdateHiddenContentInForcedLayout(aFrame); + ReflowForHiddenContentIfNeeded(); +} + +bool PresShell::IsForcingLayoutForHiddenContent(const nsIFrame* aFrame) const { + return mHiddenContentInForcedLayout.Contains(aFrame->GetContent()); +} + +void PresShell::UpdateRelevancyOfContentVisibilityAutoFrames() { + if (mContentVisibilityRelevancyToUpdate.isEmpty()) { + return; + } + + for (nsIFrame* frame : mContentVisibilityAutoFrames) { + frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate); + } + + if (nsPresContext* presContext = GetPresContext()) { + presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded(); + } + + mContentVisibilityRelevancyToUpdate.clear(); +} + +void PresShell::ScheduleContentRelevancyUpdate(ContentRelevancyReason aReason) { + if (MOZ_UNLIKELY(mIsDestroying)) { + return; + } + + mContentVisibilityRelevancyToUpdate += aReason; + + SetNeedLayoutFlush(); + if (nsPresContext* presContext = GetPresContext()) { + presContext->RefreshDriver()->EnsureContentRelevancyUpdateHappens(); + } +} + +PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() { + ProximityToViewportResult result; + if (mContentVisibilityAutoFrames.IsEmpty()) { + return result; + } + + auto margin = LengthPercentage::FromPercentage( + StaticPrefs::layout_css_content_visibility_relevant_content_margin() / + 100.0f); + + auto rootMargin = StyleRect<LengthPercentage>::WithAllSides(margin); + + auto input = DOMIntersectionObserver::ComputeInput( + *mDocument, /* aRoot = */ nullptr, &rootMargin); + + for (nsIFrame* frame : mContentVisibilityAutoFrames) { + auto* element = frame->GetContent()->AsElement(); + result.mAnyScrollIntoViewFlag |= + element->TemporarilyVisibleForScrolledIntoViewDescendant(); + + // 14.2.3.1 + Maybe<bool> oldVisibility = element->GetVisibleForContentVisibility(); + bool checkForInitialDetermination = + oldVisibility.isNothing() && + (element->GetContentRelevancy().isNothing() || + element->GetContentRelevancy()->isEmpty()); + + // 14.2.3.2 + bool intersects = + DOMIntersectionObserver::Intersect( + input, *element, + DOMIntersectionObserver::IsForProximityToViewport::Yes) + .Intersects(); + element->SetVisibleForContentVisibility(intersects); + if (oldVisibility.isNothing() || *oldVisibility != intersects) { + frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible); + } + + // 14.2.3.3 + if (checkForInitialDetermination && intersects) { + result.mHadInitialDetermination = true; + } + } + if (nsPresContext* presContext = GetPresContext()) { + presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded(); + } + + return result; +} + +void PresShell::ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags() + const { + for (nsIFrame* frame : mContentVisibilityAutoFrames) { + frame->GetContent() + ->AsElement() + ->SetTemporarilyVisibleForScrolledIntoViewDescendant(false); + } +} + +void PresShell::UpdateContentRelevancyImmediately( + ContentRelevancyReason aReason) { + if (MOZ_UNLIKELY(mIsDestroying)) { + return; + } + + mContentVisibilityRelevancyToUpdate += aReason; + + SetNeedLayoutFlush(); + UpdateRelevancyOfContentVisibilityAutoFrames(); +} |