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 * Chrome and each tab have their own RefreshDriver, which in turn
12 * hooks into one of a few global timer based on RefreshDriverTimer,
13 * defined below. There are two main global timers -- one for active
14 * animations, and one for inactive ones. These are implemented as
15 * subclasses of RefreshDriverTimer; see below for a description of
16 * their implementations. In the future, additional timer types may
17 * implement things like blocking on vsync.
18 */
19
20 #include "nsRefreshDriver.h"
21
22 #ifdef XP_WIN
23 # include <windows.h>
24 // mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have
25 // to manually include it
26 # include <mmsystem.h>
27 # include "WinUtils.h"
28 #endif
29
30 #include "mozilla/AnimationEventDispatcher.h"
31 #include "mozilla/ArrayUtils.h"
32 #include "mozilla/Assertions.h"
33 #include "mozilla/AutoRestore.h"
34 #include "mozilla/BasePrincipal.h"
35 #include "mozilla/CycleCollectedJSContext.h"
36 #include "mozilla/DebugOnly.h"
37 #include "mozilla/DisplayPortUtils.h"
38 #include "mozilla/InputTaskManager.h"
39 #include "mozilla/IntegerRange.h"
40 #include "mozilla/PresShell.h"
41 #include "mozilla/dom/FontTableURIProtocolHandler.h"
42 #include "nsITimer.h"
43 #include "nsLayoutUtils.h"
44 #include "nsPresContext.h"
45 #include "nsComponentManagerUtils.h"
46 #include "mozilla/Logging.h"
47 #include "mozilla/dom/Document.h"
48 #include "mozilla/dom/DocumentInlines.h"
49 #include "nsIXULRuntime.h"
50 #include "jsapi.h"
51 #include "nsContentUtils.h"
52 #include "mozilla/PendingAnimationTracker.h"
53 #include "mozilla/PendingFullscreenEvent.h"
54 #include "mozilla/dom/PerformanceMainThread.h"
55 #include "mozilla/Preferences.h"
56 #include "mozilla/StaticPrefs_apz.h"
57 #include "mozilla/StaticPrefs_gfx.h"
58 #include "mozilla/StaticPrefs_layout.h"
59 #include "mozilla/StaticPrefs_page_load.h"
60 #include "nsViewManager.h"
61 #include "GeckoProfiler.h"
62 #include "mozilla/dom/BrowserChild.h"
63 #include "mozilla/dom/CallbackDebuggerNotification.h"
64 #include "mozilla/dom/Event.h"
65 #include "mozilla/dom/Performance.h"
66 #include "mozilla/dom/Selection.h"
67 #include "mozilla/dom/VsyncChild.h"
68 #include "mozilla/dom/WindowBinding.h"
69 #include "mozilla/RestyleManager.h"
70 #include "mozilla/TaskController.h"
71 #include "Layers.h"
72 #include "imgIContainer.h"
73 #include "mozilla/dom/ScriptSettings.h"
74 #include "nsDocShell.h"
75 #include "nsISimpleEnumerator.h"
76 #include "nsJSEnvironment.h"
77 #include "mozilla/ScopeExit.h"
78 #include "mozilla/Telemetry.h"
79
80 #include "mozilla/ipc/BackgroundChild.h"
81 #include "mozilla/ipc/PBackgroundChild.h"
82 #include "VsyncSource.h"
83 #include "mozilla/VsyncDispatcher.h"
84 #include "mozilla/Unused.h"
85 #include "mozilla/TimelineConsumers.h"
86 #include "nsAnimationManager.h"
87 #include "nsDisplayList.h"
88 #include "nsDOMNavigationTiming.h"
89 #include "nsTransitionManager.h"
90
91 #if defined(MOZ_WIDGET_ANDROID)
92 # include "VRManagerChild.h"
93 #endif // defined(MOZ_WIDGET_ANDROID)
94
95 #ifdef MOZ_XUL
96 # include "nsXULPopupManager.h"
97 #endif
98
99 #include <numeric>
100
101 using namespace mozilla;
102 using namespace mozilla::widget;
103 using namespace mozilla::ipc;
104 using namespace mozilla::dom;
105 using namespace mozilla::layout;
106
107 static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver");
108 #define LOG(...) \
109 MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
110
111 #define DEFAULT_THROTTLED_FRAME_RATE 1
112 #define DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS 1000
113 #define DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS 100
114 // after 10 minutes, stop firing off inactive timers
115 #define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
116
117 // The number of seconds spent skipping frames because we are waiting for the
118 // compositor before logging.
119 #if defined(MOZ_ASAN)
120 # define REFRESH_WAIT_WARNING 5
121 #elif defined(DEBUG) && !defined(MOZ_VALGRIND)
122 # define REFRESH_WAIT_WARNING 5
123 #elif defined(DEBUG) && defined(MOZ_VALGRIND)
124 # define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 20 : 5)
125 #elif defined(MOZ_VALGRIND)
126 # define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 10 : 1)
127 #else
128 # define REFRESH_WAIT_WARNING 1
129 #endif
130
131 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsRefreshDriver::TickReasons);
132
133 namespace {
134 // The number outstanding nsRefreshDrivers (that have been created but not
135 // disconnected). When this reaches zero we will call
136 // nsRefreshDriver::Shutdown.
137 static uint32_t sRefreshDriverCount = 0;
138
139 // RAII-helper for recording elapsed duration for refresh tick phases.
140 class AutoRecordPhase {
141 public:
AutoRecordPhase(double * aResultMs)142 explicit AutoRecordPhase(double* aResultMs)
143 : mTotalMs(aResultMs), mStartTime(TimeStamp::Now()) {
144 MOZ_ASSERT(mTotalMs);
145 }
~AutoRecordPhase()146 ~AutoRecordPhase() {
147 *mTotalMs = (TimeStamp::Now() - mStartTime).ToMilliseconds();
148 }
149
150 private:
151 double* mTotalMs;
152 mozilla::TimeStamp mStartTime;
153 };
154
155 } // namespace
156
157 namespace mozilla {
158
159 /*
160 * The base class for all global refresh driver timers. It takes care
161 * of managing the list of refresh drivers attached to them and
162 * provides interfaces for querying/setting the rate and actually
163 * running a timer 'Tick'. Subclasses must implement StartTimer(),
164 * StopTimer(), and ScheduleNextTick() -- the first two just
165 * start/stop whatever timer mechanism is in use, and ScheduleNextTick
166 * is called at the start of the Tick() implementation to set a time
167 * for the next tick.
168 */
169 class RefreshDriverTimer {
170 public:
171 RefreshDriverTimer() = default;
172
NS_INLINE_DECL_REFCOUNTING(RefreshDriverTimer)173 NS_INLINE_DECL_REFCOUNTING(RefreshDriverTimer)
174
175 virtual void AddRefreshDriver(nsRefreshDriver* aDriver) {
176 LOG("[%p] AddRefreshDriver %p", this, aDriver);
177
178 bool startTimer =
179 mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
180 if (IsRootRefreshDriver(aDriver)) {
181 NS_ASSERTION(!mRootRefreshDrivers.Contains(aDriver),
182 "Adding a duplicate root refresh driver!");
183 mRootRefreshDrivers.AppendElement(aDriver);
184 } else {
185 NS_ASSERTION(!mContentRefreshDrivers.Contains(aDriver),
186 "Adding a duplicate content refresh driver!");
187 mContentRefreshDrivers.AppendElement(aDriver);
188 }
189
190 if (startTimer) {
191 StartTimer();
192 }
193 }
194
RemoveRefreshDriver(nsRefreshDriver * aDriver)195 void RemoveRefreshDriver(nsRefreshDriver* aDriver) {
196 LOG("[%p] RemoveRefreshDriver %p", this, aDriver);
197
198 if (IsRootRefreshDriver(aDriver)) {
199 NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver),
200 "RemoveRefreshDriver for a refresh driver that's not in the "
201 "root refresh list!");
202 mRootRefreshDrivers.RemoveElement(aDriver);
203 } else {
204 nsPresContext* pc = aDriver->GetPresContext();
205 nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
206 // During PresContext shutdown, we can't accurately detect
207 // if a root refresh driver exists or not. Therefore, we have to
208 // search and find out which list this driver exists in.
209 if (!rootContext) {
210 if (mRootRefreshDrivers.Contains(aDriver)) {
211 mRootRefreshDrivers.RemoveElement(aDriver);
212 } else {
213 NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
214 "RemoveRefreshDriver without a display root for a "
215 "driver that is not in the content refresh list");
216 mContentRefreshDrivers.RemoveElement(aDriver);
217 }
218 } else {
219 NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
220 "RemoveRefreshDriver for a driver that is not in the "
221 "content refresh list");
222 mContentRefreshDrivers.RemoveElement(aDriver);
223 }
224 }
225
226 bool stopTimer =
227 mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
228 if (stopTimer) {
229 StopTimer();
230 }
231 }
232
MostRecentRefresh() const233 TimeStamp MostRecentRefresh() const { return mLastFireTime; }
MostRecentRefreshVsyncId() const234 VsyncId MostRecentRefreshVsyncId() const { return mLastFireId; }
235
236 virtual TimeDuration GetTimerRate() = 0;
237
GetIdleDeadlineHint(TimeStamp aDefault)238 TimeStamp GetIdleDeadlineHint(TimeStamp aDefault) {
239 MOZ_ASSERT(NS_IsMainThread());
240
241 TimeStamp mostRecentRefresh = MostRecentRefresh();
242 TimeDuration refreshPeriod = GetTimerRate();
243 TimeStamp idleEnd = mostRecentRefresh + refreshPeriod;
244
245 // If we haven't painted for some time, then guess that we won't paint
246 // again for a while, so the refresh driver is not a good way to predict
247 // idle time.
248 if (idleEnd +
249 refreshPeriod *
250 StaticPrefs::layout_idle_period_required_quiescent_frames() <
251 TimeStamp::Now()) {
252 return aDefault;
253 }
254
255 // End the predicted idle time a little early, the amount controlled by a
256 // pref, to prevent overrunning the idle time and delaying a frame.
257 idleEnd = idleEnd - TimeDuration::FromMilliseconds(
258 StaticPrefs::layout_idle_period_time_limit());
259 return idleEnd < aDefault ? idleEnd : aDefault;
260 }
261
GetNextTickHint()262 Maybe<TimeStamp> GetNextTickHint() {
263 MOZ_ASSERT(NS_IsMainThread());
264 TimeStamp nextTick = MostRecentRefresh() + GetTimerRate();
265 return nextTick < TimeStamp::Now() ? Nothing() : Some(nextTick);
266 }
267
268 // Returns null if the RefreshDriverTimer is attached to several
269 // RefreshDrivers. That may happen for example when there are
270 // several windows open.
GetPresContextForOnlyRefreshDriver()271 nsPresContext* GetPresContextForOnlyRefreshDriver() {
272 if (mRootRefreshDrivers.Length() == 1 && mContentRefreshDrivers.IsEmpty()) {
273 return mRootRefreshDrivers[0]->GetPresContext();
274 }
275 if (mContentRefreshDrivers.Length() == 1 && mRootRefreshDrivers.IsEmpty()) {
276 return mContentRefreshDrivers[0]->GetPresContext();
277 }
278 return nullptr;
279 }
280
281 protected:
~RefreshDriverTimer()282 virtual ~RefreshDriverTimer() {
283 MOZ_ASSERT(
284 mContentRefreshDrivers.Length() == 0,
285 "Should have removed all content refresh drivers from here by now!");
286 MOZ_ASSERT(
287 mRootRefreshDrivers.Length() == 0,
288 "Should have removed all root refresh drivers from here by now!");
289 }
290
291 virtual void StartTimer() = 0;
292 virtual void StopTimer() = 0;
293 virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
294
IsRootRefreshDriver(nsRefreshDriver * aDriver)295 bool IsRootRefreshDriver(nsRefreshDriver* aDriver) {
296 nsPresContext* pc = aDriver->GetPresContext();
297 nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
298 if (!rootContext) {
299 return false;
300 }
301
302 return aDriver == rootContext->RefreshDriver();
303 }
304
305 /*
306 * Actually runs a tick, poking all the attached RefreshDrivers.
307 * Grabs the "now" time via TimeStamp::Now().
308 */
Tick()309 void Tick() {
310 TimeStamp now = TimeStamp::Now();
311 Tick(VsyncId(), now);
312 }
313
TickRefreshDrivers(VsyncId aId,TimeStamp aNow,nsTArray<RefPtr<nsRefreshDriver>> & aDrivers)314 void TickRefreshDrivers(VsyncId aId, TimeStamp aNow,
315 nsTArray<RefPtr<nsRefreshDriver>>& aDrivers) {
316 if (aDrivers.IsEmpty()) {
317 return;
318 }
319
320 for (nsRefreshDriver* driver : aDrivers.Clone()) {
321 // don't poke this driver if it's in test mode
322 if (driver->IsTestControllingRefreshesEnabled()) {
323 continue;
324 }
325
326 TickDriver(driver, aId, aNow);
327 }
328 }
329
330 /*
331 * Tick the refresh drivers based on the given timestamp.
332 */
Tick(VsyncId aId,TimeStamp now)333 void Tick(VsyncId aId, TimeStamp now) {
334 ScheduleNextTick(now);
335
336 mLastFireTime = now;
337 mLastFireId = aId;
338
339 LOG("[%p] ticking drivers...", this);
340
341 TickRefreshDrivers(aId, now, mContentRefreshDrivers);
342 TickRefreshDrivers(aId, now, mRootRefreshDrivers);
343
344 LOG("[%p] done.", this);
345 }
346
TickDriver(nsRefreshDriver * driver,VsyncId aId,TimeStamp now)347 static void TickDriver(nsRefreshDriver* driver, VsyncId aId, TimeStamp now) {
348 driver->Tick(aId, now);
349 }
350
351 TimeStamp mLastFireTime;
352 VsyncId mLastFireId;
353 TimeStamp mTargetTime;
354
355 nsTArray<RefPtr<nsRefreshDriver>> mContentRefreshDrivers;
356 nsTArray<RefPtr<nsRefreshDriver>> mRootRefreshDrivers;
357
358 // useful callback for nsITimer-based derived classes, here
359 // because of c++ protected shenanigans
TimerTick(nsITimer * aTimer,void * aClosure)360 static void TimerTick(nsITimer* aTimer, void* aClosure) {
361 RefPtr<RefreshDriverTimer> timer =
362 static_cast<RefreshDriverTimer*>(aClosure);
363 timer->Tick();
364 }
365 };
366
367 /*
368 * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that
369 * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to
370 * implement ScheduleNextTick and intelligently calculate the next time to tick,
371 * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain
372 * with its attempt at intelligent slack removal and such, so we don't do it.
373 */
374 class SimpleTimerBasedRefreshDriverTimer : public RefreshDriverTimer {
375 public:
376 /*
377 * aRate -- the delay, in milliseconds, requested between timer firings
378 */
SimpleTimerBasedRefreshDriverTimer(double aRate)379 explicit SimpleTimerBasedRefreshDriverTimer(double aRate) {
380 SetRate(aRate);
381 mTimer = NS_NewTimer();
382 }
383
~SimpleTimerBasedRefreshDriverTimer()384 virtual ~SimpleTimerBasedRefreshDriverTimer() override { StopTimer(); }
385
386 // will take effect at next timer tick
SetRate(double aNewRate)387 virtual void SetRate(double aNewRate) {
388 mRateMilliseconds = aNewRate;
389 mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
390 }
391
GetRate() const392 double GetRate() const { return mRateMilliseconds; }
393
GetTimerRate()394 TimeDuration GetTimerRate() override { return mRateDuration; }
395
396 protected:
StartTimer()397 void StartTimer() override {
398 // pretend we just fired, and we schedule the next tick normally
399 mLastFireTime = TimeStamp::Now();
400 mLastFireId = VsyncId();
401
402 mTargetTime = mLastFireTime + mRateDuration;
403
404 uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
405 mTimer->InitWithNamedFuncCallback(
406 TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT,
407 "SimpleTimerBasedRefreshDriverTimer::StartTimer");
408 }
409
StopTimer()410 void StopTimer() override { mTimer->Cancel(); }
411
412 double mRateMilliseconds;
413 TimeDuration mRateDuration;
414 RefPtr<nsITimer> mTimer;
415 };
416
417 /*
418 * A refresh driver that listens to vsync events and ticks the refresh driver
419 * on vsync intervals. We throttle the refresh driver if we get too many
420 * vsync events and wait to catch up again.
421 */
422 class VsyncRefreshDriverTimer : public RefreshDriverTimer {
423 public:
VsyncRefreshDriverTimer()424 VsyncRefreshDriverTimer()
425 : mVsyncDispatcher(nullptr),
426 mVsyncChild(nullptr),
427 mVsyncRate(TimeDuration::Forever()) {
428 MOZ_ASSERT(XRE_IsParentProcess());
429 MOZ_ASSERT(NS_IsMainThread());
430 mVsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync();
431 mVsyncObserver = new RefreshDriverVsyncObserver(this);
432 MOZ_ALWAYS_TRUE(mVsyncDispatcher =
433 mVsyncSource->GetRefreshTimerVsyncDispatcher());
434 }
435
436 // Constructor for when we have a local vsync source. As it is local, we do
437 // not have to worry about it being re-inited by gfxPlatform on frame rate
438 // change on the global source.
VsyncRefreshDriverTimer(const RefPtr<gfx::VsyncSource> & aVsyncSource)439 explicit VsyncRefreshDriverTimer(const RefPtr<gfx::VsyncSource>& aVsyncSource)
440 : mVsyncSource(aVsyncSource),
441 mVsyncDispatcher(nullptr),
442 mVsyncChild(nullptr),
443 mVsyncRate(TimeDuration::Forever()) {
444 MOZ_ASSERT(XRE_IsParentProcess());
445 MOZ_ASSERT(NS_IsMainThread());
446 mVsyncObserver = new RefreshDriverVsyncObserver(this);
447 MOZ_ALWAYS_TRUE(mVsyncDispatcher =
448 aVsyncSource->GetRefreshTimerVsyncDispatcher());
449 }
450
VsyncRefreshDriverTimer(const RefPtr<VsyncChild> & aVsyncChild)451 explicit VsyncRefreshDriverTimer(const RefPtr<VsyncChild>& aVsyncChild)
452 : mVsyncSource(nullptr),
453 mVsyncDispatcher(nullptr),
454 mVsyncChild(aVsyncChild),
455 mVsyncRate(TimeDuration::Forever()) {
456 MOZ_ASSERT(XRE_IsContentProcess());
457 MOZ_ASSERT(NS_IsMainThread());
458 mVsyncObserver = new RefreshDriverVsyncObserver(this);
459 }
460
GetTimerRate()461 TimeDuration GetTimerRate() override {
462 if (mVsyncSource) {
463 mVsyncRate = mVsyncSource->GetGlobalDisplay().GetVsyncRate();
464 } else if (mVsyncChild) {
465 mVsyncRate = mVsyncChild->GetVsyncRate();
466 }
467
468 // If hardware queries fail / are unsupported, we have to just guess.
469 return mVsyncRate != TimeDuration::Forever()
470 ? mVsyncRate
471 : TimeDuration::FromMilliseconds(1000.0 / 60.0);
472 }
473
474 private:
475 // Since VsyncObservers are refCounted, but the RefreshDriverTimer are
476 // explicitly shutdown. We create an inner class that has the VsyncObserver
477 // and is shutdown when the RefreshDriverTimer is deleted.
478 class RefreshDriverVsyncObserver final : public VsyncObserver {
479 public:
RefreshDriverVsyncObserver(VsyncRefreshDriverTimer * aVsyncRefreshDriverTimer)480 explicit RefreshDriverVsyncObserver(
481 VsyncRefreshDriverTimer* aVsyncRefreshDriverTimer)
482 : mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer),
483 mParentProcessRefreshTickLock("RefreshTickLock"),
484 mPendingParentProcessVsync(false),
485 mRecentVsync(TimeStamp::Now()),
486 mLastTick(TimeStamp::Now()),
487 mVsyncRate(TimeDuration::Forever()),
488 mProcessedVsync(true) {
489 MOZ_ASSERT(NS_IsMainThread());
490 }
491
492 class ParentProcessVsyncNotifier final : public Runnable,
493 public nsIRunnablePriority {
494 public:
ParentProcessVsyncNotifier(RefreshDriverVsyncObserver * aObserver)495 explicit ParentProcessVsyncNotifier(RefreshDriverVsyncObserver* aObserver)
496 : Runnable(
497 "VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::"
498 "ParentProcessVsyncNotifier"),
499 mObserver(aObserver) {}
500
501 NS_DECL_ISUPPORTS_INHERITED
502
Run()503 NS_IMETHOD Run() override {
504 MOZ_ASSERT(NS_IsMainThread());
505 sVsyncPriorityEnabled = mozilla::BrowserTabsRemoteAutostart();
506
507 mObserver->NotifyParentProcessVsync();
508 return NS_OK;
509 }
510
GetPriority(uint32_t * aPriority)511 NS_IMETHOD GetPriority(uint32_t* aPriority) override {
512 *aPriority = sVsyncPriorityEnabled
513 ? nsIRunnablePriority::PRIORITY_VSYNC
514 : nsIRunnablePriority::PRIORITY_NORMAL;
515 return NS_OK;
516 }
517
518 private:
519 ~ParentProcessVsyncNotifier() = default;
520 RefPtr<RefreshDriverVsyncObserver> mObserver;
521 static mozilla::Atomic<bool> sVsyncPriorityEnabled;
522 };
523
NotifyVsync(const VsyncEvent & aVsync)524 bool NotifyVsync(const VsyncEvent& aVsync) override {
525 // Compress vsync notifications such that only 1 may run at a time
526 // This is so that we don't flood the refresh driver with vsync messages
527 // if the main thread is blocked for long periods of time
528 { // scope lock
529 MonitorAutoLock lock(mParentProcessRefreshTickLock);
530 mRecentParentProcessVsync = aVsync;
531 if (mPendingParentProcessVsync) {
532 return true;
533 }
534 mPendingParentProcessVsync = true;
535 }
536
537 if (XRE_IsContentProcess()) {
538 NotifyParentProcessVsync();
539 return true;
540 }
541
542 nsCOMPtr<nsIRunnable> vsyncEvent = new ParentProcessVsyncNotifier(this);
543 NS_DispatchToMainThread(vsyncEvent);
544 return true;
545 }
546
NotifyParentProcessVsync()547 void NotifyParentProcessVsync() {
548 // IMPORTANT: All paths through this method MUST hold a strong ref on
549 // |this| for the duration of the TickRefreshDriver callback.
550 MOZ_ASSERT(NS_IsMainThread());
551
552 // This clears the input handling start time.
553 InputTaskManager::Get()->SetInputHandlingStartTime(TimeStamp());
554
555 VsyncEvent aVsync;
556 {
557 MonitorAutoLock lock(mParentProcessRefreshTickLock);
558 aVsync = mRecentParentProcessVsync;
559 mPendingParentProcessVsync = false;
560 }
561
562 mRecentVsync = aVsync.mTime;
563 mRecentVsyncId = aVsync.mId;
564 if (!mBlockUntil.IsNull() && mBlockUntil > aVsync.mTime) {
565 if (mProcessedVsync) {
566 // Re-post vsync update as a normal priority runnable. This way
567 // runnables already in normal priority queue get processed.
568 mProcessedVsync = false;
569 nsCOMPtr<nsIRunnable> vsyncEvent = NewRunnableMethod<>(
570 "RefreshDriverVsyncObserver::NormalPriorityNotify", this,
571 &RefreshDriverVsyncObserver::NormalPriorityNotify);
572 NS_DispatchToMainThread(vsyncEvent);
573 }
574
575 return;
576 }
577
578 if (StaticPrefs::layout_lower_priority_refresh_driver_during_load() &&
579 mVsyncRefreshDriverTimer) {
580 nsPresContext* pctx =
581 mVsyncRefreshDriverTimer->GetPresContextForOnlyRefreshDriver();
582 if (pctx && pctx->HadContentfulPaint() && pctx->Document() &&
583 pctx->Document()->GetReadyStateEnum() <
584 Document::READYSTATE_COMPLETE) {
585 nsPIDOMWindowInner* win = pctx->Document()->GetInnerWindow();
586 uint32_t frameRateMultiplier = pctx->GetNextFrameRateMultiplier();
587 if (!frameRateMultiplier) {
588 pctx->DidUseFrameRateMultiplier();
589 }
590 if (win && frameRateMultiplier) {
591 dom::Performance* perf = win->GetPerformance();
592 // Limit slower refresh rate to 5 seconds between the
593 // first contentful paint and page load.
594 if (perf && perf->Now() <
595 StaticPrefs::page_load_deprioritization_period()) {
596 if (mProcessedVsync) {
597 mProcessedVsync = false;
598 // Handle this case similarly to the code above, but just
599 // use idle queue.
600 TimeDuration rate = mVsyncRefreshDriverTimer->GetTimerRate();
601 uint32_t slowRate = static_cast<uint32_t>(
602 rate.ToMilliseconds() * frameRateMultiplier);
603 pctx->DidUseFrameRateMultiplier();
604 nsCOMPtr<nsIRunnable> vsyncEvent = NewRunnableMethod<>(
605 "RefreshDriverVsyncObserver::NormalPriorityNotify[IDLE]",
606 this, &RefreshDriverVsyncObserver::NormalPriorityNotify);
607 NS_DispatchToCurrentThreadQueue(vsyncEvent.forget(), slowRate,
608 EventQueuePriority::Idle);
609 }
610 return;
611 }
612 }
613 }
614 }
615
616 RefPtr<RefreshDriverVsyncObserver> kungFuDeathGrip(this);
617 TickRefreshDriver(aVsync.mId, aVsync.mTime);
618 }
619
Shutdown()620 void Shutdown() {
621 MOZ_ASSERT(NS_IsMainThread());
622 mVsyncRefreshDriverTimer = nullptr;
623 }
624
OnTimerStart()625 void OnTimerStart() { mLastTick = TimeStamp::Now(); }
626
NormalPriorityNotify()627 void NormalPriorityNotify() {
628 if (mLastProcessedTick.IsNull() || mRecentVsync > mLastProcessedTick) {
629 // mBlockUntil is for high priority vsync notifications only.
630 mBlockUntil = TimeStamp();
631 TickRefreshDriver(mRecentVsyncId, mRecentVsync);
632 }
633
634 mProcessedVsync = true;
635 }
636
637 private:
638 ~RefreshDriverVsyncObserver() = default;
639
RecordTelemetryProbes(TimeStamp aVsyncTimestamp)640 void RecordTelemetryProbes(TimeStamp aVsyncTimestamp) {
641 MOZ_ASSERT(NS_IsMainThread());
642 #ifndef ANDROID /* bug 1142079 */
643 if (XRE_IsParentProcess()) {
644 TimeDuration vsyncLatency = TimeStamp::Now() - aVsyncTimestamp;
645 uint32_t sample = (uint32_t)vsyncLatency.ToMilliseconds();
646 Telemetry::Accumulate(
647 Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS, sample);
648 Telemetry::Accumulate(
649 Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, sample);
650 } else if (mVsyncRate != TimeDuration::Forever()) {
651 TimeDuration contentDelay = (TimeStamp::Now() - mLastTick) - mVsyncRate;
652 if (contentDelay.ToMilliseconds() < 0) {
653 // Vsyncs are noisy and some can come at a rate quicker than
654 // the reported hardware rate. In those cases, consider that we have 0
655 // delay.
656 contentDelay = TimeDuration::FromMilliseconds(0);
657 }
658 uint32_t sample = (uint32_t)contentDelay.ToMilliseconds();
659 Telemetry::Accumulate(
660 Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS, sample);
661 Telemetry::Accumulate(
662 Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, sample);
663 } else {
664 // Request the vsync rate from the parent process. Might be a few vsyncs
665 // until the parent responds.
666 if (mVsyncRefreshDriverTimer) {
667 mVsyncRate = mVsyncRefreshDriverTimer->mVsyncChild->GetVsyncRate();
668 }
669 }
670 #endif
671 }
672
TickRefreshDriver(VsyncId aId,TimeStamp aVsyncTimestamp)673 void TickRefreshDriver(VsyncId aId, TimeStamp aVsyncTimestamp) {
674 MOZ_ASSERT(NS_IsMainThread());
675
676 RecordTelemetryProbes(aVsyncTimestamp);
677 mLastTick = TimeStamp::Now();
678 mLastProcessedTick = aVsyncTimestamp;
679
680 // On 32-bit Windows we sometimes get times where TimeStamp::Now() is not
681 // monotonic because the underlying system apis produce non-monontonic
682 // results. (bug 1306896)
683 #if !defined(_WIN32)
684 // Do not compare timestamps unless they are both canonical or fuzzy
685 DebugOnly<TimeStamp> rightnow = TimeStamp::Now();
686 MOZ_ASSERT_IF(
687 (*&rightnow).UsedCanonicalNow() == aVsyncTimestamp.UsedCanonicalNow(),
688 aVsyncTimestamp <= *&rightnow);
689 #endif
690
691 // Let also non-RefreshDriver code to run at least for awhile if we have
692 // a mVsyncRefreshDriverTimer. Note, if nothing else is running,
693 // RefreshDriver will still run as fast as possible, some ticks will
694 // just be triggered from a normal priority runnable.
695 TimeDuration timeForOutsideTick = TimeDuration::FromMilliseconds(0.0f);
696
697 // We might have a problem that we call ~VsyncRefreshDriverTimer() before
698 // the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer
699 // before use.
700 if (mVsyncRefreshDriverTimer) {
701 timeForOutsideTick = TimeDuration::FromMilliseconds(
702 mVsyncRefreshDriverTimer->GetTimerRate().ToMilliseconds() / 100.0f);
703 RefPtr<VsyncRefreshDriverTimer> timer = mVsyncRefreshDriverTimer;
704 timer->RunRefreshDrivers(aId, aVsyncTimestamp);
705 // Note: mVsyncRefreshDriverTimer might be null now.
706 }
707
708 TimeDuration tickDuration = TimeStamp::Now() - mLastTick;
709 mBlockUntil = aVsyncTimestamp + tickDuration + timeForOutsideTick;
710 }
711
712 // VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will
713 // be always available before Shutdown(). We can just use the raw pointer
714 // here.
715 VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer;
716
717 Monitor mParentProcessRefreshTickLock;
718 VsyncEvent mRecentParentProcessVsync;
719 bool mPendingParentProcessVsync;
720
721 TimeStamp mRecentVsync;
722 VsyncId mRecentVsyncId;
723 TimeStamp mLastTick;
724 TimeStamp mLastProcessedTick;
725 TimeStamp mBlockUntil;
726 TimeDuration mVsyncRate;
727 bool mProcessedVsync;
728 }; // RefreshDriverVsyncObserver
729
~VsyncRefreshDriverTimer()730 ~VsyncRefreshDriverTimer() override {
731 if (mVsyncDispatcher) {
732 mVsyncDispatcher->RemoveChildRefreshTimer(mVsyncObserver);
733 mVsyncDispatcher = nullptr;
734 } else if (mVsyncChild) {
735 mVsyncChild->RemoveChildRefreshTimer(mVsyncObserver);
736 mVsyncChild = nullptr;
737 }
738
739 // Detach current vsync timer from this VsyncObserver. The observer will no
740 // longer tick this timer.
741 mVsyncObserver->Shutdown();
742 mVsyncObserver = nullptr;
743 }
744
StartTimer()745 void StartTimer() override {
746 MOZ_ASSERT(NS_IsMainThread());
747
748 mLastFireTime = TimeStamp::Now();
749 mLastFireId = VsyncId();
750
751 if (mVsyncDispatcher) {
752 mVsyncDispatcher->AddChildRefreshTimer(mVsyncObserver);
753 } else if (mVsyncChild) {
754 mVsyncChild->AddChildRefreshTimer(mVsyncObserver);
755 mVsyncObserver->OnTimerStart();
756 }
757 }
758
StopTimer()759 void StopTimer() override {
760 MOZ_ASSERT(NS_IsMainThread());
761
762 if (mVsyncDispatcher) {
763 mVsyncDispatcher->RemoveChildRefreshTimer(mVsyncObserver);
764 } else if (mVsyncChild) {
765 mVsyncChild->RemoveChildRefreshTimer(mVsyncObserver);
766 }
767 }
768
ScheduleNextTick(TimeStamp aNowTime)769 void ScheduleNextTick(TimeStamp aNowTime) override {
770 // Do nothing since we just wait for the next vsync from
771 // RefreshDriverVsyncObserver.
772 }
773
RunRefreshDrivers(VsyncId aId,TimeStamp aTimeStamp)774 void RunRefreshDrivers(VsyncId aId, TimeStamp aTimeStamp) {
775 Tick(aId, aTimeStamp);
776 for (auto& driver : mContentRefreshDrivers) {
777 driver->FinishedVsyncTick();
778 }
779 for (auto& driver : mRootRefreshDrivers) {
780 driver->FinishedVsyncTick();
781 }
782 }
783
784 // When using local vsync source, we keep a strong ref to it here to ensure
785 // that the weak ref in the vsync dispatcher does not end up dangling.
786 // As this is a local vsync source, it is not affected by gfxPlatform vsync
787 // source reinit.
788 RefPtr<gfx::VsyncSource> mVsyncSource;
789 RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
790 // Used for parent process.
791 RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher;
792 // Used for child process.
793 // The mVsyncChild will be always available before VsncChild::ActorDestroy().
794 // After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
795 RefPtr<VsyncChild> mVsyncChild;
796 TimeDuration mVsyncRate;
797 }; // VsyncRefreshDriverTimer
798
799 NS_IMPL_ISUPPORTS_INHERITED(
800 VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::
801 ParentProcessVsyncNotifier,
802 Runnable, nsIRunnablePriority)
803
804 mozilla::Atomic<bool> VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::
805 ParentProcessVsyncNotifier::sVsyncPriorityEnabled(false);
806
807 /**
808 * Since the content process takes some time to setup
809 * the vsync IPC connection, this timer is used
810 * during the intial startup process.
811 * During initial startup, the refresh drivers
812 * are ticked off this timer, and are swapped out once content
813 * vsync IPC connection is established.
814 */
815 class StartupRefreshDriverTimer : public SimpleTimerBasedRefreshDriverTimer {
816 public:
StartupRefreshDriverTimer(double aRate)817 explicit StartupRefreshDriverTimer(double aRate)
818 : SimpleTimerBasedRefreshDriverTimer(aRate) {}
819
820 protected:
ScheduleNextTick(TimeStamp aNowTime)821 void ScheduleNextTick(TimeStamp aNowTime) override {
822 // Since this is only used for startup, it isn't super critical
823 // that we tick at consistent intervals.
824 TimeStamp newTarget = aNowTime + mRateDuration;
825 uint32_t delay =
826 static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds());
827 mTimer->InitWithNamedFuncCallback(
828 TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT,
829 "StartupRefreshDriverTimer::ScheduleNextTick");
830 mTargetTime = newTarget;
831 }
832 };
833
834 /*
835 * A RefreshDriverTimer for inactive documents. When a new refresh driver is
836 * added, the rate is reset to the base (normally 1s/1fps). Every time
837 * it ticks, a single refresh driver is poked. Once they have all been poked,
838 * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that
839 * point, the timer is quiet and doesn't tick (until something is added to it
840 * again).
841 *
842 * When a timer is removed, there is a possibility of another timer
843 * being skipped for one cycle. We could avoid this by adjusting
844 * mNextDriverIndex in RemoveRefreshDriver, but there's little need to
845 * add that complexity. All we want is for inactive drivers to tick
846 * at some point, but we don't care too much about how often.
847 */
848 class InactiveRefreshDriverTimer final
849 : public SimpleTimerBasedRefreshDriverTimer {
850 public:
InactiveRefreshDriverTimer(double aRate)851 explicit InactiveRefreshDriverTimer(double aRate)
852 : SimpleTimerBasedRefreshDriverTimer(aRate),
853 mNextTickDuration(aRate),
854 mDisableAfterMilliseconds(-1.0),
855 mNextDriverIndex(0) {}
856
InactiveRefreshDriverTimer(double aRate,double aDisableAfterMilliseconds)857 InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
858 : SimpleTimerBasedRefreshDriverTimer(aRate),
859 mNextTickDuration(aRate),
860 mDisableAfterMilliseconds(aDisableAfterMilliseconds),
861 mNextDriverIndex(0) {}
862
AddRefreshDriver(nsRefreshDriver * aDriver)863 void AddRefreshDriver(nsRefreshDriver* aDriver) override {
864 RefreshDriverTimer::AddRefreshDriver(aDriver);
865
866 LOG("[%p] inactive timer got new refresh driver %p, resetting rate", this,
867 aDriver);
868
869 // reset the timer, and start with the newly added one next time.
870 mNextTickDuration = mRateMilliseconds;
871
872 // we don't really have to start with the newly added one, but we may as
873 // well not tick the old ones at the fastest rate any more than we need to.
874 mNextDriverIndex = GetRefreshDriverCount() - 1;
875
876 StopTimer();
877 StartTimer();
878 }
879
GetTimerRate()880 TimeDuration GetTimerRate() override {
881 return TimeDuration::FromMilliseconds(mNextTickDuration);
882 }
883
884 protected:
GetRefreshDriverCount()885 uint32_t GetRefreshDriverCount() {
886 return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length();
887 }
888
StartTimer()889 void StartTimer() override {
890 mLastFireTime = TimeStamp::Now();
891 mLastFireId = VsyncId();
892
893 mTargetTime = mLastFireTime + mRateDuration;
894
895 uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
896 mTimer->InitWithNamedFuncCallback(TimerTickOne, this, delay,
897 nsITimer::TYPE_ONE_SHOT,
898 "InactiveRefreshDriverTimer::StartTimer");
899 }
900
StopTimer()901 void StopTimer() override { mTimer->Cancel(); }
902
ScheduleNextTick(TimeStamp aNowTime)903 void ScheduleNextTick(TimeStamp aNowTime) override {
904 if (mDisableAfterMilliseconds > 0.0 &&
905 mNextTickDuration > mDisableAfterMilliseconds) {
906 // We hit the time after which we should disable
907 // inactive window refreshes; don't schedule anything
908 // until we get kicked by an AddRefreshDriver call.
909 return;
910 }
911
912 // double the next tick time if we've already gone through all of them once
913 if (mNextDriverIndex >= GetRefreshDriverCount()) {
914 mNextTickDuration *= 2.0;
915 mNextDriverIndex = 0;
916 }
917
918 // this doesn't need to be precise; do a simple schedule
919 uint32_t delay = static_cast<uint32_t>(mNextTickDuration);
920 mTimer->InitWithNamedFuncCallback(
921 TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT,
922 "InactiveRefreshDriverTimer::ScheduleNextTick");
923
924 LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this,
925 mNextTickDuration, mNextDriverIndex, GetRefreshDriverCount());
926 }
927
928 /* Runs just one driver's tick. */
TickOne()929 void TickOne() {
930 TimeStamp now = TimeStamp::Now();
931
932 ScheduleNextTick(now);
933
934 mLastFireTime = now;
935 mLastFireId = VsyncId();
936
937 nsTArray<RefPtr<nsRefreshDriver>> drivers(mContentRefreshDrivers.Clone());
938 drivers.AppendElements(mRootRefreshDrivers);
939 size_t index = mNextDriverIndex;
940
941 if (index < drivers.Length() &&
942 !drivers[index]->IsTestControllingRefreshesEnabled()) {
943 TickDriver(drivers[index], VsyncId(), now);
944 }
945
946 mNextDriverIndex++;
947 }
948
TimerTickOne(nsITimer * aTimer,void * aClosure)949 static void TimerTickOne(nsITimer* aTimer, void* aClosure) {
950 RefPtr<InactiveRefreshDriverTimer> timer =
951 static_cast<InactiveRefreshDriverTimer*>(aClosure);
952 timer->TickOne();
953 }
954
955 double mNextTickDuration;
956 double mDisableAfterMilliseconds;
957 uint32_t mNextDriverIndex;
958 };
959
960 } // namespace mozilla
961
962 static StaticRefPtr<RefreshDriverTimer> sRegularRateTimer;
963 static nsTArray<RefreshDriverTimer*>* sRegularRateTimerList;
964 static StaticRefPtr<InactiveRefreshDriverTimer> sThrottledRateTimer;
965
CreateVsyncRefreshTimer()966 void nsRefreshDriver::CreateVsyncRefreshTimer() {
967 MOZ_ASSERT(NS_IsMainThread());
968
969 if (gfxPlatform::IsInLayoutAsapMode()) {
970 return;
971 }
972
973 if (!mOwnTimer) {
974 // If available, we fetch the widget-specific vsync source.
975 nsPresContext* pc = GetPresContext();
976 nsIWidget* widget = pc->GetRootWidget();
977 if (widget) {
978 if (RefPtr<gfx::VsyncSource> localVsyncSource =
979 widget->GetVsyncSource()) {
980 mOwnTimer = new VsyncRefreshDriverTimer(localVsyncSource);
981 sRegularRateTimerList->AppendElement(mOwnTimer.get());
982 return;
983 }
984 if (BrowserChild* browserChild = widget->GetOwningBrowserChild()) {
985 if (RefPtr<VsyncChild> localVsyncSource =
986 browserChild->GetVsyncChild()) {
987 mOwnTimer = new VsyncRefreshDriverTimer(localVsyncSource);
988 sRegularRateTimerList->AppendElement(mOwnTimer.get());
989 return;
990 }
991 }
992 }
993 }
994 if (!sRegularRateTimer) {
995 if (XRE_IsParentProcess()) {
996 // Make sure all vsync systems are ready.
997 gfxPlatform::GetPlatform();
998 // In parent process, we can create the VsyncRefreshDriverTimer directly.
999 sRegularRateTimer = new VsyncRefreshDriverTimer();
1000 } else {
1001 PBackgroundChild* actorChild =
1002 BackgroundChild::GetOrCreateForCurrentThread();
1003 if (NS_WARN_IF(!actorChild)) {
1004 return;
1005 }
1006
1007 dom::PVsyncChild* actor = actorChild->SendPVsyncConstructor();
1008 if (NS_WARN_IF(!actor)) {
1009 return;
1010 }
1011
1012 dom::VsyncChild* child = static_cast<dom::VsyncChild*>(actor);
1013
1014 RefPtr<RefreshDriverTimer> vsyncRefreshDriverTimer =
1015 new VsyncRefreshDriverTimer(child);
1016
1017 sRegularRateTimer = std::move(vsyncRefreshDriverTimer);
1018 }
1019 }
1020 }
1021
GetFirstFrameDelay(imgIRequest * req)1022 static uint32_t GetFirstFrameDelay(imgIRequest* req) {
1023 nsCOMPtr<imgIContainer> container;
1024 if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) {
1025 return 0;
1026 }
1027
1028 // If this image isn't animated, there isn't a first frame delay.
1029 int32_t delay = container->GetFirstFrameDelay();
1030 if (delay < 0) return 0;
1031
1032 return static_cast<uint32_t>(delay);
1033 }
1034
1035 /* static */
Shutdown()1036 void nsRefreshDriver::Shutdown() {
1037 MOZ_ASSERT(NS_IsMainThread());
1038 // clean up our timers
1039 sRegularRateTimer = nullptr;
1040 delete sRegularRateTimerList;
1041 sRegularRateTimerList = nullptr;
1042 sThrottledRateTimer = nullptr;
1043 }
1044
1045 /* static */
DefaultInterval()1046 int32_t nsRefreshDriver::DefaultInterval() {
1047 return NSToIntRound(1000.0 / gfxPlatform::GetDefaultFrameRate());
1048 }
1049
1050 // Compute the interval to use for the refresh driver timer, in milliseconds.
1051 // outIsDefault indicates that rate was not explicitly set by the user
1052 // so we might choose other, more appropriate rates (e.g. vsync, etc)
1053 // layout.frame_rate=0 indicates "ASAP mode".
1054 // In ASAP mode rendering is iterated as fast as possible (typically for stress
1055 // testing). A target rate of 10k is used internally instead of special-handling
1056 // 0. Backends which block on swap/present/etc should try to not block when
1057 // layout.frame_rate=0 - to comply with "ASAP" as much as possible.
GetRegularTimerInterval() const1058 double nsRefreshDriver::GetRegularTimerInterval() const {
1059 int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
1060 if (rate < 0) {
1061 rate = gfxPlatform::GetDefaultFrameRate();
1062 } else if (rate == 0) {
1063 rate = 10000;
1064 }
1065
1066 return 1000.0 / rate;
1067 }
1068
1069 /* static */
GetThrottledTimerInterval()1070 double nsRefreshDriver::GetThrottledTimerInterval() {
1071 int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1);
1072 if (rate <= 0) {
1073 rate = DEFAULT_THROTTLED_FRAME_RATE;
1074 }
1075 return 1000.0 / rate;
1076 }
1077
1078 /* static */ mozilla::TimeDuration
GetMinRecomputeVisibilityInterval()1079 nsRefreshDriver::GetMinRecomputeVisibilityInterval() {
1080 int32_t interval =
1081 Preferences::GetInt("layout.visibility.min-recompute-interval-ms", -1);
1082 if (interval <= 0) {
1083 interval = DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS;
1084 }
1085 return TimeDuration::FromMilliseconds(interval);
1086 }
1087
ChooseTimer()1088 RefreshDriverTimer* nsRefreshDriver::ChooseTimer() {
1089 if (mThrottled) {
1090 if (!sThrottledRateTimer)
1091 sThrottledRateTimer = new InactiveRefreshDriverTimer(
1092 GetThrottledTimerInterval(),
1093 DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0);
1094 return sThrottledRateTimer;
1095 }
1096
1097 if (!mOwnTimer) {
1098 CreateVsyncRefreshTimer();
1099 }
1100
1101 if (mOwnTimer) {
1102 return mOwnTimer.get();
1103 }
1104
1105 if (!sRegularRateTimer) {
1106 double rate = GetRegularTimerInterval();
1107 sRegularRateTimer = new StartupRefreshDriverTimer(rate);
1108 }
1109
1110 return sRegularRateTimer;
1111 }
1112
nsRefreshDriver(nsPresContext * aPresContext)1113 nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
1114 : mActiveTimer(nullptr),
1115 mOwnTimer(nullptr),
1116 mPresContext(aPresContext),
1117 mRootRefresh(nullptr),
1118 mNextTransactionId{0},
1119 mFreezeCount(0),
1120 mThrottledFrameRequestInterval(
1121 TimeDuration::FromMilliseconds(GetThrottledTimerInterval())),
1122 mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
1123 mThrottled(false),
1124 mNeedToRecomputeVisibility(false),
1125 mTestControllingRefreshes(false),
1126 mViewManagerFlushIsPending(false),
1127 mHasScheduleFlush(false),
1128 mInRefresh(false),
1129 mWaitingForTransaction(false),
1130 mSkippedPaints(false),
1131 mResizeSuppressed(false),
1132 mNotifyDOMContentFlushed(false),
1133 mNeedToUpdateIntersectionObservations(false),
1134 mInNormalTick(false),
1135 mAttemptedExtraTickSinceLastVsync(false),
1136 mHasExceededAfterLoadTickPeriod(false),
1137 mWarningThreshold(REFRESH_WAIT_WARNING) {
1138 MOZ_ASSERT(NS_IsMainThread());
1139 MOZ_ASSERT(mPresContext,
1140 "Need a pres context to tell us to call Disconnect() later "
1141 "and decrement sRefreshDriverCount.");
1142 mMostRecentRefresh = TimeStamp::Now();
1143 mNextThrottledFrameRequestTick = mMostRecentRefresh;
1144 mNextRecomputeVisibilityTick = mMostRecentRefresh;
1145
1146 if (!sRegularRateTimerList) {
1147 sRegularRateTimerList = new nsTArray<RefreshDriverTimer*>();
1148 }
1149 ++sRefreshDriverCount;
1150 }
1151
~nsRefreshDriver()1152 nsRefreshDriver::~nsRefreshDriver() {
1153 MOZ_ASSERT(NS_IsMainThread());
1154 MOZ_ASSERT(ObserverCount() == mEarlyRunners.Length(),
1155 "observers, except pending selection scrolls, "
1156 "should have been unregistered");
1157 MOZ_ASSERT(!mActiveTimer, "timer should be gone");
1158 MOZ_ASSERT(!mPresContext,
1159 "Should have called Disconnect() and decremented "
1160 "sRefreshDriverCount!");
1161
1162 if (mRootRefresh) {
1163 mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
1164 mRootRefresh = nullptr;
1165 }
1166 if (mOwnTimer && sRegularRateTimerList) {
1167 sRegularRateTimerList->RemoveElement(mOwnTimer.get());
1168 }
1169 }
1170
1171 // Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh
1172 // for description.
AdvanceTimeAndRefresh(int64_t aMilliseconds)1173 void nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) {
1174 // ensure that we're removed from our driver
1175 StopTimer();
1176
1177 if (!mTestControllingRefreshes) {
1178 mMostRecentRefresh = TimeStamp::Now();
1179
1180 mTestControllingRefreshes = true;
1181 if (mWaitingForTransaction) {
1182 // Disable any refresh driver throttling when entering test mode
1183 mWaitingForTransaction = false;
1184 mSkippedPaints = false;
1185 mWarningThreshold = REFRESH_WAIT_WARNING;
1186 }
1187 }
1188
1189 mMostRecentRefresh += TimeDuration::FromMilliseconds((double)aMilliseconds);
1190
1191 mozilla::dom::AutoNoJSAPI nojsapi;
1192 DoTick();
1193 }
1194
RestoreNormalRefresh()1195 void nsRefreshDriver::RestoreNormalRefresh() {
1196 mTestControllingRefreshes = false;
1197 EnsureTimerStarted(eAllowTimeToGoBackwards);
1198 mPendingTransactions.Clear();
1199 }
1200
MostRecentRefresh(bool aEnsureTimerStarted) const1201 TimeStamp nsRefreshDriver::MostRecentRefresh(bool aEnsureTimerStarted) const {
1202 // In case of stylo traversal, we have already activated the refresh driver in
1203 // RestyleManager::ProcessPendingRestyles().
1204 if (aEnsureTimerStarted && !ServoStyleSet::IsInServoTraversal()) {
1205 const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
1206 }
1207
1208 return mMostRecentRefresh;
1209 }
1210
AddRefreshObserver(nsARefreshObserver * aObserver,FlushType aFlushType,const char * aObserverDescription)1211 void nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
1212 FlushType aFlushType,
1213 const char* aObserverDescription) {
1214 ObserverArray& array = ArrayFor(aFlushType);
1215 array.AppendElement(ObserverData{
1216 aObserver, aObserverDescription, TimeStamp::Now(),
1217 mPresContext
1218 ? MarkerInnerWindowIdFromDocShell(mPresContext->GetDocShell())
1219 : MarkerInnerWindowId::NoId(),
1220 profiler_capture_backtrace(), aFlushType});
1221 EnsureTimerStarted();
1222 }
1223
RemoveRefreshObserver(nsARefreshObserver * aObserver,FlushType aFlushType)1224 bool nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
1225 FlushType aFlushType) {
1226 ObserverArray& array = ArrayFor(aFlushType);
1227 auto index = array.IndexOf(aObserver);
1228 if (index == ObserverArray::array_type::NoIndex) {
1229 return false;
1230 }
1231
1232 if (profiler_can_accept_markers()) {
1233 auto& data = array.ElementAt(index);
1234 nsPrintfCString str("%s [%s]", data.mDescription,
1235 kFlushTypeNames[aFlushType]);
1236 PROFILER_MARKER_TEXT(
1237 "RefreshObserver", GRAPHICS,
1238 MarkerOptions(MarkerStack::TakeBacktrace(std::move(data.mCause)),
1239 MarkerTiming::IntervalUntilNowFrom(data.mRegisterTime),
1240 std::move(data.mInnerWindowId)),
1241 str);
1242 }
1243
1244 array.RemoveElementAt(index);
1245 return true;
1246 }
1247
AddTimerAdjustmentObserver(nsATimerAdjustmentObserver * aObserver)1248 void nsRefreshDriver::AddTimerAdjustmentObserver(
1249 nsATimerAdjustmentObserver* aObserver) {
1250 MOZ_ASSERT(!mTimerAdjustmentObservers.Contains(aObserver));
1251 mTimerAdjustmentObservers.AppendElement(aObserver);
1252 }
1253
RemoveTimerAdjustmentObserver(nsATimerAdjustmentObserver * aObserver)1254 void nsRefreshDriver::RemoveTimerAdjustmentObserver(
1255 nsATimerAdjustmentObserver* aObserver) {
1256 MOZ_ASSERT(mTimerAdjustmentObservers.Contains(aObserver));
1257 mTimerAdjustmentObservers.RemoveElement(aObserver);
1258 }
1259
PostVisualViewportResizeEvent(VVPResizeEvent * aResizeEvent)1260 void nsRefreshDriver::PostVisualViewportResizeEvent(
1261 VVPResizeEvent* aResizeEvent) {
1262 mVisualViewportResizeEvents.AppendElement(aResizeEvent);
1263 EnsureTimerStarted();
1264 }
1265
DispatchVisualViewportResizeEvents()1266 void nsRefreshDriver::DispatchVisualViewportResizeEvents() {
1267 // We're taking a hint from scroll events and only dispatch the current set
1268 // of queued resize events. If additional events are posted in response to
1269 // the current events being dispatched, we'll dispatch them on the next tick.
1270 VisualViewportResizeEventArray events =
1271 std::move(mVisualViewportResizeEvents);
1272 for (auto& event : events) {
1273 event->Run();
1274 }
1275 }
1276
PostScrollEvent(mozilla::Runnable * aScrollEvent,bool aDelayed)1277 void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent,
1278 bool aDelayed) {
1279 if (aDelayed) {
1280 mDelayedScrollEvents.AppendElement(aScrollEvent);
1281 } else {
1282 mScrollEvents.AppendElement(aScrollEvent);
1283 EnsureTimerStarted();
1284 }
1285 }
1286
DispatchScrollEvents()1287 void nsRefreshDriver::DispatchScrollEvents() {
1288 // Scroll events are one-shot, so after running them we can drop them.
1289 // However, dispatching a scroll event can potentially cause more scroll
1290 // events to be posted, so we move the initial set into a temporary array
1291 // first. (Newly posted scroll events will be dispatched on the next tick.)
1292 ScrollEventArray events = std::move(mScrollEvents);
1293 for (auto& event : events) {
1294 event->Run();
1295 }
1296 }
1297
PostVisualViewportScrollEvent(VVPScrollEvent * aScrollEvent)1298 void nsRefreshDriver::PostVisualViewportScrollEvent(
1299 VVPScrollEvent* aScrollEvent) {
1300 mVisualViewportScrollEvents.AppendElement(aScrollEvent);
1301 EnsureTimerStarted();
1302 }
1303
DispatchVisualViewportScrollEvents()1304 void nsRefreshDriver::DispatchVisualViewportScrollEvents() {
1305 // Scroll events are one-shot, so after running them we can drop them.
1306 // However, dispatching a scroll event can potentially cause more scroll
1307 // events to be posted, so we move the initial set into a temporary array
1308 // first. (Newly posted scroll events will be dispatched on the next tick.)
1309 VisualViewportScrollEventArray events =
1310 std::move(mVisualViewportScrollEvents);
1311 for (auto& event : events) {
1312 event->Run();
1313 }
1314 }
1315
AddPostRefreshObserver(nsAPostRefreshObserver * aObserver)1316 void nsRefreshDriver::AddPostRefreshObserver(
1317 nsAPostRefreshObserver* aObserver) {
1318 MOZ_DIAGNOSTIC_ASSERT(!mPostRefreshObservers.Contains(aObserver));
1319 mPostRefreshObservers.AppendElement(aObserver);
1320 }
1321
RemovePostRefreshObserver(nsAPostRefreshObserver * aObserver)1322 void nsRefreshDriver::RemovePostRefreshObserver(
1323 nsAPostRefreshObserver* aObserver) {
1324 bool removed = mPostRefreshObservers.RemoveElement(aObserver);
1325 MOZ_DIAGNOSTIC_ASSERT(removed);
1326 Unused << removed;
1327 }
1328
AddImageRequest(imgIRequest * aRequest)1329 bool nsRefreshDriver::AddImageRequest(imgIRequest* aRequest) {
1330 uint32_t delay = GetFirstFrameDelay(aRequest);
1331 if (delay == 0) {
1332 mRequests.Insert(aRequest);
1333 } else {
1334 auto* const start = mStartTable.GetOrInsertNew(delay);
1335 start->mEntries.Insert(aRequest);
1336 }
1337
1338 EnsureTimerStarted();
1339
1340 return true;
1341 }
1342
RemoveImageRequest(imgIRequest * aRequest)1343 void nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest) {
1344 // Try to remove from both places, just in case, because we can't tell
1345 // whether RemoveEntry() succeeds.
1346 mRequests.Remove(aRequest);
1347 uint32_t delay = GetFirstFrameDelay(aRequest);
1348 if (delay != 0) {
1349 ImageStartData* start = mStartTable.Get(delay);
1350 if (start) {
1351 start->mEntries.Remove(aRequest);
1352 }
1353 }
1354 }
1355
NotifyDOMContentLoaded()1356 void nsRefreshDriver::NotifyDOMContentLoaded() {
1357 // If the refresh driver is going to tick, we mark the timestamp after
1358 // everything is flushed in the next tick. If it isn't, mark ourselves as
1359 // flushed now.
1360 if (!HasObservers()) {
1361 GetPresContext()->NotifyDOMContentFlushed();
1362 } else {
1363 mNotifyDOMContentFlushed = true;
1364 }
1365 }
1366
RegisterCompositionPayload(const mozilla::layers::CompositionPayload & aPayload)1367 void nsRefreshDriver::RegisterCompositionPayload(
1368 const mozilla::layers::CompositionPayload& aPayload) {
1369 mCompositionPayloads.AppendElement(aPayload);
1370 }
1371
AddForceNotifyContentfulPaintPresContext(nsPresContext * aPresContext)1372 void nsRefreshDriver::AddForceNotifyContentfulPaintPresContext(
1373 nsPresContext* aPresContext) {
1374 mForceNotifyContentfulPaintPresContexts.AppendElement(aPresContext);
1375 }
1376
FlushForceNotifyContentfulPaintPresContext()1377 void nsRefreshDriver::FlushForceNotifyContentfulPaintPresContext() {
1378 while (!mForceNotifyContentfulPaintPresContexts.IsEmpty()) {
1379 WeakPtr<nsPresContext> presContext =
1380 mForceNotifyContentfulPaintPresContexts.PopLastElement();
1381 if (presContext) {
1382 presContext->NotifyContentfulPaint();
1383 }
1384 }
1385 }
1386
RunDelayedEventsSoon()1387 void nsRefreshDriver::RunDelayedEventsSoon() {
1388 // Place entries for delayed events into their corresponding normal list,
1389 // and schedule a refresh. When these delayed events run, if their document
1390 // still has events suppressed then they will be readded to the delayed
1391 // events list.
1392
1393 mScrollEvents.AppendElements(mDelayedScrollEvents);
1394 mDelayedScrollEvents.Clear();
1395
1396 mResizeEventFlushObservers.AppendElements(mDelayedResizeEventFlushObservers);
1397 mDelayedResizeEventFlushObservers.Clear();
1398
1399 EnsureTimerStarted();
1400 }
1401
CanDoCatchUpTick()1402 bool nsRefreshDriver::CanDoCatchUpTick() {
1403 if (mTestControllingRefreshes || !mActiveTimer) {
1404 return false;
1405 }
1406
1407 // If we've already ticked for the current timer refresh (or more recently
1408 // than that), then we don't need to do any catching up.
1409 if (mMostRecentRefresh >= mActiveTimer->MostRecentRefresh()) {
1410 return false;
1411 }
1412
1413 return true;
1414 }
1415
CanDoExtraTick()1416 bool nsRefreshDriver::CanDoExtraTick() {
1417 // Only allow one extra tick per normal vsync tick.
1418 if (mAttemptedExtraTickSinceLastVsync) {
1419 return false;
1420 }
1421
1422 // If we don't have a timer, or we didn't tick on the timer's
1423 // refresh then we can't do an 'extra' tick (but we may still
1424 // do a catch up tick).
1425 if (!mActiveTimer ||
1426 mActiveTimer->MostRecentRefresh() != mMostRecentRefresh) {
1427 return false;
1428 }
1429
1430 // Grab the current timestamp before checking the tick hint to be sure
1431 // sure that it's equal or smaller than the value used within checking
1432 // the tick hint.
1433 TimeStamp now = TimeStamp::Now();
1434 Maybe<TimeStamp> nextTick = mActiveTimer->GetNextTickHint();
1435 int32_t minimumRequiredTime = StaticPrefs::layout_extra_tick_minimum_ms();
1436 // If there's less than 4 milliseconds until the next tick, it's probably
1437 // not worth trying to catch up.
1438 if (minimumRequiredTime < 0 || !nextTick ||
1439 (*nextTick - now) < TimeDuration::FromMilliseconds(minimumRequiredTime)) {
1440 return false;
1441 }
1442
1443 return true;
1444 }
1445
EnsureTimerStarted(EnsureTimerStartedFlags aFlags)1446 void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
1447 // FIXME: Bug 1346065: We should also assert the case where we have
1448 // STYLO_THREADS=1.
1449 MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal() || NS_IsMainThread(),
1450 "EnsureTimerStarted should be called only when we are not "
1451 "in servo traversal or on the main-thread");
1452
1453 if (mTestControllingRefreshes) return;
1454
1455 if (!mRefreshTimerStartedCause) {
1456 mRefreshTimerStartedCause = profiler_capture_backtrace();
1457 }
1458
1459 // will it already fire, and no other changes needed?
1460 if (mActiveTimer && !(aFlags & eForceAdjustTimer)) {
1461 // If we're being called from within a user input handler, and we think
1462 // there's time to rush an extra tick immediately, then schedule a runnable
1463 // to run the extra tick.
1464 if (mUserInputProcessingCount && CanDoExtraTick()) {
1465 RefPtr<nsRefreshDriver> self = this;
1466 NS_DispatchToCurrentThreadQueue(
1467 NS_NewRunnableFunction(
1468 "RefreshDriver::EnsureTimerStarted::extra",
1469 [self]() -> void {
1470 // Re-check if we can still do an extra tick, in case anything
1471 // changed while the runnable was pending.
1472 if (self->CanDoExtraTick()) {
1473 PROFILER_MARKER_UNTYPED("ExtraRefreshDriverTick", GRAPHICS);
1474 LOG("[%p] Doing extra tick for user input", self.get());
1475 self->mAttemptedExtraTickSinceLastVsync = true;
1476 self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
1477 self->mActiveTimer->MostRecentRefresh(),
1478 IsExtraTick::Yes);
1479 }
1480 }),
1481 EventQueuePriority::Vsync);
1482 }
1483 return;
1484 }
1485
1486 if (IsFrozen() || !mPresContext) {
1487 // If we don't want to start it now, or we've been disconnected.
1488 StopTimer();
1489 return;
1490 }
1491
1492 if (mPresContext->Document()->IsBeingUsedAsImage()) {
1493 // Image documents receive ticks from clients' refresh drivers.
1494 // XXXdholbert Exclude SVG-in-opentype fonts from this optimization, until
1495 // they receive refresh-driver ticks from their client docs (bug 1107252).
1496 nsIURI* uri = mPresContext->Document()->GetDocumentURI();
1497 if (!uri || !mozilla::dom::IsFontTableURI(uri)) {
1498 MOZ_ASSERT(!mActiveTimer,
1499 "image doc refresh driver should never have its own timer");
1500 return;
1501 }
1502 }
1503
1504 // We got here because we're either adjusting the time *or* we're
1505 // starting it for the first time. Add to the right timer,
1506 // prehaps removing it from a previously-set one.
1507 RefreshDriverTimer* newTimer = ChooseTimer();
1508 if (newTimer != mActiveTimer) {
1509 if (mActiveTimer) mActiveTimer->RemoveRefreshDriver(this);
1510 mActiveTimer = newTimer;
1511 mActiveTimer->AddRefreshDriver(this);
1512
1513 // If the timer has ticked since we last ticked, consider doing a 'catch-up'
1514 // tick immediately.
1515 if (CanDoCatchUpTick()) {
1516 RefPtr<nsRefreshDriver> self = this;
1517 NS_DispatchToCurrentThreadQueue(
1518 NS_NewRunnableFunction(
1519 "RefreshDriver::EnsureTimerStarted::catch-up",
1520 [self]() -> void {
1521 // Re-check if we can still do a catch-up, in case anything
1522 // changed while the runnable was pending.
1523 if (self->CanDoCatchUpTick()) {
1524 LOG("[%p] Doing catch up tick", self.get());
1525 self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
1526 self->mActiveTimer->MostRecentRefresh());
1527 }
1528 }),
1529 EventQueuePriority::Vsync);
1530 }
1531 }
1532
1533 // When switching from an inactive timer to an active timer, the root
1534 // refresh driver is skipped due to being set to the content refresh
1535 // driver's timestamp. In case of EnsureTimerStarted is called from
1536 // ScheduleViewManagerFlush, we should avoid this behavior to flush
1537 // a paint in the same tick on the root refresh driver.
1538 if (aFlags & eNeverAdjustTimer) {
1539 return;
1540 }
1541
1542 // Since the different timers are sampled at different rates, when switching
1543 // timers, the most recent refresh of the new timer may be *before* the
1544 // most recent refresh of the old timer.
1545 // If we are restoring the refresh driver from test control, the time is
1546 // expected to go backwards (see bug 1043078), otherwise we just keep the most
1547 // recent tick of this driver (which may be older than the most recent tick of
1548 // the timer).
1549 if (!(aFlags & eAllowTimeToGoBackwards)) {
1550 return;
1551 }
1552
1553 if (mMostRecentRefresh != mActiveTimer->MostRecentRefresh()) {
1554 mMostRecentRefresh = mActiveTimer->MostRecentRefresh();
1555
1556 for (nsATimerAdjustmentObserver* obs :
1557 mTimerAdjustmentObservers.EndLimitedRange()) {
1558 obs->NotifyTimerAdjusted(mMostRecentRefresh);
1559 }
1560 }
1561 }
1562
StopTimer()1563 void nsRefreshDriver::StopTimer() {
1564 if (!mActiveTimer) return;
1565
1566 mActiveTimer->RemoveRefreshDriver(this);
1567 mActiveTimer = nullptr;
1568 mRefreshTimerStartedCause = nullptr;
1569 }
1570
ObserverCount() const1571 uint32_t nsRefreshDriver::ObserverCount() const {
1572 uint32_t sum = 0;
1573 for (const ObserverArray& array : mObservers) {
1574 sum += array.Length();
1575 }
1576
1577 // Even while throttled, we need to process layout and style changes. Style
1578 // changes can trigger transitions which fire events when they complete, and
1579 // layout changes can affect media queries on child documents, triggering
1580 // style changes, etc.
1581 sum += mAnimationEventFlushObservers.Length();
1582 sum += mResizeEventFlushObservers.Length();
1583 sum += mStyleFlushObservers.Length();
1584 sum += mLayoutFlushObservers.Length();
1585 sum += mPendingFullscreenEvents.Length();
1586 sum += mFrameRequestCallbackDocs.Length();
1587 sum += mThrottledFrameRequestCallbackDocs.Length();
1588 sum += mViewManagerFlushIsPending;
1589 sum += mEarlyRunners.Length();
1590 sum += mTimerAdjustmentObservers.Length();
1591 return sum;
1592 }
1593
HasObservers() const1594 bool nsRefreshDriver::HasObservers() const {
1595 for (const ObserverArray& array : mObservers) {
1596 if (!array.IsEmpty()) {
1597 return true;
1598 }
1599 }
1600
1601 // We should NOT count mTimerAdjustmentObservers here since this method is
1602 // used to determine whether or not to stop the timer or re-start it and timer
1603 // adjustment observers should not influence timer starting or stopping.
1604 return mViewManagerFlushIsPending || !mStyleFlushObservers.IsEmpty() ||
1605 !mLayoutFlushObservers.IsEmpty() ||
1606 !mAnimationEventFlushObservers.IsEmpty() ||
1607 !mResizeEventFlushObservers.IsEmpty() ||
1608 !mPendingFullscreenEvents.IsEmpty() ||
1609 !mFrameRequestCallbackDocs.IsEmpty() ||
1610 !mThrottledFrameRequestCallbackDocs.IsEmpty() ||
1611 !mEarlyRunners.IsEmpty();
1612 }
1613
AppendObserverDescriptionsToString(nsACString & aStr) const1614 void nsRefreshDriver::AppendObserverDescriptionsToString(
1615 nsACString& aStr) const {
1616 for (const ObserverArray& array : mObservers) {
1617 for (const auto& observer : array.EndLimitedRange()) {
1618 aStr.AppendPrintf("%s [%s], ", observer.mDescription,
1619 kFlushTypeNames[observer.mFlushType]);
1620 }
1621 }
1622 if (mViewManagerFlushIsPending) {
1623 aStr.AppendLiteral("View manager flush pending, ");
1624 }
1625 if (!mAnimationEventFlushObservers.IsEmpty()) {
1626 aStr.AppendPrintf("%zux Animation event flush observer, ",
1627 mAnimationEventFlushObservers.Length());
1628 }
1629 if (!mResizeEventFlushObservers.IsEmpty()) {
1630 aStr.AppendPrintf("%zux Resize event flush observer, ",
1631 mResizeEventFlushObservers.Length());
1632 }
1633 if (!mStyleFlushObservers.IsEmpty()) {
1634 aStr.AppendPrintf("%zux Style flush observer, ",
1635 mStyleFlushObservers.Length());
1636 }
1637 if (!mLayoutFlushObservers.IsEmpty()) {
1638 aStr.AppendPrintf("%zux Layout flush observer, ",
1639 mLayoutFlushObservers.Length());
1640 }
1641 if (!mPendingFullscreenEvents.IsEmpty()) {
1642 aStr.AppendPrintf("%zux Pending fullscreen event, ",
1643 mPendingFullscreenEvents.Length());
1644 }
1645 if (!mFrameRequestCallbackDocs.IsEmpty()) {
1646 aStr.AppendPrintf("%zux Frame request callback doc, ",
1647 mFrameRequestCallbackDocs.Length());
1648 }
1649 if (!mThrottledFrameRequestCallbackDocs.IsEmpty()) {
1650 aStr.AppendPrintf("%zux Throttled frame request callback doc, ",
1651 mThrottledFrameRequestCallbackDocs.Length());
1652 }
1653 if (!mEarlyRunners.IsEmpty()) {
1654 aStr.AppendPrintf("%zux Early runner, ", mEarlyRunners.Length());
1655 }
1656 // Remove last ", "
1657 aStr.Truncate(aStr.Length() - 2);
1658 }
1659
HasImageRequests() const1660 bool nsRefreshDriver::HasImageRequests() const {
1661 for (const auto& data : mStartTable.Values()) {
1662 if (!data->mEntries.IsEmpty()) {
1663 return true;
1664 }
1665 }
1666
1667 return !mRequests.IsEmpty();
1668 }
1669
GetReasonsToTick() const1670 auto nsRefreshDriver::GetReasonsToTick() const -> TickReasons {
1671 TickReasons reasons = TickReasons::eNone;
1672 if (HasObservers()) {
1673 reasons |= TickReasons::eHasObservers;
1674 }
1675 if (HasImageRequests()) {
1676 reasons |= TickReasons::eHasImageRequests;
1677 }
1678 if (mNeedToUpdateIntersectionObservations) {
1679 reasons |= TickReasons::eNeedsToUpdateIntersectionObservations;
1680 }
1681 if (!mVisualViewportResizeEvents.IsEmpty()) {
1682 reasons |= TickReasons::eHasVisualViewportResizeEvents;
1683 }
1684 if (!mScrollEvents.IsEmpty()) {
1685 reasons |= TickReasons::eHasScrollEvents;
1686 }
1687 if (!mVisualViewportScrollEvents.IsEmpty()) {
1688 reasons |= TickReasons::eHasVisualViewportScrollEvents;
1689 }
1690 return reasons;
1691 }
1692
AppendTickReasonsToString(TickReasons aReasons,nsACString & aStr) const1693 void nsRefreshDriver::AppendTickReasonsToString(TickReasons aReasons,
1694 nsACString& aStr) const {
1695 if (aReasons == TickReasons::eNone) {
1696 aStr.AppendLiteral(" <none>");
1697 return;
1698 }
1699
1700 if (aReasons & TickReasons::eHasObservers) {
1701 aStr.AppendLiteral(" HasObservers (");
1702 AppendObserverDescriptionsToString(aStr);
1703 aStr.AppendLiteral(")");
1704 }
1705 if (aReasons & TickReasons::eHasImageRequests) {
1706 aStr.AppendLiteral(" HasImageRequests");
1707 }
1708 if (aReasons & TickReasons::eNeedsToUpdateIntersectionObservations) {
1709 aStr.AppendLiteral(" NeedsToUpdateIntersectionObservations");
1710 }
1711 if (aReasons & TickReasons::eHasVisualViewportResizeEvents) {
1712 aStr.AppendLiteral(" HasVisualViewportResizeEvents");
1713 }
1714 if (aReasons & TickReasons::eHasScrollEvents) {
1715 aStr.AppendLiteral(" HasScrollEvents");
1716 }
1717 if (aReasons & TickReasons::eHasVisualViewportScrollEvents) {
1718 aStr.AppendLiteral(" HasVisualViewportScrollEvents");
1719 }
1720 }
1721
1722 bool nsRefreshDriver::
ShouldKeepTimerRunningWhileWaitingForFirstContentfulPaint()1723 ShouldKeepTimerRunningWhileWaitingForFirstContentfulPaint() {
1724 // On top level content pages keep the timer running initially so that we
1725 // paint the page soon enough.
1726 if (mThrottled || mTestControllingRefreshes || !XRE_IsContentProcess() ||
1727 !mPresContext->Document()->IsTopLevelContentDocument() ||
1728 gfxPlatform::IsInLayoutAsapMode() || mPresContext->HadContentfulPaint() ||
1729 mPresContext->Document()->GetReadyStateEnum() ==
1730 Document::READYSTATE_COMPLETE) {
1731 return false;
1732 }
1733 if (mBeforeFirstContentfulPaintTimerRunningLimit.IsNull()) {
1734 // Don't let the timer to run forever, so limit to 4s for now.
1735 mBeforeFirstContentfulPaintTimerRunningLimit =
1736 TimeStamp::Now() + TimeDuration::FromSeconds(4.0f);
1737 }
1738
1739 return TimeStamp::Now() <= mBeforeFirstContentfulPaintTimerRunningLimit;
1740 }
1741
ShouldKeepTimerRunningAfterPageLoad()1742 bool nsRefreshDriver::ShouldKeepTimerRunningAfterPageLoad() {
1743 if (mHasExceededAfterLoadTickPeriod ||
1744 !StaticPrefs::layout_keep_ticking_after_load_ms() || mThrottled ||
1745 mTestControllingRefreshes || !XRE_IsContentProcess() ||
1746 !mPresContext->Document()->IsTopLevelContentDocument() ||
1747 gfxPlatform::IsInLayoutAsapMode()) {
1748 // Make the next check faster.
1749 mHasExceededAfterLoadTickPeriod = true;
1750 return false;
1751 }
1752
1753 nsPIDOMWindowInner* innerWindow = mPresContext->Document()->GetInnerWindow();
1754 if (innerWindow) {
1755 if (PerformanceMainThread* perf = static_cast<PerformanceMainThread*>(
1756 innerWindow->GetPerformance())) {
1757 nsDOMNavigationTiming* timing = perf->GetDOMTiming();
1758 if (timing) {
1759 TimeStamp loadend = timing->LoadEventEnd();
1760 if (loadend) {
1761 // Keep ticking after the page load for some time.
1762 bool retval =
1763 (loadend +
1764 TimeDuration::FromMilliseconds(
1765 StaticPrefs::layout_keep_ticking_after_load_ms())) >
1766 TimeStamp::Now();
1767 if (!retval) {
1768 mHasExceededAfterLoadTickPeriod = true;
1769 }
1770 return retval;
1771 }
1772 }
1773 }
1774 }
1775
1776 return false;
1777 }
1778
ArrayFor(FlushType aFlushType)1779 nsRefreshDriver::ObserverArray& nsRefreshDriver::ArrayFor(
1780 FlushType aFlushType) {
1781 switch (aFlushType) {
1782 case FlushType::Event:
1783 return mObservers[0];
1784 case FlushType::Style:
1785 case FlushType::Frames:
1786 return mObservers[1];
1787 case FlushType::Layout:
1788 return mObservers[2];
1789 case FlushType::Display:
1790 return mObservers[3];
1791 default:
1792 MOZ_CRASH("We don't track refresh observers for this flush type");
1793 }
1794 }
1795
1796 /*
1797 * nsITimerCallback implementation
1798 */
1799
DoTick()1800 void nsRefreshDriver::DoTick() {
1801 MOZ_ASSERT(!IsFrozen(), "Why are we notified while frozen?");
1802 MOZ_ASSERT(mPresContext, "Why are we notified after disconnection?");
1803 MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
1804 "Shouldn't have a JSContext on the stack");
1805
1806 if (mTestControllingRefreshes) {
1807 Tick(VsyncId(), mMostRecentRefresh);
1808 } else {
1809 Tick(VsyncId(), TimeStamp::Now());
1810 }
1811 }
1812
1813 struct DocumentFrameCallbacks {
DocumentFrameCallbacksDocumentFrameCallbacks1814 explicit DocumentFrameCallbacks(Document* aDocument) : mDocument(aDocument) {}
1815
1816 RefPtr<Document> mDocument;
1817 nsTArray<Document::FrameRequest> mCallbacks;
1818 };
1819
GetDocShell(nsPresContext * aPresContext)1820 static nsDocShell* GetDocShell(nsPresContext* aPresContext) {
1821 return static_cast<nsDocShell*>(aPresContext->GetDocShell());
1822 }
1823
HasPendingAnimations(PresShell * aPresShell)1824 static bool HasPendingAnimations(PresShell* aPresShell) {
1825 Document* doc = aPresShell->GetDocument();
1826 if (!doc) {
1827 return false;
1828 }
1829
1830 PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
1831 return tracker && tracker->HasPendingAnimations();
1832 }
1833
1834 /**
1835 * Return a list of all the child docShells in a given root docShell that are
1836 * visible and are recording markers for the profilingTimeline
1837 */
GetProfileTimelineSubDocShells(nsDocShell * aRootDocShell,nsTArray<nsDocShell * > & aShells)1838 static void GetProfileTimelineSubDocShells(nsDocShell* aRootDocShell,
1839 nsTArray<nsDocShell*>& aShells) {
1840 if (!aRootDocShell) {
1841 return;
1842 }
1843
1844 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
1845 if (!timelines || timelines->IsEmpty()) {
1846 return;
1847 }
1848
1849 RefPtr<BrowsingContext> bc = aRootDocShell->GetBrowsingContext();
1850 if (!bc) {
1851 return;
1852 }
1853
1854 bc->PostOrderWalk([&](BrowsingContext* aContext) {
1855 if (!aContext->IsActive()) {
1856 return;
1857 }
1858
1859 nsDocShell* shell = nsDocShell::Cast(aContext->GetDocShell());
1860 if (!shell || !shell->GetRecordProfileTimelineMarkers()) {
1861 // This process isn't painting OOP iframes so we ignore
1862 // docshells that are OOP.
1863 return;
1864 }
1865
1866 aShells.AppendElement(shell);
1867 });
1868 }
1869
TakeFrameRequestCallbacksFrom(Document * aDocument,nsTArray<DocumentFrameCallbacks> & aTarget)1870 static void TakeFrameRequestCallbacksFrom(
1871 Document* aDocument, nsTArray<DocumentFrameCallbacks>& aTarget) {
1872 aTarget.AppendElement(aDocument);
1873 aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks);
1874 }
1875
1876 // https://fullscreen.spec.whatwg.org/#run-the-fullscreen-steps
RunFullscreenSteps()1877 void nsRefreshDriver::RunFullscreenSteps() {
1878 // Swap out the current pending events
1879 nsTArray<UniquePtr<PendingFullscreenEvent>> pendings(
1880 std::move(mPendingFullscreenEvents));
1881 for (UniquePtr<PendingFullscreenEvent>& event : pendings) {
1882 event->Dispatch();
1883 }
1884 }
1885
UpdateIntersectionObservations(TimeStamp aNowTime)1886 void nsRefreshDriver::UpdateIntersectionObservations(TimeStamp aNowTime) {
1887 AutoTArray<RefPtr<Document>, 32> documents;
1888
1889 if (mPresContext->Document()->HasIntersectionObservers()) {
1890 documents.AppendElement(mPresContext->Document());
1891 }
1892
1893 mPresContext->Document()->CollectDescendantDocuments(
1894 documents, [](const Document* document) -> bool {
1895 return document->HasIntersectionObservers();
1896 });
1897
1898 for (uint32_t i = 0; i < documents.Length(); ++i) {
1899 Document* doc = documents[i];
1900 doc->UpdateIntersectionObservations(aNowTime);
1901 doc->ScheduleIntersectionObserverNotification();
1902 }
1903
1904 mNeedToUpdateIntersectionObservations = false;
1905 }
1906
DispatchAnimationEvents()1907 void nsRefreshDriver::DispatchAnimationEvents() {
1908 if (!mPresContext) {
1909 return;
1910 }
1911
1912 // Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as
1913 // a RefPtr<> array since each AnimationEventDispatcher might be destroyed
1914 // during processing the previous dispatcher.
1915 AutoTArray<RefPtr<AnimationEventDispatcher>, 16> dispatchers;
1916 dispatchers.AppendElements(mAnimationEventFlushObservers);
1917 mAnimationEventFlushObservers.Clear();
1918
1919 for (auto& dispatcher : dispatchers) {
1920 dispatcher->DispatchEvents();
1921 }
1922 }
1923
RunFrameRequestCallbacks(TimeStamp aNowTime)1924 void nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime) {
1925 // Grab all of our frame request callbacks up front.
1926 nsTArray<DocumentFrameCallbacks> frameRequestCallbacks(
1927 mFrameRequestCallbackDocs.Length() +
1928 mThrottledFrameRequestCallbackDocs.Length());
1929
1930 // First, grab throttled frame request callbacks.
1931 {
1932 nsTArray<Document*> docsToRemove;
1933
1934 // We always tick throttled frame requests if the entire refresh driver is
1935 // throttled, because in that situation throttled frame requests tick at the
1936 // same frequency as non-throttled frame requests.
1937 bool tickThrottledFrameRequests = mThrottled;
1938
1939 if (!tickThrottledFrameRequests &&
1940 aNowTime >= mNextThrottledFrameRequestTick) {
1941 mNextThrottledFrameRequestTick =
1942 aNowTime + mThrottledFrameRequestInterval;
1943 tickThrottledFrameRequests = true;
1944 }
1945
1946 for (Document* doc : mThrottledFrameRequestCallbackDocs) {
1947 if (tickThrottledFrameRequests) {
1948 // We're ticking throttled documents, so grab this document's requests.
1949 // We don't bother appending to docsToRemove because we're going to
1950 // clear mThrottledFrameRequestCallbackDocs anyway.
1951 TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
1952 } else if (!doc->ShouldThrottleFrameRequests()) {
1953 // This document is no longer throttled, so grab its requests even
1954 // though we're not ticking throttled frame requests right now. If
1955 // this is the first unthrottled document with frame requests, we'll
1956 // enter high precision mode the next time the callback is scheduled.
1957 TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
1958 docsToRemove.AppendElement(doc);
1959 }
1960 }
1961
1962 // Remove all the documents we're ticking from
1963 // mThrottledFrameRequestCallbackDocs so they can be readded as needed.
1964 if (tickThrottledFrameRequests) {
1965 mThrottledFrameRequestCallbackDocs.Clear();
1966 } else {
1967 // XXX(seth): We're using this approach to avoid concurrent modification
1968 // of mThrottledFrameRequestCallbackDocs. docsToRemove usually has either
1969 // zero elements or a very small number, so this should be OK in practice.
1970 for (Document* doc : docsToRemove) {
1971 mThrottledFrameRequestCallbackDocs.RemoveElement(doc);
1972 }
1973 }
1974 }
1975
1976 // Now grab unthrottled frame request callbacks.
1977 for (Document* doc : mFrameRequestCallbackDocs) {
1978 TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
1979 }
1980
1981 // Reset mFrameRequestCallbackDocs so they can be readded as needed.
1982 mFrameRequestCallbackDocs.Clear();
1983
1984 if (!frameRequestCallbacks.IsEmpty()) {
1985 AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint",
1986 "requestAnimationFrame callbacks",
1987 GRAPHICS, GetDocShell(mPresContext));
1988 for (const DocumentFrameCallbacks& docCallbacks : frameRequestCallbacks) {
1989 TimeStamp startTime = TimeStamp::Now();
1990
1991 // XXXbz Bug 863140: GetInnerWindow can return the outer
1992 // window in some cases.
1993 nsPIDOMWindowInner* innerWindow =
1994 docCallbacks.mDocument->GetInnerWindow();
1995 DOMHighResTimeStamp timeStamp = 0;
1996 if (innerWindow) {
1997 if (Performance* perf = innerWindow->GetPerformance()) {
1998 timeStamp = perf->TimeStampToDOMHighResForRendering(aNowTime);
1999 }
2000 // else window is partially torn down already
2001 }
2002 for (auto& callback : docCallbacks.mCallbacks) {
2003 if (docCallbacks.mDocument->IsCanceledFrameRequestCallback(
2004 callback.mHandle)) {
2005 continue;
2006 }
2007
2008 nsCOMPtr<nsIGlobalObject> global(innerWindow ? innerWindow->AsGlobal()
2009 : nullptr);
2010 CallbackDebuggerNotificationGuard guard(
2011 global, DebuggerNotificationType::RequestAnimationFrameCallback);
2012
2013 // MOZ_KnownLive is OK, because the stack array frameRequestCallbacks
2014 // keeps callback alive and the mCallback strong reference can't be
2015 // mutated by the call.
2016 LogFrameRequestCallback::Run run(callback.mCallback);
2017 MOZ_KnownLive(callback.mCallback)->Call(timeStamp);
2018 }
2019
2020 if (docCallbacks.mDocument->GetReadyStateEnum() ==
2021 Document::READYSTATE_COMPLETE) {
2022 Telemetry::AccumulateTimeDelta(
2023 Telemetry::PERF_REQUEST_ANIMATION_CALLBACK_NON_PAGELOAD_MS,
2024 startTime, TimeStamp::Now());
2025 } else {
2026 Telemetry::AccumulateTimeDelta(
2027 Telemetry::PERF_REQUEST_ANIMATION_CALLBACK_PAGELOAD_MS, startTime,
2028 TimeStamp::Now());
2029 }
2030 }
2031 }
2032 }
2033
2034 static AutoTArray<RefPtr<Task>, 8>* sPendingIdleTasks = nullptr;
2035
DispatchIdleTaskAfterTickUnlessExists(Task * aTask)2036 void nsRefreshDriver::DispatchIdleTaskAfterTickUnlessExists(Task* aTask) {
2037 if (!sPendingIdleTasks) {
2038 sPendingIdleTasks = new AutoTArray<RefPtr<Task>, 8>();
2039 } else {
2040 if (sPendingIdleTasks->Contains(aTask)) {
2041 return;
2042 }
2043 }
2044
2045 sPendingIdleTasks->AppendElement(aTask);
2046 }
2047
CancelIdleTask(Task * aTask)2048 void nsRefreshDriver::CancelIdleTask(Task* aTask) {
2049 if (!sPendingIdleTasks) {
2050 return;
2051 }
2052
2053 sPendingIdleTasks->RemoveElement(aTask);
2054
2055 if (sPendingIdleTasks->IsEmpty()) {
2056 delete sPendingIdleTasks;
2057 sPendingIdleTasks = nullptr;
2058 }
2059 }
2060
ReduceAnimations(Document & aDocument)2061 static CallState ReduceAnimations(Document& aDocument) {
2062 if (nsPresContext* pc = aDocument.GetPresContext()) {
2063 if (pc->EffectCompositor()->NeedsReducing()) {
2064 pc->EffectCompositor()->ReduceAnimations();
2065 }
2066 }
2067 aDocument.EnumerateSubDocuments(ReduceAnimations);
2068 return CallState::Continue;
2069 }
2070
Tick(VsyncId aId,TimeStamp aNowTime,IsExtraTick aIsExtraTick)2071 void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
2072 IsExtraTick aIsExtraTick /* = No */) {
2073 MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
2074 "Shouldn't have a JSContext on the stack");
2075
2076 // We're either frozen or we were disconnected (likely in the middle
2077 // of a tick iteration). Just do nothing here, since our
2078 // prescontext went away.
2079 if (IsFrozen() || !mPresContext) {
2080 return;
2081 }
2082
2083 // We can have a race condition where the vsync timestamp
2084 // is before the most recent refresh due to a forced refresh.
2085 // The underlying assumption is that the refresh driver tick can only
2086 // go forward in time, not backwards. To prevent the refresh
2087 // driver from going back in time, just skip this tick and
2088 // wait until the next tick.
2089 // If this is an 'extra' tick, then we expect it to be using the same
2090 // vsync id and timestamp as the original tick, so also allow those.
2091 if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes &&
2092 aIsExtraTick == IsExtraTick::No) {
2093 return;
2094 }
2095 auto cleanupInExtraTick = MakeScopeExit([&] { mInNormalTick = false; });
2096 mInNormalTick = aIsExtraTick != IsExtraTick::Yes;
2097
2098 bool isPresentingInVR = false;
2099 #if defined(MOZ_WIDGET_ANDROID)
2100 isPresentingInVR = gfx::VRManagerChild::IsPresenting();
2101 #endif // defined(MOZ_WIDGET_ANDROID)
2102
2103 if (!isPresentingInVR && IsWaitingForPaint(aNowTime)) {
2104 // In immersive VR mode, we do not get notifications when frames are
2105 // presented, so we do not wait for the compositor in that mode.
2106
2107 // We're currently suspended waiting for earlier Tick's to
2108 // be completed (on the Compositor). Mark that we missed the paint
2109 // and keep waiting.
2110 PROFILER_MARKER_UNTYPED(
2111 "RefreshDriverTick waiting for paint", GRAPHICS,
2112 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)));
2113 return;
2114 }
2115
2116 TimeStamp previousRefresh = mMostRecentRefresh;
2117 mMostRecentRefresh = aNowTime;
2118
2119 if (mRootRefresh) {
2120 mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
2121 mRootRefresh = nullptr;
2122 }
2123 mSkippedPaints = false;
2124 mWarningThreshold = 1;
2125
2126 RefPtr<PresShell> presShell = mPresContext->GetPresShell();
2127 if (!presShell) {
2128 StopTimer();
2129 return;
2130 }
2131
2132 TickReasons tickReasons = GetReasonsToTick();
2133 if (tickReasons == TickReasons::eNone) {
2134 // We no longer have any observers.
2135 // Discard composition payloads because there is no paint.
2136 mCompositionPayloads.Clear();
2137
2138 // We don't want to stop the timer when observers are initially
2139 // removed, because sometimes observers can be added and removed
2140 // often depending on what other things are going on and in that
2141 // situation we don't want to thrash our timer. So instead we
2142 // wait until we get a Notify() call when we have no observers
2143 // before stopping the timer.
2144 // On top level content pages keep the timer running initially so that we
2145 // paint the page soon enough.
2146 if (ShouldKeepTimerRunningWhileWaitingForFirstContentfulPaint()) {
2147 PROFILER_MARKER(
2148 "RefreshDriverTick waiting for first contentful paint", GRAPHICS,
2149 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)), Tracing,
2150 "Paint");
2151 } else if (ShouldKeepTimerRunningAfterPageLoad()) {
2152 PROFILER_MARKER(
2153 "RefreshDriverTick after page load", GRAPHICS,
2154 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)), Tracing,
2155 "Paint");
2156 } else {
2157 StopTimer();
2158 }
2159 return;
2160 }
2161
2162 AUTO_PROFILER_LABEL("nsRefreshDriver::Tick", LAYOUT);
2163
2164 nsAutoCString profilerStr;
2165 if (profiler_can_accept_markers()) {
2166 profilerStr.AppendLiteral("Tick reasons:");
2167 AppendTickReasonsToString(tickReasons, profilerStr);
2168 }
2169 AUTO_PROFILER_MARKER_TEXT(
2170 "RefreshDriverTick", GRAPHICS,
2171 MarkerOptions(
2172 MarkerStack::TakeBacktrace(std::move(mRefreshTimerStartedCause)),
2173 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext))),
2174 profilerStr);
2175
2176 mResizeSuppressed = false;
2177
2178 bool oldInRefresh = mInRefresh;
2179 auto restoreInRefresh = MakeScopeExit([&] { mInRefresh = oldInRefresh; });
2180 mInRefresh = true;
2181
2182 AutoRestore<TimeStamp> restoreTickStart(mTickStart);
2183 mTickStart = TimeStamp::Now();
2184 mTickVsyncId = aId;
2185 mTickVsyncTime = aNowTime;
2186
2187 gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset();
2188
2189 // We want to process any pending APZ metrics ahead of their positions
2190 // in the queue. This will prevent us from spending precious time
2191 // painting a stale displayport.
2192 if (StaticPrefs::apz_peek_messages_enabled()) {
2193 DisplayPortUtils::UpdateDisplayPortMarginsFromPendingMessages();
2194 }
2195
2196 FlushForceNotifyContentfulPaintPresContext();
2197
2198 AutoTArray<nsCOMPtr<nsIRunnable>, 16> earlyRunners = std::move(mEarlyRunners);
2199 for (auto& runner : earlyRunners) {
2200 runner->Run();
2201 }
2202
2203 // Resize events should be fired before layout flushes or
2204 // calling animation frame callbacks.
2205 AutoTArray<RefPtr<PresShell>, 16> observers;
2206 observers.AppendElements(mResizeEventFlushObservers);
2207 for (RefPtr<PresShell>& presShell : Reversed(observers)) {
2208 if (!mPresContext || !mPresContext->GetPresShell()) {
2209 StopTimer();
2210 return;
2211 }
2212 // Make sure to not process observers which might have been removed
2213 // during previous iterations.
2214 if (!mResizeEventFlushObservers.RemoveElement(presShell)) {
2215 continue;
2216 }
2217 // MOZ_KnownLive because 'observers' is guaranteed to
2218 // keep it alive.
2219 //
2220 // Fixing https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 on its own
2221 // won't help here, because 'observers' is non-const and we have the
2222 // Reversed() going on too...
2223 MOZ_KnownLive(presShell)->FireResizeEvent();
2224 }
2225 DispatchVisualViewportResizeEvents();
2226
2227 double phaseMetrics[MOZ_ARRAY_LENGTH(mObservers)] = {
2228 0.0,
2229 };
2230
2231 /*
2232 * The timer holds a reference to |this| while calling |Notify|.
2233 * However, implementations of |WillRefresh| are permitted to destroy
2234 * the pres context, which will cause our |mPresContext| to become
2235 * null. If this happens, we must stop notifying observers.
2236 */
2237 for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
2238 AutoRecordPhase phaseRecord(&phaseMetrics[i]);
2239
2240 for (RefPtr<nsARefreshObserver> obs : mObservers[i].EndLimitedRange()) {
2241 obs->WillRefresh(aNowTime);
2242
2243 if (!mPresContext || !mPresContext->GetPresShell()) {
2244 StopTimer();
2245 return;
2246 }
2247 }
2248
2249 // Any animation timelines updated above may cause animations to queue
2250 // Promise resolution microtasks. We shouldn't run these, however, until we
2251 // have fully updated the animation state.
2252 //
2253 // As per the "update animations and send events" procedure[1], we should
2254 // remove replaced animations and then run these microtasks before
2255 // dispatching the corresponding animation events.
2256 //
2257 // [1]
2258 // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events
2259 if (i == 1) {
2260 nsAutoMicroTask mt;
2261 ReduceAnimations(*mPresContext->Document());
2262 }
2263
2264 // Check if running the microtask checkpoint caused the pres context to
2265 // be destroyed.
2266 if (i == 1 && (!mPresContext || !mPresContext->GetPresShell())) {
2267 StopTimer();
2268 return;
2269 }
2270
2271 if (i == 1) {
2272 // This is the FlushType::Style case.
2273
2274 DispatchScrollEvents();
2275 DispatchVisualViewportScrollEvents();
2276 DispatchAnimationEvents();
2277 RunFullscreenSteps();
2278 RunFrameRequestCallbacks(aNowTime);
2279
2280 if (mPresContext && mPresContext->GetPresShell()) {
2281 AutoTArray<PresShell*, 16> observers;
2282 observers.AppendElements(mStyleFlushObservers);
2283 for (uint32_t j = observers.Length();
2284 j && mPresContext && mPresContext->GetPresShell(); --j) {
2285 // Make sure to not process observers which might have been removed
2286 // during previous iterations.
2287 PresShell* rawPresShell = observers[j - 1];
2288 if (!mStyleFlushObservers.RemoveElement(rawPresShell)) {
2289 continue;
2290 }
2291
2292 LogPresShellObserver::Run run(rawPresShell, this);
2293
2294 RefPtr<PresShell> presShell = rawPresShell;
2295 presShell->mObservingStyleFlushes = false;
2296 presShell->FlushPendingNotifications(
2297 ChangesToFlush(FlushType::Style, false));
2298 // Inform the FontFaceSet that we ticked, so that it can resolve its
2299 // ready promise if it needs to (though it might still be waiting on
2300 // a layout flush).
2301 presShell->NotifyFontFaceSetOnRefresh();
2302 mNeedToRecomputeVisibility = true;
2303
2304 // Record the telemetry for events that occurred between ticks.
2305 presShell->PingPerTickTelemetry(FlushType::Style);
2306 }
2307 }
2308 } else if (i == 2) {
2309 // This is the FlushType::Layout case.
2310 AutoTArray<PresShell*, 16> observers;
2311 observers.AppendElements(mLayoutFlushObservers);
2312 for (uint32_t j = observers.Length();
2313 j && mPresContext && mPresContext->GetPresShell(); --j) {
2314 // Make sure to not process observers which might have been removed
2315 // during previous iterations.
2316 PresShell* rawPresShell = observers[j - 1];
2317 if (!mLayoutFlushObservers.RemoveElement(rawPresShell)) {
2318 continue;
2319 }
2320
2321 LogPresShellObserver::Run run(rawPresShell, this);
2322
2323 RefPtr<PresShell> presShell = rawPresShell;
2324 presShell->mObservingLayoutFlushes = false;
2325 presShell->mWasLastReflowInterrupted = false;
2326 FlushType flushType = HasPendingAnimations(presShell)
2327 ? FlushType::Layout
2328 : FlushType::InterruptibleLayout;
2329 presShell->FlushPendingNotifications(ChangesToFlush(flushType, false));
2330 // Inform the FontFaceSet that we ticked, so that it can resolve its
2331 // ready promise if it needs to.
2332 presShell->NotifyFontFaceSetOnRefresh();
2333 mNeedToRecomputeVisibility = true;
2334
2335 // Record the telemetry for events that occurred between ticks.
2336 presShell->PingPerTickTelemetry(FlushType::Layout);
2337 }
2338 }
2339
2340 // The pres context may be destroyed during we do the flushing.
2341 if (!mPresContext || !mPresContext->GetPresShell()) {
2342 StopTimer();
2343 return;
2344 }
2345 }
2346
2347 // Recompute approximate frame visibility if it's necessary and enough time
2348 // has passed since the last time we did it.
2349 if (mNeedToRecomputeVisibility && !mThrottled &&
2350 aNowTime >= mNextRecomputeVisibilityTick &&
2351 !presShell->IsPaintingSuppressed()) {
2352 mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval;
2353 mNeedToRecomputeVisibility = false;
2354
2355 presShell->ScheduleApproximateFrameVisibilityUpdateNow();
2356 }
2357
2358 #ifdef MOZ_XUL
2359 // Update any popups that may need to be moved or hidden due to their
2360 // anchor changing.
2361 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
2362 pm->UpdatePopupPositions(this);
2363 }
2364 #endif
2365
2366 UpdateIntersectionObservations(aNowTime);
2367
2368 /*
2369 * Perform notification to imgIRequests subscribed to listen
2370 * for refresh events.
2371 */
2372
2373 for (const auto& entry : mStartTable) {
2374 const uint32_t& delay = entry.GetKey();
2375 ImageStartData* data = entry.GetWeak();
2376
2377 if (data->mStartTime) {
2378 TimeStamp& start = *data->mStartTime;
2379 TimeDuration prev = previousRefresh - start;
2380 TimeDuration curr = aNowTime - start;
2381 uint32_t prevMultiple = uint32_t(prev.ToMilliseconds()) / delay;
2382
2383 // We want to trigger images' refresh if we've just crossed over a
2384 // multiple of the first image's start time. If so, set the animation
2385 // start time to the nearest multiple of the delay and move all the
2386 // images in this table to the main requests table.
2387 if (prevMultiple != uint32_t(curr.ToMilliseconds()) / delay) {
2388 mozilla::TimeStamp desired =
2389 start + TimeDuration::FromMilliseconds(prevMultiple * delay);
2390 BeginRefreshingImages(data->mEntries, desired);
2391 }
2392 } else {
2393 // This is the very first time we've drawn images with this time delay.
2394 // Set the animation start time to "now" and move all the images in this
2395 // table to the main requests table.
2396 mozilla::TimeStamp desired = aNowTime;
2397 BeginRefreshingImages(data->mEntries, desired);
2398 data->mStartTime.emplace(aNowTime);
2399 }
2400 }
2401
2402 if (mRequests.Count()) {
2403 // RequestRefresh may run scripts, so it's not safe to directly call it
2404 // while using a hashtable enumerator to enumerate mRequests in case
2405 // script modifies the hashtable. Instead, we build a (local) array of
2406 // images to refresh, and then we refresh each image in that array.
2407 nsCOMArray<imgIContainer> imagesToRefresh(mRequests.Count());
2408
2409 for (nsISupports* entry : mRequests) {
2410 auto* req = static_cast<imgIRequest*>(entry);
2411 MOZ_ASSERT(req, "Unable to retrieve the image request");
2412 nsCOMPtr<imgIContainer> image;
2413 if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
2414 imagesToRefresh.AppendElement(image.forget());
2415 }
2416 }
2417
2418 for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) {
2419 imagesToRefresh[i]->RequestRefresh(aNowTime);
2420 }
2421 }
2422
2423 double phasePaint = 0.0;
2424 bool dispatchTasksAfterTick = false;
2425 if (mViewManagerFlushIsPending) {
2426 AutoRecordPhase paintRecord(&phasePaint);
2427 nsCString transactionId;
2428 if (profiler_can_accept_markers()) {
2429 transactionId.AppendLiteral("Transaction ID: ");
2430 transactionId.AppendInt((uint64_t)mNextTransactionId);
2431 }
2432 AUTO_PROFILER_MARKER_TEXT(
2433 "ViewManagerFlush", GRAPHICS,
2434 MarkerOptions(
2435 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)),
2436 MarkerStack::TakeBacktrace(std::move(mViewManagerFlushCause))),
2437 transactionId);
2438
2439 // Forward our composition payloads to the layer manager.
2440 if (!mCompositionPayloads.IsEmpty()) {
2441 nsIWidget* widget = mPresContext->GetRootWidget();
2442 layers::LayerManager* lm = widget ? widget->GetLayerManager() : nullptr;
2443 if (lm) {
2444 lm->RegisterPayloads(mCompositionPayloads);
2445 }
2446 mCompositionPayloads.Clear();
2447 }
2448
2449 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
2450
2451 nsTArray<nsDocShell*> profilingDocShells;
2452 GetProfileTimelineSubDocShells(GetDocShell(mPresContext),
2453 profilingDocShells);
2454 for (nsDocShell* docShell : profilingDocShells) {
2455 // For the sake of the profile timeline's simplicity, this is flagged as
2456 // paint even if it includes creating display lists
2457 MOZ_ASSERT(timelines);
2458 MOZ_ASSERT(timelines->HasConsumer(docShell));
2459 timelines->AddMarkerForDocShell(docShell, "Paint",
2460 MarkerTracingType::START);
2461 }
2462
2463 #ifdef MOZ_DUMP_PAINTING
2464 if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
2465 printf_stderr("Starting ProcessPendingUpdates\n");
2466 }
2467 #endif
2468
2469 mViewManagerFlushIsPending = false;
2470 RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
2471 const bool skipPaint = isPresentingInVR;
2472 // Skip the paint in immersive VR mode because whatever we paint here will
2473 // not end up on the screen. The screen is displaying WebGL content from a
2474 // single canvas in that mode.
2475 if (!skipPaint) {
2476 PaintTelemetry::AutoRecordPaint record;
2477 vm->ProcessPendingUpdates();
2478 }
2479
2480 #ifdef MOZ_DUMP_PAINTING
2481 if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
2482 printf_stderr("Ending ProcessPendingUpdates\n");
2483 }
2484 #endif
2485
2486 for (nsDocShell* docShell : profilingDocShells) {
2487 MOZ_ASSERT(timelines);
2488 MOZ_ASSERT(timelines->HasConsumer(docShell));
2489 timelines->AddMarkerForDocShell(docShell, "Paint",
2490 MarkerTracingType::END);
2491 }
2492
2493 dispatchTasksAfterTick = true;
2494 mHasScheduleFlush = false;
2495 } else {
2496 // No paint happened, discard composition payloads.
2497 mCompositionPayloads.Clear();
2498 }
2499
2500 double totalMs = (TimeStamp::Now() - mTickStart).ToMilliseconds();
2501
2502 #ifndef ANDROID /* bug 1142079 */
2503 mozilla::Telemetry::Accumulate(mozilla::Telemetry::REFRESH_DRIVER_TICK,
2504 static_cast<uint32_t>(totalMs));
2505 #endif
2506
2507 // Bug 1568107: If the totalMs is greater than 1/60th second (ie. 1000/60 ms)
2508 // then record, via telemetry, the percentage of time spent in each
2509 // sub-system.
2510 if (totalMs > 1000.0 / 60.0) {
2511 auto record = [=](const nsCString& aKey, double aDurationMs) -> void {
2512 MOZ_ASSERT(aDurationMs <= totalMs);
2513 auto phasePercent = static_cast<uint32_t>(aDurationMs * 100.0 / totalMs);
2514 Telemetry::Accumulate(Telemetry::REFRESH_DRIVER_TICK_PHASE_WEIGHT, aKey,
2515 phasePercent);
2516 };
2517
2518 record("Event"_ns, phaseMetrics[0]);
2519 record("Style"_ns, phaseMetrics[1]);
2520 record("Reflow"_ns, phaseMetrics[2]);
2521 record("Display"_ns, phaseMetrics[3]);
2522 record("Paint"_ns, phasePaint);
2523
2524 // Explicitly record the time unaccounted for.
2525 double other = totalMs -
2526 std::accumulate(phaseMetrics, ArrayEnd(phaseMetrics), 0.0) -
2527 phasePaint;
2528 record("Other"_ns, other);
2529 }
2530
2531 if (mNotifyDOMContentFlushed) {
2532 mNotifyDOMContentFlushed = false;
2533 mPresContext->NotifyDOMContentFlushed();
2534 }
2535
2536 for (nsAPostRefreshObserver* observer :
2537 mPostRefreshObservers.ForwardRange()) {
2538 observer->DidRefresh();
2539 }
2540
2541 NS_ASSERTION(mInRefresh, "Still in refresh");
2542
2543 if (mPresContext->IsRoot() && XRE_IsContentProcess() &&
2544 StaticPrefs::gfx_content_always_paint()) {
2545 ScheduleViewManagerFlush();
2546 }
2547
2548 if (dispatchTasksAfterTick && sPendingIdleTasks) {
2549 AutoTArray<RefPtr<Task>, 8>* tasks = sPendingIdleTasks;
2550 sPendingIdleTasks = nullptr;
2551 for (RefPtr<Task>& taskWithDelay : *tasks) {
2552 TaskController::Get()->AddTask(taskWithDelay.forget());
2553 }
2554 delete tasks;
2555 }
2556 }
2557
BeginRefreshingImages(RequestTable & aEntries,mozilla::TimeStamp aDesired)2558 void nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
2559 mozilla::TimeStamp aDesired) {
2560 for (const auto& key : aEntries) {
2561 auto* req = static_cast<imgIRequest*>(key);
2562 MOZ_ASSERT(req, "Unable to retrieve the image request");
2563
2564 mRequests.Insert(req);
2565
2566 nsCOMPtr<imgIContainer> image;
2567 if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
2568 image->SetAnimationStartTime(aDesired);
2569 }
2570 }
2571 aEntries.Clear();
2572 }
2573
Freeze()2574 void nsRefreshDriver::Freeze() {
2575 StopTimer();
2576 mFreezeCount++;
2577 }
2578
Thaw()2579 void nsRefreshDriver::Thaw() {
2580 NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
2581
2582 if (mFreezeCount > 0) {
2583 mFreezeCount--;
2584 }
2585
2586 if (mFreezeCount == 0) {
2587 if (HasObservers() || HasImageRequests()) {
2588 // FIXME: This isn't quite right, since our EnsureTimerStarted call
2589 // updates our mMostRecentRefresh, but the DoRefresh call won't run
2590 // and notify our observers until we get back to the event loop.
2591 // Thus MostRecentRefresh() will lie between now and the DoRefresh.
2592 RefPtr<nsRunnableMethod<nsRefreshDriver>> event = NewRunnableMethod(
2593 "nsRefreshDriver::DoRefresh", this, &nsRefreshDriver::DoRefresh);
2594 nsPresContext* pc = GetPresContext();
2595 if (pc) {
2596 pc->Document()->Dispatch(TaskCategory::Other, event.forget());
2597 EnsureTimerStarted();
2598 } else {
2599 NS_ERROR("Thawing while document is being destroyed");
2600 }
2601 }
2602 }
2603 }
2604
FinishedWaitingForTransaction()2605 void nsRefreshDriver::FinishedWaitingForTransaction() {
2606 mWaitingForTransaction = false;
2607 mSkippedPaints = false;
2608 mWarningThreshold = 1;
2609 }
2610
GetTransactionId(bool aThrottle)2611 mozilla::layers::TransactionId nsRefreshDriver::GetTransactionId(
2612 bool aThrottle) {
2613 mNextTransactionId = mNextTransactionId.Next();
2614 LOG("[%p] Allocating transaction id %" PRIu64, this, mNextTransactionId.mId);
2615
2616 // If this a paint from within a normal tick, and the caller hasn't explicitly
2617 // asked for it to skip being throttled, then record this transaction as
2618 // pending and maybe disable painting until some transactions are processed.
2619 if (aThrottle && mInNormalTick) {
2620 mPendingTransactions.AppendElement(mNextTransactionId);
2621 if (TooManyPendingTransactions() && !mWaitingForTransaction &&
2622 !mTestControllingRefreshes) {
2623 LOG("[%p] Hit max pending transaction limit, entering wait mode", this);
2624 mWaitingForTransaction = true;
2625 mSkippedPaints = false;
2626 mWarningThreshold = 1;
2627 }
2628 }
2629
2630 return mNextTransactionId;
2631 }
2632
LastTransactionId() const2633 mozilla::layers::TransactionId nsRefreshDriver::LastTransactionId() const {
2634 return mNextTransactionId;
2635 }
2636
RevokeTransactionId(mozilla::layers::TransactionId aTransactionId)2637 void nsRefreshDriver::RevokeTransactionId(
2638 mozilla::layers::TransactionId aTransactionId) {
2639 MOZ_ASSERT(aTransactionId == mNextTransactionId);
2640 LOG("[%p] Revoking transaction id %" PRIu64, this, aTransactionId.mId);
2641 if (AtPendingTransactionLimit() &&
2642 mPendingTransactions.Contains(aTransactionId) && mWaitingForTransaction) {
2643 LOG("[%p] No longer over pending transaction limit, leaving wait state",
2644 this);
2645 MOZ_ASSERT(!mSkippedPaints,
2646 "How did we skip a paint when we're in the middle of one?");
2647 FinishedWaitingForTransaction();
2648 }
2649
2650 // Notify the pres context so that it can deliver MozAfterPaint for this
2651 // id if any caller was expecting it.
2652 nsPresContext* pc = GetPresContext();
2653 if (pc) {
2654 pc->NotifyRevokingDidPaint(aTransactionId);
2655 }
2656 // Remove aTransactionId from the set of outstanding transactions since we're
2657 // no longer waiting on it to be completed, but don't revert
2658 // mNextTransactionId since we can't use the id again.
2659 mPendingTransactions.RemoveElement(aTransactionId);
2660 }
2661
ClearPendingTransactions()2662 void nsRefreshDriver::ClearPendingTransactions() {
2663 LOG("[%p] ClearPendingTransactions", this);
2664 mPendingTransactions.Clear();
2665 mWaitingForTransaction = false;
2666 }
2667
ResetInitialTransactionId(mozilla::layers::TransactionId aTransactionId)2668 void nsRefreshDriver::ResetInitialTransactionId(
2669 mozilla::layers::TransactionId aTransactionId) {
2670 mNextTransactionId = aTransactionId;
2671 }
2672
GetTransactionStart()2673 mozilla::TimeStamp nsRefreshDriver::GetTransactionStart() { return mTickStart; }
2674
GetVsyncId()2675 VsyncId nsRefreshDriver::GetVsyncId() { return mTickVsyncId; }
2676
GetVsyncStart()2677 mozilla::TimeStamp nsRefreshDriver::GetVsyncStart() { return mTickVsyncTime; }
2678
NotifyTransactionCompleted(mozilla::layers::TransactionId aTransactionId)2679 void nsRefreshDriver::NotifyTransactionCompleted(
2680 mozilla::layers::TransactionId aTransactionId) {
2681 LOG("[%p] Completed transaction id %" PRIu64, this, aTransactionId.mId);
2682 mPendingTransactions.RemoveElement(aTransactionId);
2683 if (mWaitingForTransaction && !TooManyPendingTransactions()) {
2684 LOG("[%p] No longer over pending transaction limit, leaving wait state",
2685 this);
2686 FinishedWaitingForTransaction();
2687 }
2688 }
2689
WillRefresh(mozilla::TimeStamp aTime)2690 void nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime) {
2691 mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
2692 mRootRefresh = nullptr;
2693 if (mSkippedPaints) {
2694 DoRefresh();
2695 }
2696 }
2697
IsWaitingForPaint(mozilla::TimeStamp aTime)2698 bool nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime) {
2699 if (mTestControllingRefreshes) {
2700 return false;
2701 }
2702
2703 if (mWaitingForTransaction) {
2704 if (mSkippedPaints &&
2705 aTime > (mMostRecentRefresh +
2706 TimeDuration::FromMilliseconds(mWarningThreshold * 1000))) {
2707 // XXX - Bug 1303369 - too many false positives.
2708 // gfxCriticalNote << "Refresh driver waiting for the compositor for "
2709 // << (aTime - mMostRecentRefresh).ToSeconds()
2710 // << " seconds.";
2711 mWarningThreshold *= 2;
2712 }
2713
2714 LOG("[%p] Over max pending transaction limit when trying to paint, "
2715 "skipping",
2716 this);
2717 mSkippedPaints = true;
2718 return true;
2719 }
2720
2721 // Try find the 'root' refresh driver for the current window and check
2722 // if that is waiting for a paint.
2723 nsPresContext* pc = GetPresContext();
2724 nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
2725 if (rootContext) {
2726 nsRefreshDriver* rootRefresh = rootContext->RefreshDriver();
2727 if (rootRefresh && rootRefresh != this) {
2728 if (rootRefresh->IsWaitingForPaint(aTime)) {
2729 if (mRootRefresh != rootRefresh) {
2730 if (mRootRefresh) {
2731 mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
2732 }
2733 rootRefresh->AddRefreshObserver(this, FlushType::Style,
2734 "Waiting for paint");
2735 mRootRefresh = rootRefresh;
2736 }
2737 mSkippedPaints = true;
2738 return true;
2739 }
2740 }
2741 }
2742 return false;
2743 }
2744
SetThrottled(bool aThrottled)2745 void nsRefreshDriver::SetThrottled(bool aThrottled) {
2746 if (aThrottled != mThrottled) {
2747 mThrottled = aThrottled;
2748 if (mActiveTimer) {
2749 // We want to switch our timer type here, so just stop and
2750 // restart the timer.
2751 EnsureTimerStarted(eForceAdjustTimer);
2752 }
2753 }
2754 }
2755
GetPresContext() const2756 nsPresContext* nsRefreshDriver::GetPresContext() const { return mPresContext; }
2757
DoRefresh()2758 void nsRefreshDriver::DoRefresh() {
2759 // Don't do a refresh unless we're in a state where we should be refreshing.
2760 if (!IsFrozen() && mPresContext && mActiveTimer) {
2761 DoTick();
2762 }
2763 }
2764
2765 #ifdef DEBUG
IsRefreshObserver(nsARefreshObserver * aObserver,FlushType aFlushType)2766 bool nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
2767 FlushType aFlushType) {
2768 ObserverArray& array = ArrayFor(aFlushType);
2769 return array.Contains(aObserver);
2770 }
2771 #endif
2772
ScheduleViewManagerFlush()2773 void nsRefreshDriver::ScheduleViewManagerFlush() {
2774 NS_ASSERTION(mPresContext->IsRoot(),
2775 "Should only schedule view manager flush on root prescontexts");
2776 mViewManagerFlushIsPending = true;
2777 if (!mViewManagerFlushCause) {
2778 mViewManagerFlushCause = profiler_capture_backtrace();
2779 }
2780 mHasScheduleFlush = true;
2781 EnsureTimerStarted(eNeverAdjustTimer);
2782 }
2783
ScheduleFrameRequestCallbacks(Document * aDocument)2784 void nsRefreshDriver::ScheduleFrameRequestCallbacks(Document* aDocument) {
2785 NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
2786 mFrameRequestCallbackDocs.NoIndex &&
2787 mThrottledFrameRequestCallbackDocs.IndexOf(aDocument) ==
2788 mThrottledFrameRequestCallbackDocs.NoIndex,
2789 "Don't schedule the same document multiple times");
2790 if (aDocument->ShouldThrottleFrameRequests()) {
2791 mThrottledFrameRequestCallbackDocs.AppendElement(aDocument);
2792 } else {
2793 mFrameRequestCallbackDocs.AppendElement(aDocument);
2794 }
2795
2796 // make sure that the timer is running
2797 EnsureTimerStarted();
2798 }
2799
RevokeFrameRequestCallbacks(Document * aDocument)2800 void nsRefreshDriver::RevokeFrameRequestCallbacks(Document* aDocument) {
2801 mFrameRequestCallbackDocs.RemoveElement(aDocument);
2802 mThrottledFrameRequestCallbackDocs.RemoveElement(aDocument);
2803 // No need to worry about restarting our timer in slack mode if it's already
2804 // running; that will happen automatically when it fires.
2805 }
2806
ScheduleFullscreenEvent(UniquePtr<PendingFullscreenEvent> aEvent)2807 void nsRefreshDriver::ScheduleFullscreenEvent(
2808 UniquePtr<PendingFullscreenEvent> aEvent) {
2809 mPendingFullscreenEvents.AppendElement(std::move(aEvent));
2810 // make sure that the timer is running
2811 EnsureTimerStarted();
2812 }
2813
CancelPendingFullscreenEvents(Document * aDocument)2814 void nsRefreshDriver::CancelPendingFullscreenEvents(Document* aDocument) {
2815 for (auto i : Reversed(IntegerRange(mPendingFullscreenEvents.Length()))) {
2816 if (mPendingFullscreenEvents[i]->Document() == aDocument) {
2817 mPendingFullscreenEvents.RemoveElementAt(i);
2818 }
2819 }
2820 }
2821
CancelPendingAnimationEvents(AnimationEventDispatcher * aDispatcher)2822 void nsRefreshDriver::CancelPendingAnimationEvents(
2823 AnimationEventDispatcher* aDispatcher) {
2824 MOZ_ASSERT(aDispatcher);
2825 aDispatcher->ClearEventQueue();
2826 mAnimationEventFlushObservers.RemoveElement(aDispatcher);
2827 }
2828
2829 /* static */
GetIdleDeadlineHint(TimeStamp aDefault)2830 TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault) {
2831 MOZ_ASSERT(NS_IsMainThread());
2832 MOZ_ASSERT(!aDefault.IsNull());
2833
2834 // For computing idleness of refresh drivers we only care about
2835 // sRegularRateTimerList, since we consider refresh drivers attached to
2836 // sThrottledRateTimer to be inactive. This implies that tasks
2837 // resulting from a tick on the sRegularRateTimer counts as being
2838 // busy but tasks resulting from a tick on sThrottledRateTimer
2839 // counts as being idle.
2840 if (sRegularRateTimer) {
2841 return sRegularRateTimer->GetIdleDeadlineHint(aDefault);
2842 }
2843
2844 // The following calculation is only used on platform using per-BrowserChild
2845 // Vsync. This is hard to properly map on static calls such as this -
2846 // optimally we'd only want to query the timers that are relevant for the
2847 // caller, not all in this process. Further more, in this scenario we often
2848 // hit cases where timers would return their fallback value that is aDefault,
2849 // giving us a much higher value than intended.
2850 // For now we use a somewhat simplistic approach that in many situations
2851 // gives us similar behaviour to what we would get using sRegularRateTimer:
2852 // use the highest result that is still lower than the aDefault fallback.
2853 TimeStamp hint = TimeStamp();
2854 if (sRegularRateTimerList) {
2855 for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
2856 TimeStamp newHint = timer->GetIdleDeadlineHint(aDefault);
2857 if (newHint < aDefault && (hint.IsNull() || newHint > hint)) {
2858 hint = newHint;
2859 }
2860 }
2861 }
2862
2863 return hint.IsNull() ? aDefault : hint;
2864 }
2865
2866 /* static */
GetNextTickHint()2867 Maybe<TimeStamp> nsRefreshDriver::GetNextTickHint() {
2868 MOZ_ASSERT(NS_IsMainThread());
2869
2870 if (sRegularRateTimer) {
2871 return sRegularRateTimer->GetNextTickHint();
2872 }
2873
2874 Maybe<TimeStamp> hint = Nothing();
2875 if (sRegularRateTimerList) {
2876 for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
2877 if (Maybe<TimeStamp> newHint = timer->GetNextTickHint()) {
2878 if (!hint || newHint.value() < hint.value()) {
2879 hint = newHint;
2880 }
2881 }
2882 }
2883 }
2884 return hint;
2885 }
2886
Disconnect()2887 void nsRefreshDriver::Disconnect() {
2888 MOZ_ASSERT(NS_IsMainThread());
2889
2890 StopTimer();
2891
2892 if (mPresContext) {
2893 mPresContext = nullptr;
2894 if (--sRefreshDriverCount == 0) {
2895 Shutdown();
2896 }
2897 }
2898 }
2899
2900 #undef LOG
2901