summaryrefslogtreecommitdiffstats
path: root/layout/base/nsRefreshDriver.h
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/nsRefreshDriver.h')
-rw-r--r--layout/base/nsRefreshDriver.h523
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_) */