/* -*- 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/EventForwards.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/MouseEventBinding.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_test.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 #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 mRange; nsDisplayListBuilder mBuilder; nsDisplayList mList; // offset of builder's reference frame to the root frame nsPoint mRootOffset; // Resolution at which the items are normally painted. So if we're painting // these items in a range separately from the "full display list", we may want // to paint them at this resolution. float mResolution = 1.0; RangePaintInfo(nsRange* aRange, nsIFrame* aFrame) : mRange(aRange), mBuilder(aFrame, nsDisplayListBuilderMode::Painting, false), mList(&mBuilder) { MOZ_COUNT_CTOR(RangePaintInfo); mBuilder.BeginFrame(); } ~RangePaintInfo() { mList.DeleteAll(&mBuilder); mBuilder.EndFrame(); MOZ_COUNT_DTOR(RangePaintInfo); } }; #undef NOISY // ---------------------------------------------------------------------- #ifdef DEBUG // Set the environment variable GECKO_VERIFY_REFLOW_FLAGS to one or // more of the following flags (comma separated) for handy debug // output. static VerifyReflowFlags gVerifyReflowFlags; struct VerifyReflowFlagData { const char* name; VerifyReflowFlags bit; }; static const VerifyReflowFlagData gFlags[] = { // clang-format off { "verify", VerifyReflowFlags::On }, { "reflow", VerifyReflowFlags::Noisy }, { "all", VerifyReflowFlags::All }, { "list-commands", VerifyReflowFlags::DumpCommands }, { "noisy-commands", VerifyReflowFlags::NoisyCommands }, { "really-noisy-commands", VerifyReflowFlags::ReallyNoisyCommands }, { "resize", VerifyReflowFlags::DuringResizeReflow }, // clang-format on }; # define NUM_VERIFY_REFLOW_FLAGS (sizeof(gFlags) / sizeof(gFlags[0])) static void ShowVerifyReflowFlags() { printf("Here are the available GECKO_VERIFY_REFLOW_FLAGS:\n"); const VerifyReflowFlagData* flag = gFlags; const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS; while (flag < limit) { printf(" %s\n", flag->name); ++flag; } printf("Note: GECKO_VERIFY_REFLOW_FLAGS is a comma separated list of flag\n"); printf("names (no whitespace)\n"); } #endif //======================================================================== //======================================================================== //======================================================================== #ifdef MOZ_REFLOW_PERF class ReflowCountMgr; static const char kGrandTotalsStr[] = "Grand Totals"; // Counting Class class ReflowCounter { public: explicit ReflowCounter(ReflowCountMgr* aMgr = nullptr); ~ReflowCounter(); void ClearTotals(); void DisplayTotals(const char* aStr); void DisplayDiffTotals(const char* aStr); void DisplayHTMLTotals(const char* aStr); void Add() { mTotal++; } void Add(uint32_t aTotal) { mTotal += aTotal; } void CalcDiffInTotals(); void SetTotalsCache(); void SetMgr(ReflowCountMgr* aMgr) { mMgr = aMgr; } uint32_t GetTotal() { return mTotal; } protected: void DisplayTotals(uint32_t aTotal, const char* aTitle); void DisplayHTMLTotals(uint32_t aTotal, const char* aTitle); uint32_t mTotal; uint32_t mCacheTotal; ReflowCountMgr* mMgr; // weak reference (don't delete) }; // Counting Class class IndiReflowCounter { public: explicit IndiReflowCounter(ReflowCountMgr* aMgr = nullptr) : mFrame(nullptr), mCount(0), mMgr(aMgr), mCounter(aMgr), mHasBeenOutput(false) {} virtual ~IndiReflowCounter() = default; nsAutoString mName; nsIFrame* mFrame; // weak reference (don't delete) int32_t mCount; ReflowCountMgr* mMgr; // weak reference (don't delete) ReflowCounter mCounter; bool mHasBeenOutput; }; //-------------------- // Manager Class //-------------------- class ReflowCountMgr { public: ReflowCountMgr(); virtual ~ReflowCountMgr(); void ClearTotals(); void ClearGrandTotals(); void DisplayTotals(const char* aStr); void DisplayHTMLTotals(const char* aStr); void DisplayDiffsInTotals(); void Add(const char* aName, nsIFrame* aFrame); ReflowCounter* LookUp(const char* aName); void PaintCount(const char* aName, gfxContext* aRenderingContext, nsPresContext* aPresContext, nsIFrame* aFrame, const nsPoint& aOffset, uint32_t aColor); FILE* GetOutFile() { return mFD; } void SetPresContext(nsPresContext* aPresContext) { mPresContext = aPresContext; // weak reference } void SetPresShell(PresShell* aPresShell) { mPresShell = aPresShell; // weak reference } void SetDumpFrameCounts(bool aVal) { mDumpFrameCounts = aVal; } void SetDumpFrameByFrameCounts(bool aVal) { mDumpFrameByFrameCounts = aVal; } void SetPaintFrameCounts(bool aVal) { mPaintFrameByFrameCounts = aVal; } bool IsPaintingFrameCounts() { return mPaintFrameByFrameCounts; } protected: void DisplayTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle); void DisplayHTMLTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle); void DoGrandTotals(); void DoIndiTotalsTree(); // HTML Output Methods void DoGrandHTMLTotals(); nsClassHashtable mCounts; nsClassHashtable mIndiFrameCounts; FILE* mFD; bool mDumpFrameCounts; bool mDumpFrameByFrameCounts; bool mPaintFrameByFrameCounts; bool mCycledOnce; // Root Frame for Individual Tracking nsPresContext* mPresContext; PresShell* mPresShell; // ReflowCountMgr gReflowCountMgr; }; #endif //======================================================================== // comment out to hide caret #define SHOW_CARET // The upper bound on the amount of time to spend reflowing, in // microseconds. When this bound is exceeded and reflow commands are // still queued up, a reflow event is posted. The idea is for reflow // to not hog the processor beyond the time specifed in // gMaxRCProcessingTime. This data member is initialized from the // layout.reflow.timeslice pref. #define NS_MAX_REFLOW_TIME 1000000 static int32_t gMaxRCProcessingTime = -1; struct nsCallbackEventRequest { nsIReflowCallback* callback; nsCallbackEventRequest* next; }; // ---------------------------------------------------------------------------- // // NOTE(emilio): It'd be nice for this to assert that our document isn't in the // bfcache, but font pref changes don't care about that, and maybe / probably // shouldn't. #ifdef DEBUG # define ASSERT_REFLOW_SCHEDULED_STATE() \ { \ if (ObservingLayoutFlushes()) { \ MOZ_ASSERT( \ mDocument->GetBFCacheEntry() || \ mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \ "Unexpected state"); \ } else { \ MOZ_ASSERT( \ !mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \ "Unexpected state"); \ } \ } #else # define ASSERT_REFLOW_SCHEDULED_STATE() /* nothing */ #endif class nsAutoCauseReflowNotifier { public: MOZ_CAN_RUN_SCRIPT explicit nsAutoCauseReflowNotifier(PresShell* aPresShell) : mPresShell(aPresShell) { mPresShell->WillCauseReflow(); } MOZ_CAN_RUN_SCRIPT ~nsAutoCauseReflowNotifier() { // This check should not be needed. Currently the only place that seem // to need it is the code that deals with bug 337586. if (!mPresShell->mHaveShutDown) { RefPtr presShell(mPresShell); presShell->DidCauseReflow(); } else { nsContentUtils::RemoveScriptBlocker(); } } PresShell* mPresShell; }; class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback { public: explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {} MOZ_CAN_RUN_SCRIPT virtual void HandleEvent(EventChainPostVisitor& aVisitor) override { if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) { if (aVisitor.mEvent->mMessage == eMouseDown || aVisitor.mEvent->mMessage == eMouseUp) { // Mouse-up and mouse-down events call nsIFrame::HandlePress/Release // which call GetContentOffsetsFromPoint which requires up-to-date // layout. Bring layout up-to-date now so that GetCurrentEventFrame() // below will return a real frame and we don't have to worry about // destroying it by flushing later. MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout); } else if (aVisitor.mEvent->mMessage == eWheel && aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) { nsIFrame* frame = mPresShell->GetCurrentEventFrame(); if (frame) { // chrome (including addons) should be able to know if content // handles both D3E "wheel" event and legacy mouse scroll events. // We should dispatch legacy mouse events before dispatching the // "wheel" event into system group. RefPtr esm = aVisitor.mPresContext->EventStateManager(); esm->DispatchLegacyMouseScrollEvents( frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus); } } nsIFrame* frame = mPresShell->GetCurrentEventFrame(); if (!frame && (aVisitor.mEvent->mMessage == eMouseUp || aVisitor.mEvent->mMessage == eTouchEnd)) { // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure // that capturing is released. frame = mPresShell->GetRootFrame(); } if (frame) { frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(), &aVisitor.mEventStatus); } } } RefPtr mPresShell; }; class nsBeforeFirstPaintDispatcher : public Runnable { public: explicit nsBeforeFirstPaintDispatcher(Document* aDocument) : mozilla::Runnable("nsBeforeFirstPaintDispatcher"), mDocument(aDocument) {} // Fires the "before-first-paint" event so that interested parties (right now, // the mobile browser) are aware of it. NS_IMETHOD Run() override { nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->NotifyObservers(ToSupports(mDocument), "before-first-paint", nullptr); } return NS_OK; } private: RefPtr mDocument; }; // This is a helper class to track whether the targeted frame is destroyed after // dispatching pointer events. In that case, we need the original targeted // content so that we can dispatch the mouse events to it. class MOZ_STACK_CLASS AutoPointerEventTargetUpdater final { public: AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent, nsIFrame* aFrame, nsIContent* aTargetContent, nsIContent** aOutTargetContent) { MOZ_ASSERT(aEvent); if (!aOutTargetContent || aEvent->mClass != ePointerEventClass) { // Make the destructor happy. mOutTargetContent = nullptr; return; } MOZ_ASSERT(aShell); MOZ_ASSERT_IF(aFrame && aFrame->GetContent(), aShell->GetDocument() == aFrame->GetContent()->OwnerDoc()); mShell = aShell; mWeakFrame = aFrame; mOutTargetContent = aOutTargetContent; mFromTouch = aEvent->AsPointerEvent()->mFromTouchEvent; // Touch event target may have no frame, e.g., removed from the DOM MOZ_ASSERT_IF(!mFromTouch, aFrame); mOriginalPointerEventTarget = aShell->mPointerEventTarget = aFrame ? aFrame->GetContent() : aTargetContent; } ~AutoPointerEventTargetUpdater() { if (!mOutTargetContent || !mShell || mWeakFrame.IsAlive()) { return; } if (mFromTouch) { // If the source event is a touch event, the touch event target should // always be same target as preceding ePointerDown. Therefore, we should // always set it back to the original event target. mOriginalPointerEventTarget.swap(*mOutTargetContent); } else { // If the source event is not a touch event (must be a mouse event in // this case), the event should be fired on the closest inclusive ancestor // of the pointer event target which is still connected. The mutations // are tracked by PresShell::ContentRemoved. Therefore, we should set it. mShell->mPointerEventTarget.swap(*mOutTargetContent); } } private: RefPtr mShell; nsCOMPtr mOriginalPointerEventTarget; AutoWeakFrame mWeakFrame; nsIContent** mOutTargetContent; bool mFromTouch = false; }; bool PresShell::sDisableNonTestMouseEvents = false; int16_t PresShell::sMouseButtons = MouseButtonsFlag::eNoButtons; LazyLogModule PresShell::gLog("PresShell"); TimeStamp PresShell::EventHandler::sLastInputCreated; TimeStamp PresShell::EventHandler::sLastInputProcessed; StaticRefPtr PresShell::EventHandler::sLastKeyDownEventTargetElement; bool PresShell::sProcessInteractable = false; static bool gVerifyReflowEnabled; bool PresShell::GetVerifyReflowEnable() { #ifdef DEBUG static bool firstTime = true; if (firstTime) { firstTime = false; char* flags = PR_GetEnv("GECKO_VERIFY_REFLOW_FLAGS"); if (flags) { bool error = false; for (;;) { char* comma = strchr(flags, ','); if (comma) *comma = '\0'; bool found = false; const VerifyReflowFlagData* flag = gFlags; const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS; while (flag < limit) { if (nsCRT::strcasecmp(flag->name, flags) == 0) { gVerifyReflowFlags |= flag->bit; found = true; break; } ++flag; } if (!found) error = true; if (!comma) break; *comma = ','; flags = comma + 1; } if (error) ShowVerifyReflowFlags(); } if (VerifyReflowFlags::On & gVerifyReflowFlags) { gVerifyReflowEnabled = true; printf("Note: verifyreflow is enabled"); if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) { printf(" (noisy)"); } if (VerifyReflowFlags::All & gVerifyReflowFlags) { printf(" (all)"); } if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) { printf(" (show reflow commands)"); } if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) { printf(" (noisy reflow commands)"); if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) { printf(" (REALLY noisy reflow commands)"); } } printf("\n"); } } #endif return gVerifyReflowEnabled; } void PresShell::SetVerifyReflowEnable(bool aEnabled) { gVerifyReflowEnabled = aEnabled; } void PresShell::AddAutoWeakFrame(AutoWeakFrame* aWeakFrame) { if (aWeakFrame->GetFrame()) { aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE); } aWeakFrame->SetPreviousWeakFrame(mAutoWeakFrames); mAutoWeakFrames = aWeakFrame; } void PresShell::AddWeakFrame(WeakFrame* aWeakFrame) { if (aWeakFrame->GetFrame()) { aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE); } MOZ_ASSERT(!mWeakFrames.Contains(aWeakFrame)); mWeakFrames.Insert(aWeakFrame); } void PresShell::RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame) { if (mAutoWeakFrames == aWeakFrame) { mAutoWeakFrames = aWeakFrame->GetPreviousWeakFrame(); return; } AutoWeakFrame* nextWeak = mAutoWeakFrames; while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) { nextWeak = nextWeak->GetPreviousWeakFrame(); } if (nextWeak) { nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame()); } } void PresShell::RemoveWeakFrame(WeakFrame* aWeakFrame) { MOZ_ASSERT(mWeakFrames.Contains(aWeakFrame)); mWeakFrames.Remove(aWeakFrame); } already_AddRefed PresShell::FrameSelection() { RefPtr ret = mSelection; return ret.forget(); } //---------------------------------------------------------------------- static uint32_t sNextPresShellId = 0; /* static */ bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) { // If the pref forces it on, then enable it. if (StaticPrefs::layout_accessiblecaret_enabled()) { return true; } // If the touch pref is on, and touch events are enabled (this depends // on the specific device running), then enable it. if (StaticPrefs::layout_accessiblecaret_enabled_on_touch() && dom::TouchEvent::PrefEnabled(aDocShell)) { return true; } // Otherwise, disabled. return false; } PresShell::PresShell(Document* aDocument) : mDocument(aDocument), mViewManager(nullptr), mFrameManager(nullptr), mAutoWeakFrames(nullptr), #ifdef ACCESSIBILITY mDocAccessible(nullptr), #endif // ACCESSIBILITY mCurrentEventFrame(nullptr), mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), mLastResolutionChangeOrigin(ResolutionChangeOrigin::Apz), mPaintCount(0), mAPZFocusSequenceNumber(0), 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(); mReflowCountMgr->SetPresContext(mPresContext); mReflowCountMgr->SetPresShell(this); #endif mLastOSWake = mLoadBegin = TimeStamp::Now(); } NS_INTERFACE_TABLE_HEAD(PresShell) NS_INTERFACE_TABLE_BEGIN // In most cases, PresShell should be treated as concrete class, but need to // QI for weak reference. Therefore, the case needed by do_QueryReferent() // should be tested first. NS_INTERFACE_TABLE_ENTRY(PresShell, PresShell) NS_INTERFACE_TABLE_ENTRY(PresShell, nsIDocumentObserver) NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionController) NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionDisplay) NS_INTERFACE_TABLE_ENTRY(PresShell, nsIObserver) NS_INTERFACE_TABLE_ENTRY(PresShell, nsISupportsWeakReference) NS_INTERFACE_TABLE_ENTRY(PresShell, nsIMutationObserver) NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(PresShell, nsISupports, nsIObserver) NS_INTERFACE_TABLE_END NS_INTERFACE_TABLE_TO_MAP_SEGUE NS_INTERFACE_MAP_END NS_IMPL_ADDREF(PresShell) NS_IMPL_RELEASE(PresShell) PresShell::~PresShell() { MOZ_RELEASE_ASSERT(!mForbiddenToFlush, "Flag should only be set temporarily, while doing things " "that shouldn't cause destruction"); MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::~PresShell this=%p", this)); if (!mHaveShutDown) { MOZ_ASSERT_UNREACHABLE("Someone did not call PresShell::Destroy()"); Destroy(); } NS_ASSERTION(mCurrentEventContentStack.Count() == 0, "Huh, event content left on the stack in pres shell dtor!"); NS_ASSERTION(mFirstCallbackEventRequest == nullptr && mLastCallbackEventRequest == nullptr, "post-reflow queues not empty. This means we're leaking"); MOZ_ASSERT(!mAllocatedPointers || mAllocatedPointers->IsEmpty(), "Some pres arena objects were not freed"); mFrameManager = nullptr; mFrameConstructor = nullptr; mCurrentEventContent = nullptr; } /** * Initialize the presentation shell. Create view manager and style * manager. * Note this can't be merged into our constructor because caret initialization * calls AddRef() on us. */ void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) { MOZ_ASSERT(mDocument); MOZ_ASSERT(aPresContext); MOZ_ASSERT(aViewManager); MOZ_ASSERT(!mViewManager, "already initialized"); mViewManager = aViewManager; // mDocument is now set. It might have a display document whose "need layout/ // style" flush flags are not set, but ours will be set. To keep these // consistent, call the flag setting functions to propagate those flags up // to the display document. SetNeedLayoutFlush(); SetNeedStyleFlush(); // Create our frame constructor. mFrameConstructor = MakeUnique(mDocument, this); mFrameManager = mFrameConstructor.get(); // The document viewer owns both view manager and pres shell. mViewManager->SetPresShell(this); // Bind the context to the presentation shell. // FYI: We cannot initialize mPresContext in the constructor because we // cannot call AttachPresShell() in it and once we initialize // mPresContext, other objects may refer refresh driver or restyle // manager via mPresContext and that causes hitting MOZ_ASSERT in some // places. Therefore, we should initialize mPresContext here with // const_cast hack since we want to guarantee that mPresContext lives // as long as the PresShell. const_cast&>(mPresContext) = aPresContext; mPresContext->AttachPresShell(this); mPresContext->InitFontCache(); // FIXME(emilio, bug 1544185): Some Android code somehow depends on the shell // being eagerly registered as a style flush observer. This shouldn't be // needed otherwise. EnsureStyleFlush(); 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 os = mozilla::services::GetObserverService(); if (os) { os->AddObserver(this, "memory-pressure", false); os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false); if (XRE_IsParentProcess() && !sProcessInteractable) { os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false); } os->AddObserver(this, "font-info-updated", false); os->AddObserver(this, "internal-look-and-feel-changed", false); } } #ifdef MOZ_REFLOW_PERF if (mReflowCountMgr) { bool paintFrameCounts = Preferences::GetBool("layout.reflow.showframecounts"); bool dumpFrameCounts = Preferences::GetBool("layout.reflow.dumpframecounts"); bool dumpFrameByFrameCounts = Preferences::GetBool("layout.reflow.dumpframebyframecounts"); mReflowCountMgr->SetDumpFrameCounts(dumpFrameCounts); mReflowCountMgr->SetDumpFrameByFrameCounts(dumpFrameByFrameCounts); mReflowCountMgr->SetPaintFrameCounts(paintFrameCounts); } #endif if (mDocument->HasAnimationController()) { SMILAnimationController* animCtrl = mDocument->GetAnimationController(); animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver()); } for (DocumentTimeline* timeline : mDocument->Timelines()) { timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver()); } // Get our activeness from the docShell. ActivenessMaybeChanged(); // Setup our font inflation preferences. mFontSizeInflationEmPerLine = StaticPrefs::font_size_inflation_emPerLine(); mFontSizeInflationMinTwips = StaticPrefs::font_size_inflation_minTwips(); mFontSizeInflationLineThreshold = StaticPrefs::font_size_inflation_lineThreshold(); mFontSizeInflationForceEnabled = StaticPrefs::font_size_inflation_forceEnabled(); mFontSizeInflationDisabledInMasterProcess = StaticPrefs::font_size_inflation_disabledInMasterProcess(); // We'll compute the font size inflation state in Initialize(), when we know // the document type. mTouchManager.Init(this, mDocument); if (mPresContext->IsRootContentDocumentCrossProcess()) { mZoomConstraintsClient = new ZoomConstraintsClient(); mZoomConstraintsClient->Init(this, mDocument); // We call this to create mMobileViewportManager, if it is needed. MaybeRecreateMobileViewportManager(false); } if (nsCOMPtr docShell = mPresContext->GetDocShell()) { if (BrowsingContext* bc = docShell->GetBrowsingContext()) { mUnderHiddenEmbedderElement = bc->IsUnderHiddenEmbedderElement(); } } } enum TextPerfLogType { eLog_reflow, eLog_loaddone, eLog_totals }; static void LogTextPerfStats(gfxTextPerfMetrics* aTextPerf, PresShell* aPresShell, const gfxTextPerfMetrics::TextCounts& aCounts, float aTime, TextPerfLogType aLogType, const char* aURL) { LogModule* tpLog = gfxPlatform::GetLog(eGfxLog_textperf); // ignore XUL contexts unless at debug level mozilla::LogLevel logLevel = LogLevel::Warning; if (aCounts.numContentTextRuns == 0) { logLevel = LogLevel::Debug; } if (!MOZ_LOG_TEST(tpLog, logLevel)) { return; } char prefix[256]; switch (aLogType) { case eLog_reflow: SprintfLiteral(prefix, "(textperf-reflow) %p time-ms: %7.0f", aPresShell, aTime); break; case eLog_loaddone: SprintfLiteral(prefix, "(textperf-loaddone) %p time-ms: %7.0f", aPresShell, aTime); break; default: MOZ_ASSERT(aLogType == eLog_totals, "unknown textperf log type"); SprintfLiteral(prefix, "(textperf-totals) %p", aPresShell); } double hitRatio = 0.0; uint32_t lookups = aCounts.wordCacheHit + aCounts.wordCacheMiss; if (lookups) { hitRatio = double(aCounts.wordCacheHit) / double(lookups); } if (aLogType == eLog_loaddone) { MOZ_LOG( tpLog, logLevel, ("%s reflow: %d chars: %d " "[%s] " "content-textruns: %d chrome-textruns: %d " "max-textrun-len: %d " "word-cache-lookups: %d word-cache-hit-ratio: %4.3f " "word-cache-space: %d word-cache-long: %d " "pref-fallbacks: %d system-fallbacks: %d " "textruns-const: %d textruns-destr: %d " "generic-lookups: %d " "cumulative-textruns-destr: %d\n", prefix, aTextPerf->reflowCount, aCounts.numChars, (aURL ? aURL : ""), aCounts.numContentTextRuns, aCounts.numChromeTextRuns, aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules, aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem, aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups, aTextPerf->cumulative.textrunDestr)); } else { MOZ_LOG( tpLog, logLevel, ("%s reflow: %d chars: %d " "content-textruns: %d chrome-textruns: %d " "max-textrun-len: %d " "word-cache-lookups: %d word-cache-hit-ratio: %4.3f " "word-cache-space: %d word-cache-long: %d " "pref-fallbacks: %d system-fallbacks: %d " "textruns-const: %d textruns-destr: %d " "generic-lookups: %d " "cumulative-textruns-destr: %d\n", prefix, aTextPerf->reflowCount, aCounts.numChars, aCounts.numContentTextRuns, aCounts.numChromeTextRuns, aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules, aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem, aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups, aTextPerf->cumulative.textrunDestr)); } } bool PresShell::InRDMPane() { if (Document* doc = GetDocument()) { if (BrowsingContext* bc = doc->GetBrowsingContext()) { return bc->InRDMPane(); } } return false; } #if defined(MOZ_WIDGET_ANDROID) void PresShell::MaybeNotifyShowDynamicToolbar() { const DynamicToolbarState dynToolbarState = GetDynamicToolbarState(); if ((dynToolbarState == DynamicToolbarState::Collapsed || dynToolbarState == DynamicToolbarState::InTransition)) { MOZ_ASSERT(mPresContext && mPresContext->IsRootContentDocumentCrossProcess()); if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { browserChild->SendShowDynamicToolbar(); } } } #endif // defined(MOZ_WIDGET_ANDROID) void PresShell::Destroy() { // Do not add code before this line please! if (mHaveShutDown) { return; } NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), "destroy called on presshell while scripts not blocked"); [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI(); AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( "Layout tree destruction", LAYOUT_Destroy, uri ? uri->GetSpecOrDefault() : "N/A"_ns); // Try to determine if the page is the user had a meaningful opportunity to // zoom this page. This is not 100% accurate but should be "good enough" for // telemetry purposes. auto isUserZoomablePage = [&]() -> bool { if (mIsFirstPaint) { // Page was never painted, so it wasn't zoomable by the user. We get a // handful of these "transient" presShells. return false; } if (!mPresContext->IsRootContentDocumentCrossProcess()) { // Not a root content document, so APZ doesn't support zooming it. return false; } if (InRDMPane()) { // Responsive design mode is a special case that we want to ignore here. return false; } if (mDocument && mDocument->IsInitialDocument()) { // Ignore initial about:blank page loads return false; } if (XRE_IsContentProcess() && IsExtensionRemoteType(ContentChild::GetSingleton()->GetRemoteType())) { // Also omit presShells from the extension process because they sometimes // can't be zoomed by the user. return false; } // Otherwise assume the page is user-zoomable. return true; }; if (isUserZoomablePage()) { Telemetry::Accumulate(Telemetry::APZ_ZOOM_ACTIVITY, IsResolutionUpdatedByApz()); } // dump out cumulative text perf metrics gfxTextPerfMetrics* tp; if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) { tp->Accumulate(); if (tp->cumulative.numChars > 0) { LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr); } } if (mPresContext) { if (gfxUserFontSet* fs = mPresContext->GetUserFontSet()) { uint32_t fontCount; uint64_t fontSize; fs->GetLoadStatistics(fontCount, fontSize); Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, fontCount); Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, uint32_t(fontSize / 1024)); } else { Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0); Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0); } } #ifdef MOZ_REFLOW_PERF DumpReflows(); mReflowCountMgr = nullptr; #endif if (mZoomConstraintsClient) { mZoomConstraintsClient->Destroy(); mZoomConstraintsClient = nullptr; } if (mMobileViewportManager) { mMobileViewportManager->Destroy(); mMobileViewportManager = nullptr; mMVMContext = nullptr; } #ifdef ACCESSIBILITY if (mDocAccessible) { # ifdef DEBUG if (a11y::logging::IsEnabled(a11y::logging::eDocDestroy)) a11y::logging::DocDestroy("presshell destroyed", mDocument); # endif mDocAccessible->Shutdown(); mDocAccessible = nullptr; } #endif // ACCESSIBILITY MaybeReleaseCapturingContent(); EventHandler::OnPresShellDestroy(mDocument); if (mContentToScrollTo) { mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); mContentToScrollTo = nullptr; } if (mPresContext) { // We need to notify the destroying the nsPresContext to ESM for // suppressing to use from ESM. mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext); } if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) { ss->UnregisterPresShell(this); } { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->RemoveObserver(this, "memory-pressure"); os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC); if (XRE_IsParentProcess()) { os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored"); } os->RemoveObserver(this, "font-info-updated"); os->RemoveObserver(this, "internal-look-and-feel-changed"); } } // If our paint suppression timer is still active, kill it. CancelPaintSuppressionTimer(); // Same for our reflow continuation timer if (mReflowContinueTimer) { mReflowContinueTimer->Cancel(); mReflowContinueTimer = nullptr; } mSynthMouseMoveEvent.Revoke(); mUpdateApproximateFrameVisibilityEvent.Revoke(); ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages)); if (mCaret) { mCaret->Terminate(); mCaret = nullptr; } mFocusedFrameSelection = nullptr; if (mSelection) { RefPtr frameSelection = mSelection; frameSelection->DisconnectFromPresShell(); } mIsDestroying = true; // We can't release all the event content in // mCurrentEventContentStack here since there might be code on the // stack that will release the event content too. Double release // bad! // The frames will be torn down, so remove them from the current // event frame stack (since they'd be dangling references if we'd // leave them in) and null out the mCurrentEventFrame pointer as // well. mCurrentEventFrame = nullptr; int32_t i, count = mCurrentEventFrameStack.Length(); for (i = 0; i < count; i++) { mCurrentEventFrameStack[i] = nullptr; } mFramesToDirty.Clear(); mPendingScrollAnchorSelection.Clear(); mPendingScrollAnchorAdjustment.Clear(); mPendingScrollResnap.Clear(); if (mViewManager) { // Clear the view manager's weak pointer back to |this| in case it // was leaked. mViewManager->SetPresShell(nullptr); mViewManager = nullptr; } nsRefreshDriver* rd = GetPresContext()->RefreshDriver(); // This shell must be removed from the document before the frame // hierarchy is torn down to avoid finding deleted frames through // this presshell while the frames are being torn down if (mDocument) { NS_ASSERTION(mDocument->GetPresShell() == this, "Wrong shell?"); mDocument->ClearServoRestyleRoot(); mDocument->DeletePresShell(); if (mDocument->HasAnimationController()) { mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd); } for (DocumentTimeline* timeline : mDocument->Timelines()) { timeline->NotifyRefreshDriverDestroying(rd); } } if (mPresContext) { rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher()); } // Revoke any pending events. We need to do this and cancel pending reflows // before we destroy the frame manager, since apparently frame destruction // sometimes spins the event queue when plug-ins are involved(!). // XXXmats is this still needed now that plugins are gone? StopObservingRefreshDriver(); if (rd->GetPresContext() == GetPresContext()) { rd->RevokeViewManagerFlush(); rd->ClearHasScheduleFlush(); } CancelAllPendingReflows(); CancelPostedReflowCallbacks(); // Destroy the frame manager. This will destroy the frame hierarchy mFrameConstructor->WillDestroyFrameTree(); NS_WARNING_ASSERTION(!mAutoWeakFrames && mWeakFrames.IsEmpty(), "Weak frames alive after destroying FrameManager"); while (mAutoWeakFrames) { mAutoWeakFrames->Clear(this); } const nsTArray weakFrames = ToArray(mWeakFrames); for (WeakFrame* weakFrame : weakFrames) { weakFrame->Clear(this); } // Terminate AccessibleCaretEventHub after tearing down the frame tree so that // we don't need to remove caret element's frame in // AccessibleCaret::RemoveCaretElement(). if (mAccessibleCaretEventHub) { mAccessibleCaretEventHub->Terminate(); mAccessibleCaretEventHub = nullptr; } if (mPresContext) { // We hold a reference to the pres context, and it holds a weak link back // to us. To avoid the pres context having a dangling reference, set its // pres shell to nullptr mPresContext->DetachPresShell(); } mHaveShutDown = true; mTouchManager.Destroy(); } void PresShell::StopObservingRefreshDriver() { nsRefreshDriver* rd = mPresContext->RefreshDriver(); if (mResizeEventPending) { rd->RemoveResizeEventFlushObserver(this); } if (mObservingLayoutFlushes) { rd->RemoveLayoutFlushObserver(this); } if (mObservingStyleFlushes) { rd->RemoveStyleFlushObserver(this); } } void PresShell::StartObservingRefreshDriver() { nsRefreshDriver* rd = mPresContext->RefreshDriver(); if (mResizeEventPending) { rd->AddResizeEventFlushObserver(this); } if (mObservingLayoutFlushes) { rd->AddLayoutFlushObserver(this); } if (mObservingStyleFlushes) { rd->AddStyleFlushObserver(this); } } nsRefreshDriver* PresShell::GetRefreshDriver() const { return mPresContext ? mPresContext->RefreshDriver() : nullptr; } void PresShell::SetAuthorStyleDisabled(bool aStyleDisabled) { if (aStyleDisabled != StyleSet()->GetAuthorStyleDisabled()) { StyleSet()->SetAuthorStyleDisabled(aStyleDisabled); mDocument->ApplicableStylesChanged(); nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->NotifyObservers( ToSupports(mDocument), "author-style-disabled-changed", nullptr); } } } bool PresShell::GetAuthorStyleDisabled() const { return StyleSet()->GetAuthorStyleDisabled(); } void PresShell::AddUserSheet(StyleSheet* aSheet) { // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt // ordering. We want this new sheet to come after all the existing stylesheet // service sheets (which are at the start), but before other user sheets; see // nsIStyleSheetService.idl for the ordering. nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); nsTArray>& userSheets = *sheetService->UserStyleSheets(); // Search for the place to insert the new user sheet. Since all of the // stylesheet service provided user sheets should be at the start of the style // set's list, and aSheet should be at the end of userSheets. Given that, we // can find the right place to insert the new sheet based on the length of // userSheets. MOZ_ASSERT(aSheet); MOZ_ASSERT(userSheets.LastElement() == aSheet); size_t index = userSheets.Length() - 1; // Assert that all of userSheets (except for the last, new element) matches up // with what's in the style set. for (size_t i = 0; i < index; ++i) { MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]); } if (index == static_cast(StyleSet()->SheetCount(StyleOrigin::User))) { StyleSet()->AppendStyleSheet(*aSheet); } else { StyleSheet* ref = StyleSet()->SheetAt(StyleOrigin::User, index); StyleSet()->InsertStyleSheetBefore(*aSheet, *ref); } mDocument->ApplicableStylesChanged(); } void PresShell::AddAgentSheet(StyleSheet* aSheet) { // Make sure this does what nsDocumentViewer::CreateStyleSet does // wrt ordering. StyleSet()->AppendStyleSheet(*aSheet); mDocument->ApplicableStylesChanged(); } void PresShell::AddAuthorSheet(StyleSheet* aSheet) { // Document specific "additional" Author sheets should be stronger than the // ones added with the StyleSheetService. StyleSheet* firstAuthorSheet = mDocument->GetFirstAdditionalAuthorSheet(); if (firstAuthorSheet) { StyleSet()->InsertStyleSheetBefore(*aSheet, *firstAuthorSheet); } else { StyleSet()->AppendStyleSheet(*aSheet); } mDocument->ApplicableStylesChanged(); } bool PresShell::FixUpFocus() { if (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 window = mDocument->GetWindow(); if (NS_WARN_IF(!window)) { return false; } fm->ClearFocus(window); return true; } void PresShell::SelectionWillTakeFocus() { if (mSelection) { FrameSelectionWillTakeFocus(*mSelection); } } void PresShell::SelectionWillLoseFocus() { // Do nothing, the main selection is the default focused selection. } // Selection repainting code relies on selection offsets being properly // adjusted (see bug 1626291), so we need to wait until the DOM is finished // notifying. static void RepaintNormalSelectionWhenSafe(nsFrameSelection& aFrameSelection) { if (nsContentUtils::IsSafeToRunScript()) { aFrameSelection.RepaintSelection(SelectionType::eNormal); return; } // Note that importantly we don't defer changing the DisplaySelection. That'd // be potentially racy with other code that may change it. nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( "RepaintNormalSelectionWhenSafe", [sel = RefPtr(&aFrameSelection)] { sel->RepaintSelection(SelectionType::eNormal); })); } void PresShell::FrameSelectionWillLoseFocus(nsFrameSelection& aFrameSelection) { if (mFocusedFrameSelection != &aFrameSelection) { return; } // Do nothing, the main selection is the default focused selection. if (&aFrameSelection == mSelection) { return; } RefPtr old = std::move(mFocusedFrameSelection); MOZ_ASSERT(!mFocusedFrameSelection); if (old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) { old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); RepaintNormalSelectionWhenSafe(*old); } if (mSelection) { FrameSelectionWillTakeFocus(*mSelection); } } void PresShell::FrameSelectionWillTakeFocus(nsFrameSelection& aFrameSelection) { if (mFocusedFrameSelection == &aFrameSelection) { #ifdef XP_MACOSX // FIXME: Mac needs to update the global selection cache, even if the // document's focused selection doesn't change, and this is currently done // from RepaintSelection. Maybe we should move part of the global selection // handling here, or something of that sort, unclear. RepaintNormalSelectionWhenSafe(aFrameSelection); #endif return; } RefPtr old = std::move(mFocusedFrameSelection); mFocusedFrameSelection = &aFrameSelection; if (old && old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) { old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); RepaintNormalSelectionWhenSafe(*old); } if (aFrameSelection.GetDisplaySelection() != nsISelectionController::SELECTION_ON) { aFrameSelection.SetDisplaySelection(nsISelectionController::SELECTION_ON); RepaintNormalSelectionWhenSafe(aFrameSelection); } } NS_IMETHODIMP PresShell::SetDisplaySelection(int16_t aToggle) { RefPtr frameSelection = mSelection; frameSelection->SetDisplaySelection(aToggle); return NS_OK; } NS_IMETHODIMP PresShell::GetDisplaySelection(int16_t* aToggle) { RefPtr frameSelection = mSelection; *aToggle = frameSelection->GetDisplaySelection(); return NS_OK; } NS_IMETHODIMP PresShell::GetSelectionFromScript(RawSelectionType aRawSelectionType, Selection** aSelection) { if (!aSelection || !mSelection) return NS_ERROR_NULL_POINTER; RefPtr frameSelection = mSelection; RefPtr selection = frameSelection->GetSelection(ToSelectionType(aRawSelectionType)); if (!selection) { return NS_ERROR_INVALID_ARG; } selection.forget(aSelection); return NS_OK; } Selection* PresShell::GetSelection(RawSelectionType aRawSelectionType) { if (!mSelection) { return nullptr; } RefPtr frameSelection = mSelection; return frameSelection->GetSelection(ToSelectionType(aRawSelectionType)); } Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) { if (!mSelection) { return nullptr; } RefPtr frameSelection = mSelection; return frameSelection->GetSelection(aSelectionType); } nsFrameSelection* PresShell::GetLastFocusedFrameSelection() { return mFocusedFrameSelection ? mFocusedFrameSelection : mSelection; } NS_IMETHODIMP PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType, SelectionRegion aRegion, int16_t aFlags) { if (!mSelection) return NS_ERROR_NULL_POINTER; RefPtr frameSelection = mSelection; return frameSelection->ScrollSelectionIntoView( ToSelectionType(aRawSelectionType), aRegion, aFlags); } NS_IMETHODIMP PresShell::RepaintSelection(RawSelectionType aRawSelectionType) { if (!mSelection) { return NS_ERROR_NULL_POINTER; } if (MOZ_UNLIKELY(mIsDestroying)) { return NS_OK; } RefPtr frameSelection = mSelection; return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType)); } // Make shell be a document observer void PresShell::BeginObservingDocument() { if (mDocument && !mIsDestroying) { mIsObservingDocument = true; if (mIsDocumentGone) { NS_WARNING( "Adding a presshell that was disconnected from the document " "as a document observer? Sounds wrong..."); mIsDocumentGone = false; } } } // Make shell stop being a document observer void PresShell::EndObservingDocument() { // XXXbz do we need to tell the frame constructor that the document // is gone, perhaps? Except for printing it's NOT gone, sometimes. mIsDocumentGone = true; mIsObservingDocument = false; } #ifdef DEBUG_kipp char* nsPresShell_ReflowStackPointerTop; #endif void PresShell::InitPaintSuppressionTimer() { // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value. Document* doc = mDocument->GetDisplayDocument() ? mDocument->GetDisplayDocument() : mDocument.get(); const bool inProcess = !doc->GetBrowsingContext() || doc->GetBrowsingContext()->Top()->IsInProcess(); int32_t delay = inProcess ? StaticPrefs::nglayout_initialpaint_delay() : StaticPrefs::nglayout_initialpaint_delay_in_oopif(); mPaintSuppressionTimer->InitWithNamedFuncCallback( [](nsITimer* aTimer, void* aPresShell) { RefPtr self = static_cast(aPresShell); self->UnsuppressPainting(); }, this, delay, nsITimer::TYPE_ONE_SHOT, "PresShell::sPaintSuppressionCallback"); } nsresult PresShell::Initialize() { if (mIsDestroying) { return NS_OK; } if (!mDocument) { // Nothing to do return NS_OK; } MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::Initialize this=%p", this)); NS_ASSERTION(!mDidInitialize, "Why are we being called?"); RefPtr kungFuDeathGrip(this); RecomputeFontSizeInflationEnabled(); MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying); // Ensure the pres context doesn't think it has changed, since we haven't even // started layout. This avoids spurious restyles / reflows afterwards. // // Note that this is very intentionally before setting mDidInitialize so it // doesn't notify the document, or run media query change events. mPresContext->FlushPendingMediaFeatureValuesChanged(); MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying); mDidInitialize = true; #ifdef DEBUG if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) { if (mDocument) { nsIURI* uri = mDocument->GetDocumentURI(); if (uri) { printf("*** PresShell::Initialize (this=%p, url='%s')\n", (void*)this, uri->GetSpecOrDefault().get()); } } } #endif // Get the root frame from the frame manager // XXXbz it would be nice to move this somewhere else... like frame manager // Init(), say. But we need to make sure our views are all set up by the // time we do this! nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); NS_ASSERTION(!rootFrame, "How did that happen, exactly?"); if (!rootFrame) { nsAutoScriptBlocker scriptBlocker; rootFrame = mFrameConstructor->ConstructRootFrame(); mFrameConstructor->SetRootFrame(rootFrame); } NS_ENSURE_STATE(!mHaveShutDown); if (!rootFrame) { return NS_ERROR_OUT_OF_MEMORY; } if (Element* root = mDocument->GetRootElement()) { { nsAutoCauseReflowNotifier reflowNotifier(this); // Have the style sheet processor construct frame for the root // content object down mFrameConstructor->ContentInserted( root, nsCSSFrameConstructor::InsertionKind::Sync); } // Something in mFrameConstructor->ContentInserted may have caused // Destroy() to get called, bug 337586. Or, nsAutoCauseReflowNotifier // (which sets up a script blocker) going out of scope may have killed us // too NS_ENSURE_STATE(!mHaveShutDown); } if (mDocument->HasAutoFocusCandidates()) { mDocument->ScheduleFlushAutoFocusCandidates(); } NS_ASSERTION(rootFrame, "How did that happen?"); // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit // set, but XBL processing could have caused a reflow which clears it. if (MOZ_LIKELY(rootFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))) { // Unset the DIRTY bits so that FrameNeedsReflow() will work right. rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); NS_ASSERTION(!mDirtyRoots.Contains(rootFrame), "Why is the root in mDirtyRoots already?"); FrameNeedsReflow(rootFrame, IntrinsicDirty::None, NS_FRAME_IS_DIRTY); NS_ASSERTION(mDirtyRoots.Contains(rootFrame), "Should be in mDirtyRoots now"); NS_ASSERTION(mObservingLayoutFlushes, "Why no reflow scheduled?"); } // Restore our root scroll position now if we're getting here after EndLoad // got called, since this is our one chance to do it. Note that we need not // have reflowed for this to work; when the scrollframe is finally reflowed // it'll pick up the position we store in it here. if (!mDocumentLoading) { RestoreRootScrollPosition(); } // For printing, we just immediately unsuppress. if (!mPresContext->IsPaginated()) { // Kick off a one-shot timer based off our pref value. When this timer // fires, if painting is still locked down, then we will go ahead and // trigger a full invalidate and allow painting to proceed normally. mPaintingSuppressed = true; // Don't suppress painting if the document isn't loading. Document::ReadyState readyState = mDocument->GetReadyStateEnum(); if (readyState != Document::READYSTATE_COMPLETE) { mPaintSuppressionTimer = NS_NewTimer(); } if (!mPaintSuppressionTimer) { mPaintingSuppressed = false; } else { // Initialize the timer. mPaintSuppressionTimer->SetTarget(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 kungFuDeathGrip(this); auto postResizeEventIfNeeded = [this, initialized]() { if (initialized) { AddResizeEventFlushObserverIfNeeded(); } }; if (!(aOptions & ResizeReflowOptions::BSizeLimit)) { nsSize oldSize = mPresContext->GetVisibleArea().Size(); if (oldSize == nsSize(aWidth, aHeight)) { return false; } bool changed = SimpleResizeReflow(aWidth, aHeight); postResizeEventIfNeeded(); return changed; } // Make sure that style is flushed before setting the pres context // VisibleArea. // // Otherwise we may end up with bogus viewport units resolved against the // unconstrained bsize, or restyling the whole document resolving viewport // units against targetWidth, which may end up doing wasteful work. mDocument->FlushPendingNotifications(FlushType::Frames); nsIFrame* rootFrame = GetRootFrame(); if (mIsDestroying || !rootFrame) { // If we don't have a root frame yet, that means we haven't had our initial // reflow... If that's the case, and aWidth or aHeight is unconstrained, // ignore them altogether. if (aHeight == NS_UNCONSTRAINEDSIZE || aWidth == NS_UNCONSTRAINEDSIZE) { // We can't do the work needed for SizeToContent without a root // frame, and we want to return before setting the visible area. return false; } mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight)); // There isn't anything useful we can do if the initial reflow hasn't // happened. return true; } WritingMode wm = rootFrame->GetWritingMode(); MOZ_ASSERT((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE, "unconstrained isize not allowed"); nscoord targetWidth = aWidth; nscoord targetHeight = aHeight; if (wm.IsVertical()) { targetWidth = NS_UNCONSTRAINEDSIZE; } else { targetHeight = NS_UNCONSTRAINEDSIZE; } mPresContext->SetVisibleArea(nsRect(0, 0, targetWidth, targetHeight)); // XXX Do a full invalidate at the beginning so that invalidates along // the way don't have region accumulation issues? // For height:auto BSizes (i.e. layout-controlled), descendant // intrinsic sizes can't depend on them. So the only other case is // viewport-controlled BSizes which we handle here. nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame); { nsAutoCauseReflowNotifier crNotifier(this); WillDoReflow(); // Kick off a top-down reflow AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow); nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager); mDirtyRoots.Remove(rootFrame); DoReflow(rootFrame, true, nullptr); const bool reflowAgain = wm.IsVertical() ? mPresContext->GetVisibleArea().width > aWidth : mPresContext->GetVisibleArea().height > aHeight; if (reflowAgain) { mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight)); DoReflow(rootFrame, true, nullptr); } } // Now, we may have been destroyed by the destructor of // `nsAutoCauseReflowNotifier`. mPendingDidDoReflow = true; DidDoReflow(true); // the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE, // and the isize shouldn't be NS_UNCONSTRAINEDSIZE anyway. MOZ_DIAGNOSTIC_ASSERT( mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE, "width should not be NS_UNCONSTRAINEDSIZE after reflow"); MOZ_DIAGNOSTIC_ASSERT( mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE, "height should not be NS_UNCONSTRAINEDSIZE after reflow"); postResizeEventIfNeeded(); return true; } void PresShell::FireResizeEvent() { if (mIsDocumentGone) { return; } // If event handling is suppressed, repost the resize event to the refresh // driver. The event is marked as delayed so that the refresh driver does not // continue ticking. if (mDocument->EventHandlingSuppressed()) { if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) { mDocument->SetHasDelayedRefreshEvent(); mPresContext->RefreshDriver()->AddResizeEventFlushObserver( this, /* aDelayed = */ true); } return; } mResizeEventPending = false; FireResizeEventSync(); } void PresShell::FireResizeEventSync() { if (mIsDocumentGone) { return; } // Send resize event from here. WidgetEvent event(true, mozilla::eResize); nsEventStatus status = nsEventStatus_eIgnore; if (RefPtr window = mDocument->GetWindow()) { // 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 PresShell::GetCaret() const { RefPtr caret = mCaret; return caret.forget(); } already_AddRefed PresShell::GetAccessibleCaretEventHub() const { RefPtr eventHub = mAccessibleCaretEventHub; return eventHub.forget(); } void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; } void PresShell::RestoreCaret() { mCaret = mOriginalCaret; } NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) { bool oldEnabled = mCaretEnabled; mCaretEnabled = aInEnable; if (mCaretEnabled != oldEnabled) { MOZ_ASSERT(mCaret); if (mCaret) { mCaret->SetVisible(mCaretEnabled); } } return NS_OK; } NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly) { if (mCaret) mCaret->SetCaretReadOnly(aReadOnly); return NS_OK; } NS_IMETHODIMP PresShell::GetCaretEnabled(bool* aOutEnabled) { NS_ENSURE_ARG_POINTER(aOutEnabled); *aOutEnabled = mCaretEnabled; return NS_OK; } NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility) { if (mCaret) mCaret->SetVisibilityDuringSelection(aVisibility); return NS_OK; } NS_IMETHODIMP PresShell::GetCaretVisible(bool* aOutIsVisible) { *aOutIsVisible = false; if (mCaret) { *aOutIsVisible = mCaret->IsVisible(); } return NS_OK; } NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t aFlags) { mSelectionFlags = aFlags; return NS_OK; } NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t* aFlags) { if (!aFlags) { return NS_ERROR_INVALID_ARG; } *aFlags = mSelectionFlags; return NS_OK; } // implementation of nsISelectionController NS_IMETHODIMP PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) { RefPtr frameSelection = mSelection; return frameSelection->PhysicalMove(aDirection, aAmount, aExtend); } NS_IMETHODIMP PresShell::CharacterMove(bool aForward, bool aExtend) { RefPtr frameSelection = mSelection; return frameSelection->CharacterMove(aForward, aExtend); } NS_IMETHODIMP PresShell::WordMove(bool aForward, bool aExtend) { RefPtr frameSelection = mSelection; nsresult result = frameSelection->WordMove(aForward, aExtend); // if we can't go down/up any more we must then move caret completely to // end/beginning respectively. if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend); return result; } NS_IMETHODIMP PresShell::LineMove(bool aForward, bool aExtend) { RefPtr frameSelection = mSelection; nsresult result = frameSelection->LineMove(aForward, aExtend); // if we can't go down/up any more we must then move caret completely to // end/beginning respectively. if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend); return result; } NS_IMETHODIMP PresShell::IntraLineMove(bool aForward, bool aExtend) { RefPtr frameSelection = mSelection; return frameSelection->IntraLineMove(aForward, aExtend); } NS_IMETHODIMP PresShell::PageMove(bool aForward, bool aExtend) { nsIFrame* frame = nullptr; if (!aExtend) { frame = do_QueryFrame(GetScrollableFrameToScroll(VerticalScrollDirection)); // If there is no scrollable frame, get the frame to move caret instead. } if (!frame || frame->PresContext() != mPresContext) { frame = mSelection->GetFrameToPageSelect(); if (!frame) { return NS_OK; } } // We may scroll parent scrollable element of current selection limiter. // In such case, we don't want to scroll selection into view unless // selection is changed. RefPtr frameSelection = mSelection; return frameSelection->PageMove( aForward, aExtend, frame, nsFrameSelection::SelectionIntoView::IfChanged); } NS_IMETHODIMP PresShell::ScrollPage(bool aForward) { nsIScrollableFrame* scrollFrame = GetScrollableFrameToScroll(VerticalScrollDirection); ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Pages); if (scrollFrame) { scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES, scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection | ScrollSnapFlags::IntendedEndPosition); } return NS_OK; } NS_IMETHODIMP PresShell::ScrollLine(bool aForward) { nsIScrollableFrame* scrollFrame = GetScrollableFrameToScroll(VerticalScrollDirection); ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines); if (scrollFrame) { nsRect scrollPort = scrollFrame->GetScrollPortRect(); nsSize lineSize = scrollFrame->GetLineScrollAmount(); int32_t lineCount = StaticPrefs::toolkit_scrollbox_verticalScrollDistance(); if (lineCount * lineSize.height > scrollPort.Height()) { return ScrollPage(aForward); } scrollFrame->ScrollBy( nsIntPoint(0, aForward ? lineCount : -lineCount), ScrollUnit::LINES, scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection); } return NS_OK; } NS_IMETHODIMP PresShell::ScrollCharacter(bool aRight) { nsIScrollableFrame* scrollFrame = GetScrollableFrameToScroll(HorizontalScrollDirection); ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines); if (scrollFrame) { int32_t h = StaticPrefs::toolkit_scrollbox_horizontalScrollDistance(); scrollFrame->ScrollBy( nsIntPoint(aRight ? h : -h, 0), ScrollUnit::LINES, scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection); } return NS_OK; } NS_IMETHODIMP PresShell::CompleteScroll(bool aForward) { nsIScrollableFrame* scrollFrame = GetScrollableFrameToScroll(VerticalScrollDirection); ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Other); if (scrollFrame) { scrollFrame->ScrollBy( nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE, scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedEndPosition); } return NS_OK; } NS_IMETHODIMP PresShell::CompleteMove(bool aForward, bool aExtend) { // Beware! This may flush notifications via synchronous // ScrollSelectionIntoView. RefPtr frameSelection = mSelection; nsIContent* limiter = frameSelection->GetAncestorLimiter(); nsIFrame* frame = limiter ? limiter->GetPrimaryFrame() : FrameConstructor()->GetRootElementFrame(); if (!frame) return NS_ERROR_FAILURE; nsIFrame::CaretPosition pos = frame->GetExtremeCaretPosition(!aForward); const nsFrameSelection::FocusMode focusMode = aExtend ? nsFrameSelection::FocusMode::kExtendSelection : nsFrameSelection::FocusMode::kCollapseToNewPoint; frameSelection->HandleClick( MOZ_KnownLive(pos.mResultContent) /* bug 1636889 */, pos.mContentOffset, pos.mContentOffset, focusMode, aForward ? 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 frameSelection = FrameSelection(); if (frameSelection) { frameSelection->SetDragState(false); } if (sCapturingContentInfo.mContent && sCapturingContentInfo.mContent->OwnerDoc() == mDocument) { PresShell::ReleaseCapturingContent(); } } void PresShell::BeginLoad(Document* aDocument) { mDocumentLoading = true; gfxTextPerfMetrics* tp = nullptr; if (mPresContext) { tp = mPresContext->GetTextPerfMetrics(); } bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug); if (shouldLog || tp) { mLoadBegin = TimeStamp::Now(); } if (shouldLog) { nsIURI* uri = mDocument->GetDocumentURI(); MOZ_LOG(gLog, LogLevel::Debug, ("(presshell) %p load begin [%s]\n", this, uri ? uri->GetSpecOrDefault().get() : "")); } } void PresShell::EndLoad(Document* aDocument) { MOZ_ASSERT(aDocument == mDocument, "Wrong document"); RestoreRootScrollPosition(); mDocumentLoading = false; } bool PresShell::IsLayoutFlushObserver() { return GetPresContext()->RefreshDriver()->IsLayoutFlushObserver(this); } void PresShell::LoadComplete() { gfxTextPerfMetrics* tp = nullptr; if (mPresContext) { tp = mPresContext->GetTextPerfMetrics(); } // log load bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug); if (shouldLog || tp) { TimeDuration loadTime = TimeStamp::Now() - mLoadBegin; nsIURI* uri = mDocument->GetDocumentURI(); nsAutoCString spec; if (uri) { spec = uri->GetSpecOrDefault(); } if (shouldLog) { MOZ_LOG(gLog, LogLevel::Debug, ("(presshell) %p load done time-ms: %9.2f [%s]\n", this, loadTime.ToMilliseconds(), spec.get())); } if (tp) { tp->Accumulate(); if (tp->cumulative.numChars > 0) { LogTextPerfStats(tp, this, tp->cumulative, loadTime.ToMilliseconds(), eLog_loaddone, spec.get()); } } } } #ifdef DEBUG void PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame) { // XXXbz due to bug 372769, can't actually assert anything here... // XXX Since bug 372769 is now fixed, the assertion is being enabled in bug // 1758104. # if 0 // XXXbz shouldn't need this part; remove it once FrameNeedsReflow // handles the root frame correctly. if (!aFrame->GetParent()) { return; } // Make sure that there is a reflow root ancestor of |aFrame| that's // in mDirtyRoots already. while (aFrame && aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN)) { if ((aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_DYNAMIC_REFLOW_ROOT) || !aFrame->GetParent()) && mDirtyRoots.Contains(aFrame)) { return; } aFrame = aFrame->GetParent(); } MOZ_ASSERT_UNREACHABLE( "Frame has dirty bits set but isn't scheduled to be " "reflowed?"); # endif } #endif void PresShell::PostPendingScrollAnchorSelection( mozilla::layout::ScrollAnchorContainer* aContainer) { mPendingScrollAnchorSelection.Insert(aContainer->ScrollableFrame()); } void PresShell::FlushPendingScrollAnchorSelections() { for (nsIScrollableFrame* scroll : mPendingScrollAnchorSelection) { scroll->Anchor()->SelectAnchor(); } mPendingScrollAnchorSelection.Clear(); } void PresShell::PostPendingScrollAnchorAdjustment( ScrollAnchorContainer* aContainer) { mPendingScrollAnchorAdjustment.Insert(aContainer->ScrollableFrame()); } void PresShell::FlushPendingScrollAnchorAdjustments() { for (nsIScrollableFrame* scroll : mPendingScrollAnchorAdjustment) { scroll->Anchor()->ApplyAdjustments(); } mPendingScrollAnchorAdjustment.Clear(); } void PresShell::PostPendingScrollResnap(nsIScrollableFrame* aScrollableFrame) { mPendingScrollResnap.Insert(aScrollableFrame); } void PresShell::FlushPendingScrollResnap() { for (nsIScrollableFrame* scrollableFrame : mPendingScrollResnap) { scrollableFrame->TryResnap(); } mPendingScrollResnap.Clear(); } void PresShell::FrameNeedsReflow(nsIFrame* aFrame, IntrinsicDirty aIntrinsicDirty, nsFrameState aBitToAdd, ReflowRootHandling aRootHandling) { MOZ_ASSERT(aBitToAdd == NS_FRAME_IS_DIRTY || aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN || !aBitToAdd, "Unexpected bits being added"); // FIXME bug 478135 NS_ASSERTION( aIntrinsicDirty != IntrinsicDirty::FrameAncestorsAndDescendants || aBitToAdd != NS_FRAME_HAS_DIRTY_CHILDREN, "bits don't correspond to style change reason"); // FIXME bug 457400 NS_ASSERTION(!mIsReflowing, "can't mark frame dirty during reflow"); // If we've not yet done the initial reflow, then don't bother // enqueuing a reflow command yet. if (!mDidInitialize) return; // If we're already destroying, don't bother with this either. if (mIsDestroying) return; #ifdef DEBUG // printf("gShellCounter: %d\n", gShellCounter++); if (mInVerifyReflow) return; if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) { printf("\nPresShell@%p: frame %p needs reflow\n", (void*)this, (void*)aFrame); if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) { printf("Current content model:\n"); Element* rootElement = mDocument->GetRootElement(); if (rootElement) { rootElement->List(stdout, 0); } } } #endif AutoTArray subtrees; subtrees.AppendElement(aFrame); do { nsIFrame* subtreeRoot = subtrees.PopLastElement(); // Grab |wasDirty| now so we can go ahead and update the bits on // subtreeRoot. bool wasDirty = subtreeRoot->IsSubtreeDirty(); subtreeRoot->AddStateBits(aBitToAdd); // Determine whether we need to keep looking for the next ancestor // reflow root if subtreeRoot itself is a reflow root. bool targetNeedsReflowFromParent; switch (aRootHandling) { case ReflowRootHandling::PositionOrSizeChange: targetNeedsReflowFromParent = true; break; case ReflowRootHandling::NoPositionOrSizeChange: targetNeedsReflowFromParent = false; break; case ReflowRootHandling::InferFromBitToAdd: targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY); break; } auto FrameIsReflowRoot = [](const nsIFrame* aFrame) { return aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_DYNAMIC_REFLOW_ROOT); }; auto CanStopClearingAncestorIntrinsics = [&](const nsIFrame* aFrame) { return FrameIsReflowRoot(aFrame) && aFrame != subtreeRoot; }; auto IsReflowBoundary = [&](const nsIFrame* aFrame) { return FrameIsReflowRoot(aFrame) && (aFrame != subtreeRoot || !targetNeedsReflowFromParent); }; // Mark the intrinsic widths as dirty on the frame, all of its ancestors, // and all of its descendants, if needed: if (aIntrinsicDirty != IntrinsicDirty::None) { // Mark argument and all ancestors dirty. (Unless we hit a reflow root // that should contain the reflow. for (nsIFrame* a = subtreeRoot; a && !CanStopClearingAncestorIntrinsics(a); a = a->GetParent()) { a->MarkIntrinsicISizesDirty(); if (a->IsAbsolutelyPositioned()) { // If we get here, 'a' is abspos, so its subtree's intrinsic sizing // has no effect on its ancestors' intrinsic sizing. So, don't loop // upwards any further. break; } } } const bool frameAncestorAndDescendantISizesDirty = (aIntrinsicDirty == IntrinsicDirty::FrameAncestorsAndDescendants); const bool dirty = (aBitToAdd == NS_FRAME_IS_DIRTY); if (frameAncestorAndDescendantISizesDirty || dirty) { // Mark all descendants dirty (using an nsTArray stack rather than // recursion). // Note that ReflowInput::InitResizeFlags has some similar // code; see comments there for how and why it differs. AutoTArray stack; stack.AppendElement(subtreeRoot); do { nsIFrame* f = stack.PopLastElement(); if (frameAncestorAndDescendantISizesDirty && f->IsPlaceholderFrame()) { // Call `GetOutOfFlowFrame` directly because we can get here from // frame destruction and the placeholder might be already torn down. if (nsIFrame* oof = static_cast(f)->GetOutOfFlowFrame()) { if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) { // We have another distinct subtree we need to mark. subtrees.AppendElement(oof); } } } for (const auto& childList : f->ChildLists()) { for (nsIFrame* kid : childList.mList) { if (frameAncestorAndDescendantISizesDirty) { kid->MarkIntrinsicISizesDirty(); } if (dirty) { kid->AddStateBits(NS_FRAME_IS_DIRTY); } stack.AppendElement(kid); } } } while (stack.Length() != 0); } // Skip setting dirty bits up the tree if we weren't given a bit to add. if (!aBitToAdd) { continue; } // Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty) // up the tree until we reach either a frame that's already dirty or // a reflow root. nsIFrame* f = subtreeRoot; for (;;) { if (IsReflowBoundary(f) || !f->GetParent()) { // we've hit a reflow root or the root frame if (!wasDirty) { mDirtyRoots.Add(f); SetNeedLayoutFlush(); } #ifdef DEBUG else { VerifyHasDirtyRootAncestor(f); } #endif break; } nsIFrame* child = f; f = f->GetParent(); wasDirty = f->IsSubtreeDirty(); f->ChildIsDirty(child); NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN), "ChildIsDirty didn't do its job"); if (wasDirty) { // This frame was already marked dirty. #ifdef DEBUG VerifyHasDirtyRootAncestor(f); #endif break; } } } while (subtrees.Length() != 0); MaybeScheduleReflow(); } void PresShell::FrameNeedsToContinueReflow(nsIFrame* aFrame) { NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty."); MOZ_ASSERT(mCurrentReflowRoot, "Must have a current reflow root here"); NS_ASSERTION( aFrame == mCurrentReflowRoot || nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame), "Frame passed in is not the descendant of mCurrentReflowRoot"); NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW), "Frame passed in not in reflow?"); mFramesToDirty.Insert(aFrame); } already_AddRefed PresShell::GetContentForScrolling() const { if (nsCOMPtr focused = GetFocusedContentInOurWindow()) { return focused.forget(); } return GetSelectedContentForScrolling(); } already_AddRefed PresShell::GetSelectedContentForScrolling() const { nsCOMPtr selectedContent; if (mSelection) { Selection* domSelection = mSelection->GetSelection(SelectionType::eNormal); if (domSelection) { selectedContent = nsIContent::FromNodeOrNull(domSelection->GetFocusNode()); } } return selectedContent.forget(); } nsIScrollableFrame* PresShell::GetScrollableFrameToScrollForContent( nsIContent* aContent, ScrollDirections aDirections) { nsIScrollableFrame* scrollFrame = nullptr; if (aContent) { nsIFrame* startFrame = aContent->GetPrimaryFrame(); if (startFrame) { scrollFrame = startFrame->GetScrollTargetFrame(); if (scrollFrame) { startFrame = scrollFrame->GetScrolledFrame(); } scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection( startFrame, aDirections); } } if (!scrollFrame) { scrollFrame = GetRootScrollFrameAsScrollable(); if (!scrollFrame || !scrollFrame->GetScrolledFrame()) { return nullptr; } scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection( scrollFrame->GetScrolledFrame(), aDirections); } return scrollFrame; } nsIScrollableFrame* PresShell::GetScrollableFrameToScroll( ScrollDirections aDirections) { nsCOMPtr content = GetContentForScrolling(); return GetScrollableFrameToScrollForContent(content.get(), aDirections); } void PresShell::CancelAllPendingReflows() { mDirtyRoots.Clear(); if (mObservingLayoutFlushes) { GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this); mObservingLayoutFlushes = false; } ASSERT_REFLOW_SCHEDULED_STATE(); } static bool DestroyFramesAndStyleDataFor( Element* aElement, nsPresContext& aPresContext, RestyleManager::IncludeRoot aIncludeRoot) { bool didReconstruct = aPresContext.FrameConstructor()->DestroyFramesFor(aElement); RestyleManager::ClearServoDataFromSubtree(aElement, aIncludeRoot); return didReconstruct; } void PresShell::SlotAssignmentWillChange(Element& aElement, HTMLSlotElement* aOldSlot, HTMLSlotElement* aNewSlot) { MOZ_ASSERT(aOldSlot != aNewSlot); if (MOZ_UNLIKELY(!mDidInitialize)) { return; } // If the old slot is about to become empty and show fallback, let layout know // that it needs to do work. if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1 && aOldSlot->HasChildren()) { DestroyFramesForAndRestyle(aOldSlot); } // Ensure the new element starts off clean. DestroyFramesAndStyleDataFor(&aElement, *mPresContext, RestyleManager::IncludeRoot::Yes); if (aNewSlot) { // If the new slot will stop showing fallback content, we need to reframe it // altogether. if (aNewSlot->AssignedNodes().IsEmpty() && aNewSlot->HasChildren()) { DestroyFramesForAndRestyle(aNewSlot); // Otherwise we just care about the element, but we need to ensure that // something takes care of traversing to the relevant slot, if needed. } else if (aNewSlot->HasServoData() && !Servo_Element_IsDisplayNone(aNewSlot)) { // Set the reframe bits... aNewSlot->NoteDescendantsNeedFramesForServo(); aElement.SetFlags(NODE_NEEDS_FRAME); // Now the style dirty bits. Note that we can't just do // aElement.NoteDirtyForServo(), because the new slot is not setup yet. aNewSlot->SetHasDirtyDescendantsForServo(); aNewSlot->NoteDirtySubtreeForServo(); } } } #ifdef DEBUG static void AssertNoFramesOrStyleDataInDescendants(Element& aElement) { for (nsINode* node : ShadowIncludingTreeIterator(aElement)) { nsIContent* c = nsIContent::FromNode(node); if (c == &aElement) { continue; } // FIXME(emilio): The check is needed because of bug 135040. MOZ_ASSERT(!c->GetPrimaryFrame() || c->IsHTMLElement(nsGkAtoms::area)); MOZ_ASSERT(!c->IsElement() || !c->AsElement()->HasServoData()); } } #endif void PresShell::DestroyFramesForAndRestyle(Element* aElement) { #ifdef DEBUG auto postCondition = MakeScopeExit([&]() { MOZ_ASSERT(!aElement->GetPrimaryFrame()); AssertNoFramesOrStyleDataInDescendants(*aElement); }); #endif MOZ_ASSERT(aElement); if (!aElement->HasServoData()) { // Nothing to do here, the element already is out of the flat tree or is not // styled. return; } // Mark ourselves as not safe to flush while we're doing frame destruction. nsAutoScriptBlocker scriptBlocker; ++mChangeNestCount; const bool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement); // Clear the style data from all the flattened tree descendants, but _not_ // from us, since otherwise we wouldn't see the reframe. RestyleManager::ClearServoDataFromSubtree(aElement, RestyleManager::IncludeRoot::No); auto changeHint = didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame; mPresContext->RestyleManager()->PostRestyleEvent( aElement, RestyleHint::RestyleSubtree(), changeHint); --mChangeNestCount; } void PresShell::ShadowRootWillBeAttached(Element& aElement) { #ifdef DEBUG auto postCondition = MakeScopeExit( [&]() { AssertNoFramesOrStyleDataInDescendants(aElement); }); #endif if (!aElement.HasServoData()) { // Nothing to do here, the element already is out of the flat tree or is not // styled. return; } if (!aElement.HasChildren()) { // The element has no children, just avoid the work. return; } // Mark ourselves as not safe to flush while we're doing frame destruction. nsAutoScriptBlocker scriptBlocker; ++mChangeNestCount; // NOTE(emilio): We use FlattenedChildIterator intentionally here (rather than // StyleChildrenIterator), since we don't want to remove ::before / ::after // content. FlattenedChildIterator iter(&aElement); nsCSSFrameConstructor* fc = FrameConstructor(); for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) { fc->DestroyFramesFor(c); if (c->IsElement()) { RestyleManager::ClearServoDataFromSubtree(c->AsElement()); } } #ifdef ACCESSIBILITY if (nsAccessibilityService* accService = GetAccService()) { accService->ScheduleAccessibilitySubtreeUpdate(this, &aElement); } #endif --mChangeNestCount; } void PresShell::PostRecreateFramesFor(Element* aElement) { if (MOZ_UNLIKELY(!mDidInitialize)) { // Nothing to do here. In fact, if we proceed and aElement is the root, we // will crash. return; } mPresContext->RestyleManager()->PostRestyleEvent( aElement, RestyleHint{0}, nsChangeHint_ReconstructFrame); } void PresShell::RestyleForAnimation(Element* aElement, RestyleHint aHint) { // Now that we no longer have separate non-animation and animation // restyles, this method having a distinct identity is less important, // but it still seems useful to offer as a "more public" API and as a // checkpoint for these restyles to go through. mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint, nsChangeHint(0)); } void PresShell::SetForwardingContainer(const WeakPtr& aContainer) { mForwardingContainer = aContainer; } void PresShell::ClearFrameRefs(nsIFrame* aFrame) { mPresContext->EventStateManager()->ClearFrameRefs(aFrame); AutoWeakFrame* weakFrame = mAutoWeakFrames; while (weakFrame) { AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame(); if (weakFrame->GetFrame() == aFrame) { // This removes weakFrame from mAutoWeakFrames. weakFrame->Clear(this); } weakFrame = prev; } AutoTArray toRemove; for (WeakFrame* weakFrame : mWeakFrames) { if (weakFrame->GetFrame() == aFrame) { toRemove.AppendElement(weakFrame); } } for (WeakFrame* weakFrame : toRemove) { weakFrame->Clear(this); } } UniquePtr PresShell::CreateReferenceRenderingContext() { if (mPresContext->IsScreen()) { return gfxContext::CreateOrNull( gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get()); } // We assume the devCtx has positive width and height for this call. // However, width and height, may be outside of the reasonable range // so rc may still be null. nsDeviceContext* devCtx = mPresContext->DeviceContext(); return devCtx->CreateReferenceRenderingContext(); } // https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier nsresult PresShell::GoToAnchor(const nsAString& aAnchorName, bool aScroll, ScrollFlags aAdditionalScrollFlags) { if (!mDocument) { return NS_ERROR_FAILURE; } const Element* root = mDocument->GetRootElement(); if (root && root->IsSVGElement(nsGkAtoms::svg)) { // We need to execute this even if there is an empty anchor name // so that any existing SVG fragment identifier effect is removed if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument, aAnchorName)) { return NS_OK; } } // Hold a reference to the ESM in case event dispatch tears us down. RefPtr esm = mPresContext->EventStateManager(); // 1. If there is no indicated part of the document, set the Document's target // element to null. // // FIXME(emilio): Per spec empty fragment string should take the same // code-path as "top"! if (aAnchorName.IsEmpty()) { NS_ASSERTION(!aScroll, "can't scroll to empty anchor name"); esm->SetContentState(nullptr, ElementState::URLTARGET); return NS_OK; } // 2. If the indicated part of the document is the top of the document, // then: // (handled below when `target` is null, and anchor is `top`) // 3.1. Let target be element that is the indicated part of the document. // // https://html.spec.whatwg.org/#target-element // https://html.spec.whatwg.org/#find-a-potential-indicated-element RefPtr target = nsContentUtils::GetTargetElement(mDocument, aAnchorName); // 1. If there is no indicated part of the document, set the Document's // target element to null. // 2.1. Set the Document's target element to null. // 3.2. Set the Document's target element to target. esm->SetContentState(target, ElementState::URLTARGET); // TODO: Spec probably needs a section to account for this. if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) { if (rootScroll->DidHistoryRestore()) { // Scroll position restored from history trumps scrolling to anchor. aScroll = false; rootScroll->ClearDidHistoryRestore(); } } if (target) { if (aScroll) { // 3.3. TODO: Run the ancestor details revealing algorithm on target. // 3.4. Scroll target into view, with behavior set to "auto", block set to // "start", and inline set to "nearest". // FIXME(emilio): Not all callers pass ScrollSmoothAuto (but we use auto // smooth scroll for `top` regardless below, so maybe they should!). ScrollingInteractionContext scrollToAnchorContext(true); MOZ_TRY(ScrollContentIntoView( target, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always), ScrollAxis(), ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags)); if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) { mLastAnchorScrolledTo = target; mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y; } } { // 3.6. Move the sequential focus navigation starting point to target. // // Move the caret to the anchor. That way tabbing will start from the new // location. // // TODO(emilio): Do we want to do this even if aScroll is false? // // NOTE: Intentionally out of order for now with the focus steps, see // https://github.com/whatwg/html/issues/7759 RefPtr jumpToRange = nsRange::Create(mDocument); nsCOMPtr nodeToSelect = target.get(); while (nodeToSelect->GetFirstChild()) { nodeToSelect = nodeToSelect->GetFirstChild(); } jumpToRange->SelectNodeContents(*nodeToSelect, IgnoreErrors()); if (RefPtr sel = mSelection->GetSelection(SelectionType::eNormal)) { sel->RemoveAllRanges(IgnoreErrors()); sel->AddRangeAndSelectFramesAndNotifyListeners(*jumpToRange, IgnoreErrors()); if (!StaticPrefs::layout_selectanchor()) { // Use a caret (collapsed selection) at the start of the anchor. sel->CollapseToStart(IgnoreErrors()); } } } // 3.5. Run the focusing steps for target, with the Document's viewport as // the fallback target. // // Note that ScrollContentIntoView flushes, so we don't need to do that // again here. We also don't need to scroll again either. // // We intentionally focus the target only when aScroll is true, we need to // sort out if the spec needs to differentiate these cases. When aScroll is // false we still clear the focus unconditionally, that's legacy behavior, // maybe we shouldn't do it. // // TODO(emilio): Do we really want to clear the focus even if aScroll is // false? const bool shouldFocusTarget = [&] { if (!aScroll) { return false; } nsIFrame* targetFrame = target->GetPrimaryFrame(); return targetFrame && targetFrame->IsFocusable(); }(); if (shouldFocusTarget) { FocusOptions options; options.mPreventScroll = true; target->Focus(options, CallerType::NonSystem, IgnoreErrors()); } else if (RefPtr fm = nsFocusManager::GetFocusManager()) { if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) { // Now focus the document itself if focus is on an element within it. nsCOMPtr focusedWindow; fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); if (SameCOMIdentity(win, focusedWindow)) { fm->ClearFocus(focusedWindow); } } } // If the target is an animation element, activate the animation if (auto* animationElement = SVGAnimationElement::FromNode(target.get())) { animationElement->ActivateByHyperlink(); } #ifdef ACCESSIBILITY if (nsAccessibilityService* accService = GetAccService()) { accService->NotifyOfAnchorJumpTo(target); } #endif } else if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, u"top"_ns)) { // 2.2. Scroll to the beginning of the document for the Document. nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable(); // Check |aScroll| after setting |rv| so we set |rv| to the same // thing whether or not |aScroll| is true. if (aScroll && sf) { ScrollMode scrollMode = sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant; // Scroll to the top of the page sf->ScrollTo(nsPoint(0, 0), scrollMode); } } else { return NS_ERROR_FAILURE; } return NS_OK; } nsresult PresShell::ScrollToAnchor() { nsCOMPtr lastAnchor = std::move(mLastAnchorScrolledTo); if (!lastAnchor) { return NS_OK; } NS_ASSERTION(mDidInitialize, "should have done initial reflow by now"); nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable(); if (!rootScroll || mLastAnchorScrollPositionY != rootScroll->GetScrollPosition().y) { return NS_OK; } return ScrollContentIntoView( lastAnchor, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always), ScrollAxis(), ScrollFlags::AnchorScrollFlags); } /* * Helper (per-continuation) for ScrollContentIntoView. * * @param aContainerFrame [in] the frame which aRect is relative to * @param aFrame [in] Frame whose bounds should be unioned * @param aUseWholeLineHeightForInlines [in] if true, then for inline frames * we should include the top of the line in the added rectangle * @param aRect [inout] rect into which its bounds should be unioned * @param aHaveRect [inout] whether aRect contains data yet * @param aPrevBlock [inout] the block aLines is a line iterator for * @param aLines [inout] the line iterator we're using * @param aCurLine [inout] the line to start looking from in this iterator */ static void AccumulateFrameBounds(nsIFrame* aContainerFrame, nsIFrame* aFrame, bool aUseWholeLineHeightForInlines, nsRect& aRect, bool& aHaveRect, nsIFrame*& aPrevBlock, nsILineIterator*& aLines, int32_t& aCurLine) { nsIFrame* frame = aFrame; nsRect frameBounds = nsRect(nsPoint(0, 0), aFrame->GetSize()); // If this is an inline frame and either the bounds height is 0 (quirks // layout model) or aUseWholeLineHeightForInlines is set, we need to // change the top of the bounds to include the whole line. if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) { nsIFrame* prevFrame = aFrame; nsIFrame* f = aFrame; while (f && f->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 composedDoc = aContent->GetComposedDoc(); NS_ENSURE_STATE(composedDoc); NS_ASSERTION(mDidInitialize, "should have done initial reflow by now"); if (mContentToScrollTo) { mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); } mContentToScrollTo = aContent; ScrollIntoViewData* data = new ScrollIntoViewData(); data->mContentScrollVAxis = aVertical; data->mContentScrollHAxis = aHorizontal; data->mContentToScrollToFlags = aScrollFlags; if (NS_FAILED(mContentToScrollTo->SetProperty( nsGkAtoms::scrolling, data, nsINode::DeleteProperty))) { mContentToScrollTo = nullptr; } // If the target frame 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( mContentToScrollTo->GetProperty(nsGkAtoms::scrolling)); if (MOZ_UNLIKELY(!data)) { mContentToScrollTo = nullptr; return; } ScrollFrameIntoView(frame, Nothing(), data->mContentScrollVAxis, data->mContentScrollHAxis, data->mContentToScrollToFlags); } bool PresShell::ScrollFrameIntoView( nsIFrame* aTargetFrame, const Maybe& aKnownRectRelativeToTarget, ScrollAxis aVertical, ScrollAxis aHorizontal, ScrollFlags aScrollFlags) { // The scroll margin only applies to the whole bounds of the element, so don't // apply it if we get an arbitrary rect / point to scroll to. const nsMargin scrollMargin = aKnownRectRelativeToTarget ? nsMargin() : GetScrollMargin(aTargetFrame); Sides skipPaddingSides; const auto MaybeSkipPaddingSides = [&](nsIFrame* aFrame) { if (!aFrame->IsStickyPositioned()) { return; } const nsPoint pos = aFrame->GetPosition(); const nsPoint normalPos = aFrame->GetNormalPosition(); if (pos == normalPos) { return; // Frame is not stuck. } // If we're targetting a sticky element, make sure not to apply // scroll-padding on the direction we're stuck. const auto& offsets = aFrame->StylePosition()->mOffset; for (auto side : AllPhysicalSides()) { if (offsets.Get(side).IsAuto()) { continue; } // See if this axis is stuck. const bool yAxis = side == eSideTop || side == eSideBottom; const bool stuck = yAxis ? pos.y != normalPos.y : pos.x != normalPos.x; if (!stuck) { continue; } skipPaddingSides |= SideToSideBit(side); } }; nsIFrame* container = aTargetFrame; // This function needs to work even if rect has a width or height of 0. nsRect rect = [&] { if (aKnownRectRelativeToTarget) { return *aKnownRectRelativeToTarget; } MaybeSkipPaddingSides(aTargetFrame); while (nsIFrame* parent = container->GetParent()) { container = parent; if (static_cast(do_QueryFrame(container))) { // We really just need a non-fragmented frame so that we can accumulate // the bounds of all our continuations relative to it. We shouldn't jump // out of our nearest scrollable frame, and that's an ok reference // frame, so try to use that, or the root frame if there's nothing to // scroll in this document. break; } MaybeSkipPaddingSides(container); } MOZ_DIAGNOSTIC_ASSERT(container); nsRect targetFrameBounds; { bool haveRect = false; const bool useWholeLineHeightForInlines = aVertical.mWhenToScroll != WhenToScroll::IfNotFullyVisible; AutoAssertNoDomMutations guard; // Ensure use of nsILineIterators is safe. nsIFrame* prevBlock = nullptr; // Reuse the same line iterator across calls to AccumulateFrameBounds. // We set it every time we detect a new block (stored in prevBlock). nsILineIterator* lines = nullptr; // The last line we found a continuation on in |lines|. We assume that // later continuations cannot come on earlier lines. int32_t curLine = 0; nsIFrame* frame = aTargetFrame; do { AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines, targetFrameBounds, haveRect, prevBlock, lines, curLine); } while ((frame = frame->GetNextContinuation())); } return targetFrameBounds; }(); bool didScroll = false; const nsIFrame* target = aTargetFrame; // Walk up the frame hierarchy scrolling the rect into view and // keeping rect relative to container do { if (nsIScrollableFrame* sf = do_QueryFrame(container)) { nsPoint oldPosition = sf->GetScrollPosition(); nsRect targetRect = rect; // Inflate the scrolled rect by the container's padding in each dimension, // unless we have 'overflow-clip-box-*: content-box' in that dimension. auto* disp = container->StyleDisplay(); if (disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox || disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) { WritingMode wm = container->GetWritingMode(); bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock : disp->mOverflowClipBoxInline) == StyleOverflowClipBox::ContentBox; bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline : disp->mOverflowClipBoxBlock) == StyleOverflowClipBox::ContentBox; nsMargin padding = container->GetUsedPadding(); if (!cbH) { padding.left = padding.right = nscoord(0); } if (!cbV) { padding.top = padding.bottom = nscoord(0); } targetRect.Inflate(padding); } targetRect -= sf->GetScrolledFrame()->GetPosition(); { AutoWeakFrame wf(container); ScrollToShowRect(sf, container, target, targetRect, skipPaddingSides, scrollMargin, aVertical, aHorizontal, aScrollFlags); if (!wf.IsAlive()) { return didScroll; } } nsPoint newPosition = sf->LastScrollDestination(); // If the scroll position increased, that means our content moved up, // so our rect's offset should decrease rect += oldPosition - newPosition; if (oldPosition != newPosition) { didScroll = true; } // only scroll one container when this flag is set if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) { break; } // This scroll container will be the next target element in the nearest // ancestor scroll container. target = container; // We found a sticky scroll container, we shouldn't skip that side // anymore. skipPaddingSides = {}; } MaybeSkipPaddingSides(container); nsIFrame* parent; if (container->IsTransformed()) { container->GetTransformMatrix(ViewportType::Layout, RelativeTo{nullptr}, &parent); rect = nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent); } else { rect += container->GetPosition(); parent = container->GetParent(); } if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) { nsPoint extraOffset(0, 0); int32_t APD = container->PresContext()->AppUnitsPerDevPixel(); parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(container, &extraOffset); if (parent) { int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel(); rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD); rect += extraOffset; } else { nsCOMPtr docShell = container->PresContext()->GetDocShell(); if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { // Defer to the parent document if this is an out-of-process iframe. Unused << browserChild->SendScrollRectIntoView( rect, aVertical, aHorizontal, aScrollFlags, APD); } } } container = parent; } while (container); return didScroll; } void PresShell::ScheduleViewManagerFlush() { if (MOZ_UNLIKELY(mIsDestroying)) { return; } nsPresContext* presContext = GetPresContext(); if (presContext) { presContext->RefreshDriver()->ScheduleViewManagerFlush(); } SetNeedLayoutFlush(); } void PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent) { AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "DispatchSynthMouseMove", GRAPHICS, mPresContext->GetDocShell()); nsEventStatus status = nsEventStatus_eIgnore; nsView* targetView = nsView::GetViewFor(aEvent->mWidget); if (!targetView) return; RefPtr viewManager = targetView->GetViewManager(); viewManager->DispatchEvent(aEvent, targetView, &status); } void PresShell::ClearMouseCaptureOnView(nsView* aView) { if (nsIContent* capturingContent = GetCapturingContent()) { if (aView) { // if a view was specified, ensure that the captured content is within // this view. nsIFrame* frame = capturingContent->GetPrimaryFrame(); if (frame) { nsView* view = frame->GetClosestView(); // if there is no view, capturing won't be handled any more, so // just release the capture. if (view) { do { if (view == aView) { ReleaseCapturingContent(); // the view containing the captured content likely disappeared so // disable capture for now. AllowMouseCapture(false); break; } view = view->GetParent(); } while (view); // return if the view wasn't found return; } } } ReleaseCapturingContent(); } // disable mouse capture until the next mousedown as a dialog has opened // or a drag has started. Otherwise, someone could start capture during // the modal dialog or drag. AllowMouseCapture(false); } void PresShell::ClearMouseCapture() { ReleaseCapturingContent(); AllowMouseCapture(false); } void PresShell::ClearMouseCapture(nsIFrame* aFrame) { MOZ_ASSERT(aFrame); nsIContent* capturingContent = GetCapturingContent(); if (!capturingContent) { return; } nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame(); const bool shouldClear = !capturingFrame || nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aFrame, capturingFrame); if (shouldClear) { ClearMouseCapture(); } } nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) { MOZ_ASSERT(nullptr != aState, "null state pointer"); // We actually have to mess with the docshell here, since we want to // store the state back in it. // XXXbz this isn't really right, since this is being called in the // content viewer's Hide() method... by that point the docshell's // state could be wrong. We should sort out a better ownership // model for the layout history state. nsCOMPtr docShell(mPresContext->GetDocShell()); if (!docShell) return NS_ERROR_FAILURE; nsCOMPtr historyState; docShell->GetLayoutHistoryState(getter_AddRefs(historyState)); if (!historyState) { // Create the document state object historyState = NS_NewLayoutHistoryState(); docShell->SetLayoutHistoryState(historyState); } *aState = historyState; NS_IF_ADDREF(*aState); // Capture frame state for the entire frame hierarchy nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); if (!rootFrame) return NS_OK; mFrameConstructor->CaptureFrameState(rootFrame, historyState); return NS_OK; } void PresShell::ScheduleBeforeFirstPaint() { if (!mDocument->IsResourceDoc()) { // Notify observers that a new page is about to be drawn. Execute this // as soon as it is safe to run JS, which is guaranteed to be before we // go back to the event loop and actually draw the page. MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::ScheduleBeforeFirstPaint this=%p", this)); nsContentUtils::AddScriptRunner( new nsBeforeFirstPaintDispatcher(mDocument)); } } void PresShell::UnsuppressAndInvalidate() { // Note: We ignore the EnsureVisible check for resource documents, because // they won't have a docshell, so they'll always fail EnsureVisible. if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) || mHaveShutDown) { // No point; we're about to be torn down anyway. return; } ScheduleBeforeFirstPaint(); PROFILER_MARKER_UNTYPED("UnsuppressAndInvalidate", GRAPHICS); mPaintingSuppressed = false; if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) { // let's assume that outline on a root frame is not supported rootFrame->InvalidateFrame(); } if (mPresContext->IsRootContentDocumentCrossProcess()) { if (auto* bc = BrowserChild::GetFrom(mDocument->GetDocShell())) { if (mDocument->IsInitialDocument()) { bc->SendDidUnsuppressPaintingNormalPriority(); } else { bc->SendDidUnsuppressPainting(); } } } // now that painting is unsuppressed, focus may be set on the document if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) { win->SetReadyForFocus(); } if (!mHaveShutDown) { SynthesizeMouseMove(false); ScheduleApproximateFrameVisibilityUpdateNow(); } } void PresShell::CancelPaintSuppressionTimer() { if (mPaintSuppressionTimer) { mPaintSuppressionTimer->Cancel(); mPaintSuppressionTimer = nullptr; } } void PresShell::UnsuppressPainting() { CancelPaintSuppressionTimer(); if (mIsDocumentGone || !mPaintingSuppressed) { return; } // If we have reflows pending, just wait until we process // the reflows and get all the frames where we want them // before actually unlocking the painting. Otherwise // go ahead and unlock now. if (!mDirtyRoots.IsEmpty()) mShouldUnsuppressPainting = true; else UnsuppressAndInvalidate(); } // Post a request to handle an arbitrary callback after reflow has finished. nsresult PresShell::PostReflowCallback(nsIReflowCallback* aCallback) { void* result = AllocateByObjectID(eArenaObjectID_nsCallbackEventRequest, sizeof(nsCallbackEventRequest)); nsCallbackEventRequest* request = (nsCallbackEventRequest*)result; request->callback = aCallback; request->next = nullptr; if (mLastCallbackEventRequest) { mLastCallbackEventRequest = mLastCallbackEventRequest->next = request; } else { mFirstCallbackEventRequest = request; mLastCallbackEventRequest = request; } return NS_OK; } void PresShell::CancelReflowCallback(nsIReflowCallback* aCallback) { nsCallbackEventRequest* before = nullptr; nsCallbackEventRequest* node = mFirstCallbackEventRequest; while (node) { nsIReflowCallback* callback = node->callback; if (callback == aCallback) { nsCallbackEventRequest* toFree = node; if (node == mFirstCallbackEventRequest) { node = node->next; mFirstCallbackEventRequest = node; NS_ASSERTION(before == nullptr, "impossible"); } else { node = node->next; before->next = node; } if (toFree == mLastCallbackEventRequest) { mLastCallbackEventRequest = before; } FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, toFree); } else { before = node; node = node->next; } } } void PresShell::CancelPostedReflowCallbacks() { while (mFirstCallbackEventRequest) { nsCallbackEventRequest* node = mFirstCallbackEventRequest; mFirstCallbackEventRequest = node->next; if (!mFirstCallbackEventRequest) { mLastCallbackEventRequest = nullptr; } nsIReflowCallback* callback = node->callback; FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node); if (callback) { callback->ReflowCallbackCanceled(); } } } void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) { while (true) { // Call all our callbacks, tell us if we need to flush again. bool shouldFlush = false; while (mFirstCallbackEventRequest) { nsCallbackEventRequest* node = mFirstCallbackEventRequest; mFirstCallbackEventRequest = node->next; if (!mFirstCallbackEventRequest) { mLastCallbackEventRequest = nullptr; } nsIReflowCallback* callback = node->callback; FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node); if (callback && callback->ReflowFinished()) { shouldFlush = true; } } if (!shouldFlush || mIsDestroying) { return; } // The flush might cause us to have more callbacks. const auto flushType = aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout; FlushPendingNotifications(flushType); } } bool PresShell::IsSafeToFlush() const { // Not safe if we are getting torn down, reflowing, or in the middle of frame // construction. if (mIsReflowing || mChangeNestCount || mIsDestroying) { return false; } // Not safe if we are painting if (nsViewManager* viewManager = GetViewManager()) { bool isPainting = false; viewManager->IsPainting(isPainting); if (isPainting) { return false; } } return true; } void PresShell::NotifyFontFaceSetOnRefresh() { if (FontFaceSet* set = mDocument->GetFonts()) { set->DidRefresh(); } } void PresShell::DoFlushPendingNotifications(FlushType aType) { // by default, flush animations if aType >= FlushType::Style mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style); FlushPendingNotifications(flush); } #ifdef DEBUG static void AssertFrameSubtreeIsSane(const nsIFrame& aRoot) { if (const nsIContent* content = aRoot.GetContent()) { MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle(), "Node not in the flattened tree still has a frame?"); } for (const auto& childList : aRoot.ChildLists()) { for (const nsIFrame* child : childList.mList) { AssertFrameSubtreeIsSane(*child); } } } #endif static inline void AssertFrameTreeIsSane(const PresShell& aPresShell) { #ifdef DEBUG if (const nsIFrame* root = aPresShell.GetRootFrame()) { AssertFrameSubtreeIsSane(*root); } #endif } static void TriggerPendingScrollTimelineAnimations(Document* aDocument) { auto* tracker = aDocument->GetScrollTimelineAnimationTracker(); if (!tracker || !tracker->HasPendingAnimations()) { return; } tracker->TriggerPendingAnimations(); } void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) { // FIXME(emilio, bug 1530177): Turn into a release assert when bug 1530188 and // bug 1530190 are fixed. MOZ_DIAGNOSTIC_ASSERT(!mForbiddenToFlush, "This is bad!"); // Per our API contract, hold a strong ref to ourselves until we return. RefPtr kungFuDeathGrip = this; /** * VERY IMPORTANT: If you add some sort of new flushing to this * method, make sure to add the relevant SetNeedLayoutFlush or * SetNeedStyleFlush calls on the shell. */ FlushType flushType = aFlush.mFlushType; // If this is a layout flush, first update the relevancy of any content // of elements with `content-visibility: auto` so that the values // returned from script queries are up-to-date. if (flushType >= mozilla::FlushType::Layout) { UpdateRelevancyOfContentVisibilityAutoFrames(); } MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?"); AUTO_PROFILER_MARKER_TEXT( "DoFlushPendingNotifications", LAYOUT, MarkerOptions(MarkerStack::Capture(), MarkerInnerWindowIdFromDocShell( mPresContext->GetDocShell())), nsDependentCString(kFlushTypeNames[flushType])); AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE( "PresShell::DoFlushPendingNotifications", LAYOUT, kFlushTypeNames[flushType]); #ifdef ACCESSIBILITY # ifdef DEBUG if (nsAccessibilityService* accService = GetAccService()) { NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(), "Flush during accessible tree update!"); } # endif #endif NS_ASSERTION(flushType >= FlushType::Style, "Why did we get called?"); mNeedStyleFlush = false; mNeedThrottledAnimationFlush = mNeedThrottledAnimationFlush && !aFlush.mFlushAnimations; mNeedLayoutFlush = mNeedLayoutFlush && (flushType < FlushType::InterruptibleLayout); bool isSafeToFlush = IsSafeToFlush(); // If layout could possibly trigger scripts, then it's only safe to flush if // it's safe to run script. bool hasHadScriptObject; if (mDocument->GetScriptHandlingObject(hasHadScriptObject) || hasHadScriptObject) { isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript(); } // Don't flush if the doc is already in the bfcache. if (MOZ_UNLIKELY(mDocument->GetPresShell() != this)) { MOZ_DIAGNOSTIC_ASSERT(!mDocument->GetPresShell(), "Where did this shell come from?"); isSafeToFlush = false; } MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying || !isSafeToFlush); MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mViewManager); MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mDocument->HasShellOrBFCacheEntry()); // Make sure the view manager stays alive. RefPtr viewManager = mViewManager; bool didStyleFlush = false; bool didLayoutFlush = false; if (isSafeToFlush) { // Record that we are in a flush, so that our optimization in // Document::FlushPendingNotifications doesn't skip any re-entrant // calls to us. Otherwise, we might miss some needed flushes, since // we clear mNeedStyleFlush / mNeedLayoutFlush here at the top of // the function but we might not have done the work yet. AutoRestore guard(mInFlush); mInFlush = true; // We need to make sure external resource documents are flushed too (for // example, svg filters that reference a filter in an external document // need the frames in the external document to be constructed for the // filter to work). We only need external resources to be flushed when the // main document is flushing >= FlushType::Frames, so we flush external // resources here instead of Document::FlushPendingNotifications. mDocument->FlushExternalResources(flushType); // Force flushing of any pending content notifications that might have // queued up while our event was pending. That will ensure that we don't // construct frames for content right now that's still waiting to be // notified on, mDocument->FlushPendingNotifications(FlushType::ContentAndNotify); mDocument->UpdateSVGUseElementShadowTrees(); // Process pending restyles, since any flush of the presshell wants // up-to-date style data. if (MOZ_LIKELY(!mIsDestroying)) { viewManager->FlushDelayedResize(); mPresContext->FlushPendingMediaFeatureValuesChanged(); } if (MOZ_LIKELY(!mIsDestroying)) { // Now that we have flushed media queries, update the rules before looking // up @font-face / @counter-style / @font-feature-values rules. StyleSet()->UpdateStylistIfNeeded(); // Flush any pending update of the user font set, since that could // cause style changes (for updating ex/ch units, and to cause a // reflow). mDocument->FlushUserFontSet(); mPresContext->FlushCounterStyles(); mPresContext->FlushFontFeatureValues(); mPresContext->FlushFontPaletteValues(); // Flush any requested SMIL samples. if (mDocument->HasAnimationController()) { mDocument->GetAnimationController()->FlushResampleRequests(); } } // The FlushResampleRequests() above flushed style changes. if (MOZ_LIKELY(!mIsDestroying) && aFlush.mFlushAnimations && mPresContext->EffectCompositor()) { mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations(); } // The FlushResampleRequests() above flushed style changes. if (MOZ_LIKELY(!mIsDestroying)) { nsAutoScriptBlocker scriptBlocker; Maybe innerWindowID; if (auto* window = mDocument->GetInnerWindow()) { innerWindowID = Some(window->WindowID()); } AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause), innerWindowID); PerfStats::AutoMetricRecording autoRecording; LAYOUT_TELEMETRY_RECORD_BASE(Restyle); mPresContext->RestyleManager()->ProcessPendingRestyles(); mNeedStyleFlush = false; } AssertFrameTreeIsSane(*this); didStyleFlush = true; // There might be more pending constructors now, but we're not going to // worry about them. They can't be triggered during reflow, so we should // be good. if (flushType >= (SuppressInterruptibleReflows() ? FlushType::Layout : FlushType::InterruptibleLayout) && !mIsDestroying) { didLayoutFlush = true; if (DoFlushLayout(/* aInterruptible = */ flushType < FlushType::Layout)) { if (mContentToScrollTo) { DoScrollContentIntoView(); if (mContentToScrollTo) { mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); mContentToScrollTo = nullptr; } } } } FlushPendingScrollResnap(); if (MOZ_LIKELY(!mIsDestroying)) { // Try to trigger pending scroll-driven animations after we flush // style and layout (if any). If we try to trigger them after flushing // style but the frame tree is not ready, we will check them again after // we flush layout because the requirement to trigger scroll-driven // animations is that the associated scroll containers are ready (i.e. the // scroll-timeline is active), and this depends on the readiness of the // scrollable frame and the primary frame of the scroll container. TriggerPendingScrollTimelineAnimations(mDocument); } if (flushType >= FlushType::Layout) { if (!mIsDestroying) { viewManager->UpdateWidgetGeometry(); } } } if (!didStyleFlush && flushType >= FlushType::Style && !mIsDestroying) { SetNeedStyleFlush(); if (aFlush.mFlushAnimations) { SetNeedThrottledAnimationFlush(); } } if (!didLayoutFlush && flushType >= FlushType::InterruptibleLayout && !mIsDestroying) { // We suppressed this flush either due to it not being safe to flush, // or due to SuppressInterruptibleReflows(). Either way, the // mNeedLayoutFlush flag needs to be re-set. SetNeedLayoutFlush(); } // Update flush counters if (didStyleFlush) { mLayoutTelemetry.IncReqsPerFlush(FlushType::Style); } if (didLayoutFlush) { mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout); } // Record telemetry for the number of requests per each flush type. // // Flushes happen as style or style+layout. This depends upon the `flushType` // where flushType >= InterruptibleLayout means flush layout and flushType >= // Style means flush style. We only report if didLayoutFlush or didStyleFlush // is true because we care if a flush really did take place. (Flush is guarded // by `isSafeToFlush == true`.) if (flushType >= FlushType::InterruptibleLayout && didLayoutFlush) { MOZ_ASSERT(didLayoutFlush == didStyleFlush); mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Layout); } else if (flushType >= FlushType::Style && didStyleFlush) { MOZ_ASSERT(!didLayoutFlush); mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Style); } } MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CharacterDataChanged( nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged"); MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document"); nsAutoCauseReflowNotifier crNotifier(this); mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo); mFrameConstructor->CharacterDataChanged(aContent, aInfo); } MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ElementStateChanged( Document* aDocument, Element* aElement, ElementState aStateMask) { MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentStateChanged"); MOZ_ASSERT(aDocument == mDocument, "Unexpected aDocument"); if (mDidInitialize) { nsAutoCauseReflowNotifier crNotifier(this); mPresContext->RestyleManager()->ElementStateChanged(aElement, aStateMask); } } void PresShell::DocumentStatesChanged(DocumentState aStateMask) { MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged"); MOZ_ASSERT(mDocument); MOZ_ASSERT(!aStateMask.IsEmpty()); if (mDidInitialize) { StyleSet()->InvalidateStyleForDocumentStateChanges(aStateMask); } if (aStateMask.HasState(DocumentState::WINDOW_INACTIVE)) { if (nsIFrame* root = mFrameConstructor->GetRootFrame()) { root->SchedulePaint(); } } } MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeWillChange( Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeWillChange"); MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document"); // XXXwaterson it might be more elegant to wait until after the // initial reflow to begin observing the document. That would // squelch any other inappropriate notifications as well. if (mDidInitialize) { nsAutoCauseReflowNotifier crNotifier(this); mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID, aAttribute, aModType); } } MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeChanged( Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeChanged"); MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document"); // XXXwaterson it might be more elegant to wait until after the // initial reflow to begin observing the document. That would // squelch any other inappropriate notifications as well. if (mDidInitialize) { nsAutoCauseReflowNotifier crNotifier(this); mPresContext->RestyleManager()->AttributeChanged( aElement, aNameSpaceID, aAttribute, aModType, aOldValue); } } MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentAppended( nsIContent* aFirstNewContent) { MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentAppended"); MOZ_ASSERT(aFirstNewContent->OwnerDoc() == mDocument, "Unexpected document"); // We never call ContentAppended with a document as the container, so we can // assert that we have an nsIContent parent. MOZ_ASSERT(aFirstNewContent->GetParent()); MOZ_ASSERT(aFirstNewContent->GetParent()->IsElement() || aFirstNewContent->GetParent()->IsShadowRoot()); if (!mDidInitialize) { return; } nsAutoCauseReflowNotifier crNotifier(this); // Call this here so it only happens for real content mutations and // not cases when the frame constructor calls its own methods to force // frame reconstruction. mPresContext->RestyleManager()->ContentAppended(aFirstNewContent); mFrameConstructor->ContentAppended( aFirstNewContent, nsCSSFrameConstructor::InsertionKind::Async); } MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentInserted( nsIContent* aChild) { MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentInserted"); MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document"); if (!mDidInitialize) { return; } nsAutoCauseReflowNotifier crNotifier(this); // Call this here so it only happens for real content mutations and // not cases when the frame constructor calls its own methods to force // frame reconstruction. mPresContext->RestyleManager()->ContentInserted(aChild); mFrameConstructor->ContentInserted( aChild, nsCSSFrameConstructor::InsertionKind::Async); } MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentRemoved( nsIContent* aChild, nsIContent* aPreviousSibling) { MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentRemoved"); MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document"); nsINode* container = aChild->GetParentNode(); // Notify the ESM that the content has been removed, so that // it can clean up any state related to the content. mPresContext->EventStateManager()->ContentRemoved(mDocument, aChild); nsAutoCauseReflowNotifier crNotifier(this); // Call this here so it only happens for real content mutations and // not cases when the frame constructor calls its own methods to force // frame reconstruction. nsIContent* oldNextSibling = nullptr; // Editor calls into here with NAC via HTMLEditor::DeleteRefToAnonymousNode. // This could be asserted if that caller is fixed. if (MOZ_LIKELY(!aChild->IsRootOfNativeAnonymousSubtree())) { oldNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling() : container->GetFirstChild(); } // After removing aChild from tree we should save information about live // ancestor if (mPointerEventTarget && mPointerEventTarget->IsInclusiveDescendantOf(aChild)) { mPointerEventTarget = aChild->GetParent(); } mFrameConstructor->ContentRemoved(aChild, oldNextSibling, nsCSSFrameConstructor::REMOVE_CONTENT); // NOTE(emilio): It's important that this goes after the frame constructor // stuff, otherwise the frame constructor can't see elements which are // display: contents / display: none, because we'd have cleared all the style // data from there. mPresContext->RestyleManager()->ContentRemoved(aChild, oldNextSibling); } void PresShell::NotifyCounterStylesAreDirty() { // TODO: Looks like that nsFrameConstructor::NotifyCounterStylesAreDirty() // does not run script. If so, we don't need to block script with // nsAutoCauseReflowNotifier here. Instead, there should be methods // and stack only class which manages only mChangeNestCount for // avoiding unnecessary `MOZ_CAN_RUN_SCRIPT` marking. nsAutoCauseReflowNotifier reflowNotifier(this); mFrameConstructor->NotifyCounterStylesAreDirty(); } bool PresShell::FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const { return mDirtyRoots.FrameIsAncestorOfAnyElement(aFrame); } void PresShell::ReconstructFrames() { MOZ_ASSERT(!mFrameConstructor->GetRootFrame() || mDidInitialize, "Must not have root frame before initial reflow"); if (!mDidInitialize || mIsDestroying) { // Nothing to do here return; } if (Element* root = mDocument->GetRootElement()) { PostRecreateFramesFor(root); } mDocument->FlushPendingNotifications(FlushType::Frames); } nsresult PresShell::RenderDocument(const nsRect& aRect, RenderDocumentFlags aFlags, nscolor aBackgroundColor, gfxContext* aThebesContext) { NS_ENSURE_TRUE(!(aFlags & RenderDocumentFlags::IsUntrusted), NS_ERROR_NOT_IMPLEMENTED); nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext(); if (rootPresContext) { rootPresContext->FlushWillPaintObservers(); if (mIsDestroying) return NS_OK; } nsAutoScriptBlocker blockScripts; // Set up the rectangle as the path in aThebesContext gfxRect r(0, 0, nsPresContext::AppUnitsToFloatCSSPixels(aRect.width), nsPresContext::AppUnitsToFloatCSSPixels(aRect.height)); aThebesContext->NewPath(); #ifdef MOZ_GFX_OPTIMIZE_MOBILE aThebesContext->SnappedRectangle(r); #else aThebesContext->Rectangle(r); #endif nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); if (!rootFrame) { // Nothing to paint, just fill the rect aThebesContext->SetColor(sRGBColor::FromABGR(aBackgroundColor)); aThebesContext->Fill(); return NS_OK; } gfxContextAutoSaveRestore save(aThebesContext); MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER); aThebesContext->Clip(); nsDeviceContext* devCtx = mPresContext->DeviceContext(); gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x), -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y)); gfxFloat scale = gfxFloat(devCtx->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel(); // Since canvas APIs use floats to set up their matrices, we may have some // slight rounding errors here. We use NudgeToIntegers() here to adjust // matrix components that are integers up to the accuracy of floats to be // those integers. gfxMatrix newTM = aThebesContext->CurrentMatrixDouble() .PreTranslate(offset) .PreScale(scale, scale) .NudgeToIntegers(); aThebesContext->SetMatrixDouble(newTM); AutoSaveRestoreRenderingState _(this); bool wouldFlushRetainedLayers = false; PaintFrameFlags flags = PaintFrameFlags::IgnoreSuppression; if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) { flags |= PaintFrameFlags::InTransform; } if (!(aFlags & RenderDocumentFlags::AsyncDecodeImages)) { flags |= PaintFrameFlags::SyncDecodeImages; } if (aFlags & RenderDocumentFlags::UseHighQualityScaling) { flags |= PaintFrameFlags::UseHighQualityScaling; } if (aFlags & RenderDocumentFlags::UseWidgetLayers) { // We only support using widget layers on display root's with widgets. nsView* view = rootFrame->GetView(); if (view && view->GetWidget() && nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) { WindowRenderer* renderer = view->GetWidget()->GetWindowRenderer(); // WebRenderLayerManagers in content processes // don't support taking snapshots. if (renderer && (!renderer->AsKnowsCompositor() || XRE_IsParentProcess())) { flags |= PaintFrameFlags::WidgetLayers; } } } if (!(aFlags & RenderDocumentFlags::DrawCaret)) { wouldFlushRetainedLayers = true; flags |= PaintFrameFlags::HideCaret; } if (aFlags & RenderDocumentFlags::IgnoreViewportScrolling) { wouldFlushRetainedLayers = !IgnoringViewportScrolling(); mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling; } if (aFlags & RenderDocumentFlags::ResetViewportScrolling) { wouldFlushRetainedLayers = true; flags |= PaintFrameFlags::ResetViewportScrolling; } if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) { mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing; } if (aFlags & RenderDocumentFlags::DocumentRelative) { // XXX be smarter about this ... drawWindow might want a rect // that's "pretty close" to what our retained layer tree covers. // In that case, it wouldn't disturb normal rendering too much, // and we should allow it. wouldFlushRetainedLayers = true; flags |= PaintFrameFlags::DocumentRelative; } // Don't let drawWindow blow away our retained layer tree if ((flags & PaintFrameFlags::WidgetLayers) && wouldFlushRetainedLayers) { flags &= ~PaintFrameFlags::WidgetLayers; } nsLayoutUtils::PaintFrame(aThebesContext, rootFrame, nsRegion(aRect), aBackgroundColor, nsDisplayListBuilderMode::Painting, flags); return NS_OK; } /* * Clip the display list aList to a range. Returns the clipped * rectangle surrounding the range. */ nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder, nsDisplayList* aList, nsRange* aRange) { // iterate though the display items and add up the bounding boxes of each. // This will allow the total area of the frames within the range to be // determined. To do this, remove an item from the bottom of the list, check // whether it should be part of the range, and if so, append it to the top // of the temporary list tmpList. If the item is a text frame at the end of // the selection range, clip it to the portion of the text frame that is // part of the selection. Then, append the wrapper to the top of the list. // Otherwise, just delete the item and don't append it. nsRect surfaceRect; for (nsDisplayItem* i : aList->TakeItems()) { if (i->GetType() == DisplayItemType::TYPE_CONTAINER) { aList->AppendToTop(i); surfaceRect.UnionRect( surfaceRect, ClipListToRange(aBuilder, i->GetChildren(), aRange)); continue; } // itemToInsert indiciates the item that should be inserted into the // temporary list. If null, no item should be inserted. nsDisplayItem* itemToInsert = nullptr; nsIFrame* frame = i->Frame(); nsIContent* content = frame->GetContent(); if (content) { bool atStart = (content == aRange->GetStartContainer()); bool atEnd = (content == aRange->GetEndContainer()); if ((atStart || atEnd) && frame->IsTextFrame()) { auto [frameStartOffset, frameEndOffset] = frame->GetOffsets(); int32_t hilightStart = atStart ? std::max(static_cast(aRange->StartOffset()), frameStartOffset) : frameStartOffset; int32_t hilightEnd = atEnd ? std::min(static_cast(aRange->EndOffset()), frameEndOffset) : frameEndOffset; if (hilightStart < hilightEnd) { // determine the location of the start and end edges of the range. nsPoint startPoint, endPoint; frame->GetPointFromOffset(hilightStart, &startPoint); frame->GetPointFromOffset(hilightEnd, &endPoint); // The clip rectangle is determined by taking the the start and // end points of the range, offset from the reference frame. // Because of rtl, the end point may be to the left of (or above, // in vertical mode) the start point, so x (or y) is set to the // lower of the values. nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize()); if (frame->GetWritingMode().IsVertical()) { nscoord y = std::min(startPoint.y, endPoint.y); textRect.y += y; textRect.height = std::max(startPoint.y, endPoint.y) - y; } else { nscoord x = std::min(startPoint.x, endPoint.x); textRect.x += x; textRect.width = std::max(startPoint.x, endPoint.x) - x; } surfaceRect.UnionRect(surfaceRect, textRect); const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot(); DisplayItemClip newClip; newClip.SetTo(textRect); const DisplayItemClipChain* newClipChain = aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr); i->IntersectClip(aBuilder, newClipChain, true); itemToInsert = i; } } // Don't try to descend into subdocuments. // If this ever changes we'd need to add handling for subdocuments with // different zoom levels. else if (content->GetUncomposedDoc() == aRange->GetStartContainer()->GetUncomposedDoc()) { // if the node is within the range, append it to the temporary list bool before, after; nsresult rv = RangeUtils::CompareNodeToRange(content, aRange, &before, &after); if (NS_SUCCEEDED(rv) && !before && !after) { itemToInsert = i; bool snap; surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap)); } } } // insert the item into the list if necessary. If the item has a child // list, insert that as well nsDisplayList* sublist = i->GetSameCoordinateSystemChildren(); if (itemToInsert || sublist) { aList->AppendToTop(itemToInsert ? itemToInsert : i); // if the item is a list, iterate over it as well if (sublist) surfaceRect.UnionRect(surfaceRect, ClipListToRange(aBuilder, sublist, aRange)); } else { // otherwise, just delete the item and don't readd it to the list i->Destroy(aBuilder); } } return surfaceRect; } #ifdef DEBUG # include static bool gDumpRangePaintList = false; #endif UniquePtr PresShell::CreateRangePaintInfo( nsRange* aRange, nsRect& aSurfaceRect, bool aForPrimarySelection) { nsIFrame* ancestorFrame = nullptr; nsIFrame* rootFrame = GetRootFrame(); // If the start or end of the range is the document, just use the root // frame, otherwise get the common ancestor of the two endpoints of the // range. nsINode* startContainer = aRange->GetStartContainer(); nsINode* endContainer = aRange->GetEndContainer(); Document* doc = startContainer->GetComposedDoc(); if (startContainer == doc || endContainer == doc) { ancestorFrame = rootFrame; } else { nsINode* ancestor = nsContentUtils::GetClosestCommonInclusiveAncestor( startContainer, endContainer); NS_ASSERTION(!ancestor || ancestor->IsContent(), "common ancestor is not content"); while (ancestor && ancestor->IsContent()) { ancestorFrame = ancestor->AsContent()->GetPrimaryFrame(); if (ancestorFrame) { break; } ancestor = ancestor->GetParentOrShadowHostNode(); } // use the nearest ancestor frame that includes all continuations as the // root for building the display list while (ancestorFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame)) ancestorFrame = ancestorFrame->GetParent(); } if (!ancestorFrame) { return nullptr; } // get a display list containing the range auto info = MakeUnique(aRange, ancestorFrame); info->mBuilder.SetIncludeAllOutOfFlows(); if (aForPrimarySelection) { info->mBuilder.SetSelectedFramesOnly(); } info->mBuilder.EnterPresShell(ancestorFrame); ContentSubtreeIterator subtreeIter; nsresult rv = subtreeIter.Init(aRange); if (NS_FAILED(rv)) { return nullptr; } auto BuildDisplayListForNode = [&](nsINode* aNode) { if (MOZ_UNLIKELY(!aNode->IsContent())) { return; } nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); // XXX deal with frame being null due to display:contents for (; frame; frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) { info->mBuilder.SetVisibleRect(frame->InkOverflowRect()); info->mBuilder.SetDirtyRect(frame->InkOverflowRect()); frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList); } }; if (startContainer->NodeType() == nsINode::TEXT_NODE) { BuildDisplayListForNode(startContainer); } for (; !subtreeIter.IsDone(); subtreeIter.Next()) { nsCOMPtr node = subtreeIter.GetCurrentNode(); BuildDisplayListForNode(node); } if (endContainer != startContainer && endContainer->NodeType() == nsINode::TEXT_NODE) { BuildDisplayListForNode(endContainer); } // If one of the ancestor presShells (including this one) has a resolution // set, we may have some APZ zoom applied. That means we may want to rasterize // the nodes at that zoom level. Populate `info` with the relevant information // so that the caller can decide what to do. Also wrap the display list in // appropriate nsDisplayAsyncZoom display items. This code handles the general // case with nested async zooms (even though that never actually happens), // because it fell out of the implementation for free. // // TODO: Do we need to do the same for ancestor transforms? for (nsPresContext* ctx = GetPresContext(); ctx; ctx = ctx->GetParentPresContext()) { PresShell* shell = ctx->PresShell(); float resolution = shell->GetResolution(); // If we are at the root document in the process, try to see if documents // in enclosing processes have a resolution and include that as well. if (!ctx->GetParentPresContext()) { // xScale is an arbitrary choice. Outside of edge cases involving CSS // transforms, xScale == yScale so it doesn't matter. resolution *= ViewportUtils::TryInferEnclosingResolution(shell).xScale; } if (resolution == 1.0) { continue; } info->mResolution *= resolution; nsIFrame* rootScrollFrame = shell->GetRootScrollFrame(); ViewID zoomedId = nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent()); nsDisplayList wrapped(&info->mBuilder); wrapped.AppendNewToTop(&info->mBuilder, rootScrollFrame, &info->mList, nullptr, zoomedId); info->mList.AppendToTop(&wrapped); } #ifdef DEBUG if (gDumpRangePaintList) { fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n"); nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList); } #endif nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, aRange); info->mBuilder.LeavePresShell(ancestorFrame, &info->mList); #ifdef DEBUG if (gDumpRangePaintList) { fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n"); nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList); } #endif // determine the offset of the reference frame for the display list // to the root frame. This will allow the coordinates used when painting // to all be offset from the same point info->mRootOffset = ancestorFrame->GetBoundingClientRect().TopLeft(); rangeRect.MoveBy(info->mRootOffset); aSurfaceRect.UnionRect(aSurfaceRect, rangeRect); return info; } already_AddRefed PresShell::PaintRangePaintInfo( const nsTArray>& aItems, Selection* aSelection, const Maybe& aRegion, nsRect aArea, const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) { nsPresContext* pc = GetPresContext(); if (!pc || aArea.width == 0 || aArea.height == 0) return nullptr; // use the rectangle to create the surface LayoutDeviceIntRect pixelArea = LayoutDeviceIntRect::FromAppUnitsToOutside( aArea, pc->AppUnitsPerDevPixel()); // if the image should not be resized, scale must be 1 float scale = 1.0; nsRect maxSize; pc->DeviceContext()->GetClientRect(maxSize); // check if the image should be resized bool resize = !!(aFlags & RenderImageFlags::AutoScale); if (resize) { // check if image-resizing-algorithm should be used if (aFlags & RenderImageFlags::IsImage) { // get max screensize int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width); int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height); // resize image relative to the screensize // get best height/width relative to screensize float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR; float bestWidth = float(maxWidth) * RELATIVE_SCALEFACTOR; // calculate scale for bestWidth float adjustedScale = bestWidth / float(pixelArea.width); // get the worst height (height when width is perfect) float worstHeight = float(pixelArea.height) * adjustedScale; // get the difference of best and worst height float difference = bestHeight - worstHeight; // halve the difference and add it to worstHeight to get // the best compromise between bestHeight and bestWidth, // then calculate the corresponding scale factor adjustedScale = (worstHeight + difference / 2) / float(pixelArea.height); // prevent upscaling scale = std::min(scale, adjustedScale); } else { // get half of max screensize int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1); int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1); if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) { // divide the maximum size by the image size in both directions. // Whichever direction produces the smallest result determines how much // should be scaled. if (pixelArea.width > maxWidth) scale = std::min(scale, float(maxWidth) / pixelArea.width); if (pixelArea.height > maxHeight) scale = std::min(scale, float(maxHeight) / pixelArea.height); } } // Pick a resolution scale factor that is the highest we need for any of // the items. This means some items may get rendered at a higher-than-needed // resolution but at least nothing will be avoidably blurry. float resolutionScale = 1.0; for (const UniquePtr& rangeInfo : aItems) { resolutionScale = std::max(resolutionScale, rangeInfo->mResolution); } float unclampedResolution = resolutionScale; // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and // `resolutionScale` isn't bigger than `maxSize`. This prevents creating // giant/unbounded images. resolutionScale = std::min(resolutionScale, maxSize.width / (scale * pixelArea.width)); resolutionScale = std::min(resolutionScale, maxSize.height / (scale * pixelArea.height)); // The following assert should only get hit if pixelArea scaled by `scale` // alone would already have been bigger than `maxSize`, which should never // be the case. For release builds we handle gracefully by reverting // resolutionScale to 1.0 to avoid unexpected consequences. MOZ_ASSERT(resolutionScale >= 1.0); resolutionScale = std::max(1.0f, resolutionScale); scale *= resolutionScale; // Now we need adjust the output screen position of the surface based on the // scaling factor and any APZ zoom that may be in effect. The goal is here // to set `aScreenRect`'s top-left corner (in screen-relative LD pixels) // such that the scaling effect on the surface appears anchored at `aPoint` // ("anchor" here is like "transform-origin"). When this code is e.g. used // to generate a drag image for dragging operations, `aPoint` refers to the // position of the mouse cursor (also in screen-relative LD pixels), and the // user-visible effect of doing this is that the point at which the user // clicked to start the drag remains under the mouse during the drag. // In order to do this we first compute the top-left corner of the // pixelArea is screen-relative LD pixels. LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual( LayoutDevicePoint(pixelArea.TopLeft()), pc); // And then adjust the output screen position based on that, which we can do // since everything here is screen-relative LD pixels. Note that the scale // factor we use here is the effective "transform" scale applied to the // content we're painting, relative to the scale at which it would normally // get painted at as part of page rendering (`unclampedResolution`). float scaleRelativeToNormalContent = scale / unclampedResolution; aScreenRect->x = NSToIntFloor(aPoint.x - float(aPoint.x.value - visualPoint.x.value) * scaleRelativeToNormalContent); aScreenRect->y = NSToIntFloor(aPoint.y - float(aPoint.y.value - visualPoint.y.value) * scaleRelativeToNormalContent); pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale); pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale); if (!pixelArea.width || !pixelArea.height) { return nullptr; } } else { // move aScreenRect to the position of the surface in screen coordinates LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual( LayoutDevicePoint(pixelArea.TopLeft()), pc); aScreenRect->MoveTo(RoundedToInt(visualPoint)); } aScreenRect->width = pixelArea.width; aScreenRect->height = pixelArea.height; RefPtr dt = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8); if (!dt || !dt->IsValid()) { return nullptr; } gfxContext ctx(dt); if (aRegion) { RefPtr builder = dt->CreatePathBuilder(FillRule::FILL_WINDING); // Convert aRegion from CSS pixels to dev pixels nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel()) .ToOutsidePixels(pc->AppUnitsPerDevPixel()); for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { const IntRect& rect = iter.Get(); builder->MoveTo(rect.TopLeft()); builder->LineTo(rect.TopRight()); builder->LineTo(rect.BottomRight()); builder->LineTo(rect.BottomLeft()); builder->LineTo(rect.TopLeft()); } RefPtr path = builder->Finish(); ctx.Clip(path); } gfxMatrix initialTM = ctx.CurrentMatrixDouble(); if (resize) { initialTM.PreScale(scale, scale); } // translate so that points are relative to the surface area gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint( -aArea.TopLeft(), pc->AppUnitsPerDevPixel()); initialTM.PreTranslate(surfaceOffset); // temporarily hide the selection so that text is drawn normally. If a // selection is being rendered, use that, otherwise use the presshell's // selection. RefPtr frameSelection; if (aSelection) { frameSelection = aSelection->GetFrameSelection(); } else { frameSelection = FrameSelection(); } int16_t oldDisplaySelection = frameSelection->GetDisplaySelection(); frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); // next, paint each range in the selection for (const UniquePtr& rangeInfo : aItems) { // the display lists paint relative to the offset from the reference // frame, so account for that translation too: gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint( rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel()); ctx.SetMatrixDouble(initialTM.PreTranslate(rootOffset)); aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y); nsRegion visible(aArea); rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &ctx, nsDisplayList::PAINT_DEFAULT, Nothing()); aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y); } // restore the old selection display state frameSelection->SetDisplaySelection(oldDisplaySelection); return dt->Snapshot(); } already_AddRefed PresShell::RenderNode( nsINode* aNode, const Maybe& aRegion, const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) { // area will hold the size of the surface needed to draw the node, measured // from the root frame. nsRect area; nsTArray> rangeItems; // nothing to draw if the node isn't in a document if (!aNode->IsInComposedDoc()) { return nullptr; } RefPtr range = nsRange::Create(aNode); IgnoredErrorResult rv; range->SelectNode(*aNode, rv); if (rv.Failed()) { return nullptr; } UniquePtr info = CreateRangePaintInfo(range, area, false); if (info) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier, or change the return type to void. rangeItems.AppendElement(std::move(info)); } Maybe region = aRegion; if (region) { // combine the area with the supplied region CSSIntRect rrectPixels = region->GetBounds(); nsRect rrect = ToAppUnits(rrectPixels, AppUnitsPerCSSPixel()); area.IntersectRect(area, rrect); nsPresContext* pc = GetPresContext(); if (!pc) return nullptr; // move the region so that it is offset from the topleft corner of the // surface region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x), -nsPresContext::AppUnitsToIntCSSPixels(area.y)); } return PaintRangePaintInfo(rangeItems, nullptr, region, area, aPoint, aScreenRect, aFlags); } already_AddRefed PresShell::RenderSelection( Selection* aSelection, const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) { // area will hold the size of the surface needed to draw the selection, // measured from the root frame. nsRect area; nsTArray> rangeItems; // iterate over each range and collect them into the rangeItems array. // This is done so that the size of selection can be determined so as // to allocate a surface area const uint32_t rangeCount = aSelection->RangeCount(); NS_ASSERTION(rangeCount > 0, "RenderSelection called with no selection"); for (const uint32_t r : IntegerRange(rangeCount)) { MOZ_ASSERT(aSelection->RangeCount() == rangeCount); RefPtr range = aSelection->GetRangeAt(r); UniquePtr info = CreateRangePaintInfo(range, area, true); if (info) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier. rangeItems.AppendElement(std::move(info)); } } return PaintRangePaintInfo(rangeItems, aSelection, Nothing(), area, aPoint, aScreenRect, aFlags); } void AddDisplayItemToBottom(nsDisplayListBuilder* aBuilder, nsDisplayList* aList, nsDisplayItem* aItem) { if (!aItem) { return; } nsDisplayList list(aBuilder); list.AppendToTop(aItem); list.AppendToTop(aList); aList->AppendToTop(&list); } static bool AddCanvasBackgroundColor(const nsDisplayList* aList, nsIFrame* aCanvasFrame, nscolor aColor, bool aCSSBackgroundColor) { for (nsDisplayItem* i : *aList) { const DisplayItemType type = i->GetType(); if (i->Frame() == aCanvasFrame && type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR) { auto* bg = static_cast(i); bg->SetExtraBackgroundColor(aColor); return true; } const bool isBlendContainer = type == DisplayItemType::TYPE_BLEND_CONTAINER || type == DisplayItemType::TYPE_TABLE_BLEND_CONTAINER; nsDisplayList* sublist = i->GetSameCoordinateSystemChildren(); if (sublist && !(isBlendContainer && !aCSSBackgroundColor) && AddCanvasBackgroundColor(sublist, aCanvasFrame, aColor, aCSSBackgroundColor)) { return true; } } return false; } void PresShell::AddCanvasBackgroundColorItem(nsDisplayListBuilder* aBuilder, nsDisplayList* aList, nsIFrame* aFrame, const nsRect& aBounds, nscolor aBackstopColor) { 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( 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: 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 ev = new nsSynthMouseMoveEvent(this, aFromScroll); GetPresContext()->RefreshDriver()->AddRefreshObserver( ev, FlushType::Display, "Synthetic mouse move event"); mSynthMouseMoveEvent = std::move(ev); } } static nsView* FindFloatingViewContaining(nsPresContext* aRootPresContext, nsIWidget* aRootWidget, const LayoutDeviceIntPoint& aPt) { nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint( aRootPresContext, aRootWidget, aPt, nsLayoutUtils::GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets); return popupFrame ? popupFrame->GetView() : nullptr; } /* * This finds the first view with a frame that contains the given point in a * postorder traversal of the view tree, assuming that the point is not in a * floating view. It assumes that only floating views extend outside the bounds * of their parents. * * This methods should only be called if FindFloatingViewContaining returns * null. * * aPt is relative aRelativeToView with the viewport type * aRelativeToViewportType. aRelativeToView will always have a frame. If aView * has a frame then aRelativeToView will be aView. (The reason aRelativeToView * and aView are separate is because we need to traverse into views without * frames (ie the inner view of a subdocument frame) but we can only easily * transform between views using TransformPoint which takes frames.) */ static nsView* FindViewContaining(nsView* aRelativeToView, ViewportType aRelativeToViewportType, nsView* aView, nsPoint aPt) { MOZ_ASSERT(aRelativeToView->GetFrame()); if (aView->GetVisibility() == ViewVisibility::Hide) { return nullptr; } nsIFrame* frame = aView->GetFrame(); if (frame) { if (!frame->PresShell()->IsActive() || !frame->IsVisibleConsideringAncestors( nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) { return nullptr; } // We start out in visual coords and then if we cross the zoom boundary we // become in layout coords. The zoom boundary always occurs in a document // with IsRootContentDocumentCrossProcess. The root view of such a document // is outside the zoom boundary and any child view must be inside the zoom // boundary because we only create views for certain kinds of frames and // none of them can be between the root frame and the zoom boundary. bool crossingZoomBoundary = false; if (aRelativeToViewportType == ViewportType::Visual) { if (!aRelativeToView->GetParent() || aRelativeToView->GetViewManager() != aRelativeToView->GetParent()->GetViewManager()) { if (aRelativeToView->GetFrame() ->PresContext() ->IsRootContentDocumentCrossProcess()) { crossingZoomBoundary = true; } } } ViewportType nextRelativeToViewportType = aRelativeToViewportType; if (crossingZoomBoundary) { nextRelativeToViewportType = ViewportType::Layout; } nsLayoutUtils::TransformResult result = nsLayoutUtils::TransformPoint( RelativeTo{aRelativeToView->GetFrame(), aRelativeToViewportType}, RelativeTo{frame, nextRelativeToViewportType}, aPt); if (result != nsLayoutUtils::TRANSFORM_SUCCEEDED) { return nullptr; } // Even though aPt is in visual coordinates until we cross the zoom boundary // it is valid to compare it to view coords (which are in layout coords) // because visual coords are the same as layout coords for every view // outside of the zoom boundary except for the root view of the root content // document. // For the root view of the root content document, its bounds don't // actually correspond to what is visible when we have a // MobileViewportManager. So we skip the hit test. This is okay because the // point has already been hit test: 1) if we are the root view in the // process then the point comes from a real mouse event so it must have been // over our widget, or 2) if we are the root of a subdocument then // hittesting against the view of the subdocument frame that contains us // already happened and succeeded before getting here. if (!crossingZoomBoundary) { if (!aView->GetDimensions().Contains(aPt)) { return nullptr; } } aRelativeToView = aView; aRelativeToViewportType = nextRelativeToViewportType; } for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) { nsView* r = FindViewContaining(aRelativeToView, aRelativeToViewportType, v, aPt); if (r) return r; } return frame ? aView : nullptr; } static BrowserBridgeChild* GetChildBrowser(nsView* aView) { if (!aView) { return nullptr; } nsIFrame* frame = aView->GetFrame(); if (!frame && aView->GetParent()) { // If frame is null then view is an anonymous inner view, and we want // the frame from the corresponding outer view. frame = aView->GetParent()->GetFrame(); } if (!frame || !frame->GetContent()) { return nullptr; } return BrowserBridgeChild::GetFrom(frame->GetContent()); } void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) { // If drag session has started, we shouldn't synthesize mousemove event. nsCOMPtr dragSession = nsContentUtils::GetDragSession(); if (dragSession) { mSynthMouseMoveEvent.Forget(); return; } // allow new event to be posted while handling this one only if the // source of the event is a scroll (to prevent infinite reflow loops) if (aFromScroll) { mSynthMouseMoveEvent.Forget(); } nsView* rootView = mViewManager ? mViewManager->GetRootView() : nullptr; if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) || !rootView || !rootView->HasWidget() || !mPresContext) { mSynthMouseMoveEvent.Forget(); return; } NS_ASSERTION(mPresContext->IsRoot(), "Only a root pres shell should be here"); // Hold a ref to ourselves so DispatchEvent won't destroy us (since // we need to access members after we call DispatchEvent). RefPtr kungFuDeathGrip(this); #ifdef DEBUG_MOUSE_LOCATION printf("[ps=%p]synthesizing mouse move to (%d,%d)\n", this, mMouseLocation.x, mMouseLocation.y); #endif int32_t APD = mPresContext->AppUnitsPerDevPixel(); // We need a widget to put in the event we are going to dispatch so we look // for a view that has a widget and the mouse location is over. We first look // for floating views, if there isn't one we use the root view. |view| holds // that view. nsView* view = nullptr; // The appunits per devpixel ratio of |view|. int32_t viewAPD; // mRefPoint will be mMouseLocation relative to the widget of |view|, the // widget we will put in the event we dispatch, in viewAPD appunits nsPoint refpoint(0, 0); // We always dispatch the event to the pres shell that contains the view that // the mouse is over. pointVM is the VM of that pres shell. nsViewManager* pointVM = nullptr; if (rootView->GetFrame()) { view = FindFloatingViewContaining( mPresContext, rootView->GetWidget(), LayoutDeviceIntPoint::FromAppUnitsToNearest( mMouseLocation + rootView->ViewToWidgetOffset(), APD)); } nsView* pointView = view; if (!view) { view = rootView; if (rootView->GetFrame()) { pointView = FindViewContaining(rootView, ViewportType::Visual, rootView, mMouseLocation); } else { pointView = rootView; } // pointView can be null in situations related to mouse capture pointVM = (pointView ? pointView : view)->GetViewManager(); refpoint = mMouseLocation + rootView->ViewToWidgetOffset(); viewAPD = APD; } else { pointVM = view->GetViewManager(); nsIFrame* frame = view->GetFrame(); NS_ASSERTION(frame, "floating views can't be anonymous"); viewAPD = frame->PresContext()->AppUnitsPerDevPixel(); refpoint = mMouseLocation; DebugOnly result = nsLayoutUtils::TransformPoint( RelativeTo{rootView->GetFrame(), ViewportType::Visual}, RelativeTo{frame, ViewportType::Layout}, refpoint); MOZ_ASSERT(result == nsLayoutUtils::TRANSFORM_SUCCEEDED); refpoint += view->ViewToWidgetOffset(); } NS_ASSERTION(view->GetWidget(), "view should have a widget here"); WidgetMouseEvent event(true, eMouseMove, view->GetWidget(), WidgetMouseEvent::eSynthesized); // 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 = pointVM->GetPresShell()) { // Since this gets run in a refresh tick there isn't an InputAPZContext on // the stack from the nsBaseWidget. We need to simulate one with at least // the correct target guid, so that the correct callback transform gets // applied if this event goes to a child process. The input block id is set // to 0 because this is a synthetic event which doesn't really belong to any // input block. Same for the APZ response field. InputAPZContext apzContext(mMouseEventTargetGuid, 0, nsEventStatus_eIgnore); presShell->DispatchSynthMouseMove(&event); } if (!aFromScroll) { mSynthMouseMoveEvent.Forget(); } } /* static */ void PresShell::MarkFramesInListApproximatelyVisible( const nsDisplayList& aList) { for (nsDisplayItem* item : aList) { nsDisplayList* sublist = item->GetChildren(); if (sublist) { MarkFramesInListApproximatelyVisible(*sublist); continue; } nsIFrame* frame = item->Frame(); MOZ_ASSERT(frame); if (!frame->TrackingVisibility()) { continue; } // Use the presshell containing the frame. PresShell* presShell = frame->PresShell(); MOZ_ASSERT(!presShell->AssumeAllFramesVisible()); if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) { // The frame was added to mApproximatelyVisibleFrames, so increment its // visible count. frame->IncApproximateVisibleCount(); } } } /* static */ void PresShell::DecApproximateVisibleCount( VisibleFrames& aFrames, const Maybe& aNonvisibleAction /* = Nothing() */) { for (nsIFrame* frame : aFrames) { // Decrement the frame's visible count if we're still tracking its // visibility. (We may not be, if the frame disabled visibility tracking // after we added it to the visible frames list.) if (frame->TrackingVisibility()) { frame->DecApproximateVisibleCount(aNonvisibleAction); } } } void PresShell::RebuildApproximateFrameVisibilityDisplayList( const nsDisplayList& aList) { MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?"); mApproximateFrameVisibilityVisited = true; // Remove the entries of the mApproximatelyVisibleFrames hashtable and put // them in oldApproxVisibleFrames. VisibleFrames oldApproximatelyVisibleFrames = std::move(mApproximatelyVisibleFrames); MarkFramesInListApproximatelyVisible(aList); DecApproximateVisibleCount(oldApproximatelyVisibleFrames); } /* static */ void PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView, bool aClear) { nsViewManager* vm = aView->GetViewManager(); if (aClear) { PresShell* presShell = vm->GetPresShell(); if (!presShell->mApproximateFrameVisibilityVisited) { presShell->ClearApproximatelyVisibleFramesList(); } presShell->mApproximateFrameVisibilityVisited = false; } for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) { ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm); } } void PresShell::ClearApproximatelyVisibleFramesList( const Maybe& aNonvisibleAction /* = Nothing() */) { DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction); mApproximatelyVisibleFrames.Clear(); } void PresShell::MarkFramesInSubtreeApproximatelyVisible( nsIFrame* aFrame, const nsRect& aRect, bool aRemoveOnly /* = false */) { MOZ_DIAGNOSTIC_ASSERT(aFrame, "aFrame arg should be a valid frame pointer"); MOZ_ASSERT(aFrame->PresShell() == this, "wrong presshell"); if (aFrame->TrackingVisibility() && aFrame->StyleVisibility()->IsVisible() && (!aRemoveOnly || aFrame->GetVisibility() == Visibility::ApproximatelyVisible)) { MOZ_ASSERT(!AssumeAllFramesVisible()); if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) { // The frame was added to mApproximatelyVisibleFrames, so increment its // visible count. aFrame->IncApproximateVisibleCount(); } } nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame); if (subdocFrame) { PresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting( nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION); if (presShell && !presShell->AssumeAllFramesVisible()) { nsRect rect = aRect; nsIFrame* root = presShell->GetRootFrame(); if (root) { rect.MoveBy(aFrame->GetOffsetToCrossDoc(root)); } else { rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft()); } rect = rect.ScaleToOtherAppUnitsRoundOut( aFrame->PresContext()->AppUnitsPerDevPixel(), presShell->GetPresContext()->AppUnitsPerDevPixel()); presShell->RebuildApproximateFrameVisibility(&rect); } return; } nsRect rect = aRect; nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame); if (scrollFrame) { bool ignoreDisplayPort = false; if (DisplayPortUtils::IsMissingDisplayPortBaseRect(aFrame->GetContent())) { // We can properly set the base rect for root scroll frames on top level // and root content documents. Otherwise the base rect we compute might // be way too big without the limiting that // nsHTMLScrollFrame::DecideScrollableLayer does, so we just ignore the // displayport in that case. nsPresContext* pc = aFrame->PresContext(); if (scrollFrame->IsRootScrollFrameOfDocument() && (pc->IsRootContentDocumentCrossProcess() || (pc->IsChrome() && !pc->GetParentPresContext()))) { nsRect baseRect( nsPoint(), nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame)); DisplayPortUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect); } else { ignoreDisplayPort = true; } } nsRect displayPort; bool usingDisplayport = !ignoreDisplayPort && DisplayPortUtils::GetDisplayPortForVisibilityTesting( aFrame->GetContent(), &displayPort); scrollFrame->NotifyApproximateFrameVisibilityUpdate(!usingDisplayport); if (usingDisplayport) { rect = displayPort; } else { rect = rect.Intersect(scrollFrame->GetScrollPortRect()); } rect = scrollFrame->ExpandRectToNearlyVisible(rect); } bool preserves3DChildren = aFrame->Extend3DContext(); for (const auto& [list, listID] : aFrame->ChildLists()) { if (listID == FrameChildListID::Popup) { // We assume all frames in popups are visible, so we skip them here. continue; } for (nsIFrame* child : list) { // Note: This assert should be trivially satisfied, just by virtue of how // nsFrameList and its iterator works (with nullptr being an end-of-list // sentinel which should terminate the loop). But we do somehow get // crash reports inside this loop that suggest `child` is null... MOZ_DIAGNOSTIC_ASSERT(child, "shouldn't have null values in child lists"); nsRect r = rect - child->GetPosition(); if (!r.IntersectRect(r, child->InkOverflowRect())) { continue; } if (child->IsTransformed()) { // for children of a preserve3d element we just pass down the same dirty // rect if (!preserves3DChildren || !child->Combines3DTransformWithAncestors()) { const nsRect overflow = child->InkOverflowRectRelativeToSelf(); nsRect out; if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) { r = out; } else { r.SetEmpty(); } } } MarkFramesInSubtreeApproximatelyVisible(child, r, aRemoveOnly); } } } void PresShell::RebuildApproximateFrameVisibility( nsRect* aRect, bool aRemoveOnly /* = false */) { MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?"); mApproximateFrameVisibilityVisited = true; nsIFrame* rootFrame = GetRootFrame(); if (!rootFrame) { return; } // Remove the entries of the mApproximatelyVisibleFrames hashtable and put // them in oldApproximatelyVisibleFrames. VisibleFrames oldApproximatelyVisibleFrames = std::move(mApproximatelyVisibleFrames); nsRect vis(nsPoint(0, 0), rootFrame->GetSize()); if (aRect) { vis = *aRect; } // If we are in-process root but not the top level content, we need to take // the intersection with the iframe visible rect. if (mPresContext->IsRootContentDocumentInProcess() && !mPresContext->IsRootContentDocumentCrossProcess()) { // There are two possibilities that we can't get the iframe's visible // rect other than the iframe is out side of ancestors' display ports. // a) the BrowserChild is being torn down // b) the visible rect hasn't been delivered the BrowserChild // In both cases we consider the visible rect is empty. Maybe visibleRect; if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { visibleRect = browserChild->GetVisibleRect(); } vis = vis.Intersect(visibleRect.valueOr(nsRect())); } MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, aRemoveOnly); DecApproximateVisibleCount(oldApproximatelyVisibleFrames); } void PresShell::UpdateApproximateFrameVisibility() { DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false); } void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) { MOZ_ASSERT( !mPresContext || mPresContext->IsRootContentDocumentInProcess(), "Updating approximate frame visibility on a non-root content document?"); mUpdateApproximateFrameVisibilityEvent.Revoke(); if (mHaveShutDown || mIsDestroying) { return; } // call update on that frame nsIFrame* rootFrame = GetRootFrame(); if (!rootFrame) { ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages)); return; } RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly); ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true); #ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST // This can be used to debug the frame walker by comparing beforeFrameList // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see // if they produce the same results (mApproximatelyVisibleFrames holds the // frames the display list thinks are visible, beforeFrameList holds the // frames the frame walker thinks are visible). nsDisplayListBuilder builder( rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false); nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize()); nsIFrame* rootScroll = GetRootScrollFrame(); if (rootScroll) { nsIContent* content = rootScroll->GetContent(); if (content) { Unused << nsLayoutUtils::GetDisplayPortForVisibilityTesting( content, &updateRect, RelativeTo::ScrollFrame); } if (IgnoringViewportScrolling()) { builder.SetIgnoreScrollFrame(rootScroll); } } builder.IgnorePaintSuppression(); builder.EnterPresShell(rootFrame); nsDisplayList list; rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list); builder.LeavePresShell(rootFrame, &list); RebuildApproximateFrameVisibilityDisplayList(list); ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true); list.DeleteAll(&builder); #endif } bool PresShell::AssumeAllFramesVisible() { if (!StaticPrefs::layout_framevisibility_enabled() || !mPresContext || !mDocument) { return true; } // We assume all frames are visible in print, print preview, chrome, and // resource docs and don't keep track of them. if (mPresContext->Type() == nsPresContext::eContext_PrintPreview || mPresContext->Type() == nsPresContext::eContext_Print || mPresContext->IsChrome() || mDocument->IsResourceDoc()) { return true; } // If we're assuming all frames are visible in the top level content // document, we need to in subdocuments as well. Otherwise we can get in a // situation where things like animations won't work in subdocuments because // their frames appear not to be visible, since we won't schedule an image // visibility update if the top level content document is assuming all // frames are visible. // // Note that it's not safe to call IsRootContentDocumentInProcess() if we're // currently being destroyed, so we have to check that first. if (!mHaveShutDown && !mIsDestroying && !mPresContext->IsRootContentDocumentInProcess()) { nsPresContext* presContext = mPresContext->GetInProcessRootContentDocumentPresContext(); if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) { return true; } } return false; } void PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() { if (AssumeAllFramesVisible()) { return; } if (!mPresContext) { return; } nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver(); if (!refreshDriver) { return; } // Ask the refresh driver to update frame visibility soon. refreshDriver->ScheduleFrameVisibilityUpdate(); } void PresShell::ScheduleApproximateFrameVisibilityUpdateNow() { if (AssumeAllFramesVisible()) { return; } if (!mPresContext->IsRootContentDocumentInProcess()) { nsPresContext* presContext = mPresContext->GetInProcessRootContentDocumentPresContext(); if (!presContext) return; MOZ_ASSERT(presContext->IsRootContentDocumentInProcess(), "Didn't get a root prescontext from " "GetInProcessRootContentDocumentPresContext?"); presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow(); return; } if (mHaveShutDown || mIsDestroying) { return; } if (mUpdateApproximateFrameVisibilityEvent.IsPending()) { return; } RefPtr> event = NewRunnableMethod("PresShell::UpdateApproximateFrameVisibility", this, &PresShell::UpdateApproximateFrameVisibility); nsresult rv = mDocument->Dispatch(do_AddRef(event)); if (NS_SUCCEEDED(rv)) { mUpdateApproximateFrameVisibilityEvent = std::move(event); } } void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) { if (!aFrame->TrackingVisibility()) { return; } if (AssumeAllFramesVisible()) { aFrame->IncApproximateVisibleCount(); return; } #ifdef DEBUG // Make sure it's in this pres shell. nsCOMPtr content = aFrame->GetContent(); if (content) { PresShell* presShell = content->OwnerDoc()->GetPresShell(); MOZ_ASSERT(!presShell || presShell == this, "wrong shell"); } #endif if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) { // We inserted a new entry. aFrame->IncApproximateVisibleCount(); } } void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) { #ifdef DEBUG // Make sure it's in this pres shell. nsCOMPtr content = aFrame->GetContent(); if (content) { PresShell* presShell = content->OwnerDoc()->GetPresShell(); MOZ_ASSERT(!presShell || presShell == this, "wrong shell"); } #endif if (AssumeAllFramesVisible()) { MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0, "Shouldn't have any frames in the table"); return; } if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) && aFrame->TrackingVisibility()) { // aFrame was in the hashtable, and we're still tracking its visibility, // so we need to decrement its visible count. aFrame->DecApproximateVisibleCount(); } } void PresShell::PaintAndRequestComposite(nsView* aView, PaintFlags aFlags) { if (!mIsActive) { return; } WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer(); NS_ASSERTION(renderer, "Must be in paint event"); if (renderer->AsFallback()) { // The fallback renderer doesn't do any retaining, so we // just need to notify the view and widget that we're invalid, and // we'll do a paint+composite from the PaintWindow callback. GetViewManager()->InvalidateView(aView); return; } // Otherwise we're a retained WebRenderLayerManager, so we want to call // Paint to update with any changes and push those to WR. PaintInternalFlags flags = PaintInternalFlags::None; if (aFlags & PaintFlags::PaintSyncDecodeImages) { flags |= PaintInternalFlags::PaintSyncDecodeImages; } PaintInternal(aView, flags); } void PresShell::SyncPaintFallback(nsView* aView) { if (!mIsActive) { return; } WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer(); NS_ASSERTION(renderer->AsFallback(), "Can't do Sync paint for remote renderers"); if (!renderer->AsFallback()) { return; } PaintInternal(aView, PaintInternalFlags::PaintComposite); GetPresContext()->NotifyDidPaintForSubtree(); } void PresShell::PaintInternal(nsView* aViewToPaint, PaintInternalFlags aFlags) { nsCString url; nsIURI* uri = mDocument->GetDocumentURI(); Document* contentRoot = GetPrimaryContentDocument(); if (contentRoot) { uri = contentRoot->GetDocumentURI(); } url = uri ? uri->GetSpecOrDefault() : "N/A"_ns; AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( "Paint", GRAPHICS, Substring(url, std::min(size_t(128), url.Length()))); Maybe nojs; // On Android, Flash can call into content JS during painting, so we can't // assert there. However, we don't rely on this assertion on Android because // we don't paint while JS is running. #if !defined(MOZ_WIDGET_ANDROID) if (!(aFlags & PaintInternalFlags::PaintComposite)) { // We need to allow content JS when the flag is set since we may trigger // MozAfterPaint events in content in those cases. nojs.emplace(dom::danger::GetJSContext()); } #endif NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell"); NS_ASSERTION(aViewToPaint, "null view"); MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared"); if (!mIsActive) { return; } if (StaticPrefs::apz_keyboard_enabled_AtStartup()) { // Update the focus target for async keyboard scrolling. This will be // forwarded to APZ by nsDisplayList::PaintRoot. We need to to do this // before we enter the paint phase because dispatching eVoid events can // cause layout to happen. mAPZFocusTarget = FocusTarget(this, mAPZFocusSequenceNumber); } nsPresContext* presContext = GetPresContext(); AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint); nsIFrame* frame = aViewToPaint->GetFrame(); WindowRenderer* renderer = aViewToPaint->GetWidget()->GetWindowRenderer(); NS_ASSERTION(renderer, "Must be in paint event"); WebRenderLayerManager* layerManager = renderer->AsWebRender(); // Whether or not we should set first paint when painting is suppressed // is debatable. For now we'll do it because B2G relied on first paint // to configure the viewport and we only want to do that when we have // real content to paint. See Bug 798245 if (mIsFirstPaint && !mPaintingSuppressed) { MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::Paint, first paint, this=%p", this)); if (layerManager) { layerManager->SetIsFirstPaint(); } mIsFirstPaint = false; } if (!renderer->BeginTransaction(url)) { return; } // Send an updated focus target with this transaction. Be sure to do this // before we paint in the case this is an empty transaction. if (layerManager) { layerManager->SetFocusTarget(mAPZFocusTarget); } if (frame) { if (!(aFlags & PaintInternalFlags::PaintSyncDecodeImages) && !frame->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) { if (layerManager) { layerManager->SetTransactionIdAllocator(presContext->RefreshDriver()); } if (renderer->EndEmptyTransaction( (aFlags & PaintInternalFlags::PaintComposite) ? WindowRenderer::END_DEFAULT : WindowRenderer::END_NO_COMPOSITE)) { return; } } frame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE); } nscolor bgcolor = ComputeBackstopColor(aViewToPaint); PaintFrameFlags flags = PaintFrameFlags::WidgetLayers | PaintFrameFlags::ExistingTransaction; // We force sync-decode for printing / print-preview (printing already does // this from nsPageSequenceFrame::PrintNextSheet). // We also force sync-decoding via pref for reftests. if (aFlags & PaintInternalFlags::PaintSyncDecodeImages || mDocument->IsStaticDocument() || StaticPrefs::image_decode_sync_enabled()) { flags |= PaintFrameFlags::SyncDecodeImages; } if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) { flags |= PaintFrameFlags::ForWebRender; } if (frame) { // We can paint directly into the widget using its layer manager. nsLayoutUtils::PaintFrame(nullptr, frame, nsRegion(), bgcolor, nsDisplayListBuilderMode::Painting, flags); return; } bgcolor = NS_ComposeColors(bgcolor, 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 PresShell::GetEventTargetContent( WidgetEvent* aEvent) { nsCOMPtr content = GetCurrentEventContent(); if (!content) { nsIFrame* currentEventFrame = GetCurrentEventFrame(); if (currentEventFrame) { currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content)); NS_ASSERTION(!content || content->GetComposedDoc() == mDocument, "handing out content from a different doc"); } } return content.forget(); } void PresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent) { if (mCurrentEventFrame || mCurrentEventContent) { mCurrentEventFrameStack.InsertElementAt(0, mCurrentEventFrame); mCurrentEventContentStack.InsertObjectAt(mCurrentEventContent, 0); } mCurrentEventFrame = aFrame; mCurrentEventContent = aContent; } void PresShell::PopCurrentEventInfo() { mCurrentEventFrame = nullptr; mCurrentEventContent = nullptr; if (0 != mCurrentEventFrameStack.Length()) { mCurrentEventFrame = mCurrentEventFrameStack.ElementAt(0); mCurrentEventFrameStack.RemoveElementAt(0); mCurrentEventContent = mCurrentEventContentStack.ObjectAt(0); mCurrentEventContentStack.RemoveObjectAt(0); // Don't use it if it has moved to a different document. if (mCurrentEventContent && mCurrentEventContent->GetComposedDoc() != mDocument) { mCurrentEventContent = nullptr; mCurrentEventFrame = nullptr; } } } // static bool PresShell::EventHandler::InZombieDocument(nsIContent* aContent) { // If a content node points to a null document, or the document is not // attached to a window, then it is possibly in a zombie document, // about to be replaced by a newly loading document. // Such documents cannot handle DOM events. // It might actually be in a node not attached to any document, // in which case there is not parent presshell to retarget it to. Document* doc = aContent->GetComposedDoc(); return !doc || !doc->GetWindow(); } already_AddRefed PresShell::GetRootWindow() { nsCOMPtr window = mDocument->GetWindow(); if (window) { nsCOMPtr rootWindow = window->GetPrivateRoot(); NS_ASSERTION(rootWindow, "nsPIDOMWindow::GetPrivateRoot() returns NULL"); return rootWindow.forget(); } // If we don't have DOM window, we're zombie, we should find the root window // with our parent shell. RefPtr parentPresShell = GetParentPresShellForEventHandling(); NS_ENSURE_TRUE(parentPresShell, nullptr); return parentPresShell->GetRootWindow(); } already_AddRefed PresShell::GetFocusedDOMWindowInOurWindow() { nsCOMPtr rootWindow = GetRootWindow(); NS_ENSURE_TRUE(rootWindow, nullptr); nsCOMPtr focusedWindow; nsFocusManager::GetFocusedDescendant(rootWindow, nsFocusManager::eIncludeAllDescendants, getter_AddRefs(focusedWindow)); return focusedWindow.forget(); } already_AddRefed PresShell::GetFocusedContentInOurWindow() const { nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (fm && mDocument) { RefPtr focusedElement; fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr, getter_AddRefs(focusedElement)); return focusedElement.forget(); } return nullptr; } already_AddRefed PresShell::GetParentPresShellForEventHandling() { if (!mPresContext) { return nullptr; } // Now, find the parent pres shell and send the event there RefPtr docShell = mPresContext->GetDocShell(); if (!docShell) { docShell = mForwardingContainer.get(); } // Might have gone away, or never been around to start with if (!docShell) { return nullptr; } BrowsingContext* bc = docShell->GetBrowsingContext(); if (!bc) { return nullptr; } RefPtr parentBC; if (XRE_IsParentProcess()) { parentBC = bc->Canonical()->GetParentCrossChromeBoundary(); } else { parentBC = bc->GetParent(); } RefPtr parentDocShell = parentBC ? parentBC->GetDocShell() : nullptr; if (!parentDocShell) { return nullptr; } RefPtr parentPresShell = parentDocShell->GetPresShell(); return parentPresShell.forget(); } nsresult PresShell::EventHandler::RetargetEventToParent( WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { // Send this events straight up to the parent pres shell. // We do this for keystroke events in zombie documents or if either a frame // or a root content is not present. // That way at least the UI key bindings can work. RefPtr parentPresShell = GetParentPresShellForEventHandling(); NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE); // Fake the event as though it's from the parent pres shell's root frame. return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(), aGUIEvent, true, aEventStatus); } void PresShell::DisableNonTestMouseEvents(bool aDisable) { sDisableNonTestMouseEvents = aDisable; } bool PresShell::MouseLocationWasSetBySynthesizedMouseEventForTests() const { if (!mPresContext) { return false; } if (mPresContext->IsRoot()) { return mMouseLocationWasSetBySynthesizedMouseEventForTests; } PresShell* rootPresShell = GetRootPresShell(); return rootPresShell && rootPresShell->mMouseLocationWasSetBySynthesizedMouseEventForTests; } nsPoint PresShell::GetEventLocation(const WidgetMouseEvent& aEvent) const { nsIFrame* rootFrame = GetRootFrame(); if (rootFrame) { RelativeTo relativeTo{rootFrame}; if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) { relativeTo.mViewportType = ViewportType::Visual; } return nsLayoutUtils::GetEventCoordinatesRelativeTo(&aEvent, relativeTo); } nsView* rootView = mViewManager->GetRootView(); return nsLayoutUtils::TranslateWidgetToView(mPresContext, aEvent.mWidget, aEvent.mRefPoint, rootView); } void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) { if (!mPresContext) { return; } if (!mPresContext->IsRoot()) { PresShell* rootPresShell = GetRootPresShell(); if (rootPresShell) { rootPresShell->RecordPointerLocation(aEvent); } return; } if ((aEvent->mMessage == eMouseMove && aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) || aEvent->mMessage == eMouseEnterIntoWidget || aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp) { mMouseLocation = GetEventLocation(*aEvent->AsMouseEvent()); mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid(); mMouseLocationWasSetBySynthesizedMouseEventForTests = aEvent->mFlags.mIsSynthesizedForTests; #ifdef DEBUG_MOUSE_LOCATION if (aEvent->mMessage == eMouseEnterIntoWidget) { printf("[ps=%p]got mouse enter for %p\n", this, aEvent->mWidget); } printf("[ps=%p]setting mouse location to (%d,%d)\n", this, mMouseLocation.x, mMouseLocation.y); #endif if (aEvent->mMessage == eMouseEnterIntoWidget) { SynthesizeMouseMove(false); } } else if (aEvent->mMessage == eMouseExitFromWidget) { // Although we only care about the mouse moving into an area for which this // pres shell doesn't receive mouse move events, we don't check which widget // the mouse exit was for since this seems to vary by platform. Hopefully // this won't matter at all since we'll get the mouse move or enter after // the mouse exit when the mouse moves from one of our widgets into another. mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid(); mMouseLocationWasSetBySynthesizedMouseEventForTests = aEvent->mFlags.mIsSynthesizedForTests; #ifdef DEBUG_MOUSE_LOCATION printf("[ps=%p]got mouse exit for %p\n", this, aEvent->mWidget); printf("[ps=%p]clearing mouse location\n", this); #endif } else if ((aEvent->mMessage == ePointerMove && aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) || aEvent->mMessage == ePointerDown || aEvent->mMessage == ePointerUp) { // TODO: instead, encapsulate `mMouseLocation` and // `mLastOverWindowPointerLocation` in a struct. mLastOverWindowPointerLocation = GetEventLocation(*aEvent->AsMouseEvent()); } } void PresShell::nsSynthMouseMoveEvent::Revoke() { if (mPresShell) { mPresShell->GetPresContext()->RefreshDriver()->RemoveRefreshObserver( this, FlushType::Display); mPresShell = nullptr; } } // static nsIFrame* PresShell::EventHandler::GetNearestFrameContainingPresShell( PresShell* aPresShell) { nsViewManager* vm = aPresShell->GetViewManager(); if (!vm) { return nullptr; } nsView* view = vm->GetRootView(); while (view && !view->GetFrame()) { view = view->GetParent(); } nsIFrame* frame = nullptr; if (view) { frame = view->GetFrame(); } return frame; } static CallState FlushThrottledStyles(Document& aDocument) { PresShell* presShell = aDocument.GetPresShell(); if (presShell && presShell->IsVisible()) { if (nsPresContext* presContext = presShell->GetPresContext()) { presContext->RestyleManager()->UpdateOnlyAnimationStyles(); } } aDocument.EnumerateSubDocuments(FlushThrottledStyles); return CallState::Continue; } bool PresShell::CanDispatchEvent(const WidgetGUIEvent* aEvent) const { bool rv = mPresContext && !mHaveShutDown && nsContentUtils::IsSafeToRunScript(); if (aEvent) { rv &= (aEvent && aEvent->mWidget && !aEvent->mWidget->Destroyed()); } return rv; } /* static */ PresShell* PresShell::GetShellForEventTarget(nsIFrame* aFrame, nsIContent* aContent) { if (aFrame) { return aFrame->PresShell(); } if (aContent) { Document* doc = aContent->GetComposedDoc(); if (!doc) { return nullptr; } return doc->GetPresShell(); } return nullptr; } /* static */ PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) { switch (aEvent->mMessage) { case eTouchMove: case eTouchCancel: case eTouchEnd: { // get the correct shell to dispatch to WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); for (dom::Touch* touch : touchEvent->mTouches) { if (!touch) { return nullptr; } RefPtr oldTouch = TouchManager::GetCapturedTouch(touch->Identifier()); if (!oldTouch) { return nullptr; } nsIContent* const content = nsIContent::FromEventTargetOrNull(oldTouch->GetTarget()); if (!content) { return nullptr; } if (PresShell* const presShell = content->OwnerDoc()->GetPresShell()) { 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 rootPresShell = mPresContext->IsRoot() ? this : GetRootPresShell(); if (rootPresShell && rootPresShell->mSynthMouseMoveEvent.IsPending()) { AutoWeakFrame frameForPresShellWeak(aFrameForPresShell); RefPtr 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 capturingContent = EventHandler::GetCapturingContentFor(aGUIEvent); if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) { PointerLockManager::Unlock(); } nsIFrame* frameForPresShell = MaybeFlushThrottledStyles(aFrameForPresShell); if (NS_WARN_IF(!frameForPresShell)) { return NS_OK; } bool isCapturingContentIgnored = false; bool isCaptureRetargeted = false; nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEvent( frameForPresShell, aGUIEvent, capturingContent, &isCapturingContentIgnored, &isCaptureRetargeted); if (isCapturingContentIgnored) { capturingContent = nullptr; } // The order to generate pointer event is // 1. check pending pointer capture. // 2. check if there is a capturing content. // 3. hit test // 4. dispatch pointer events // 5. check whether the targets of all Touch instances are in the same // document and suppress invalid instances. // 6. dispatch mouse or touch events. // Try to keep frame for following check, because frame can be damaged // during MaybeProcessPointerCapture. { AutoWeakFrame frameKeeper(rootFrameToHandleEvent); PointerEventHandler::MaybeProcessPointerCapture(aGUIEvent); // Prevent application crashes, in case damaged frame. if (!frameKeeper.IsAlive()) { NS_WARNING("Nothing to handle this event!"); return NS_OK; } } // Only capture mouse events and pointer events. RefPtr pointerCapturingElement = PointerEventHandler::GetPointerCapturingElement(aGUIEvent); if (pointerCapturingElement) { rootFrameToHandleEvent = pointerCapturingElement->GetPrimaryFrame(); if (!rootFrameToHandleEvent) { return HandleEventWithPointerCapturingContentWithoutItsFrame( aFrameForPresShell, aGUIEvent, pointerCapturingElement, aEventStatus); } } WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); bool isWindowLevelMouseExit = (aGUIEvent->mMessage == eMouseExitFromWidget) && (mouseEvent && (mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePlatformTopLevel || mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet)); // Get the frame at the event point. However, don't do this if we're // capturing and retargeting the event because the captured frame will // be used instead below. Also keep using the root frame if we're dealing // with a window-level mouse exit event since we want to start sending // mouse out events at the root EventStateManager. EventTargetData eventTargetData(rootFrameToHandleEvent); if (!isCaptureRetargeted && !isWindowLevelMouseExit && !pointerCapturingElement) { if (!ComputeEventTargetFrameAndPresShellAtEventPoint( rootFrameToHandleEvent, aGUIEvent, &eventTargetData)) { *aEventStatus = nsEventStatus_eIgnore; return NS_OK; } } // if a node is capturing the mouse, check if the event needs to be // retargeted at the capturing content instead. This will be the case when // capture retargeting is being used, no frame was found or the frame's // content is not a descendant of the capturing content. if (capturingContent && !pointerCapturingElement && (PresShell::sCapturingContentInfo.mRetargetToElement || !eventTargetData.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; } // 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)); if (NS_FAILED(rv) || MOZ_UNLIKELY(eventTargetData.mPresShell->IsDestroying())) { return rv; } if (aGUIEvent->mMessage == eTouchEnd) { MaybeSynthesizeCompatMouseEventsForTouchEnd(aGUIEvent->AsTouchEvent(), aEventStatus); } return NS_OK; } bool PresShell::EventHandler::MaybeFlushPendingNotifications( WidgetGUIEvent* aGUIEvent) { MOZ_ASSERT(aGUIEvent); switch (aGUIEvent->mMessage) { case eMouseDown: case eMouseUp: { RefPtr presContext = mPresShell->GetPresContext(); if (NS_WARN_IF(!presContext)) { return false; } uint64_t framesConstructedCount = presContext->FramesConstructedCount(); uint64_t framesReflowedCount = presContext->FramesReflowedCount(); MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout); return framesConstructedCount != presContext->FramesConstructedCount() || framesReflowedCount != presContext->FramesReflowedCount(); } default: return false; } } // The type of coordinates to use for hit-testing input events // that are relative to the RCD's viewport frame. // On most platforms, use visual coordinates so that scrollbars // can be targeted. // On mobile, use layout coordinates because hit-testing in // visual coordinates clashes with mobile viewport sizing, where // the ViewportFrame is sized to the initial containing block // (ICB) size, which is in layout coordinates. This is fine // because we don't need to be able to target scrollbars on mobile // (scrollbar dragging isn't supported). static ViewportType ViewportTypeForInputEventsRelativeToRoot() { #ifdef MOZ_WIDGET_ANDROID return ViewportType::Layout; #else return ViewportType::Visual; #endif } nsIFrame* PresShell::EventHandler::GetFrameToHandleNonTouchEvent( nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) { MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aGUIEvent->mClass != eTouchEventClass); ViewportType viewportType = ViewportType::Layout; if (aRootFrameToHandleEvent->Type() == LayoutFrameType::Viewport) { nsPresContext* pc = aRootFrameToHandleEvent->PresContext(); if (pc->IsChrome()) { viewportType = ViewportType::Visual; } else if (pc->IsRootContentDocumentCrossProcess()) { viewportType = ViewportTypeForInputEventsRelativeToRoot(); } } RelativeTo relativeTo{aRootFrameToHandleEvent, viewportType}; nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo); uint32_t flags = 0; if (aGUIEvent->mClass == eMouseEventClass) { WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); if (mouseEvent && mouseEvent->mIgnoreRootScrollFrame) { flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME; } } nsIFrame* targetFrame = FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags); if (!targetFrame) { return aRootFrameToHandleEvent; } if (targetFrame->PresShell() == mPresShell) { // If found target is in mPresShell, we've already found it in the latest // layout so that we can use it. return targetFrame; } // If target is in a child document, we've not flushed its layout yet. PresShell* childPresShell = targetFrame->PresShell(); EventHandler childEventHandler(*childPresShell); AutoWeakFrame weakFrame(aRootFrameToHandleEvent); bool layoutChanged = childEventHandler.MaybeFlushPendingNotifications(aGUIEvent); if (!weakFrame.IsAlive()) { // Stop handling the event if the root frame to handle event is destroyed // by the reflow. (but why?) return nullptr; } if (!layoutChanged) { // If the layout in the child PresShell hasn't been changed, we don't // need to recompute the target. return targetFrame; } // Finally, we need to recompute the target with the latest layout. targetFrame = FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags); return targetFrame ? targetFrame : aRootFrameToHandleEvent; } bool PresShell::EventHandler::ComputeEventTargetFrameAndPresShellAtEventPoint( nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent, EventTargetData* aEventTargetData) { MOZ_ASSERT(aRootFrameToHandleEvent); MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aEventTargetData); if (aGUIEvent->mClass == eTouchEventClass) { nsIFrame* targetFrameAtTouchEvent = TouchManager::SetupTarget( aGUIEvent->AsTouchEvent(), aRootFrameToHandleEvent); aEventTargetData->SetFrameAndComputePresShell(targetFrameAtTouchEvent); return true; } nsIFrame* targetFrame = GetFrameToHandleNonTouchEvent(aRootFrameToHandleEvent, aGUIEvent); aEventTargetData->SetFrameAndComputePresShell(targetFrame); return !!aEventTargetData->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 pointerEventTargetContent( aEventTargetData->GetContent()); RefPtr presShell(aEventTargetData->mPresShell); nsCOMPtr mouseOrTouchEventTargetContent; PointerEventHandler::DispatchPointerFromMouseOrTouch( presShell, aEventTargetData->GetFrame(), pointerEventTargetContent, aGUIEvent, aDontRetargetEvents, aEventStatus, getter_AddRefs(mouseOrTouchEventTargetContent)); // If the target frame is alive, the caller should keep handling the event // unless event target frame is destroyed. if (weakTargetFrame.IsAlive() && weakFrame.IsAlive()) { aEventTargetData->UpdateTouchEventTarget(aGUIEvent); return true; } presShell->FlushPendingNotifications(FlushType::Layout); if (MOZ_UNLIKELY(mPresShell->IsDestroying())) { return false; } // The spec defines that mouse events must be dispatched to the same target as // the pointer event. // The Touch Events spec defines that touch events must be dispatched to the // same target as touch start and the other browsers dispatch touch events // even if the touch event target is not connected to the document. // Retargetting the event is handled by AutoPointerEventTargetUpdater and // mouseOrTouchEventTargetContent stores the result. // If the target is no longer participating in its ownerDocument's tree, // fire the event at the original target's nearest ancestor node. if (!mouseOrTouchEventTargetContent) { MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass); return false; } aEventTargetData->SetFrameAndContent( mouseOrTouchEventTargetContent->GetPrimaryFrame(), mouseOrTouchEventTargetContent); aEventTargetData->mPresShell = mouseOrTouchEventTargetContent->IsInComposedDoc() ? PresShell::GetShellForEventTarget(aEventTargetData->GetFrame(), aEventTargetData->GetContent()) : mouseOrTouchEventTargetContent->OwnerDoc()->GetPresShell(); // If new target PresShel is not found, we cannot keep handling the event. if (!aEventTargetData->mPresShell) { return false; } aEventTargetData->UpdateTouchEventTarget(aGUIEvent); return true; } /** * 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 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 window = GetFocusedDOMWindowInOurWindow(); if (!window) { return false; } RefPtr retargetEventDoc = window->GetExtantDoc(); if (!retargetEventDoc) { return false; } RefPtr presShell = retargetEventDoc->GetPresShell(); if (!presShell) { return false; } RefPtr eventHub = presShell->GetAccessibleCaretEventHub(); if (!eventHub) { return false; } *aEventStatus = eventHub->HandleEvent(aGUIEvent); if (*aEventStatus != nsEventStatus_eConsumeNoDefault) { return false; } // If the event is consumed, cancel APZC panning by setting // mMultipleActionsPrevented. aGUIEvent->mFlags.mMultipleActionsPrevented = true; autoEventTargetPointResetter.SetHandledByAccessibleCaret(); return true; } void PresShell::EventHandler::MaybeSynthesizeCompatMouseEventsForTouchEnd( const WidgetTouchEvent* aTouchEndEvent, const nsEventStatus* aStatus) const { MOZ_ASSERT(aTouchEndEvent->mMessage == eTouchEnd); // If the eTouchEnd event is dispatched via APZ, APZCCallbackHelper dispatches // a set of mouse events with better handling. Therefore, we don't need to // handle that here. if (!aTouchEndEvent->mFlags.mIsSynthesizedForTests || StaticPrefs::test_events_async_enabled()) { return; } // If the tap was consumed or 2 or more touches occurred, we don't need the // compatibility mouse events. if (*aStatus == nsEventStatus_eConsumeNoDefault || !TouchManager::IsSingleTapEndToDoDefault(aTouchEndEvent)) { return; } if (NS_WARN_IF(!aTouchEndEvent->mWidget)) { return; } nsCOMPtr widget = aTouchEndEvent->mWidget; // NOTE: I think that we don't need to implement a double click here becase // WebDriver does not support a way to synthesize a double click and Chrome // does not fire "dblclick" even if doing `pointerDown().pointerUp()` twice. // FIXME: Currently we don't support long tap. RefPtr presShell = mPresShell; for (const EventMessage message : {eMouseMove, eMouseDown, eMouseUp}) { if (MOZ_UNLIKELY(presShell->IsDestroying())) { break; } nsIFrame* frameForPresShell = GetNearestFrameContainingPresShell(presShell); if (!frameForPresShell) { break; } WidgetMouseEvent event(true, message, widget, WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); event.mRefPoint = aTouchEndEvent->mTouches[0]->mRefPoint; event.mButton = MouseButton::ePrimary; event.mButtons = message == eMouseDown ? MouseButtonsFlag::ePrimaryFlag : MouseButtonsFlag::eNoButtons; event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH; event.mClickCount = message == eMouseMove ? 0 : 1; event.mModifiers = aTouchEndEvent->mModifiers; event.convertToPointer = false; nsEventStatus mouseEventStatus = nsEventStatus_eIgnore; presShell->HandleEvent(frameForPresShell, &event, false, &mouseEventStatus); } } bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) { MOZ_ASSERT(aGUIEvent); // If it is safe to dispatch events now, don't discard the event. if (nsContentUtils::IsSafeToRunScript()) { return false; } // If the event does not cause dispatching DOM event (i.e., internal event), // we can keep handling it even when it's not safe to run script. if (!aGUIEvent->IsAllowedToDispatchDOMEvent()) { return false; } // If the event is a composition event, we need to let IMEStateManager know // it's discarded because it needs to listen all composition events to manage // TextComposition instance. if (aGUIEvent->mClass == eCompositionEventClass) { IMEStateManager::OnCompositionEventDiscarded( aGUIEvent->AsCompositionEvent()); } #ifdef DEBUG if (aGUIEvent->IsIMERelatedEvent()) { nsPrintfCString warning("%s event is discarded", ToChar(aGUIEvent->mMessage)); NS_WARNING(warning.get()); } #endif // #ifdef DEBUG nsContentUtils::WarnScriptWasIgnored(GetDocument()); return true; } // static nsIContent* PresShell::EventHandler::GetCapturingContentFor( WidgetGUIEvent* aGUIEvent) { return (aGUIEvent->mClass == ePointerEventClass || aGUIEvent->mClass == eWheelEventClass || aGUIEvent->HasMouseEventMessage()) ? PresShell::GetCapturingContent() : nullptr; } bool PresShell::EventHandler::GetRetargetEventDocument( WidgetGUIEvent* aGUIEvent, Document** aRetargetEventDocument) { MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aRetargetEventDocument); *aRetargetEventDocument = nullptr; // key and IME related events should not cross top level window boundary. // Basically, such input events should be fired only on focused widget. // However, some IMEs might need to clean up composition after focused // window is deactivated. And also some tests on MozMill want to test key // handling on deactivated window because MozMill window can be activated // during tests. So, there is no merit the events should be redirected to // active window. So, the events should be handled on the last focused // content in the last focused DOM window in same top level window. // Note, if no DOM window has been focused yet, we can discard the events. if (aGUIEvent->IsTargetedAtFocusedWindow()) { nsCOMPtr window = GetFocusedDOMWindowInOurWindow(); // No DOM window in same top level window has not been focused yet, // discard the events. if (!window) { return false; } RefPtr retargetEventDoc = window->GetExtantDoc(); if (!retargetEventDoc) { return false; } retargetEventDoc.forget(aRetargetEventDocument); return true; } nsIContent* capturingContent = EventHandler::GetCapturingContentFor(aGUIEvent); if (capturingContent) { // if the mouse is being captured then retarget the mouse event at the // document that is being captured. RefPtr retargetEventDoc = capturingContent->GetComposedDoc(); retargetEventDoc.forget(aRetargetEventDocument); return true; } #ifdef ANDROID if (aGUIEvent->mClass == eTouchEventClass || aGUIEvent->mClass == eMouseEventClass || aGUIEvent->mClass == eWheelEventClass) { RefPtr retargetEventDoc = mPresShell->GetPrimaryContentDocument(); retargetEventDoc.forget(aRetargetEventDocument); return true; } #endif // #ifdef ANDROID // When we don't find another document to handle the event, we need to keep // handling the event by ourselves. return true; } nsIFrame* PresShell::EventHandler::GetFrameForHandlingEventWith( WidgetGUIEvent* aGUIEvent, Document* aRetargetDocument, nsIFrame* aFrameForPresShell) { MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aRetargetDocument); RefPtr retargetPresShell = aRetargetDocument->GetPresShell(); // Even if the document doesn't have PresShell, i.e., it's invisible, we // need to dispatch only KeyboardEvent in its nearest visible document // because key focus shouldn't be caught by invisible document. if (!retargetPresShell) { if (!aGUIEvent->HasKeyEventMessage()) { return nullptr; } Document* retargetEventDoc = aRetargetDocument; while (!retargetPresShell) { retargetEventDoc = retargetEventDoc->GetInProcessParentDocument(); if (!retargetEventDoc) { return nullptr; } retargetPresShell = retargetEventDoc->GetPresShell(); } } // If the found PresShell is this instance, caller needs to keep handling // aGUIEvent by itself. Therefore, return the given frame which was set // to aFrame of HandleEvent(). if (retargetPresShell == mPresShell) { return aFrameForPresShell; } // Use root frame of the new PresShell if there is. nsIFrame* rootFrame = retargetPresShell->GetRootFrame(); if (rootFrame) { return rootFrame; } // Otherwise, and if aGUIEvent requires content of PresShell, caller should // stop handling the event. if (aGUIEvent->mMessage == eQueryTextContent || aGUIEvent->IsContentCommandEvent()) { return nullptr; } // Otherwise, use nearest ancestor frame which includes the PresShell. return GetNearestFrameContainingPresShell(retargetPresShell); } bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell( nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus, nsresult* aRv) { MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aEventStatus); MOZ_ASSERT(aRv); *aRv = NS_OK; RefPtr retargetEventDoc; if (!GetRetargetEventDocument(aGUIEvent, getter_AddRefs(retargetEventDoc))) { // Nobody can handle this event. So, treat as handled by somebody to make // caller do nothing anymore. return true; } // If there is no proper retarget document, the caller should handle the // event by itself. if (!retargetEventDoc) { return false; } nsIFrame* frame = GetFrameForHandlingEventWith(aGUIEvent, retargetEventDoc, aFrameForPresShell); if (!frame) { // Nobody can handle this event. So, treat as handled by somebody to make // caller do nothing anymore. return true; } // If we reached same frame as set to HandleEvent(), the caller should handle // the event by itself. if (frame == aFrameForPresShell) { return false; } // We need to handle aGUIEvent with another PresShell. RefPtr presShell = frame->PresContext()->PresShell(); *aRv = presShell->HandleEvent(frame, aGUIEvent, true, aEventStatus); return true; } bool PresShell::EventHandler::MaybeDiscardOrDelayKeyboardEvent( WidgetGUIEvent* aGUIEvent) { MOZ_ASSERT(aGUIEvent); if (aGUIEvent->mClass != eKeyboardEventClass) { return false; } Document* document = GetDocument(); if (!document || !document->EventHandlingSuppressed()) { return false; } MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent(), !InputTaskManager::Get()->IsSuspended()); if (aGUIEvent->mMessage == eKeyDown) { mPresShell->mNoDelayedKeyEvents = true; } else if (!mPresShell->mNoDelayedKeyEvents) { UniquePtr delayedKeyEvent = MakeUnique(aGUIEvent->AsKeyboardEvent()); mPresShell->mDelayedEvents.AppendElement(std::move(delayedKeyEvent)); } aGUIEvent->mFlags.mIsSuppressedOrDelayed = true; return true; } bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent( nsIFrame* aFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) { MOZ_ASSERT(aFrameToHandleEvent); MOZ_ASSERT(aGUIEvent); if (aGUIEvent->mClass != eMouseEventClass) { return false; } if (!aFrameToHandleEvent->PresContext() ->Document() ->EventHandlingSuppressed()) { return false; } MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() && aGUIEvent->mMessage != eMouseMove, !InputTaskManager::Get()->IsSuspended()); RefPtr ps = aFrameToHandleEvent->PresShell(); if (aGUIEvent->mMessage == eMouseDown) { ps->mNoDelayedMouseEvents = true; } else if (!ps->mNoDelayedMouseEvents && (aGUIEvent->mMessage == eMouseUp || // contextmenu is triggered after right mouseup on Windows and // right mousedown on other platforms. aGUIEvent->mMessage == eContextMenu || aGUIEvent->mMessage == eMouseExitFromWidget)) { UniquePtr delayedMouseEvent = MakeUnique(aGUIEvent->AsMouseEvent()); ps->mDelayedEvents.AppendElement(std::move(delayedMouseEvent)); } // If there is a suppressed event listener associated with the document, // notify it about the suppressed mouse event. This allows devtools // features to continue receiving mouse events even when the devtools // debugger has paused execution in a page. RefPtr suppressedListener = aFrameToHandleEvent->PresContext() ->Document() ->GetSuppressedEventListener(); if (!suppressedListener || aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) { return true; } nsCOMPtr targetContent; aFrameToHandleEvent->GetContentForEvent(aGUIEvent, getter_AddRefs(targetContent)); if (targetContent) { aGUIEvent->mTarget = targetContent; } nsCOMPtr eventTarget = aGUIEvent->mTarget; RefPtr event = EventDispatcher::CreateEvent( eventTarget, aFrameToHandleEvent->PresContext(), aGUIEvent, u""_ns); suppressedListener->HandleEvent(*event); return true; } nsIFrame* PresShell::EventHandler::MaybeFlushThrottledStyles( nsIFrame* aFrameForPresShell) { if (!GetDocument()) { // XXX Only when mPresShell has document, we'll try to look for a frame // containing mPresShell even if given frame is nullptr. Does this // make sense? return aFrameForPresShell; } PresShell* rootPresShell = mPresShell->GetRootPresShell(); if (NS_WARN_IF(!rootPresShell)) { return nullptr; } Document* rootDocument = rootPresShell->GetDocument(); if (NS_WARN_IF(!rootDocument)) { return nullptr; } AutoWeakFrame weakFrameForPresShell(aFrameForPresShell); { // scope for scriptBlocker. nsAutoScriptBlocker scriptBlocker; FlushThrottledStyles(*rootDocument); } if (weakFrameForPresShell.IsAlive()) { return aFrameForPresShell; } return GetNearestFrameContainingPresShell(mPresShell); } nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEvent( nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted) { MOZ_ASSERT(aFrameForPresShell); MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aIsCapturingContentIgnored); MOZ_ASSERT(aIsCaptureRetargeted); nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEventWithPopup( aFrameForPresShell, aGUIEvent, aCapturingContent, aIsCapturingContentIgnored); if (*aIsCapturingContentIgnored) { // If the capturing content is ignored, we don't need to respect it. return rootFrameToHandleEvent; } if (!aCapturingContent) { return rootFrameToHandleEvent; } // If we have capturing content, let's compute root frame with it again. return ComputeRootFrameToHandleEventWithCapturingContent( rootFrameToHandleEvent, aCapturingContent, aIsCapturingContentIgnored, aIsCaptureRetargeted); } nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEventWithPopup( nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent, nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored) { MOZ_ASSERT(aRootFrameToHandleEvent); MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aIsCapturingContentIgnored); *aIsCapturingContentIgnored = false; nsPresContext* framePresContext = aRootFrameToHandleEvent->PresContext(); nsPresContext* rootPresContext = framePresContext->GetRootPresContext(); NS_ASSERTION(rootPresContext == GetPresContext()->GetRootPresContext(), "How did we end up outside the connected " "prescontext/viewmanager hierarchy?"); nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates( rootPresContext, aGUIEvent); if (!popupFrame) { return aRootFrameToHandleEvent; } // If a remote browser is currently capturing input break out if we // detect a chrome generated popup. // XXXedgar, do we need to check fission OOP iframe? if (aCapturingContent && EventStateManager::IsTopLevelRemoteTarget(aCapturingContent)) { *aIsCapturingContentIgnored = true; } // If the popupFrame is an ancestor of the 'frame', the frame should // handle the event, otherwise, the popup should handle it. if (nsContentUtils::ContentIsCrossDocDescendantOf( framePresContext->GetPresShell()->GetDocument(), popupFrame->GetContent())) { return aRootFrameToHandleEvent; } // If we aren't starting our event dispatch from the root frame of the // root prescontext, then someone must be capturing the mouse. In that // case we only want to use the popup list if the capture is // inside the popup. if (framePresContext == rootPresContext && aRootFrameToHandleEvent == FrameConstructor()->GetRootFrame()) { return popupFrame; } if (aCapturingContent && !*aIsCapturingContentIgnored && aCapturingContent->IsInclusiveDescendantOf(popupFrame->GetContent())) { return popupFrame; } return aRootFrameToHandleEvent; } nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEventWithCapturingContent( nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted) { MOZ_ASSERT(aRootFrameToHandleEvent); MOZ_ASSERT(aCapturingContent); MOZ_ASSERT(aIsCapturingContentIgnored); MOZ_ASSERT(aIsCaptureRetargeted); *aIsCapturingContentIgnored = false; *aIsCaptureRetargeted = false; // If a capture is active, determine if the BrowsingContext is active. If // not, clear the capture and target the mouse event normally instead. This // would occur if the mouse button is held down while a tab change occurs. // If the BrowsingContext is active, look for a scrolling container. BrowsingContext* bc = GetPresContext()->Document()->GetBrowsingContext(); if (!bc || !bc->IsActive()) { ClearMouseCapture(); *aIsCapturingContentIgnored = true; return aRootFrameToHandleEvent; } if (PresShell::sCapturingContentInfo.mRetargetToElement) { *aIsCaptureRetargeted = true; return aRootFrameToHandleEvent; } // A check was already done above to ensure that aCapturingContent is // in this presshell. NS_ASSERTION(aCapturingContent->OwnerDoc() == GetDocument(), "Unexpected document"); nsIFrame* captureFrame = aCapturingContent->GetPrimaryFrame(); if (!captureFrame) { return aRootFrameToHandleEvent; } // scrollable frames should use the scrolling container as the root instead // of the document nsIScrollableFrame* scrollFrame = do_QueryFrame(captureFrame); return scrollFrame ? scrollFrame->GetScrolledFrame() : aRootFrameToHandleEvent; } nsresult PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame( nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus) { MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aPointerCapturingContent); MOZ_ASSERT(!aPointerCapturingContent->GetPrimaryFrame(), "Handle the event with frame rather than only with the content"); MOZ_ASSERT(aEventStatus); RefPtr presShellForCapturingContent = PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent); if (!presShellForCapturingContent) { // If we can't process event for the capturing content, release // the capture. PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent); // 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 overrideClickTarget = GetOverrideClickTarget(aGUIEvent, aFrameForPresShell); // Dispatch events to the capturing content even it's frame is // destroyed. PointerEventHandler::DispatchPointerFromMouseOrTouch( presShellForCapturingContent, nullptr, aPointerCapturingContent, aGUIEvent, false, aEventStatus, nullptr); if (presShellForCapturingContent == mPresShell) { return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr, overrideClickTarget); } EventHandler eventHandlerForCapturingContent( std::move(presShellForCapturingContent)); return eventHandlerForCapturingContent.HandleEventWithTarget( aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr, overrideClickTarget); } nsresult PresShell::EventHandler::HandleEventAtFocusedContent( WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent()); MOZ_ASSERT(aEventStatus); AutoCurrentEventInfoSetter eventInfoSetter(*this); RefPtr eventTargetElement = ComputeFocusedEventTargetElement(aGUIEvent); mPresShell->mCurrentEventFrame = nullptr; if (eventTargetElement) { nsresult rv = NS_OK; if (MaybeHandleEventWithAnotherPresShell(eventTargetElement, aGUIEvent, aEventStatus, &rv)) { return rv; } } // If we cannot handle the event with mPresShell, let's try to handle it // with parent PresShell. mPresShell->mCurrentEventContent = eventTargetElement; if (!mPresShell->GetCurrentEventContent() || !mPresShell->GetCurrentEventFrame() || InZombieDocument(mPresShell->mCurrentEventContent)) { return RetargetEventToParent(aGUIEvent, aEventStatus); } nsresult rv = HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr); return rv; } Element* PresShell::EventHandler::ComputeFocusedEventTargetElement( WidgetGUIEvent* aGUIEvent) { MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent()); // key and IME related events go to the focused frame in this DOM window. nsPIDOMWindowOuter* window = GetDocument()->GetWindow(); nsCOMPtr focusedWindow; Element* eventTargetElement = nsFocusManager::GetFocusedDescendant( window, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(focusedWindow)); // otherwise, if there is no focused content or the focused content has // no frame, just use the root content. This ensures that key events // still get sent to the window properly if nothing is focused or if a // frame goes away while it is focused. if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) { eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget(); } switch (aGUIEvent->mMessage) { case eKeyDown: sLastKeyDownEventTargetElement = eventTargetElement; return eventTargetElement; case eKeyPress: case eKeyUp: if (!sLastKeyDownEventTargetElement) { return eventTargetElement; } // If a different element is now focused for the keypress/keyup event // than what was focused during the keydown event, check if the new // focused element is not in a chrome document any more, and if so, // retarget the event back at the keydown target. This prevents a // content area from grabbing the focus from chrome in-between key // events. if (eventTargetElement) { bool keyDownIsChrome = nsContentUtils::IsChromeDoc( sLastKeyDownEventTargetElement->GetComposedDoc()); if (keyDownIsChrome != nsContentUtils::IsChromeDoc( eventTargetElement->GetComposedDoc()) || (keyDownIsChrome && BrowserParent::GetFrom(eventTargetElement))) { eventTargetElement = sLastKeyDownEventTargetElement; } } if (aGUIEvent->mMessage == eKeyUp) { sLastKeyDownEventTargetElement = nullptr; } [[fallthrough]]; default: return eventTargetElement; } } bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell( Element* aEventTargetElement, WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus, nsresult* aRv) { MOZ_ASSERT(aEventTargetElement); MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates()); MOZ_ASSERT(aEventStatus); MOZ_ASSERT(aRv); Document* eventTargetDocument = aEventTargetElement->OwnerDoc(); if (!eventTargetDocument || eventTargetDocument == GetDocument()) { *aRv = NS_OK; return false; } RefPtr eventTargetPresShell = eventTargetDocument->GetPresShell(); if (!eventTargetPresShell) { *aRv = NS_OK; return true; // No PresShell can handle the event. } EventHandler eventHandler(std::move(eventTargetPresShell)); *aRv = eventHandler.HandleRetargetedEvent(aGUIEvent, aEventStatus, aEventTargetElement); return true; } nsresult PresShell::EventHandler::HandleEventWithFrameForPresShell( nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { MOZ_ASSERT(aGUIEvent); MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates()); MOZ_ASSERT(!aGUIEvent->IsTargetedAtFocusedContent()); MOZ_ASSERT(aEventStatus); AutoCurrentEventInfoSetter eventInfoSetter(*this, aFrameForPresShell, nullptr); nsresult rv = NS_OK; if (mPresShell->GetCurrentEventFrame()) { rv = HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr); } return rv; } Document* PresShell::GetPrimaryContentDocument() { nsPresContext* context = GetPresContext(); if (!context || !context->IsRoot()) { return nullptr; } nsCOMPtr shellAsTreeItem = context->GetDocShell(); if (!shellAsTreeItem) { return nullptr; } nsCOMPtr owner; shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner)); if (!owner) { return nullptr; } // now get the primary content shell (active tab) nsCOMPtr item; owner->GetPrimaryContentShell(getter_AddRefs(item)); nsCOMPtr childDocShell = do_QueryInterface(item); if (!childDocShell) { return nullptr; } return childDocShell->GetExtantDocument(); } nsresult PresShell::EventHandler::HandleEventWithTarget( WidgetEvent* aEvent, nsIFrame* aNewEventFrame, nsIContent* aNewEventContent, nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent, nsIContent** aTargetContent, nsIContent* aOverrideClickTarget) { MOZ_ASSERT(aEvent); MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); #if DEBUG MOZ_ASSERT(!aNewEventFrame || aNewEventFrame->PresContext()->GetPresShell() == mPresShell, "wrong shell"); if (aNewEventContent) { Document* doc = aNewEventContent->GetComposedDoc(); NS_ASSERTION(doc, "event for content that isn't in a document"); // NOTE: We don't require that the document still have a PresShell. // See bug 1375940. } #endif NS_ENSURE_STATE(!aNewEventContent || aNewEventContent->GetComposedDoc() == GetDocument()); if (aEvent->mClass == ePointerEventClass) { mPresShell->RecordPointerLocation(aEvent->AsMouseEvent()); } AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame, aNewEventContent, aTargetContent); AutoCurrentEventInfoSetter eventInfoSetter(*this, aNewEventFrame, aNewEventContent); nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false, aOverrideClickTarget); return rv; } namespace { class MOZ_RAII AutoEventHandler final { public: AutoEventHandler(WidgetEvent* aEvent, Document* aDocument) : mEvent(aEvent) { MOZ_ASSERT(mEvent); MOZ_ASSERT(mEvent->IsTrusted()); if (mEvent->mMessage == eMouseDown) { PresShell::ReleaseCapturingContent(); PresShell::AllowMouseCapture(true); } if (NeedsToUpdateCurrentMouseBtnState()) { WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent(); if (mouseEvent) { EventStateManager::sCurrentMouseBtn = mouseEvent->mButton; } } } ~AutoEventHandler() { if (mEvent->mMessage == eMouseDown) { PresShell::AllowMouseCapture(false); } if (NeedsToUpdateCurrentMouseBtnState()) { EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed; } } protected: bool NeedsToUpdateCurrentMouseBtnState() const { return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp || mEvent->mMessage == ePointerDown || mEvent->mMessage == ePointerUp; } WidgetEvent* mEvent; }; } // anonymous namespace nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo( WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) { MOZ_ASSERT(aEvent); MOZ_ASSERT(aEventStatus); RefPtr manager = GetPresContext()->EventStateManager(); // If we cannot handle the event with mPresShell because of no target, // just record the response time. // XXX Is this intentional? In such case, the score is really good because // of nothing to do. So, it may make average and median better. if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() && !mPresShell->GetCurrentEventContent()) { RecordEventHandlingResponsePerformance(aEvent); return NS_OK; } if (mPresShell->mCurrentEventContent && aEvent->IsTargetedAtFocusedWindow() && aEvent->AllowFlushingPendingNotifications()) { if (RefPtr fm = nsFocusManager::GetFocusManager()) { // This may run script now. So, mPresShell might be destroyed after here. nsCOMPtr currentEventContent = mPresShell->mCurrentEventContent; fm->FlushBeforeEventHandlingIfNeeded(currentEventContent); } } bool touchIsNew = false; if (!PrepareToDispatchEvent(aEvent, aEventStatus, &touchIsNew)) { return NS_OK; } // We finished preparing to dispatch the event. So, let's record the // performance. RecordEventPreparationPerformance(aEvent); AutoHandlingUserInputStatePusher userInpStatePusher( UserActivation::IsUserInteractionEvent(aEvent), aEvent); AutoEventHandler eventHandler(aEvent, GetDocument()); AutoPopupStatePusher popupStatePusher( PopupBlocker::GetEventPopupControlState(aEvent)); // FIXME. If the event was reused, we need to clear the old target, // bug 329430 aEvent->mTarget = nullptr; HandlingTimeAccumulator handlingTimeAccumulator(*this, aEvent); nsresult rv = DispatchEvent(manager, aEvent, touchIsNew, aEventStatus, aOverrideClickTarget); if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent) { // Ensure that notifications to IME should be sent before getting next // native event from the event queue. // XXX Should we check the event message or event class instead of // using aIsHandlingNativeEvent? manager->TryToFlushPendingNotificationsToIME(); } FinalizeHandlingEvent(aEvent, aEventStatus); RecordEventHandlingResponsePerformance(aEvent); return rv; // Result of DispatchEvent() } nsresult PresShell::EventHandler::DispatchEvent( EventStateManager* aEventStateManager, WidgetEvent* aEvent, bool aTouchIsNew, nsEventStatus* aEventStatus, nsIContent* aOverrideClickTarget) { MOZ_ASSERT(aEventStateManager); MOZ_ASSERT(aEvent); MOZ_ASSERT(aEventStatus); // 1. Give event to event manager for pre event state changes and // generation of synthetic events. { // Scope for presContext RefPtr presContext = GetPresContext(); nsCOMPtr eventContent = mPresShell->mCurrentEventContent; nsresult rv = aEventStateManager->PreHandleEvent( presContext, aEvent, mPresShell->mCurrentEventFrame, eventContent, aEventStatus, aOverrideClickTarget); if (NS_FAILED(rv)) { return rv; } } // 2. Give event to the DOM for third party and JS use. bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent(); if (aEvent->mClass == eKeyboardEventClass) { nsContentUtils::SetIsHandlingKeyBoardEvent(true); } // If EventStateManager or something wants reply from remote process and // needs to win any other event listeners in chrome, the event is both // stopped its propagation and marked as "waiting reply from remote // process". In this case, PresShell shouldn't dispatch the event into // the DOM tree because they don't have a chance to stop propagation in // the system event group. On the other hand, if its propagation is not // stopped, that means that the event may be reserved by chrome. If it's // reserved by chrome, the event shouldn't be sent to any remote // processes. In this case, PresShell needs to dispatch the event to // the DOM tree for checking if it's reserved. if (aEvent->IsAllowedToDispatchDOMEvent() && !(aEvent->PropagationStopped() && aEvent->IsWaitingReplyFromRemoteProcess())) { MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(), "Somebody changed aEvent to cause a DOM event!"); nsPresShellEventCB eventCB(mPresShell); if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) { if (target->OnlySystemGroupDispatch(aEvent->mMessage)) { aEvent->StopPropagation(); } } if (aEvent->mClass == eTouchEventClass) { DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew); } else { DispatchEventToDOM(aEvent, aEventStatus, &eventCB); } } nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent); if (mPresShell->IsDestroying()) { return NS_OK; } // 3. Give event to event manager for post event state changes and // generation of synthetic events. // Refetch the prescontext, in case it changed. RefPtr presContext = GetPresContext(); return aEventStateManager->PostHandleEvent( presContext, aEvent, mPresShell->GetCurrentEventFrame(), aEventStatus, aOverrideClickTarget); } bool PresShell::EventHandler::PrepareToDispatchEvent( WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aTouchIsNew) { MOZ_ASSERT(aEvent->IsTrusted()); MOZ_ASSERT(aEventStatus); MOZ_ASSERT(aTouchIsNew); *aTouchIsNew = false; if (aEvent->IsUserAction()) { mPresShell->mHasHandledUserInput = true; } switch (aEvent->mMessage) { case eKeyPress: case eKeyDown: case eKeyUp: { WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent(); MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent); return true; } case eMouseMove: { bool allowCapture = EventStateManager::GetActiveEventStateManager() && GetPresContext() && GetPresContext()->EventStateManager() == EventStateManager::GetActiveEventStateManager(); PresShell::AllowMouseCapture(allowCapture); return true; } case eDrop: { nsCOMPtr session = nsContentUtils::GetDragSession(); if (session) { bool onlyChromeDrop = false; session->GetOnlyChromeDrop(&onlyChromeDrop); if (onlyChromeDrop) { aEvent->mFlags.mOnlyChromeDispatch = true; } } return true; } case eDragExit: { if (!StaticPrefs::dom_event_dragexit_enabled()) { aEvent->mFlags.mOnlyChromeDispatch = true; } return true; } case eContextMenu: { // If we cannot open context menu even though eContextMenu is fired, we // should stop dispatching it into the DOM. WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); if (mouseEvent->IsContextMenuKeyEvent() && !AdjustContextMenuKeyEvent(mouseEvent)) { return false; } // If "Shift" state is active, context menu should be forcibly opened even // if web apps want to prevent it since we respect our users' intention. // In this case, we don't fire "contextmenu" event on web content because // of not cancelable. if (mouseEvent->IsShift() && 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, const nsEventStatus* aStatus) { 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 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; } case eTouchStart: case eTouchMove: case eTouchEnd: case eTouchCancel: case eTouchPointerCancel: case eMouseLongTap: case eContextMenu: { mPresShell->mTouchManager.PostHandleEvent(aEvent, aStatus); break; } 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