/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ /* 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/. */ /* * Code to notify things that animate before a refresh, at an appropriate * refresh rate. (Perhaps temporary, until replaced by compositor.) */ #ifndef nsRefreshDriver_h_ #define nsRefreshDriver_h_ #include "mozilla/FlushType.h" #include "mozilla/TimeStamp.h" #include "mozilla/Vector.h" #include "mozilla/WeakPtr.h" #include "nsTObserverArray.h" #include "nsTArray.h" #include "nsTHashtable.h" #include "nsTObserverArray.h" #include "nsClassHashtable.h" #include "nsHashKeys.h" #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" #include "GeckoProfiler.h" #include "mozilla/layers/TransactionIdAllocator.h" class nsPresContext; class nsIPresShell; class nsIDocument; class imgIRequest; class nsIDOMEvent; class nsINode; namespace mozilla { class RefreshDriverTimer; namespace layout { class VsyncChild; } // namespace layout } // namespace mozilla /** * An abstract base class to be implemented by callers wanting to be * notified at refresh times. When nothing needs to be painted, callers * may not be notified. */ class nsARefreshObserver { public: // AddRef and Release signatures that match nsISupports. Implementors // must implement reference counting, and those that do implement // nsISupports will already have methods with the correct signature. // // The refresh driver does NOT hold references to refresh observers // except while it is notifying them. NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING virtual void WillRefresh(mozilla::TimeStamp aTime) = 0; }; /** * An abstract base class to be implemented by callers wanting to be notified * that a refresh has occurred. Callers must ensure an observer is removed * before it is destroyed. */ class nsAPostRefreshObserver { public: virtual void DidRefresh() = 0; }; class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator, public nsARefreshObserver { public: explicit nsRefreshDriver(nsPresContext *aPresContext); ~nsRefreshDriver(); /** * Methods for testing, exposed via nsIDOMWindowUtils. See * nsIDOMWindowUtils.advanceTimeAndRefresh for description. */ void AdvanceTimeAndRefresh(int64_t aMilliseconds); void RestoreNormalRefresh(); void DoTick(); bool IsTestControllingRefreshesEnabled() const { return mTestControllingRefreshes; } /** * Return the time of the most recent refresh. This is intended to be * used by callers who want to start an animation now and want to know * what time to consider the start of the animation. (This helps * ensure that multiple animations started during the same event off * the main event loop have the same start time.) */ mozilla::TimeStamp MostRecentRefresh() const; /** * Same thing, but in microseconds since the epoch. */ int64_t MostRecentRefreshEpochTime() const; /** * Add / remove refresh observers. Returns whether the operation * succeeded. * * The flush type affects: * + the order in which the observers are notified (lowest flush * type to highest, in order registered) * + (in the future) which observers are suppressed when the display * doesn't require current position data or isn't currently * painting, and, correspondingly, which get notified when there * is a flush during such suppression * and it must be FlushType::Style, FlushType::Layout, or FlushType::Display. * * The refresh driver does NOT own a reference to these observers; * they must remove themselves before they are destroyed. * * The observer will be called even if there is no other activity. */ bool AddRefreshObserver(nsARefreshObserver *aObserver, mozilla::FlushType aFlushType); bool RemoveRefreshObserver(nsARefreshObserver *aObserver, mozilla::FlushType aFlushType); /** * Add an observer that will be called after each refresh. The caller * must remove the observer before it is deleted. This does not trigger * refresh driver ticks. */ void AddPostRefreshObserver(nsAPostRefreshObserver *aObserver); void RemovePostRefreshObserver(nsAPostRefreshObserver *aObserver); /** * Add/Remove imgIRequest versions of observers. * * These are used for hooking into the refresh driver for * controlling animated images. * * @note The refresh driver owns a reference to these listeners. * * @note Technically, imgIRequest objects are not nsARefreshObservers, but * for controlling animated image repaint events, we subscribe the * imgIRequests to the nsRefreshDriver for notification of paint events. * * @returns whether the operation succeeded, or void in the case of removal. */ bool AddImageRequest(imgIRequest* aRequest); void RemoveImageRequest(imgIRequest* aRequest); /** * Add / remove presshells that we should flush style and layout on */ bool AddStyleFlushObserver(nsIPresShell* aShell) { NS_ASSERTION(!mStyleFlushObservers.Contains(aShell), "Double-adding style flush observer"); // We only get the cause for the first observer each frame because capturing // a stack is expensive. This is still useful if (1) you're trying to remove // all flushes for a particial frame or (2) the costly flush is triggered // near the call site where the first observer is triggered. if (!mStyleCause) { mStyleCause = profiler_get_backtrace(); } bool appended = mStyleFlushObservers.AppendElement(aShell) != nullptr; EnsureTimerStarted(); return appended; } void RemoveStyleFlushObserver(nsIPresShell* aShell) { mStyleFlushObservers.RemoveElement(aShell); } bool AddLayoutFlushObserver(nsIPresShell* aShell) { NS_ASSERTION(!IsLayoutFlushObserver(aShell), "Double-adding layout flush observer"); // We only get the cause for the first observer each frame because capturing // a stack is expensive. This is still useful if (1) you're trying to remove // all flushes for a particial frame or (2) the costly flush is triggered // near the call site where the first observer is triggered. if (!mReflowCause) { mReflowCause = profiler_get_backtrace(); } bool appended = mLayoutFlushObservers.AppendElement(aShell) != nullptr; EnsureTimerStarted(); return appended; } void RemoveLayoutFlushObserver(nsIPresShell* aShell) { mLayoutFlushObservers.RemoveElement(aShell); } bool IsLayoutFlushObserver(nsIPresShell* aShell) { return mLayoutFlushObservers.Contains(aShell); } /** * Remember whether our presshell's view manager needs a flush */ void ScheduleViewManagerFlush(); void RevokeViewManagerFlush() { mViewManagerFlushIsPending = false; } bool ViewManagerFlushIsPending() { return mViewManagerFlushIsPending; } /** * Add a document for which we have FrameRequestCallbacks */ void ScheduleFrameRequestCallbacks(nsIDocument* aDocument); /** * Remove a document for which we have FrameRequestCallbacks */ void RevokeFrameRequestCallbacks(nsIDocument* aDocument); /** * Queue a new event to dispatch in next tick before the style flush */ void ScheduleEventDispatch(nsINode* aTarget, nsIDOMEvent* aEvent); /** * Cancel all pending events scheduled by ScheduleEventDispatch which * targets any node in aDocument. */ void CancelPendingEvents(nsIDocument* aDocument); /** * Schedule a frame visibility update "soon", subject to the heuristics and * throttling we apply to visibility updates. */ void ScheduleFrameVisibilityUpdate() { mNeedToRecomputeVisibility = true; } /** * Tell the refresh driver that it is done driving refreshes and * should stop its timer and forget about its pres context. This may * be called from within a refresh. */ void Disconnect(); bool IsFrozen() { return mFreezeCount > 0; } /** * Freeze the refresh driver. It should stop delivering future * refreshes until thawed. Note that the number of calls to Freeze() must * match the number of calls to Thaw() in order for the refresh driver to * be un-frozen. */ void Freeze(); /** * Thaw the refresh driver. If the number of calls to Freeze() matches the * number of calls to this function, the refresh driver should start * delivering refreshes again. */ void Thaw(); /** * Throttle or unthrottle the refresh driver. This is done if the * corresponding presshell is hidden or shown. */ void SetThrottled(bool aThrottled); /** * Return the prescontext we were initialized with */ nsPresContext* GetPresContext() const { return mPresContext; } /** * PBackgroundChild actor is created asynchronously in content process. * We can't create vsync-based timers during PBackground startup. This * function will be called when PBackgroundChild actor is created. Then we can * do the pending vsync-based timer creation. */ static void PVsyncActorCreated(mozilla::layout::VsyncChild* aVsyncChild); #ifdef DEBUG /** * Check whether the given observer is an observer for the given flush type */ bool IsRefreshObserver(nsARefreshObserver *aObserver, mozilla::FlushType aFlushType); #endif /** * Default interval the refresh driver uses, in ms. */ static int32_t DefaultInterval(); bool IsInRefresh() { return mInRefresh; } void SetIsResizeSuppressed() { mResizeSuppressed = true; } bool IsResizeSuppressed() const { return mResizeSuppressed; } /** * The latest value of process-wide jank levels. * * For each i, sJankLevels[i] counts the number of times delivery of * vsync to the main thread has been delayed by at least 2^i * ms. This data structure has been designed to make it easy to * determine how much jank has taken place between two instants in * time. * * Return `false` if `aJank` needs to be grown to accomodate the * data but we didn't have enough memory. */ static bool GetJankLevels(mozilla::Vector& aJank); // mozilla::layers::TransactionIdAllocator uint64_t GetTransactionId() override; uint64_t LastTransactionId() const override; void NotifyTransactionCompleted(uint64_t aTransactionId) override; void RevokeTransactionId(uint64_t aTransactionId) override; void ClearPendingTransactions() override; void ResetInitialTransactionId(uint64_t aTransactionId) override; mozilla::TimeStamp GetTransactionStart() override; bool IsWaitingForPaint(mozilla::TimeStamp aTime); // nsARefreshObserver NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return TransactionIdAllocator::AddRef(); } NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return TransactionIdAllocator::Release(); } virtual void WillRefresh(mozilla::TimeStamp aTime) override; /** * Compute the time when the currently active refresh driver timer * will start its next tick. * * Returns 'Nothing' if the refresh driver timer hasn't been * initialized or if we can't tell when the next tick will happen. * * Returns Some(TimeStamp()), i.e. the null time, if the next tick is late. * * Otherwise returns Some(TimeStamp(t)), where t is the time of the next tick. * * Using these three types of return values it is possible to * estimate three different things about the idleness of the * currently active group of refresh drivers. This information is * used by nsThread to schedule lower priority "idle tasks". * * The 'Nothing' return value indicates to nsThread that the * currently active refresh drivers will be idle for a time * significantly longer than the current refresh rate and that it is * free to schedule longer periods for executing idle tasks. This is the * expected result when we aren't animating. * Returning the null time indicates to nsThread that we are very * busy and that it should definitely not schedule idle tasks at * all. This is the expected result when we are animating, but * aren't able to keep up with the animation and hence need to skip * paints. Since catching up to missed paints will happen as soon as * possible, this is the expected result if any of the refresh * drivers attached to the current refresh driver misses a paint. * * Returning Some(TimeStamp(t)) indicates to nsThread that we will * be idle until. This is usually the case when we're animating * without skipping paints. */ static mozilla::Maybe GetIdleDeadlineHint(); bool SkippedPaints() const { return mSkippedPaints; } private: typedef nsTObserverArray ObserverArray; typedef nsTHashtable RequestTable; struct ImageStartData { ImageStartData() { } mozilla::Maybe mStartTime; RequestTable mEntries; }; typedef nsClassHashtable ImageStartTable; void DispatchPendingEvents(); void DispatchAnimationEvents(); void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime); void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime); enum EnsureTimerStartedFlags { eNone = 0, eForceAdjustTimer = 1 << 0, eAllowTimeToGoBackwards = 1 << 1, eNeverAdjustTimer = 1 << 2, }; void EnsureTimerStarted(EnsureTimerStartedFlags aFlags = eNone); void StopTimer(); uint32_t ObserverCount() const; uint32_t ImageRequestCount() const; ObserverArray& ArrayFor(mozilla::FlushType aFlushType); // Trigger a refresh immediately, if haven't been disconnected or frozen. void DoRefresh(); double GetRefreshTimerInterval() const; double GetRegularTimerInterval(bool *outIsDefault = nullptr) const; static double GetThrottledTimerInterval(); static mozilla::TimeDuration GetMinRecomputeVisibilityInterval(); bool HaveFrameRequestCallbacks() const { return mFrameRequestCallbackDocs.Length() != 0; } void FinishedWaitingForTransaction(); mozilla::RefreshDriverTimer* ChooseTimer() const; mozilla::RefreshDriverTimer* mActiveTimer; UniqueProfilerBacktrace mReflowCause; UniqueProfilerBacktrace mStyleCause; // nsPresContext passed in constructor and unset in Disconnect. mozilla::WeakPtr mPresContext; RefPtr mRootRefresh; // The most recently allocated transaction id. uint64_t mPendingTransaction; // The most recently completed transaction id. uint64_t mCompletedTransaction; uint32_t mFreezeCount; // How long we wait between ticks for throttled (which generally means // non-visible) documents registered with a non-throttled refresh driver. const mozilla::TimeDuration mThrottledFrameRequestInterval; // How long we wait, at a minimum, before recomputing approximate frame // visibility information. This is a minimum because, regardless of this // interval, we only recompute visibility when we've seen a layout or style // flush since the last time we did it. const mozilla::TimeDuration mMinRecomputeVisibilityInterval; bool mThrottled; bool mNeedToRecomputeVisibility; bool mTestControllingRefreshes; bool mViewManagerFlushIsPending; bool mInRefresh; // True if the refresh driver is suspended waiting for transaction // id's to be returned and shouldn't do any work during Tick(). bool mWaitingForTransaction; // True if Tick() was skipped because of mWaitingForTransaction and // we should schedule a new Tick immediately when resumed instead // of waiting until the next interval. bool mSkippedPaints; // True if view managers should delay any resize request until the // next tick by the refresh driver. This flag will be reset at the // start of every tick. bool mResizeSuppressed; int64_t mMostRecentRefreshEpochTime; // Number of seconds that the refresh driver is blocked waiting for a compositor // transaction to be completed before we append a note to the gfx critical log. // The number is doubled every time the threshold is hit. uint64_t mWarningThreshold; mozilla::TimeStamp mMostRecentRefresh; mozilla::TimeStamp mMostRecentTick; mozilla::TimeStamp mTickStart; mozilla::TimeStamp mNextThrottledFrameRequestTick; mozilla::TimeStamp mNextRecomputeVisibilityTick; // separate arrays for each flush type we support ObserverArray mObservers[3]; RequestTable mRequests; ImageStartTable mStartTable; struct PendingEvent { nsCOMPtr mTarget; nsCOMPtr mEvent; }; AutoTArray mStyleFlushObservers; AutoTArray mLayoutFlushObservers; // nsTArray on purpose, because we want to be able to swap. nsTArray mFrameRequestCallbackDocs; nsTArray mThrottledFrameRequestCallbackDocs; nsTObserverArray mPostRefreshObservers; nsTArray mPendingEvents; void BeginRefreshingImages(RequestTable& aEntries, mozilla::TimeStamp aDesired); friend class mozilla::RefreshDriverTimer; static void Shutdown(); // `true` if we are currently in jank-critical mode. // // In jank-critical mode, any iteration of the event loop that takes // more than 16ms to compute will cause an ongoing animation to miss // frames. // // For simplicity, the current implementation assumes that we are // in jank-critical mode if and only if the vsync driver has at least // one observer. static bool IsJankCritical(); }; #endif /* !defined(nsRefreshDriver_h_) */