1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * Code to notify things that animate before a refresh, at an appropriate
9  * refresh rate.  (Perhaps temporary, until replaced by compositor.)
10  */
11 
12 #ifndef nsRefreshDriver_h_
13 #define nsRefreshDriver_h_
14 
15 #include "mozilla/FlushType.h"
16 #include "mozilla/TimeStamp.h"
17 #include "mozilla/Vector.h"
18 #include "mozilla/WeakPtr.h"
19 #include "nsTObserverArray.h"
20 #include "nsTArray.h"
21 #include "nsTHashtable.h"
22 #include "nsTObserverArray.h"
23 #include "nsClassHashtable.h"
24 #include "nsHashKeys.h"
25 #include "mozilla/AnimationEventDispatcher.h"
26 #include "mozilla/Attributes.h"
27 #include "mozilla/Maybe.h"
28 #include "mozilla/layers/TransactionIdAllocator.h"
29 
30 class nsPresContext;
31 class nsIPresShell;
32 class nsIDocument;
33 class imgIRequest;
34 class nsIDOMEvent;
35 class nsINode;
36 class nsIRunnable;
37 
38 namespace mozilla {
39 class RefreshDriverTimer;
40 class Runnable;
41 namespace layout {
42 class VsyncChild;
43 }  // namespace layout
44 }  // namespace mozilla
45 
46 /**
47  * An abstract base class to be implemented by callers wanting to be
48  * notified at refresh times.  When nothing needs to be painted, callers
49  * may not be notified.
50  */
51 class nsARefreshObserver {
52  public:
53   // AddRef and Release signatures that match nsISupports.  Implementors
54   // must implement reference counting, and those that do implement
55   // nsISupports will already have methods with the correct signature.
56   //
57   // The refresh driver does NOT hold references to refresh observers
58   // except while it is notifying them.
59   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
60 
61   virtual void WillRefresh(mozilla::TimeStamp aTime) = 0;
62 };
63 
64 /**
65  * An abstract base class to be implemented by callers wanting to be notified
66  * that a refresh has occurred. Callers must ensure an observer is removed
67  * before it is destroyed.
68  */
69 class nsAPostRefreshObserver {
70  public:
71   virtual void DidRefresh() = 0;
72 };
73 
74 class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
75                               public nsARefreshObserver {
76  public:
77   explicit nsRefreshDriver(nsPresContext* aPresContext);
78   ~nsRefreshDriver();
79 
80   /**
81    * Methods for testing, exposed via nsIDOMWindowUtils.  See
82    * nsIDOMWindowUtils.advanceTimeAndRefresh for description.
83    */
84   void AdvanceTimeAndRefresh(int64_t aMilliseconds);
85   void RestoreNormalRefresh();
86   void DoTick();
IsTestControllingRefreshesEnabled()87   bool IsTestControllingRefreshesEnabled() const {
88     return mTestControllingRefreshes;
89   }
90 
91   /**
92    * Return the time of the most recent refresh.  This is intended to be
93    * used by callers who want to start an animation now and want to know
94    * what time to consider the start of the animation.  (This helps
95    * ensure that multiple animations started during the same event off
96    * the main event loop have the same start time.)
97    */
98   mozilla::TimeStamp MostRecentRefresh() const;
99   /**
100    * Same thing, but in microseconds since the epoch.
101    */
102   int64_t MostRecentRefreshEpochTime() const;
103 
104   /**
105    * Add / remove refresh observers.  Returns whether the operation
106    * succeeded.
107    *
108    * The flush type affects:
109    *   + the order in which the observers are notified (lowest flush
110    *     type to highest, in order registered)
111    *   + (in the future) which observers are suppressed when the display
112    *     doesn't require current position data or isn't currently
113    *     painting, and, correspondingly, which get notified when there
114    *     is a flush during such suppression
115    * and it must be FlushType::Style, FlushType::Layout, or FlushType::Display.
116    *
117    * The refresh driver does NOT own a reference to these observers;
118    * they must remove themselves before they are destroyed.
119    *
120    * The observer will be called even if there is no other activity.
121    */
122   bool AddRefreshObserver(nsARefreshObserver* aObserver,
123                           mozilla::FlushType aFlushType);
124   bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
125                              mozilla::FlushType aFlushType);
126 
127   void PostScrollEvent(mozilla::Runnable* aScrollEvent);
128   void DispatchScrollEvents();
129 
130   /**
131    * Add an observer that will be called after each refresh. The caller
132    * must remove the observer before it is deleted. This does not trigger
133    * refresh driver ticks.
134    */
135   void AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
136   void RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver);
137 
138   /**
139    * Add/Remove imgIRequest versions of observers.
140    *
141    * These are used for hooking into the refresh driver for
142    * controlling animated images.
143    *
144    * @note The refresh driver owns a reference to these listeners.
145    *
146    * @note Technically, imgIRequest objects are not nsARefreshObservers, but
147    * for controlling animated image repaint events, we subscribe the
148    * imgIRequests to the nsRefreshDriver for notification of paint events.
149    *
150    * @returns whether the operation succeeded, or void in the case of removal.
151    */
152   bool AddImageRequest(imgIRequest* aRequest);
153   void RemoveImageRequest(imgIRequest* aRequest);
154 
155   /**
156    * Add / remove presshells which have pending resize event.
157    */
AddResizeEventFlushObserver(nsIPresShell * aShell)158   void AddResizeEventFlushObserver(nsIPresShell* aShell) {
159     NS_ASSERTION(!mResizeEventFlushObservers.Contains(aShell),
160                  "Double-adding resize event flush observer");
161     mResizeEventFlushObservers.AppendElement(aShell);
162     EnsureTimerStarted();
163   }
164 
RemoveResizeEventFlushObserver(nsIPresShell * aShell)165   void RemoveResizeEventFlushObserver(nsIPresShell* aShell) {
166     mResizeEventFlushObservers.RemoveElement(aShell);
167   }
168 
169   /**
170    * Add / remove presshells that we should flush style and layout on
171    */
AddStyleFlushObserver(nsIPresShell * aShell)172   bool AddStyleFlushObserver(nsIPresShell* aShell) {
173     NS_ASSERTION(!mStyleFlushObservers.Contains(aShell),
174                  "Double-adding style flush observer");
175     bool appended = mStyleFlushObservers.AppendElement(aShell) != nullptr;
176     EnsureTimerStarted();
177 
178     return appended;
179   }
RemoveStyleFlushObserver(nsIPresShell * aShell)180   void RemoveStyleFlushObserver(nsIPresShell* aShell) {
181     mStyleFlushObservers.RemoveElement(aShell);
182   }
AddLayoutFlushObserver(nsIPresShell * aShell)183   bool AddLayoutFlushObserver(nsIPresShell* aShell) {
184     NS_ASSERTION(!IsLayoutFlushObserver(aShell),
185                  "Double-adding layout flush observer");
186     bool appended = mLayoutFlushObservers.AppendElement(aShell) != nullptr;
187     EnsureTimerStarted();
188     return appended;
189   }
RemoveLayoutFlushObserver(nsIPresShell * aShell)190   void RemoveLayoutFlushObserver(nsIPresShell* aShell) {
191     mLayoutFlushObservers.RemoveElement(aShell);
192   }
IsLayoutFlushObserver(nsIPresShell * aShell)193   bool IsLayoutFlushObserver(nsIPresShell* aShell) {
194     return mLayoutFlushObservers.Contains(aShell);
195   }
196 
197   /**
198    * "Early Runner" runnables will be called as the first step when refresh
199    * driver tick is triggered. Runners shouldn't keep other objects alive,
200    * since it isn't guaranteed they will ever get called.
201    */
AddEarlyRunner(nsIRunnable * aRunnable)202   void AddEarlyRunner(nsIRunnable* aRunnable) {
203     mEarlyRunners.AppendElement(aRunnable);
204     EnsureTimerStarted();
205   }
206 
207   /**
208    * Remember whether our presshell's view manager needs a flush
209    */
210   void ScheduleViewManagerFlush();
RevokeViewManagerFlush()211   void RevokeViewManagerFlush() { mViewManagerFlushIsPending = false; }
ViewManagerFlushIsPending()212   bool ViewManagerFlushIsPending() { return mViewManagerFlushIsPending; }
HasScheduleFlush()213   bool HasScheduleFlush() { return mHasScheduleFlush; }
214 
215   /**
216    * Add a document for which we have FrameRequestCallbacks
217    */
218   void ScheduleFrameRequestCallbacks(nsIDocument* aDocument);
219 
220   /**
221    * Remove a document for which we have FrameRequestCallbacks
222    */
223   void RevokeFrameRequestCallbacks(nsIDocument* aDocument);
224 
225   /**
226    * Queue a new event to dispatch in next tick before the style flush
227    */
228   void ScheduleEventDispatch(nsINode* aTarget, nsIDOMEvent* aEvent);
229 
230   /**
231    * Cancel all pending events scheduled by ScheduleEventDispatch which
232    * targets any node in aDocument.
233    */
234   void CancelPendingEvents(nsIDocument* aDocument);
235 
236   /**
237    * Queue new animation events to dispatch in next tick.
238    */
ScheduleAnimationEventDispatch(mozilla::AnimationEventDispatcher * aDispatcher)239   void ScheduleAnimationEventDispatch(
240       mozilla::AnimationEventDispatcher* aDispatcher) {
241     NS_ASSERTION(!mAnimationEventFlushObservers.Contains(aDispatcher),
242                  "Double-adding animation event flush observer");
243     mAnimationEventFlushObservers.AppendElement(aDispatcher);
244     EnsureTimerStarted();
245   }
246 
247   /**
248    * Cancel all pending animation events associated with |aDispatcher|.
249    */
250   void CancelPendingAnimationEvents(
251       mozilla::AnimationEventDispatcher* aDispatcher);
252 
253   /**
254    * Schedule a frame visibility update "soon", subject to the heuristics and
255    * throttling we apply to visibility updates.
256    */
ScheduleFrameVisibilityUpdate()257   void ScheduleFrameVisibilityUpdate() { mNeedToRecomputeVisibility = true; }
258 
259   /**
260    * Tell the refresh driver that it is done driving refreshes and
261    * should stop its timer and forget about its pres context.  This may
262    * be called from within a refresh.
263    */
264   void Disconnect();
265 
IsFrozen()266   bool IsFrozen() { return mFreezeCount > 0; }
267 
268   /**
269    * Freeze the refresh driver.  It should stop delivering future
270    * refreshes until thawed. Note that the number of calls to Freeze() must
271    * match the number of calls to Thaw() in order for the refresh driver to
272    * be un-frozen.
273    */
274   void Freeze();
275 
276   /**
277    * Thaw the refresh driver.  If the number of calls to Freeze() matches the
278    * number of calls to this function, the refresh driver should start
279    * delivering refreshes again.
280    */
281   void Thaw();
282 
283   /**
284    * Throttle or unthrottle the refresh driver.  This is done if the
285    * corresponding presshell is hidden or shown.
286    */
287   void SetThrottled(bool aThrottled);
288 
289   /**
290    * Return the prescontext we were initialized with
291    */
GetPresContext()292   nsPresContext* GetPresContext() const { return mPresContext; }
293 
294   /**
295    * PBackgroundChild actor is created asynchronously in content process.
296    * We can't create vsync-based timers during PBackground startup. This
297    * function will be called when PBackgroundChild actor is created. Then we can
298    * do the pending vsync-based timer creation.
299    */
300   static void PVsyncActorCreated(mozilla::layout::VsyncChild* aVsyncChild);
301 
302 #ifdef DEBUG
303   /**
304    * Check whether the given observer is an observer for the given flush type
305    */
306   bool IsRefreshObserver(nsARefreshObserver* aObserver,
307                          mozilla::FlushType aFlushType);
308 #endif
309 
310   /**
311    * Default interval the refresh driver uses, in ms.
312    */
313   static int32_t DefaultInterval();
314 
IsInRefresh()315   bool IsInRefresh() { return mInRefresh; }
316 
SetIsResizeSuppressed()317   void SetIsResizeSuppressed() { mResizeSuppressed = true; }
IsResizeSuppressed()318   bool IsResizeSuppressed() const { return mResizeSuppressed; }
319 
320   /**
321    * The latest value of process-wide jank levels.
322    *
323    * For each i, sJankLevels[i] counts the number of times delivery of
324    * vsync to the main thread has been delayed by at least 2^i
325    * ms. This data structure has been designed to make it easy to
326    * determine how much jank has taken place between two instants in
327    * time.
328    *
329    * Return `false` if `aJank` needs to be grown to accomodate the
330    * data but we didn't have enough memory.
331    */
332   static bool GetJankLevels(mozilla::Vector<uint64_t>& aJank);
333 
334   // mozilla::layers::TransactionIdAllocator
335   uint64_t GetTransactionId(bool aThrottle) override;
336   uint64_t LastTransactionId() const override;
337   void NotifyTransactionCompleted(uint64_t aTransactionId) override;
338   void RevokeTransactionId(uint64_t aTransactionId) override;
339   void ClearPendingTransactions() override;
340   void ResetInitialTransactionId(uint64_t aTransactionId) override;
341   mozilla::TimeStamp GetTransactionStart() override;
342 
343   bool IsWaitingForPaint(mozilla::TimeStamp aTime);
344 
345   // nsARefreshObserver
AddRef(void)346   NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override {
347     return TransactionIdAllocator::AddRef();
348   }
Release(void)349   NS_IMETHOD_(MozExternalRefCountType) Release(void) override {
350     return TransactionIdAllocator::Release();
351   }
352   virtual void WillRefresh(mozilla::TimeStamp aTime) override;
353 
354   /**
355    * Compute the time when the currently active refresh driver timer
356    * will start its next tick.
357    *
358    * Expects a non-null default value that is the upper bound of the
359    * expected deadline. If the next expected deadline is later than
360    * the default value, the default value is returned.
361    *
362    * If we're animating and we have skipped paints a time in the past
363    * is returned.
364    */
365   static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault);
366 
367   /**
368    * It returns the expected timestamp of the next tick or nothing if the next
369    * tick is missed.
370    */
371   static mozilla::Maybe<mozilla::TimeStamp> GetNextTickHint();
372 
373   static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
374                                             uint32_t aDelay);
375   static void CancelIdleRunnable(nsIRunnable* aRunnable);
376 
SkippedPaints()377   bool SkippedPaints() const { return mSkippedPaints; }
378 
379  private:
380   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
381   typedef nsTArray<RefPtr<mozilla::Runnable>> ScrollEventArray;
382   typedef nsTHashtable<nsISupportsHashKey> RequestTable;
383   struct ImageStartData {
ImageStartDataImageStartData384     ImageStartData() {}
385 
386     mozilla::Maybe<mozilla::TimeStamp> mStartTime;
387     RequestTable mEntries;
388   };
389   typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
390 
391   void DispatchPendingEvents();
392   void DispatchAnimationEvents();
393   void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
394   void UpdateIntersectionObservations();
395   void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);
396 
397   enum EnsureTimerStartedFlags {
398     eNone = 0,
399     eForceAdjustTimer = 1 << 0,
400     eAllowTimeToGoBackwards = 1 << 1,
401     eNeverAdjustTimer = 1 << 2,
402   };
403   void EnsureTimerStarted(EnsureTimerStartedFlags aFlags = eNone);
404   void StopTimer();
405 
406   bool HasObservers() const;
407   uint32_t ObserverCount() const;
408   bool HasImageRequests() const;
409   ObserverArray& ArrayFor(mozilla::FlushType aFlushType);
410   // Trigger a refresh immediately, if haven't been disconnected or frozen.
411   void DoRefresh();
412 
413   double GetRefreshTimerInterval() const;
414   double GetRegularTimerInterval(bool* outIsDefault = nullptr) const;
415   static double GetThrottledTimerInterval();
416 
417   static mozilla::TimeDuration GetMinRecomputeVisibilityInterval();
418 
HaveFrameRequestCallbacks()419   bool HaveFrameRequestCallbacks() const {
420     return mFrameRequestCallbackDocs.Length() != 0;
421   }
422 
423   void FinishedWaitingForTransaction();
424 
425   mozilla::RefreshDriverTimer* ChooseTimer() const;
426   mozilla::RefreshDriverTimer* mActiveTimer;
427 
428   // nsPresContext passed in constructor and unset in Disconnect.
429   mozilla::WeakPtr<nsPresContext> mPresContext;
430 
431   RefPtr<nsRefreshDriver> mRootRefresh;
432 
433   // The most recently allocated transaction id.
434   uint64_t mPendingTransaction;
435   // The most recently completed transaction id.
436   uint64_t mCompletedTransaction;
437 
438   uint32_t mFreezeCount;
439 
440   // How long we wait between ticks for throttled (which generally means
441   // non-visible) documents registered with a non-throttled refresh driver.
442   const mozilla::TimeDuration mThrottledFrameRequestInterval;
443 
444   // How long we wait, at a minimum, before recomputing approximate frame
445   // visibility information. This is a minimum because, regardless of this
446   // interval, we only recompute visibility when we've seen a layout or style
447   // flush since the last time we did it.
448   const mozilla::TimeDuration mMinRecomputeVisibilityInterval;
449 
450   bool mThrottled;
451   bool mNeedToRecomputeVisibility;
452   bool mTestControllingRefreshes;
453   bool mViewManagerFlushIsPending;
454 
455   // True if the view manager needs a flush. Layers-free mode uses this value
456   // to know when to notify invalidation.
457   bool mHasScheduleFlush;
458 
459   bool mInRefresh;
460 
461   // True if the refresh driver is suspended waiting for transaction
462   // id's to be returned and shouldn't do any work during Tick().
463   bool mWaitingForTransaction;
464   // True if Tick() was skipped because of mWaitingForTransaction and
465   // we should schedule a new Tick immediately when resumed instead
466   // of waiting until the next interval.
467   bool mSkippedPaints;
468 
469   // True if view managers should delay any resize request until the
470   // next tick by the refresh driver. This flag will be reset at the
471   // start of every tick.
472   bool mResizeSuppressed;
473 
474   int64_t mMostRecentRefreshEpochTime;
475   // Number of seconds that the refresh driver is blocked waiting for a
476   // compositor transaction to be completed before we append a note to the gfx
477   // critical log. The number is doubled every time the threshold is hit.
478   uint64_t mWarningThreshold;
479   mozilla::TimeStamp mMostRecentRefresh;
480   mozilla::TimeStamp mMostRecentTick;
481   mozilla::TimeStamp mTickStart;
482   mozilla::TimeStamp mNextThrottledFrameRequestTick;
483   mozilla::TimeStamp mNextRecomputeVisibilityTick;
484 
485   // separate arrays for each flush type we support
486   ObserverArray mObservers[4];
487   RequestTable mRequests;
488   ImageStartTable mStartTable;
489   AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
490   ScrollEventArray mScrollEvents;
491 
492   struct PendingEvent {
493     nsCOMPtr<nsINode> mTarget;
494     nsCOMPtr<nsIDOMEvent> mEvent;
495   };
496 
497   AutoTArray<nsIPresShell*, 16> mResizeEventFlushObservers;
498   AutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
499   AutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
500   // nsTArray on purpose, because we want to be able to swap.
501   nsTArray<nsIDocument*> mFrameRequestCallbackDocs;
502   nsTArray<nsIDocument*> mThrottledFrameRequestCallbackDocs;
503   nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
504   nsTArray<PendingEvent> mPendingEvents;
505   AutoTArray<mozilla::AnimationEventDispatcher*, 16>
506       mAnimationEventFlushObservers;
507 
508   void BeginRefreshingImages(RequestTable& aEntries,
509                              mozilla::TimeStamp aDesired);
510 
511   friend class mozilla::RefreshDriverTimer;
512 
513   static void Shutdown();
514 
515   // `true` if we are currently in jank-critical mode.
516   //
517   // In jank-critical mode, any iteration of the event loop that takes
518   // more than 16ms to compute will cause an ongoing animation to miss
519   // frames.
520   //
521   // For simplicity, the current implementation assumes that we are
522   // in jank-critical mode if and only if the vsync driver has at least
523   // one observer.
524   static bool IsJankCritical();
525 };
526 
527 #endif /* !defined(nsRefreshDriver_h_) */
528