diff options
Diffstat (limited to 'layout/base/nsRefreshDriver.h')
-rw-r--r-- | layout/base/nsRefreshDriver.h | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h new file mode 100644 index 000000000..b2dd9be4b --- /dev/null +++ b/layout/base/nsRefreshDriver.h @@ -0,0 +1,523 @@ +/* -*- 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/TimeStamp.h" +#include "mozilla/Vector.h" +#include "mozilla/WeakPtr.h" +#include "mozFlushType.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_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; + NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; + + 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 either Flush_Style, Flush_Layout, or Flush_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, + mozFlushType aFlushType); + bool RemoveRefreshObserver(nsARefreshObserver *aObserver, + mozFlushType 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); + } + bool AddPresShellToInvalidateIfHidden(nsIPresShell* aShell) { + NS_ASSERTION(!mPresShellsToInvalidateIfHidden.Contains(aShell), + "Double-adding style flush observer"); + bool appended = mPresShellsToInvalidateIfHidden.AppendElement(aShell) != nullptr; + EnsureTimerStarted(); + return appended; + } + void RemovePresShellToInvalidateIfHidden(nsIPresShell* aShell) { + mPresShellsToInvalidateIfHidden.RemoveElement(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, + mozFlushType 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<uint64_t>& 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; + 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<mozilla::TimeStamp> GetIdleDeadlineHint(); + + bool SkippedPaints() const + { + return mSkippedPaints; + } + +private: + typedef nsTObserverArray<nsARefreshObserver*> ObserverArray; + typedef nsTHashtable<nsISupportsHashKey> RequestTable; + struct ImageStartData { + ImageStartData() + { + } + + mozilla::Maybe<mozilla::TimeStamp> mStartTime; + RequestTable mEntries; + }; + typedef nsClassHashtable<nsUint32HashKey, ImageStartData> 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(mozFlushType 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(); + static mozilla::TimeDuration GetMinNotifyIntersectionObserversInterval(); + + bool HaveFrameRequestCallbacks() const { + return mFrameRequestCallbackDocs.Length() != 0; + } + + void FinishedWaitingForTransaction(); + + mozilla::RefreshDriverTimer* ChooseTimer() const; + mozilla::RefreshDriverTimer* mActiveTimer; + + ProfilerBacktrace* mReflowCause; + ProfilerBacktrace* mStyleCause; + + // nsPresContext passed in constructor and unset in Disconnect. + mozilla::WeakPtr<nsPresContext> mPresContext; + + RefPtr<nsRefreshDriver> 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; + + const mozilla::TimeDuration mMinNotifyIntersectionObserversInterval; + + bool mThrottled; + bool mNeedToRecomputeVisibility; + bool mTestControllingRefreshes; + bool mViewManagerFlushIsPending; + bool mRequestedHighPrecision; + 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; + mozilla::TimeStamp mNextNotifyIntersectionObserversTick; + + // separate arrays for each flush type we support + ObserverArray mObservers[3]; + RequestTable mRequests; + ImageStartTable mStartTable; + + struct PendingEvent { + nsCOMPtr<nsINode> mTarget; + nsCOMPtr<nsIDOMEvent> mEvent; + }; + + AutoTArray<nsIPresShell*, 16> mStyleFlushObservers; + AutoTArray<nsIPresShell*, 16> mLayoutFlushObservers; + AutoTArray<nsIPresShell*, 16> mPresShellsToInvalidateIfHidden; + // nsTArray on purpose, because we want to be able to swap. + nsTArray<nsIDocument*> mFrameRequestCallbackDocs; + nsTArray<nsIDocument*> mThrottledFrameRequestCallbackDocs; + nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers; + nsTArray<PendingEvent> mPendingEvents; + + void BeginRefreshingImages(RequestTable& aEntries, + mozilla::TimeStamp aDesired); + + friend class mozilla::RefreshDriverTimer; + + // turn on or turn off high precision based on various factors + void ConfigureHighPrecision(); + void SetHighPrecisionTimersEnabled(bool aEnable); + + 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_) */ |