Bug 1877662 - expose mozconfig as an artifact from build-fat-aar. r=glandium,geckovie...
[gecko.git] / layout / base / nsRefreshDriver.cpp
bloba5c2b1ded990d883a8c1b5d9d1d8dad3c77a23f4
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/. */
7 /*
8 * Code to notify things that animate before a refresh, at an appropriate
9 * refresh rate. (Perhaps temporary, until replaced by compositor.)
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.
20 #include "nsRefreshDriver.h"
21 #include "mozilla/DataMutex.h"
22 #include "nsThreadUtils.h"
24 #ifdef XP_WIN
25 # include <windows.h>
26 // mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have
27 // to manually include it
28 # include <mmsystem.h>
29 # include "WinUtils.h"
30 #endif
32 #include "mozilla/AnimationEventDispatcher.h"
33 #include "mozilla/ArrayUtils.h"
34 #include "mozilla/Assertions.h"
35 #include "mozilla/AutoRestore.h"
36 #include "mozilla/BasePrincipal.h"
37 #include "mozilla/dom/MediaQueryList.h"
38 #include "mozilla/CycleCollectedJSContext.h"
39 #include "mozilla/DebugOnly.h"
40 #include "mozilla/DisplayPortUtils.h"
41 #include "mozilla/Hal.h"
42 #include "mozilla/InputTaskManager.h"
43 #include "mozilla/IntegerRange.h"
44 #include "mozilla/PresShell.h"
45 #include "mozilla/VsyncTaskManager.h"
46 #include "nsITimer.h"
47 #include "nsLayoutUtils.h"
48 #include "nsPresContext.h"
49 #include "imgRequest.h"
50 #include "nsComponentManagerUtils.h"
51 #include "mozilla/Logging.h"
52 #include "mozilla/dom/Document.h"
53 #include "mozilla/dom/DocumentInlines.h"
54 #include "nsIXULRuntime.h"
55 #include "jsapi.h"
56 #include "nsContentUtils.h"
57 #include "nsTextFrame.h"
58 #include "mozilla/PendingFullscreenEvent.h"
59 #include "mozilla/dom/PerformanceMainThread.h"
60 #include "mozilla/Preferences.h"
61 #include "mozilla/StaticPrefs_apz.h"
62 #include "mozilla/StaticPrefs_gfx.h"
63 #include "mozilla/StaticPrefs_idle_period.h"
64 #include "mozilla/StaticPrefs_layout.h"
65 #include "mozilla/StaticPrefs_page_load.h"
66 #include "nsViewManager.h"
67 #include "GeckoProfiler.h"
68 #include "mozilla/dom/BrowserChild.h"
69 #include "mozilla/dom/CallbackDebuggerNotification.h"
70 #include "mozilla/dom/ContentChild.h"
71 #include "mozilla/dom/Event.h"
72 #include "mozilla/dom/Performance.h"
73 #include "mozilla/dom/Selection.h"
74 #include "mozilla/dom/VsyncMainChild.h"
75 #include "mozilla/dom/WindowBinding.h"
76 #include "mozilla/dom/LargestContentfulPaint.h"
77 #include "mozilla/layers/WebRenderLayerManager.h"
78 #include "mozilla/RestyleManager.h"
79 #include "mozilla/TaskController.h"
80 #include "imgIContainer.h"
81 #include "mozilla/dom/ScriptSettings.h"
82 #include "nsDocShell.h"
83 #include "nsISimpleEnumerator.h"
84 #include "nsJSEnvironment.h"
85 #include "mozilla/ScopeExit.h"
86 #include "mozilla/Telemetry.h"
88 #include "mozilla/ipc/BackgroundChild.h"
89 #include "mozilla/ipc/PBackgroundChild.h"
90 #include "VsyncSource.h"
91 #include "mozilla/VsyncDispatcher.h"
92 #include "mozilla/Unused.h"
93 #include "nsAnimationManager.h"
94 #include "nsDisplayList.h"
95 #include "nsDOMNavigationTiming.h"
96 #include "nsTransitionManager.h"
98 #if defined(MOZ_WIDGET_ANDROID)
99 # include "VRManagerChild.h"
100 #endif // defined(MOZ_WIDGET_ANDROID)
102 #include "nsXULPopupManager.h"
104 #include <numeric>
106 using namespace mozilla;
107 using namespace mozilla::widget;
108 using namespace mozilla::ipc;
109 using namespace mozilla::dom;
110 using namespace mozilla::layout;
112 static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver");
113 #define LOG(...) \
114 MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
116 // after 10 minutes, stop firing off inactive timers
117 #define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
119 // The number of seconds spent skipping frames because we are waiting for the
120 // compositor before logging.
121 #if defined(MOZ_ASAN)
122 # define REFRESH_WAIT_WARNING 5
123 #elif defined(DEBUG) && !defined(MOZ_VALGRIND)
124 # define REFRESH_WAIT_WARNING 5
125 #elif defined(DEBUG) && defined(MOZ_VALGRIND)
126 # define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 20 : 5)
127 #elif defined(MOZ_VALGRIND)
128 # define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 10 : 1)
129 #else
130 # define REFRESH_WAIT_WARNING 1
131 #endif
133 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsRefreshDriver::TickReasons);
135 namespace {
136 // The number outstanding nsRefreshDrivers (that have been created but not
137 // disconnected). When this reaches zero we will call
138 // nsRefreshDriver::Shutdown.
139 static uint32_t sRefreshDriverCount = 0;
140 } // namespace
142 namespace mozilla {
144 static TimeStamp sMostRecentHighRateVsync;
146 static TimeDuration sMostRecentHighRate;
149 * The base class for all global refresh driver timers. It takes care
150 * of managing the list of refresh drivers attached to them and
151 * provides interfaces for querying/setting the rate and actually
152 * running a timer 'Tick'. Subclasses must implement StartTimer(),
153 * StopTimer(), and ScheduleNextTick() -- the first two just
154 * start/stop whatever timer mechanism is in use, and ScheduleNextTick
155 * is called at the start of the Tick() implementation to set a time
156 * for the next tick.
158 class RefreshDriverTimer {
159 public:
160 RefreshDriverTimer() = default;
162 NS_INLINE_DECL_REFCOUNTING(RefreshDriverTimer)
164 virtual void AddRefreshDriver(nsRefreshDriver* aDriver) {
165 LOG("[%p] AddRefreshDriver %p", this, aDriver);
167 bool startTimer =
168 mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
169 if (IsRootRefreshDriver(aDriver)) {
170 NS_ASSERTION(!mRootRefreshDrivers.Contains(aDriver),
171 "Adding a duplicate root refresh driver!");
172 mRootRefreshDrivers.AppendElement(aDriver);
173 } else {
174 NS_ASSERTION(!mContentRefreshDrivers.Contains(aDriver),
175 "Adding a duplicate content refresh driver!");
176 mContentRefreshDrivers.AppendElement(aDriver);
179 if (startTimer) {
180 StartTimer();
184 void RemoveRefreshDriver(nsRefreshDriver* aDriver) {
185 LOG("[%p] RemoveRefreshDriver %p", this, aDriver);
187 if (IsRootRefreshDriver(aDriver)) {
188 NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver),
189 "RemoveRefreshDriver for a refresh driver that's not in the "
190 "root refresh list!");
191 mRootRefreshDrivers.RemoveElement(aDriver);
192 } else {
193 nsPresContext* pc = aDriver->GetPresContext();
194 nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
195 // During PresContext shutdown, we can't accurately detect
196 // if a root refresh driver exists or not. Therefore, we have to
197 // search and find out which list this driver exists in.
198 if (!rootContext) {
199 if (mRootRefreshDrivers.Contains(aDriver)) {
200 mRootRefreshDrivers.RemoveElement(aDriver);
201 } else {
202 NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
203 "RemoveRefreshDriver without a display root for a "
204 "driver that is not in the content refresh list");
205 mContentRefreshDrivers.RemoveElement(aDriver);
207 } else {
208 NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
209 "RemoveRefreshDriver for a driver that is not in the "
210 "content refresh list");
211 mContentRefreshDrivers.RemoveElement(aDriver);
215 bool stopTimer =
216 mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
217 if (stopTimer) {
218 StopTimer();
222 TimeStamp MostRecentRefresh() const { return mLastFireTime; }
223 VsyncId MostRecentRefreshVsyncId() const { return mLastFireId; }
224 virtual bool IsBlocked() { return false; }
226 virtual TimeDuration GetTimerRate() = 0;
228 TimeStamp GetIdleDeadlineHint(TimeStamp aDefault) {
229 MOZ_ASSERT(NS_IsMainThread());
231 if (!IsTicking() && !gfxPlatform::IsInLayoutAsapMode()) {
232 return aDefault;
235 TimeStamp mostRecentRefresh = MostRecentRefresh();
236 TimeDuration refreshPeriod = GetTimerRate();
237 TimeStamp idleEnd = mostRecentRefresh + refreshPeriod;
238 double highRateMultiplier = nsRefreshDriver::HighRateMultiplier();
240 // If we haven't painted for some time, then guess that we won't paint
241 // again for a while, so the refresh driver is not a good way to predict
242 // idle time.
243 if (highRateMultiplier == 1.0 &&
244 (idleEnd +
245 refreshPeriod *
246 StaticPrefs::layout_idle_period_required_quiescent_frames() <
247 TimeStamp::Now())) {
248 return aDefault;
251 // End the predicted idle time a little early, the amount controlled by a
252 // pref, to prevent overrunning the idle time and delaying a frame.
253 // But do that only if we aren't in high rate mode.
254 idleEnd = idleEnd - TimeDuration::FromMilliseconds(
255 highRateMultiplier *
256 StaticPrefs::layout_idle_period_time_limit());
257 return idleEnd < aDefault ? idleEnd : aDefault;
260 Maybe<TimeStamp> GetNextTickHint() {
261 MOZ_ASSERT(NS_IsMainThread());
262 TimeStamp nextTick = MostRecentRefresh() + GetTimerRate();
263 return nextTick < TimeStamp::Now() ? Nothing() : Some(nextTick);
266 // Returns null if the RefreshDriverTimer is attached to several
267 // RefreshDrivers. That may happen for example when there are
268 // several windows open.
269 nsPresContext* GetPresContextForOnlyRefreshDriver() {
270 if (mRootRefreshDrivers.Length() == 1 && mContentRefreshDrivers.IsEmpty()) {
271 return mRootRefreshDrivers[0]->GetPresContext();
273 if (mContentRefreshDrivers.Length() == 1 && mRootRefreshDrivers.IsEmpty()) {
274 return mContentRefreshDrivers[0]->GetPresContext();
276 return nullptr;
279 bool IsAnyToplevelContentPageLoading() {
280 for (nsTArray<RefPtr<nsRefreshDriver>>* drivers :
281 {&mRootRefreshDrivers, &mContentRefreshDrivers}) {
282 for (RefPtr<nsRefreshDriver>& driver : *drivers) {
283 if (nsPresContext* pc = driver->GetPresContext()) {
284 if (pc->Document()->IsTopLevelContentDocument() &&
285 pc->Document()->GetReadyStateEnum() <
286 Document::READYSTATE_COMPLETE) {
287 return true;
293 return false;
296 protected:
297 virtual ~RefreshDriverTimer() {
298 MOZ_ASSERT(
299 mContentRefreshDrivers.Length() == 0,
300 "Should have removed all content refresh drivers from here by now!");
301 MOZ_ASSERT(
302 mRootRefreshDrivers.Length() == 0,
303 "Should have removed all root refresh drivers from here by now!");
306 virtual void StartTimer() = 0;
307 virtual void StopTimer() = 0;
308 virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
310 public:
311 virtual bool IsTicking() const = 0;
313 protected:
314 bool IsRootRefreshDriver(nsRefreshDriver* aDriver) {
315 nsPresContext* pc = aDriver->GetPresContext();
316 nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
317 if (!rootContext) {
318 return false;
321 return aDriver == rootContext->RefreshDriver();
325 * Actually runs a tick, poking all the attached RefreshDrivers.
326 * Grabs the "now" time via TimeStamp::Now().
328 void Tick() {
329 TimeStamp now = TimeStamp::Now();
330 Tick(VsyncId(), now);
333 void TickRefreshDrivers(VsyncId aId, TimeStamp aNow,
334 nsTArray<RefPtr<nsRefreshDriver>>& aDrivers) {
335 if (aDrivers.IsEmpty()) {
336 return;
339 for (nsRefreshDriver* driver : aDrivers.Clone()) {
340 // don't poke this driver if it's in test mode
341 if (driver->IsTestControllingRefreshesEnabled()) {
342 continue;
345 TickDriver(driver, aId, aNow);
350 * Tick the refresh drivers based on the given timestamp.
352 void Tick(VsyncId aId, TimeStamp now) {
353 ScheduleNextTick(now);
355 mLastFireTime = now;
356 mLastFireId = aId;
358 LOG("[%p] ticking drivers...", this);
360 TickRefreshDrivers(aId, now, mContentRefreshDrivers);
361 TickRefreshDrivers(aId, now, mRootRefreshDrivers);
363 LOG("[%p] done.", this);
366 static void TickDriver(nsRefreshDriver* driver, VsyncId aId, TimeStamp now) {
367 driver->Tick(aId, now);
370 TimeStamp mLastFireTime;
371 VsyncId mLastFireId;
372 TimeStamp mTargetTime;
374 nsTArray<RefPtr<nsRefreshDriver>> mContentRefreshDrivers;
375 nsTArray<RefPtr<nsRefreshDriver>> mRootRefreshDrivers;
377 // useful callback for nsITimer-based derived classes, here
378 // because of c++ protected shenanigans
379 static void TimerTick(nsITimer* aTimer, void* aClosure) {
380 RefPtr<RefreshDriverTimer> timer =
381 static_cast<RefreshDriverTimer*>(aClosure);
382 timer->Tick();
387 * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that
388 * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to
389 * implement ScheduleNextTick and intelligently calculate the next time to tick,
390 * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain
391 * with its attempt at intelligent slack removal and such, so we don't do it.
393 class SimpleTimerBasedRefreshDriverTimer : public RefreshDriverTimer {
394 public:
396 * aRate -- the delay, in milliseconds, requested between timer firings
398 explicit SimpleTimerBasedRefreshDriverTimer(double aRate) {
399 SetRate(aRate);
400 mTimer = NS_NewTimer();
403 virtual ~SimpleTimerBasedRefreshDriverTimer() override { StopTimer(); }
405 // will take effect at next timer tick
406 virtual void SetRate(double aNewRate) {
407 mRateMilliseconds = aNewRate;
408 mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
411 double GetRate() const { return mRateMilliseconds; }
413 TimeDuration GetTimerRate() override { return mRateDuration; }
415 protected:
416 void StartTimer() override {
417 // pretend we just fired, and we schedule the next tick normally
418 mLastFireTime = TimeStamp::Now();
419 mLastFireId = VsyncId();
421 mTargetTime = mLastFireTime + mRateDuration;
423 uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
424 mTimer->InitWithNamedFuncCallback(
425 TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT,
426 "SimpleTimerBasedRefreshDriverTimer::StartTimer");
429 void StopTimer() override { mTimer->Cancel(); }
431 double mRateMilliseconds;
432 TimeDuration mRateDuration;
433 RefPtr<nsITimer> mTimer;
437 * A refresh driver that listens to vsync events and ticks the refresh driver
438 * on vsync intervals. We throttle the refresh driver if we get too many
439 * vsync events and wait to catch up again.
441 class VsyncRefreshDriverTimer : public RefreshDriverTimer {
442 public:
443 // This is used in the parent process for all platforms except Linux Wayland.
444 static RefPtr<VsyncRefreshDriverTimer>
445 CreateForParentProcessWithGlobalVsync() {
446 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
447 MOZ_RELEASE_ASSERT(NS_IsMainThread());
448 RefPtr<VsyncDispatcher> vsyncDispatcher =
449 gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher();
450 RefPtr<VsyncRefreshDriverTimer> timer =
451 new VsyncRefreshDriverTimer(std::move(vsyncDispatcher), nullptr);
452 return timer.forget();
455 // This is used in the parent process for Linux Wayland only, where we have a
456 // per-widget VsyncSource which is independent from the gfxPlatform's global
457 // VsyncSource.
458 static RefPtr<VsyncRefreshDriverTimer>
459 CreateForParentProcessWithLocalVsyncDispatcher(
460 RefPtr<VsyncDispatcher>&& aVsyncDispatcher) {
461 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
462 MOZ_RELEASE_ASSERT(NS_IsMainThread());
463 RefPtr<VsyncRefreshDriverTimer> timer =
464 new VsyncRefreshDriverTimer(std::move(aVsyncDispatcher), nullptr);
465 return timer.forget();
468 // This is used in the content process.
469 static RefPtr<VsyncRefreshDriverTimer> CreateForContentProcess(
470 RefPtr<VsyncMainChild>&& aVsyncChild) {
471 MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
472 MOZ_RELEASE_ASSERT(NS_IsMainThread());
473 RefPtr<VsyncRefreshDriverTimer> timer =
474 new VsyncRefreshDriverTimer(nullptr, std::move(aVsyncChild));
475 return timer.forget();
478 TimeDuration GetTimerRate() override {
479 if (mVsyncDispatcher) {
480 mVsyncRate = mVsyncDispatcher->GetVsyncRate();
481 } else if (mVsyncChild) {
482 mVsyncRate = mVsyncChild->GetVsyncRate();
485 // If hardware queries fail / are unsupported, we have to just guess.
486 return mVsyncRate != TimeDuration::Forever()
487 ? mVsyncRate
488 : TimeDuration::FromMilliseconds(1000.0 / 60.0);
491 bool IsBlocked() override {
492 return !mSuspendVsyncPriorityTicksUntil.IsNull() &&
493 mSuspendVsyncPriorityTicksUntil > TimeStamp::Now() &&
494 ShouldGiveNonVsyncTasksMoreTime();
497 private:
498 // RefreshDriverVsyncObserver redirects vsync notifications to the main thread
499 // and calls VsyncRefreshDriverTimer::NotifyVsyncOnMainThread on it. It also
500 // acts as a weak reference to the refresh driver timer, dropping its
501 // reference when RefreshDriverVsyncObserver::Shutdown is called from the
502 // timer's destructor.
504 // RefreshDriverVsyncObserver::NotifyVsync is called from different places
505 // depending on the process type.
507 // Parent process:
508 // NotifyVsync is called by RefreshDriverVsyncDispatcher, on a background
509 // thread. RefreshDriverVsyncDispatcher keeps strong references to its
510 // VsyncObservers, both in its array of observers and while calling
511 // NotifyVsync. So it might drop its last reference to the observer on a
512 // background thread. This means that the VsyncRefreshDriverTimer itself can't
513 // be the observer (because its destructor would potentially be run on a
514 // background thread), and it's why we use this separate class.
516 // Child process:
517 // NotifyVsync is called by VsyncMainChild, on the main thread.
518 // VsyncMainChild keeps raw pointers to its observers.
519 class RefreshDriverVsyncObserver final : public VsyncObserver {
520 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(
521 VsyncRefreshDriverTimer::RefreshDriverVsyncObserver, override)
523 public:
524 explicit RefreshDriverVsyncObserver(
525 VsyncRefreshDriverTimer* aVsyncRefreshDriverTimer)
526 : mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer),
527 mLastPendingVsyncNotification(
528 "RefreshDriverVsyncObserver::mLastPendingVsyncNotification") {
529 MOZ_ASSERT(NS_IsMainThread());
532 void NotifyVsync(const VsyncEvent& aVsync) override {
533 // Compress vsync notifications such that only 1 may run at a time
534 // This is so that we don't flood the refresh driver with vsync messages
535 // if the main thread is blocked for long periods of time
536 { // scope lock
537 auto pendingVsync = mLastPendingVsyncNotification.Lock();
538 bool hadPendingVsync = pendingVsync->isSome();
539 *pendingVsync = Some(aVsync);
540 if (hadPendingVsync) {
541 return;
545 if (XRE_IsContentProcess()) {
546 // In the content process, NotifyVsync is called by VsyncMainChild on
547 // the main thread. No need to use a runnable, just call
548 // NotifyVsyncTimerOnMainThread() directly.
549 NotifyVsyncTimerOnMainThread();
550 return;
553 // In the parent process, NotifyVsync is called on the vsync thread, which
554 // on most platforms is different from the main thread, so we need to
555 // dispatch a runnable for running NotifyVsyncTimerOnMainThread on the
556 // main thread.
557 // TODO: On Linux Wayland, the vsync thread is currently the main thread,
558 // and yet we still dispatch the runnable. Do we need to?
559 bool useVsyncPriority = mozilla::BrowserTabsRemoteAutostart();
560 nsCOMPtr<nsIRunnable> vsyncEvent = new PrioritizableRunnable(
561 NS_NewRunnableFunction(
562 "RefreshDriverVsyncObserver::NotifyVsyncTimerOnMainThread",
563 [self = RefPtr{this}]() {
564 self->NotifyVsyncTimerOnMainThread();
566 useVsyncPriority ? nsIRunnablePriority::PRIORITY_VSYNC
567 : nsIRunnablePriority::PRIORITY_NORMAL);
568 NS_DispatchToMainThread(vsyncEvent);
571 void NotifyVsyncTimerOnMainThread() {
572 MOZ_ASSERT(NS_IsMainThread());
574 if (!mVsyncRefreshDriverTimer) {
575 // Ignore calls after Shutdown.
576 return;
579 VsyncEvent vsyncEvent;
581 // Get the last of the queued-up vsync notifications.
582 auto pendingVsync = mLastPendingVsyncNotification.Lock();
583 MOZ_RELEASE_ASSERT(
584 pendingVsync->isSome(),
585 "We should always have a pending vsync notification here.");
586 vsyncEvent = pendingVsync->extract();
589 // Call VsyncRefreshDriverTimer::NotifyVsyncOnMainThread, and keep a
590 // strong reference to it while calling the method.
591 RefPtr<VsyncRefreshDriverTimer> timer = mVsyncRefreshDriverTimer;
592 timer->NotifyVsyncOnMainThread(vsyncEvent);
595 void Shutdown() {
596 MOZ_ASSERT(NS_IsMainThread());
597 mVsyncRefreshDriverTimer = nullptr;
600 private:
601 ~RefreshDriverVsyncObserver() = default;
603 // VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will
604 // be always available before Shutdown(). We can just use the raw pointer
605 // here.
606 // Only accessed on the main thread.
607 VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer;
609 // Non-empty between a call to NotifyVsync and a call to
610 // NotifyVsyncOnMainThread. When multiple vsync notifications have been
611 // received between those two calls, this contains the last of the pending
612 // notifications. This is used both in the parent process and in the child
613 // process, but it only does something useful in the parent process. In the
614 // child process, both calls happen on the main thread right after one
615 // another, so there's only one notification to keep track of; vsync
616 // notification coalescing for child processes happens at the IPC level
617 // instead.
618 DataMutex<Maybe<VsyncEvent>> mLastPendingVsyncNotification;
620 }; // RefreshDriverVsyncObserver
622 VsyncRefreshDriverTimer(RefPtr<VsyncDispatcher>&& aVsyncDispatcher,
623 RefPtr<VsyncMainChild>&& aVsyncChild)
624 : mVsyncDispatcher(aVsyncDispatcher),
625 mVsyncChild(aVsyncChild),
626 mVsyncRate(TimeDuration::Forever()),
627 mRecentVsync(TimeStamp::Now()),
628 mLastTickStart(TimeStamp::Now()),
629 mLastIdleTaskCount(0),
630 mLastRunOutOfMTTasksCount(0),
631 mProcessedVsync(true),
632 mHasPendingLowPrioTask(false) {
633 mVsyncObserver = new RefreshDriverVsyncObserver(this);
636 ~VsyncRefreshDriverTimer() override {
637 if (mVsyncDispatcher) {
638 mVsyncDispatcher->RemoveVsyncObserver(mVsyncObserver);
639 mVsyncDispatcher = nullptr;
640 } else if (mVsyncChild) {
641 mVsyncChild->RemoveChildRefreshTimer(mVsyncObserver);
642 mVsyncChild = nullptr;
645 // Detach current vsync timer from this VsyncObserver. The observer will no
646 // longer tick this timer.
647 mVsyncObserver->Shutdown();
648 mVsyncObserver = nullptr;
651 bool ShouldGiveNonVsyncTasksMoreTime(bool aCheckOnlyNewPendingTasks = false) {
652 TaskController* taskController = TaskController::Get();
653 IdleTaskManager* idleTaskManager = taskController->GetIdleTaskManager();
654 VsyncTaskManager* vsyncTaskManager = VsyncTaskManager::Get();
656 // Note, pendingTaskCount includes also all the pending idle and vsync
657 // tasks.
658 uint64_t pendingTaskCount =
659 taskController->PendingMainthreadTaskCountIncludingSuspended();
660 uint64_t pendingIdleTaskCount = idleTaskManager->PendingTaskCount();
661 uint64_t pendingVsyncTaskCount = vsyncTaskManager->PendingTaskCount();
662 if (!(pendingTaskCount > (pendingIdleTaskCount + pendingVsyncTaskCount))) {
663 return false;
665 if (aCheckOnlyNewPendingTasks) {
666 return true;
669 uint64_t idleTaskCount = idleTaskManager->ProcessedTaskCount();
671 // If we haven't processed new idle tasks and we have pending
672 // non-idle tasks, give those non-idle tasks more time,
673 // but only if the main thread wasn't totally empty at some point.
674 // In the parent process RunOutOfMTTasksCount() is less meaningful
675 // because some of the tasks run through AppShell.
676 return mLastIdleTaskCount == idleTaskCount &&
677 (taskController->RunOutOfMTTasksCount() ==
678 mLastRunOutOfMTTasksCount ||
679 XRE_IsParentProcess());
682 void NotifyVsyncOnMainThread(const VsyncEvent& aVsyncEvent) {
683 MOZ_ASSERT(NS_IsMainThread());
685 mRecentVsync = aVsyncEvent.mTime;
686 mRecentVsyncId = aVsyncEvent.mId;
687 if (!mSuspendVsyncPriorityTicksUntil.IsNull() &&
688 mSuspendVsyncPriorityTicksUntil > TimeStamp::Now()) {
689 if (ShouldGiveNonVsyncTasksMoreTime()) {
690 if (!IsAnyToplevelContentPageLoading()) {
691 // If pages aren't loading and there aren't other tasks to run,
692 // trigger the pending vsync notification.
693 mPendingVsync = mRecentVsync;
694 mPendingVsyncId = mRecentVsyncId;
695 if (!mHasPendingLowPrioTask) {
696 mHasPendingLowPrioTask = true;
697 NS_DispatchToMainThreadQueue(
698 NS_NewRunnableFunction(
699 "NotifyVsyncOnMainThread[low priority]",
700 [self = RefPtr{this}]() {
701 self->mHasPendingLowPrioTask = false;
702 if (self->mRecentVsync == self->mPendingVsync &&
703 self->mRecentVsyncId == self->mPendingVsyncId &&
704 !self->ShouldGiveNonVsyncTasksMoreTime()) {
705 self->mSuspendVsyncPriorityTicksUntil = TimeStamp();
706 self->NotifyVsyncOnMainThread({self->mPendingVsyncId,
707 self->mPendingVsync,
708 /* unused */
709 TimeStamp()});
712 EventQueuePriority::Low);
715 return;
718 // Clear the value since we aren't blocking anymore because there aren't
719 // any non-idle tasks to process.
720 mSuspendVsyncPriorityTicksUntil = TimeStamp();
723 if (StaticPrefs::layout_lower_priority_refresh_driver_during_load() &&
724 ShouldGiveNonVsyncTasksMoreTime()) {
725 nsPresContext* pctx = GetPresContextForOnlyRefreshDriver();
726 if (pctx && pctx->HadFirstContentfulPaint() && pctx->Document() &&
727 pctx->Document()->GetReadyStateEnum() <
728 Document::READYSTATE_COMPLETE) {
729 nsPIDOMWindowInner* win = pctx->Document()->GetInnerWindow();
730 uint32_t frameRateMultiplier = pctx->GetNextFrameRateMultiplier();
731 if (!frameRateMultiplier) {
732 pctx->DidUseFrameRateMultiplier();
734 if (win && frameRateMultiplier) {
735 dom::Performance* perf = win->GetPerformance();
736 // Limit slower refresh rate to 5 seconds between the
737 // first contentful paint and page load.
738 if (perf &&
739 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
740 if (mProcessedVsync) {
741 mProcessedVsync = false;
742 TimeDuration rate = GetTimerRate();
743 uint32_t slowRate = static_cast<uint32_t>(rate.ToMilliseconds() *
744 frameRateMultiplier);
745 pctx->DidUseFrameRateMultiplier();
746 nsCOMPtr<nsIRunnable> vsyncEvent = NewRunnableMethod<>(
747 "VsyncRefreshDriverTimer::IdlePriorityNotify", this,
748 &VsyncRefreshDriverTimer::IdlePriorityNotify);
749 NS_DispatchToCurrentThreadQueue(vsyncEvent.forget(), slowRate,
750 EventQueuePriority::Idle);
752 return;
758 TickRefreshDriver(aVsyncEvent.mId, aVsyncEvent.mTime);
761 void RecordTelemetryProbes(TimeStamp aVsyncTimestamp) {
762 MOZ_ASSERT(NS_IsMainThread());
763 #ifndef ANDROID /* bug 1142079 */
764 if (XRE_IsParentProcess()) {
765 TimeDuration vsyncLatency = TimeStamp::Now() - aVsyncTimestamp;
766 uint32_t sample = (uint32_t)vsyncLatency.ToMilliseconds();
767 Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS,
768 sample);
769 Telemetry::Accumulate(
770 Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, sample);
771 } else if (mVsyncRate != TimeDuration::Forever()) {
772 TimeDuration contentDelay =
773 (TimeStamp::Now() - mLastTickStart) - mVsyncRate;
774 if (contentDelay.ToMilliseconds() < 0) {
775 // Vsyncs are noisy and some can come at a rate quicker than
776 // the reported hardware rate. In those cases, consider that we have 0
777 // delay.
778 contentDelay = TimeDuration::FromMilliseconds(0);
780 uint32_t sample = (uint32_t)contentDelay.ToMilliseconds();
781 Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS,
782 sample);
783 Telemetry::Accumulate(
784 Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, sample);
785 } else {
786 // Request the vsync rate which VsyncChild stored the last time it got a
787 // vsync notification.
788 mVsyncRate = mVsyncChild->GetVsyncRate();
790 #endif
793 void OnTimerStart() {
794 mLastTickStart = TimeStamp::Now();
795 mLastTickEnd = TimeStamp();
796 mLastIdleTaskCount = 0;
799 void IdlePriorityNotify() {
800 if (mLastProcessedTick.IsNull() || mRecentVsync > mLastProcessedTick) {
801 // mSuspendVsyncPriorityTicksUntil is for high priority vsync
802 // notifications only.
803 mSuspendVsyncPriorityTicksUntil = TimeStamp();
804 TickRefreshDriver(mRecentVsyncId, mRecentVsync);
807 mProcessedVsync = true;
810 hal::PerformanceHintSession* GetPerformanceHintSession() {
811 // The ContentChild creates/destroys the PerformanceHintSession in response
812 // to the process' priority being foregrounded/backgrounded. We can only use
813 // this session when using a single vsync source for the process, otherwise
814 // these threads may be performing work for multiple
815 // VsyncRefreshDriverTimers and we will misreport the work duration.
816 const ContentChild* contentChild = ContentChild::GetSingleton();
817 if (contentChild && mVsyncChild) {
818 return contentChild->PerformanceHintSession();
821 return nullptr;
824 void TickRefreshDriver(VsyncId aId, TimeStamp aVsyncTimestamp) {
825 MOZ_ASSERT(NS_IsMainThread());
827 RecordTelemetryProbes(aVsyncTimestamp);
829 TimeStamp tickStart = TimeStamp::Now();
831 const TimeDuration previousRate = mVsyncRate;
832 const TimeDuration rate = GetTimerRate();
834 hal::PerformanceHintSession* const performanceHintSession =
835 GetPerformanceHintSession();
836 if (performanceHintSession && rate != previousRate) {
837 performanceHintSession->UpdateTargetWorkDuration(
838 ContentChild::GetPerformanceHintTarget(rate));
841 if (TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval()) >
842 rate) {
843 sMostRecentHighRateVsync = tickStart;
844 sMostRecentHighRate = rate;
847 // On 32-bit Windows we sometimes get times where TimeStamp::Now() is not
848 // monotonic because the underlying system apis produce non-monontonic
849 // results. (bug 1306896)
850 #if !defined(_WIN32)
851 MOZ_ASSERT(aVsyncTimestamp <= tickStart);
852 #endif
854 bool shouldGiveNonVSyncTasksMoreTime = ShouldGiveNonVsyncTasksMoreTime();
856 // Set these variables before calling RunRefreshDrivers so that they are
857 // visible to any nested ticks.
858 mLastTickStart = tickStart;
859 mLastProcessedTick = aVsyncTimestamp;
861 RunRefreshDrivers(aId, aVsyncTimestamp);
863 TimeStamp tickEnd = TimeStamp::Now();
865 if (performanceHintSession) {
866 performanceHintSession->ReportActualWorkDuration(tickEnd - tickStart);
869 // Re-read mLastTickStart in case there was a nested tick inside this
870 // tick.
871 TimeStamp mostRecentTickStart = mLastTickStart;
873 // Let also non-RefreshDriver code to run at least for awhile if we have
874 // a mVsyncRefreshDriverTimer.
875 // Always give a tiny bit, 5% of the vsync interval, time outside the
876 // tick
877 // In case there are both normal tasks and RefreshDrivers are doing
878 // work, mSuspendVsyncPriorityTicksUntil will be set to a timestamp in the
879 // future where the period between the previous tick start
880 // (mostRecentTickStart) and the next tick needs to be at least the amount
881 // of work normal tasks and RefreshDrivers did together (minus short grace
882 // period).
883 TimeDuration gracePeriod = rate / int64_t(20);
885 if (shouldGiveNonVSyncTasksMoreTime && !mLastTickEnd.IsNull() &&
886 XRE_IsContentProcess() &&
887 // For RefreshDriver scheduling during page load there is currently
888 // idle priority based setup.
889 // XXX Consider to remove the page load specific code paths.
890 !IsAnyToplevelContentPageLoading()) {
891 // In case normal tasks are doing lots of work, we still want to paint
892 // every now and then, so only at maximum 4 * rate of work is counted
893 // here.
894 // If we're giving extra time for tasks outside a tick, try to
895 // ensure the next vsync after that period is handled, so subtract
896 // a grace period.
897 TimeDuration timeForOutsideTick = clamped(
898 tickStart - mLastTickEnd - gracePeriod, gracePeriod, rate * 4);
899 mSuspendVsyncPriorityTicksUntil = tickEnd + timeForOutsideTick;
900 } else if (ShouldGiveNonVsyncTasksMoreTime(true)) {
901 // We've got some new tasks, give them some extra time.
902 // This handles also the case when mLastTickEnd.IsNull() above and we
903 // should give some more time for non-vsync tasks.
904 mSuspendVsyncPriorityTicksUntil = tickEnd + gracePeriod;
905 } else {
906 mSuspendVsyncPriorityTicksUntil = mostRecentTickStart + gracePeriod;
909 mLastIdleTaskCount =
910 TaskController::Get()->GetIdleTaskManager()->ProcessedTaskCount();
911 mLastRunOutOfMTTasksCount = TaskController::Get()->RunOutOfMTTasksCount();
912 mLastTickEnd = tickEnd;
915 void StartTimer() override {
916 MOZ_ASSERT(NS_IsMainThread());
918 mLastFireTime = TimeStamp::Now();
919 mLastFireId = VsyncId();
921 if (mVsyncDispatcher) {
922 mVsyncDispatcher->AddVsyncObserver(mVsyncObserver);
923 } else if (mVsyncChild) {
924 mVsyncChild->AddChildRefreshTimer(mVsyncObserver);
925 OnTimerStart();
927 mIsTicking = true;
930 void StopTimer() override {
931 MOZ_ASSERT(NS_IsMainThread());
933 if (mVsyncDispatcher) {
934 mVsyncDispatcher->RemoveVsyncObserver(mVsyncObserver);
935 } else if (mVsyncChild) {
936 mVsyncChild->RemoveChildRefreshTimer(mVsyncObserver);
938 mIsTicking = false;
941 public:
942 bool IsTicking() const override { return mIsTicking; }
944 protected:
945 void ScheduleNextTick(TimeStamp aNowTime) override {
946 // Do nothing since we just wait for the next vsync from
947 // RefreshDriverVsyncObserver.
950 void RunRefreshDrivers(VsyncId aId, TimeStamp aTimeStamp) {
951 Tick(aId, aTimeStamp);
952 for (auto& driver : mContentRefreshDrivers) {
953 driver->FinishedVsyncTick();
955 for (auto& driver : mRootRefreshDrivers) {
956 driver->FinishedVsyncTick();
960 // Always non-null. Has a weak pointer to us and notifies us of vsync.
961 RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
963 // Used in the parent process. We register mVsyncObserver with it for the
964 // duration during which we want to receive vsync notifications. We also
965 // use it to query the current vsync rate.
966 RefPtr<VsyncDispatcher> mVsyncDispatcher;
967 // Used it the content process. We register mVsyncObserver with it for the
968 // duration during which we want to receive vsync notifications. The
969 // mVsyncChild will be always available before VsyncChild::ActorDestroy().
970 // After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
971 RefPtr<VsyncMainChild> mVsyncChild;
973 TimeDuration mVsyncRate;
974 bool mIsTicking = false;
976 TimeStamp mRecentVsync;
977 VsyncId mRecentVsyncId;
978 // The local start time when RefreshDrivers' Tick was called last time.
979 TimeStamp mLastTickStart;
980 // The local end time of the last RefreshDrivers' tick.
981 TimeStamp mLastTickEnd;
982 // The number of idle tasks the main thread has processed. It is updated
983 // right after RefreshDrivers' tick.
984 uint64_t mLastIdleTaskCount;
985 // If there were no idle tasks, we need to check if the main event queue
986 // was totally empty at times.
987 uint64_t mLastRunOutOfMTTasksCount;
988 // Note, mLastProcessedTick stores the vsync timestamp, which may be coming
989 // from a different process.
990 TimeStamp mLastProcessedTick;
991 // mSuspendVsyncPriorityTicksUntil is used to block too high refresh rate in
992 // case the main thread has also other non-idle tasks to process.
993 // The timestamp is effectively mLastTickEnd + some duration.
994 TimeStamp mSuspendVsyncPriorityTicksUntil;
995 bool mProcessedVsync;
997 TimeStamp mPendingVsync;
998 VsyncId mPendingVsyncId;
999 bool mHasPendingLowPrioTask;
1000 }; // VsyncRefreshDriverTimer
1003 * Since the content process takes some time to setup
1004 * the vsync IPC connection, this timer is used
1005 * during the intial startup process.
1006 * During initial startup, the refresh drivers
1007 * are ticked off this timer, and are swapped out once content
1008 * vsync IPC connection is established.
1010 class StartupRefreshDriverTimer : public SimpleTimerBasedRefreshDriverTimer {
1011 public:
1012 explicit StartupRefreshDriverTimer(double aRate)
1013 : SimpleTimerBasedRefreshDriverTimer(aRate) {}
1015 protected:
1016 void ScheduleNextTick(TimeStamp aNowTime) override {
1017 // Since this is only used for startup, it isn't super critical
1018 // that we tick at consistent intervals.
1019 TimeStamp newTarget = aNowTime + mRateDuration;
1020 uint32_t delay =
1021 static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds());
1022 mTimer->InitWithNamedFuncCallback(
1023 TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT,
1024 "StartupRefreshDriverTimer::ScheduleNextTick");
1025 mTargetTime = newTarget;
1028 public:
1029 bool IsTicking() const override { return true; }
1033 * A RefreshDriverTimer for inactive documents. When a new refresh driver is
1034 * added, the rate is reset to the base (normally 1s/1fps). Every time
1035 * it ticks, a single refresh driver is poked. Once they have all been poked,
1036 * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that
1037 * point, the timer is quiet and doesn't tick (until something is added to it
1038 * again).
1040 * When a timer is removed, there is a possibility of another timer
1041 * being skipped for one cycle. We could avoid this by adjusting
1042 * mNextDriverIndex in RemoveRefreshDriver, but there's little need to
1043 * add that complexity. All we want is for inactive drivers to tick
1044 * at some point, but we don't care too much about how often.
1046 class InactiveRefreshDriverTimer final
1047 : public SimpleTimerBasedRefreshDriverTimer {
1048 public:
1049 explicit InactiveRefreshDriverTimer(double aRate)
1050 : SimpleTimerBasedRefreshDriverTimer(aRate),
1051 mNextTickDuration(aRate),
1052 mDisableAfterMilliseconds(-1.0),
1053 mNextDriverIndex(0) {}
1055 InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
1056 : SimpleTimerBasedRefreshDriverTimer(aRate),
1057 mNextTickDuration(aRate),
1058 mDisableAfterMilliseconds(aDisableAfterMilliseconds),
1059 mNextDriverIndex(0) {}
1061 void AddRefreshDriver(nsRefreshDriver* aDriver) override {
1062 RefreshDriverTimer::AddRefreshDriver(aDriver);
1064 LOG("[%p] inactive timer got new refresh driver %p, resetting rate", this,
1065 aDriver);
1067 // reset the timer, and start with the newly added one next time.
1068 mNextTickDuration = mRateMilliseconds;
1070 // we don't really have to start with the newly added one, but we may as
1071 // well not tick the old ones at the fastest rate any more than we need to.
1072 mNextDriverIndex = GetRefreshDriverCount() - 1;
1074 StopTimer();
1075 StartTimer();
1078 TimeDuration GetTimerRate() override {
1079 return TimeDuration::FromMilliseconds(mNextTickDuration);
1082 protected:
1083 uint32_t GetRefreshDriverCount() {
1084 return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length();
1087 void StartTimer() override {
1088 mLastFireTime = TimeStamp::Now();
1089 mLastFireId = VsyncId();
1091 mTargetTime = mLastFireTime + mRateDuration;
1093 uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
1094 mTimer->InitWithNamedFuncCallback(TimerTickOne, this, delay,
1095 nsITimer::TYPE_ONE_SHOT,
1096 "InactiveRefreshDriverTimer::StartTimer");
1097 mIsTicking = true;
1100 void StopTimer() override {
1101 mTimer->Cancel();
1102 mIsTicking = false;
1105 void ScheduleNextTick(TimeStamp aNowTime) override {
1106 if (mDisableAfterMilliseconds > 0.0 &&
1107 mNextTickDuration > mDisableAfterMilliseconds) {
1108 // We hit the time after which we should disable
1109 // inactive window refreshes; don't schedule anything
1110 // until we get kicked by an AddRefreshDriver call.
1111 return;
1114 // double the next tick time if we've already gone through all of them once
1115 if (mNextDriverIndex >= GetRefreshDriverCount()) {
1116 mNextTickDuration *= 2.0;
1117 mNextDriverIndex = 0;
1120 // this doesn't need to be precise; do a simple schedule
1121 uint32_t delay = static_cast<uint32_t>(mNextTickDuration);
1122 mTimer->InitWithNamedFuncCallback(
1123 TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT,
1124 "InactiveRefreshDriverTimer::ScheduleNextTick");
1126 LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this,
1127 mNextTickDuration, mNextDriverIndex, GetRefreshDriverCount());
1130 public:
1131 bool IsTicking() const override { return mIsTicking; }
1133 protected:
1134 /* Runs just one driver's tick. */
1135 void TickOne() {
1136 TimeStamp now = TimeStamp::Now();
1138 ScheduleNextTick(now);
1140 mLastFireTime = now;
1141 mLastFireId = VsyncId();
1143 nsTArray<RefPtr<nsRefreshDriver>> drivers(mContentRefreshDrivers.Clone());
1144 drivers.AppendElements(mRootRefreshDrivers);
1145 size_t index = mNextDriverIndex;
1147 if (index < drivers.Length() &&
1148 !drivers[index]->IsTestControllingRefreshesEnabled()) {
1149 TickDriver(drivers[index], VsyncId(), now);
1152 mNextDriverIndex++;
1155 static void TimerTickOne(nsITimer* aTimer, void* aClosure) {
1156 RefPtr<InactiveRefreshDriverTimer> timer =
1157 static_cast<InactiveRefreshDriverTimer*>(aClosure);
1158 timer->TickOne();
1161 double mNextTickDuration;
1162 double mDisableAfterMilliseconds;
1163 uint32_t mNextDriverIndex;
1164 bool mIsTicking = false;
1167 } // namespace mozilla
1169 static StaticRefPtr<RefreshDriverTimer> sRegularRateTimer;
1170 static StaticAutoPtr<nsTArray<RefreshDriverTimer*>> sRegularRateTimerList;
1171 static StaticRefPtr<InactiveRefreshDriverTimer> sThrottledRateTimer;
1173 void nsRefreshDriver::CreateVsyncRefreshTimer() {
1174 MOZ_ASSERT(NS_IsMainThread());
1176 if (gfxPlatform::IsInLayoutAsapMode()) {
1177 return;
1180 if (!mOwnTimer) {
1181 // If available, we fetch the widget-specific vsync source.
1182 nsPresContext* pc = GetPresContext();
1183 nsCOMPtr<nsIWidget> widget = pc->GetRootWidget();
1184 if (widget) {
1185 if (RefPtr<VsyncDispatcher> vsyncDispatcher =
1186 widget->GetVsyncDispatcher()) {
1187 mOwnTimer = VsyncRefreshDriverTimer::
1188 CreateForParentProcessWithLocalVsyncDispatcher(
1189 std::move(vsyncDispatcher));
1190 sRegularRateTimerList->AppendElement(mOwnTimer.get());
1191 return;
1193 if (BrowserChild* browserChild = widget->GetOwningBrowserChild()) {
1194 if (RefPtr<VsyncMainChild> vsyncChildViaPBrowser =
1195 browserChild->GetVsyncChild()) {
1196 mOwnTimer = VsyncRefreshDriverTimer::CreateForContentProcess(
1197 std::move(vsyncChildViaPBrowser));
1198 sRegularRateTimerList->AppendElement(mOwnTimer.get());
1199 return;
1204 if (!sRegularRateTimer) {
1205 if (XRE_IsParentProcess()) {
1206 // Make sure all vsync systems are ready.
1207 gfxPlatform::GetPlatform();
1208 // In parent process, we can create the VsyncRefreshDriverTimer directly.
1209 sRegularRateTimer =
1210 VsyncRefreshDriverTimer::CreateForParentProcessWithGlobalVsync();
1211 } else {
1212 PBackgroundChild* actorChild =
1213 BackgroundChild::GetOrCreateForCurrentThread();
1214 if (NS_WARN_IF(!actorChild)) {
1215 return;
1218 auto vsyncChildViaPBackground = MakeRefPtr<dom::VsyncMainChild>();
1219 dom::PVsyncChild* actor =
1220 actorChild->SendPVsyncConstructor(vsyncChildViaPBackground);
1221 if (NS_WARN_IF(!actor)) {
1222 return;
1225 RefPtr<RefreshDriverTimer> vsyncRefreshDriverTimer =
1226 VsyncRefreshDriverTimer::CreateForContentProcess(
1227 std::move(vsyncChildViaPBackground));
1229 sRegularRateTimer = std::move(vsyncRefreshDriverTimer);
1234 static uint32_t GetFirstFrameDelay(imgIRequest* req) {
1235 nsCOMPtr<imgIContainer> container;
1236 if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) {
1237 return 0;
1240 // If this image isn't animated, there isn't a first frame delay.
1241 int32_t delay = container->GetFirstFrameDelay();
1242 if (delay < 0) return 0;
1244 return static_cast<uint32_t>(delay);
1247 /* static */
1248 void nsRefreshDriver::Shutdown() {
1249 MOZ_ASSERT(NS_IsMainThread());
1250 // clean up our timers
1251 sRegularRateTimer = nullptr;
1252 sRegularRateTimerList = nullptr;
1253 sThrottledRateTimer = nullptr;
1256 /* static */
1257 int32_t nsRefreshDriver::DefaultInterval() {
1258 return NSToIntRound(1000.0 / gfxPlatform::GetDefaultFrameRate());
1261 /* static */
1262 double nsRefreshDriver::HighRateMultiplier() {
1263 // We're in high rate mode if we've gotten a fast rate during the last
1264 // DefaultInterval().
1265 bool inHighRateMode =
1266 !gfxPlatform::IsInLayoutAsapMode() &&
1267 StaticPrefs::layout_expose_high_rate_mode_from_refreshdriver() &&
1268 !sMostRecentHighRateVsync.IsNull() &&
1269 (sMostRecentHighRateVsync +
1270 TimeDuration::FromMilliseconds(DefaultInterval())) > TimeStamp::Now();
1271 if (!inHighRateMode) {
1272 // Clear the timestamp so that the next call is faster.
1273 sMostRecentHighRateVsync = TimeStamp();
1274 sMostRecentHighRate = TimeDuration();
1275 return 1.0;
1278 return sMostRecentHighRate.ToMilliseconds() / DefaultInterval();
1281 // Compute the interval to use for the refresh driver timer, in milliseconds.
1282 // outIsDefault indicates that rate was not explicitly set by the user
1283 // so we might choose other, more appropriate rates (e.g. vsync, etc)
1284 // layout.frame_rate=0 indicates "ASAP mode".
1285 // In ASAP mode rendering is iterated as fast as possible (typically for stress
1286 // testing). A target rate of 10k is used internally instead of special-handling
1287 // 0. Backends which block on swap/present/etc should try to not block when
1288 // layout.frame_rate=0 - to comply with "ASAP" as much as possible.
1289 double nsRefreshDriver::GetRegularTimerInterval() const {
1290 int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
1291 if (rate < 0) {
1292 rate = gfxPlatform::GetDefaultFrameRate();
1293 } else if (rate == 0) {
1294 rate = 10000;
1297 return 1000.0 / rate;
1300 /* static */
1301 double nsRefreshDriver::GetThrottledTimerInterval() {
1302 uint32_t rate = StaticPrefs::layout_throttled_frame_rate();
1303 return 1000.0 / rate;
1306 /* static */
1307 TimeDuration nsRefreshDriver::GetMinRecomputeVisibilityInterval() {
1308 return TimeDuration::FromMilliseconds(
1309 StaticPrefs::layout_visibility_min_recompute_interval_ms());
1312 RefreshDriverTimer* nsRefreshDriver::ChooseTimer() {
1313 if (mThrottled) {
1314 if (!sThrottledRateTimer) {
1315 sThrottledRateTimer = new InactiveRefreshDriverTimer(
1316 GetThrottledTimerInterval(),
1317 DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0);
1319 return sThrottledRateTimer;
1322 if (!mOwnTimer) {
1323 CreateVsyncRefreshTimer();
1326 if (mOwnTimer) {
1327 return mOwnTimer.get();
1330 if (!sRegularRateTimer) {
1331 double rate = GetRegularTimerInterval();
1332 sRegularRateTimer = new StartupRefreshDriverTimer(rate);
1335 return sRegularRateTimer;
1338 static nsDocShell* GetDocShell(nsPresContext* aPresContext) {
1339 if (!aPresContext) {
1340 return nullptr;
1342 return static_cast<nsDocShell*>(aPresContext->GetDocShell());
1345 nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
1346 : mActiveTimer(nullptr),
1347 mOwnTimer(nullptr),
1348 mPresContext(aPresContext),
1349 mRootRefresh(nullptr),
1350 mNextTransactionId{0},
1351 mFreezeCount(0),
1352 mThrottledFrameRequestInterval(
1353 TimeDuration::FromMilliseconds(GetThrottledTimerInterval())),
1354 mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
1355 mThrottled(false),
1356 mNeedToRecomputeVisibility(false),
1357 mTestControllingRefreshes(false),
1358 mViewManagerFlushIsPending(false),
1359 mHasScheduleFlush(false),
1360 mInRefresh(false),
1361 mWaitingForTransaction(false),
1362 mSkippedPaints(false),
1363 mResizeSuppressed(false),
1364 mNotifyDOMContentFlushed(false),
1365 mNeedToUpdateIntersectionObservations(false),
1366 mNeedToUpdateResizeObservers(false),
1367 mMightNeedMediaQueryListenerUpdate(false),
1368 mNeedToUpdateContentRelevancy(false),
1369 mInNormalTick(false),
1370 mAttemptedExtraTickSinceLastVsync(false),
1371 mHasExceededAfterLoadTickPeriod(false),
1372 mHasStartedTimerAtLeastOnce(false) {
1373 MOZ_ASSERT(NS_IsMainThread());
1374 MOZ_ASSERT(mPresContext,
1375 "Need a pres context to tell us to call Disconnect() later "
1376 "and decrement sRefreshDriverCount.");
1377 mMostRecentRefresh = TimeStamp::Now();
1378 mNextThrottledFrameRequestTick = mMostRecentRefresh;
1379 mNextRecomputeVisibilityTick = mMostRecentRefresh;
1381 if (!sRegularRateTimerList) {
1382 sRegularRateTimerList = new nsTArray<RefreshDriverTimer*>();
1384 ++sRefreshDriverCount;
1387 nsRefreshDriver::~nsRefreshDriver() {
1388 MOZ_ASSERT(NS_IsMainThread());
1389 MOZ_ASSERT(ObserverCount() == mEarlyRunners.Length(),
1390 "observers, except pending selection scrolls, "
1391 "should have been unregistered");
1392 MOZ_ASSERT(!mActiveTimer, "timer should be gone");
1393 MOZ_ASSERT(!mPresContext,
1394 "Should have called Disconnect() and decremented "
1395 "sRefreshDriverCount!");
1397 if (mRootRefresh) {
1398 mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
1399 mRootRefresh = nullptr;
1401 if (mOwnTimer && sRegularRateTimerList) {
1402 sRegularRateTimerList->RemoveElement(mOwnTimer.get());
1406 // Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh
1407 // for description.
1408 void nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) {
1409 // ensure that we're removed from our driver
1410 StopTimer();
1412 if (!mTestControllingRefreshes) {
1413 mMostRecentRefresh = TimeStamp::Now();
1415 mTestControllingRefreshes = true;
1416 if (mWaitingForTransaction) {
1417 // Disable any refresh driver throttling when entering test mode
1418 mWaitingForTransaction = false;
1419 mSkippedPaints = false;
1423 mMostRecentRefresh += TimeDuration::FromMilliseconds((double)aMilliseconds);
1425 mozilla::dom::AutoNoJSAPI nojsapi;
1426 DoTick();
1429 void nsRefreshDriver::RestoreNormalRefresh() {
1430 mTestControllingRefreshes = false;
1431 EnsureTimerStarted(eAllowTimeToGoBackwards);
1432 mPendingTransactions.Clear();
1435 TimeStamp nsRefreshDriver::MostRecentRefresh(bool aEnsureTimerStarted) const {
1436 // In case of stylo traversal, we have already activated the refresh driver in
1437 // RestyleManager::ProcessPendingRestyles().
1438 if (aEnsureTimerStarted && !ServoStyleSet::IsInServoTraversal()) {
1439 const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
1442 return mMostRecentRefresh;
1445 void nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
1446 FlushType aFlushType,
1447 const char* aObserverDescription) {
1448 ObserverArray& array = ArrayFor(aFlushType);
1449 MOZ_ASSERT(!array.Contains(aObserver),
1450 "We don't want to redundantly register the same observer");
1451 array.AppendElement(
1452 ObserverData{aObserver, aObserverDescription, TimeStamp::Now(),
1453 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)),
1454 profiler_capture_backtrace(), aFlushType});
1455 #ifdef DEBUG
1456 MOZ_ASSERT(aObserver->mRegistrationCount >= 0,
1457 "Registration count shouldn't be able to go negative");
1458 aObserver->mRegistrationCount++;
1459 #endif
1460 EnsureTimerStarted();
1463 bool nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
1464 FlushType aFlushType) {
1465 ObserverArray& array = ArrayFor(aFlushType);
1466 auto index = array.IndexOf(aObserver);
1467 if (index == ObserverArray::array_type::NoIndex) {
1468 return false;
1471 if (profiler_thread_is_being_profiled_for_markers()) {
1472 auto& data = array.ElementAt(index);
1473 nsPrintfCString str("%s [%s]", data.mDescription,
1474 kFlushTypeNames[aFlushType]);
1475 PROFILER_MARKER_TEXT(
1476 "RefreshObserver", GRAPHICS,
1477 MarkerOptions(MarkerStack::TakeBacktrace(std::move(data.mCause)),
1478 MarkerTiming::IntervalUntilNowFrom(data.mRegisterTime),
1479 std::move(data.mInnerWindowId)),
1480 str);
1483 array.RemoveElementAt(index);
1484 #ifdef DEBUG
1485 aObserver->mRegistrationCount--;
1486 MOZ_ASSERT(aObserver->mRegistrationCount >= 0,
1487 "Registration count shouldn't be able to go negative");
1488 #endif
1489 return true;
1492 void nsRefreshDriver::AddTimerAdjustmentObserver(
1493 nsATimerAdjustmentObserver* aObserver) {
1494 MOZ_ASSERT(!mTimerAdjustmentObservers.Contains(aObserver));
1495 mTimerAdjustmentObservers.AppendElement(aObserver);
1498 void nsRefreshDriver::RemoveTimerAdjustmentObserver(
1499 nsATimerAdjustmentObserver* aObserver) {
1500 MOZ_ASSERT(mTimerAdjustmentObservers.Contains(aObserver));
1501 mTimerAdjustmentObservers.RemoveElement(aObserver);
1504 void nsRefreshDriver::PostVisualViewportResizeEvent(
1505 VVPResizeEvent* aResizeEvent) {
1506 mVisualViewportResizeEvents.AppendElement(aResizeEvent);
1507 EnsureTimerStarted();
1510 void nsRefreshDriver::DispatchVisualViewportResizeEvents() {
1511 // We're taking a hint from scroll events and only dispatch the current set
1512 // of queued resize events. If additional events are posted in response to
1513 // the current events being dispatched, we'll dispatch them on the next tick.
1514 VisualViewportResizeEventArray events =
1515 std::move(mVisualViewportResizeEvents);
1516 for (auto& event : events) {
1517 event->Run();
1521 void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent,
1522 bool aDelayed) {
1523 if (aDelayed) {
1524 mDelayedScrollEvents.AppendElement(aScrollEvent);
1525 } else {
1526 mScrollEvents.AppendElement(aScrollEvent);
1527 EnsureTimerStarted();
1531 void nsRefreshDriver::DispatchScrollEvents() {
1532 // Scroll events are one-shot, so after running them we can drop them.
1533 // However, dispatching a scroll event can potentially cause more scroll
1534 // events to be posted, so we move the initial set into a temporary array
1535 // first. (Newly posted scroll events will be dispatched on the next tick.)
1536 ScrollEventArray events = std::move(mScrollEvents);
1537 for (auto& event : events) {
1538 event->Run();
1542 void nsRefreshDriver::PostVisualViewportScrollEvent(
1543 VVPScrollEvent* aScrollEvent) {
1544 mVisualViewportScrollEvents.AppendElement(aScrollEvent);
1545 EnsureTimerStarted();
1548 void nsRefreshDriver::DispatchVisualViewportScrollEvents() {
1549 // Scroll events are one-shot, so after running them we can drop them.
1550 // However, dispatching a scroll event can potentially cause more scroll
1551 // events to be posted, so we move the initial set into a temporary array
1552 // first. (Newly posted scroll events will be dispatched on the next tick.)
1553 VisualViewportScrollEventArray events =
1554 std::move(mVisualViewportScrollEvents);
1555 for (auto& event : events) {
1556 event->Run();
1560 // https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes
1561 void nsRefreshDriver::EvaluateMediaQueriesAndReportChanges() {
1562 if (!mMightNeedMediaQueryListenerUpdate) {
1563 return;
1565 mMightNeedMediaQueryListenerUpdate = false;
1566 if (!mPresContext) {
1567 return;
1569 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS(
1570 "Evaluate media queries and report changes", LAYOUT);
1571 RefPtr<Document> doc = mPresContext->Document();
1572 doc->EvaluateMediaQueriesAndReportChanges(/* aRecurse = */ true);
1575 void nsRefreshDriver::AddPostRefreshObserver(
1576 nsAPostRefreshObserver* aObserver) {
1577 MOZ_ASSERT(!mPostRefreshObservers.Contains(aObserver));
1578 mPostRefreshObservers.AppendElement(aObserver);
1581 void nsRefreshDriver::RemovePostRefreshObserver(
1582 nsAPostRefreshObserver* aObserver) {
1583 bool removed = mPostRefreshObservers.RemoveElement(aObserver);
1584 MOZ_DIAGNOSTIC_ASSERT(removed);
1585 Unused << removed;
1588 void nsRefreshDriver::AddImageRequest(imgIRequest* aRequest) {
1589 uint32_t delay = GetFirstFrameDelay(aRequest);
1590 if (delay == 0) {
1591 mRequests.Insert(aRequest);
1592 } else {
1593 auto* const start = mStartTable.GetOrInsertNew(delay);
1594 start->mEntries.Insert(aRequest);
1597 EnsureTimerStarted();
1599 if (profiler_thread_is_being_profiled_for_markers()) {
1600 nsCOMPtr<nsIURI> uri = aRequest->GetURI();
1602 PROFILER_MARKER_TEXT("Image Animation", GRAPHICS,
1603 MarkerOptions(MarkerTiming::IntervalStart(),
1604 MarkerInnerWindowIdFromDocShell(
1605 GetDocShell(mPresContext))),
1606 nsContentUtils::TruncatedURLForDisplay(uri));
1610 void nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest) {
1611 // Try to remove from both places, just in case.
1612 bool removed = mRequests.EnsureRemoved(aRequest);
1613 uint32_t delay = GetFirstFrameDelay(aRequest);
1614 if (delay != 0) {
1615 ImageStartData* start = mStartTable.Get(delay);
1616 if (start) {
1617 removed = removed | start->mEntries.EnsureRemoved(aRequest);
1621 if (removed && profiler_thread_is_being_profiled_for_markers()) {
1622 nsCOMPtr<nsIURI> uri = aRequest->GetURI();
1624 PROFILER_MARKER_TEXT("Image Animation", GRAPHICS,
1625 MarkerOptions(MarkerTiming::IntervalEnd(),
1626 MarkerInnerWindowIdFromDocShell(
1627 GetDocShell(mPresContext))),
1628 nsContentUtils::TruncatedURLForDisplay(uri));
1632 void nsRefreshDriver::NotifyDOMContentLoaded() {
1633 // If the refresh driver is going to tick, we mark the timestamp after
1634 // everything is flushed in the next tick. If it isn't, mark ourselves as
1635 // flushed now.
1636 if (!HasObservers()) {
1637 if (nsPresContext* pc = GetPresContext()) {
1638 pc->NotifyDOMContentFlushed();
1640 // else, we don't have a nsPresContext, so our doc is probably being
1641 // destroyed and this notification doesn't need sending anyway.
1642 } else {
1643 mNotifyDOMContentFlushed = true;
1647 void nsRefreshDriver::RegisterCompositionPayload(
1648 const mozilla::layers::CompositionPayload& aPayload) {
1649 mCompositionPayloads.AppendElement(aPayload);
1652 void nsRefreshDriver::AddForceNotifyContentfulPaintPresContext(
1653 nsPresContext* aPresContext) {
1654 mForceNotifyContentfulPaintPresContexts.AppendElement(aPresContext);
1657 void nsRefreshDriver::FlushForceNotifyContentfulPaintPresContext() {
1658 while (!mForceNotifyContentfulPaintPresContexts.IsEmpty()) {
1659 WeakPtr<nsPresContext> presContext =
1660 mForceNotifyContentfulPaintPresContexts.PopLastElement();
1661 if (presContext) {
1662 presContext->NotifyContentfulPaint();
1667 void nsRefreshDriver::RunDelayedEventsSoon() {
1668 // Place entries for delayed events into their corresponding normal list,
1669 // and schedule a refresh. When these delayed events run, if their document
1670 // still has events suppressed then they will be readded to the delayed
1671 // events list.
1673 mScrollEvents.AppendElements(mDelayedScrollEvents);
1674 mDelayedScrollEvents.Clear();
1676 mResizeEventFlushObservers.AppendElements(mDelayedResizeEventFlushObservers);
1677 mDelayedResizeEventFlushObservers.Clear();
1679 EnsureTimerStarted();
1682 bool nsRefreshDriver::CanDoCatchUpTick() {
1683 if (mTestControllingRefreshes || !mActiveTimer) {
1684 return false;
1687 // If we've already ticked for the current timer refresh (or more recently
1688 // than that), then we don't need to do any catching up.
1689 if (mMostRecentRefresh >= mActiveTimer->MostRecentRefresh()) {
1690 return false;
1693 if (mActiveTimer->IsBlocked()) {
1694 return false;
1697 if (mTickVsyncTime.IsNull()) {
1698 // Don't try to run a catch-up tick before there has been at least one
1699 // normal tick. The catch-up tick could negatively affect page load
1700 // performance.
1701 return false;
1704 if (mPresContext && mPresContext->Document()->GetReadyStateEnum() <
1705 Document::READYSTATE_COMPLETE) {
1706 // Don't try to run a catch-up tick before the page has finished loading.
1707 // The catch-up tick could negatively affect page load performance.
1708 return false;
1711 return true;
1714 bool nsRefreshDriver::CanDoExtraTick() {
1715 // Only allow one extra tick per normal vsync tick.
1716 if (mAttemptedExtraTickSinceLastVsync) {
1717 return false;
1720 // If we don't have a timer, or we didn't tick on the timer's
1721 // refresh then we can't do an 'extra' tick (but we may still
1722 // do a catch up tick).
1723 if (!mActiveTimer ||
1724 mActiveTimer->MostRecentRefresh() != mMostRecentRefresh) {
1725 return false;
1728 // Grab the current timestamp before checking the tick hint to be sure
1729 // sure that it's equal or smaller than the value used within checking
1730 // the tick hint.
1731 TimeStamp now = TimeStamp::Now();
1732 Maybe<TimeStamp> nextTick = mActiveTimer->GetNextTickHint();
1733 int32_t minimumRequiredTime = StaticPrefs::layout_extra_tick_minimum_ms();
1734 // If there's less than 4 milliseconds until the next tick, it's probably
1735 // not worth trying to catch up.
1736 if (minimumRequiredTime < 0 || !nextTick ||
1737 (*nextTick - now) < TimeDuration::FromMilliseconds(minimumRequiredTime)) {
1738 return false;
1741 return true;
1744 void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
1745 // FIXME: Bug 1346065: We should also assert the case where we have no
1746 // stylo-threads.
1747 MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal() || NS_IsMainThread(),
1748 "EnsureTimerStarted should be called only when we are not "
1749 "in servo traversal or on the main-thread");
1751 if (mTestControllingRefreshes) return;
1753 if (!mRefreshTimerStartedCause) {
1754 mRefreshTimerStartedCause = profiler_capture_backtrace();
1757 // will it already fire, and no other changes needed?
1758 if (mActiveTimer && !(aFlags & eForceAdjustTimer)) {
1759 // If we're being called from within a user input handler, and we think
1760 // there's time to rush an extra tick immediately, then schedule a runnable
1761 // to run the extra tick.
1762 if (mUserInputProcessingCount && CanDoExtraTick()) {
1763 RefPtr<nsRefreshDriver> self = this;
1764 NS_DispatchToCurrentThreadQueue(
1765 NS_NewRunnableFunction(
1766 "RefreshDriver::EnsureTimerStarted::extra",
1767 [self]() -> void {
1768 // Re-check if we can still do an extra tick, in case anything
1769 // changed while the runnable was pending.
1770 if (self->CanDoExtraTick()) {
1771 PROFILER_MARKER_UNTYPED("ExtraRefreshDriverTick", GRAPHICS);
1772 LOG("[%p] Doing extra tick for user input", self.get());
1773 self->mAttemptedExtraTickSinceLastVsync = true;
1774 self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
1775 self->mActiveTimer->MostRecentRefresh(),
1776 IsExtraTick::Yes);
1779 EventQueuePriority::Vsync);
1781 return;
1784 if (IsFrozen() || !mPresContext) {
1785 // If we don't want to start it now, or we've been disconnected.
1786 StopTimer();
1787 return;
1790 if (mPresContext->Document()->IsBeingUsedAsImage()) {
1791 // Image documents receive ticks from clients' refresh drivers.
1792 // XXXdholbert Exclude SVG-in-opentype fonts from this optimization, until
1793 // they receive refresh-driver ticks from their client docs (bug 1107252).
1794 if (!mPresContext->Document()->IsSVGGlyphsDocument()) {
1795 MOZ_ASSERT(!mActiveTimer,
1796 "image doc refresh driver should never have its own timer");
1797 return;
1801 // We got here because we're either adjusting the time *or* we're
1802 // starting it for the first time. Add to the right timer,
1803 // prehaps removing it from a previously-set one.
1804 RefreshDriverTimer* newTimer = ChooseTimer();
1805 if (newTimer != mActiveTimer) {
1806 if (mActiveTimer) mActiveTimer->RemoveRefreshDriver(this);
1807 mActiveTimer = newTimer;
1808 mActiveTimer->AddRefreshDriver(this);
1810 if (!mHasStartedTimerAtLeastOnce) {
1811 mHasStartedTimerAtLeastOnce = true;
1812 if (profiler_thread_is_being_profiled_for_markers()) {
1813 nsCString text = "initial timer start "_ns;
1814 if (mPresContext->Document()->GetDocumentURI()) {
1815 text.Append(nsContentUtils::TruncatedURLForDisplay(
1816 mPresContext->Document()->GetDocumentURI()));
1819 PROFILER_MARKER_TEXT("nsRefreshDriver", LAYOUT,
1820 MarkerOptions(MarkerInnerWindowIdFromDocShell(
1821 GetDocShell(mPresContext))),
1822 text);
1826 // If the timer has ticked since we last ticked, consider doing a 'catch-up'
1827 // tick immediately.
1828 if (CanDoCatchUpTick()) {
1829 RefPtr<nsRefreshDriver> self = this;
1830 NS_DispatchToCurrentThreadQueue(
1831 NS_NewRunnableFunction(
1832 "RefreshDriver::EnsureTimerStarted::catch-up",
1833 [self]() -> void {
1834 // Re-check if we can still do a catch-up, in case anything
1835 // changed while the runnable was pending.
1836 if (self->CanDoCatchUpTick()) {
1837 LOG("[%p] Doing catch up tick", self.get());
1838 self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
1839 self->mActiveTimer->MostRecentRefresh());
1842 EventQueuePriority::Vsync);
1846 // When switching from an inactive timer to an active timer, the root
1847 // refresh driver is skipped due to being set to the content refresh
1848 // driver's timestamp. In case of EnsureTimerStarted is called from
1849 // ScheduleViewManagerFlush, we should avoid this behavior to flush
1850 // a paint in the same tick on the root refresh driver.
1851 if (aFlags & eNeverAdjustTimer) {
1852 return;
1855 // Since the different timers are sampled at different rates, when switching
1856 // timers, the most recent refresh of the new timer may be *before* the
1857 // most recent refresh of the old timer.
1858 // If we are restoring the refresh driver from test control, the time is
1859 // expected to go backwards (see bug 1043078), otherwise we just keep the most
1860 // recent tick of this driver (which may be older than the most recent tick of
1861 // the timer).
1862 if (!(aFlags & eAllowTimeToGoBackwards)) {
1863 return;
1866 if (mMostRecentRefresh != mActiveTimer->MostRecentRefresh()) {
1867 mMostRecentRefresh = mActiveTimer->MostRecentRefresh();
1869 for (nsATimerAdjustmentObserver* obs :
1870 mTimerAdjustmentObservers.EndLimitedRange()) {
1871 obs->NotifyTimerAdjusted(mMostRecentRefresh);
1876 void nsRefreshDriver::StopTimer() {
1877 if (!mActiveTimer) return;
1879 mActiveTimer->RemoveRefreshDriver(this);
1880 mActiveTimer = nullptr;
1881 mRefreshTimerStartedCause = nullptr;
1884 uint32_t nsRefreshDriver::ObserverCount() const {
1885 uint32_t sum = 0;
1886 for (const ObserverArray& array : mObservers) {
1887 sum += array.Length();
1890 // Even while throttled, we need to process layout and style changes. Style
1891 // changes can trigger transitions which fire events when they complete, and
1892 // layout changes can affect media queries on child documents, triggering
1893 // style changes, etc.
1894 sum += mAnimationEventFlushObservers.Length();
1895 sum += mResizeEventFlushObservers.Length();
1896 sum += mStyleFlushObservers.Length();
1897 sum += mLayoutFlushObservers.Length();
1898 sum += mPendingFullscreenEvents.Length();
1899 sum += mFrameRequestCallbackDocs.Length();
1900 sum += mThrottledFrameRequestCallbackDocs.Length();
1901 sum += mViewManagerFlushIsPending;
1902 sum += mEarlyRunners.Length();
1903 sum += mTimerAdjustmentObservers.Length();
1904 sum += mAutoFocusFlushDocuments.Length();
1905 return sum;
1908 bool nsRefreshDriver::HasObservers() const {
1909 for (const ObserverArray& array : mObservers) {
1910 if (!array.IsEmpty()) {
1911 return true;
1915 // We should NOT count mTimerAdjustmentObservers here since this method is
1916 // used to determine whether or not to stop the timer or re-start it and timer
1917 // adjustment observers should not influence timer starting or stopping.
1918 return (mViewManagerFlushIsPending && !mThrottled) ||
1919 !mStyleFlushObservers.IsEmpty() || !mLayoutFlushObservers.IsEmpty() ||
1920 !mAnimationEventFlushObservers.IsEmpty() ||
1921 !mResizeEventFlushObservers.IsEmpty() ||
1922 !mPendingFullscreenEvents.IsEmpty() ||
1923 !mFrameRequestCallbackDocs.IsEmpty() ||
1924 !mThrottledFrameRequestCallbackDocs.IsEmpty() ||
1925 !mAutoFocusFlushDocuments.IsEmpty() || !mEarlyRunners.IsEmpty();
1928 void nsRefreshDriver::AppendObserverDescriptionsToString(
1929 nsACString& aStr) const {
1930 for (const ObserverArray& array : mObservers) {
1931 for (const auto& observer : array.EndLimitedRange()) {
1932 aStr.AppendPrintf("%s [%s], ", observer.mDescription,
1933 kFlushTypeNames[observer.mFlushType]);
1936 if (mViewManagerFlushIsPending && !mThrottled) {
1937 aStr.AppendLiteral("View manager flush pending, ");
1939 if (!mAnimationEventFlushObservers.IsEmpty()) {
1940 aStr.AppendPrintf("%zux Animation event flush observer, ",
1941 mAnimationEventFlushObservers.Length());
1943 if (!mResizeEventFlushObservers.IsEmpty()) {
1944 aStr.AppendPrintf("%zux Resize event flush observer, ",
1945 mResizeEventFlushObservers.Length());
1947 if (!mStyleFlushObservers.IsEmpty()) {
1948 aStr.AppendPrintf("%zux Style flush observer, ",
1949 mStyleFlushObservers.Length());
1951 if (!mLayoutFlushObservers.IsEmpty()) {
1952 aStr.AppendPrintf("%zux Layout flush observer, ",
1953 mLayoutFlushObservers.Length());
1955 if (!mPendingFullscreenEvents.IsEmpty()) {
1956 aStr.AppendPrintf("%zux Pending fullscreen event, ",
1957 mPendingFullscreenEvents.Length());
1959 if (!mFrameRequestCallbackDocs.IsEmpty()) {
1960 aStr.AppendPrintf("%zux Frame request callback doc, ",
1961 mFrameRequestCallbackDocs.Length());
1963 if (!mThrottledFrameRequestCallbackDocs.IsEmpty()) {
1964 aStr.AppendPrintf("%zux Throttled frame request callback doc, ",
1965 mThrottledFrameRequestCallbackDocs.Length());
1967 if (!mAutoFocusFlushDocuments.IsEmpty()) {
1968 aStr.AppendPrintf("%zux AutoFocus flush doc, ",
1969 mAutoFocusFlushDocuments.Length());
1971 if (!mEarlyRunners.IsEmpty()) {
1972 aStr.AppendPrintf("%zux Early runner, ", mEarlyRunners.Length());
1974 // Remove last ", "
1975 aStr.Truncate(aStr.Length() - 2);
1978 bool nsRefreshDriver::HasImageRequests() const {
1979 for (const auto& data : mStartTable.Values()) {
1980 if (!data->mEntries.IsEmpty()) {
1981 return true;
1985 return !mRequests.IsEmpty();
1988 auto nsRefreshDriver::GetReasonsToTick() const -> TickReasons {
1989 TickReasons reasons = TickReasons::eNone;
1990 if (HasObservers()) {
1991 reasons |= TickReasons::eHasObservers;
1993 if (HasImageRequests() && !mThrottled) {
1994 reasons |= TickReasons::eHasImageRequests;
1996 if (mNeedToUpdateResizeObservers) {
1997 reasons |= TickReasons::eNeedsToNotifyResizeObservers;
1999 if (mNeedToUpdateIntersectionObservations) {
2000 reasons |= TickReasons::eNeedsToUpdateIntersectionObservations;
2002 if (mMightNeedMediaQueryListenerUpdate) {
2003 reasons |= TickReasons::eHasPendingMediaQueryListeners;
2005 if (mNeedToUpdateContentRelevancy) {
2006 reasons |= TickReasons::eNeedsToUpdateContentRelevancy;
2008 if (!mVisualViewportResizeEvents.IsEmpty()) {
2009 reasons |= TickReasons::eHasVisualViewportResizeEvents;
2011 if (!mScrollEvents.IsEmpty()) {
2012 reasons |= TickReasons::eHasScrollEvents;
2014 if (!mVisualViewportScrollEvents.IsEmpty()) {
2015 reasons |= TickReasons::eHasVisualViewportScrollEvents;
2017 if (mPresContext && mPresContext->IsRoot() &&
2018 mPresContext->NeedsMoreTicksForUserInput()) {
2019 reasons |= TickReasons::eRootNeedsMoreTicksForUserInput;
2021 return reasons;
2024 void nsRefreshDriver::AppendTickReasonsToString(TickReasons aReasons,
2025 nsACString& aStr) const {
2026 if (aReasons == TickReasons::eNone) {
2027 aStr.AppendLiteral(" <none>");
2028 return;
2031 if (aReasons & TickReasons::eHasObservers) {
2032 aStr.AppendLiteral(" HasObservers (");
2033 AppendObserverDescriptionsToString(aStr);
2034 aStr.AppendLiteral(")");
2036 if (aReasons & TickReasons::eHasImageRequests) {
2037 aStr.AppendLiteral(" HasImageAnimations");
2039 if (aReasons & TickReasons::eNeedsToNotifyResizeObservers) {
2040 aStr.AppendLiteral(" NeedsToNotifyResizeObservers");
2042 if (aReasons & TickReasons::eNeedsToUpdateIntersectionObservations) {
2043 aStr.AppendLiteral(" NeedsToUpdateIntersectionObservations");
2045 if (aReasons & TickReasons::eHasPendingMediaQueryListeners) {
2046 aStr.AppendLiteral(" HasPendingMediaQueryListeners");
2048 if (aReasons & TickReasons::eNeedsToUpdateContentRelevancy) {
2049 aStr.AppendLiteral(" NeedsToUpdateContentRelevancy");
2051 if (aReasons & TickReasons::eHasVisualViewportResizeEvents) {
2052 aStr.AppendLiteral(" HasVisualViewportResizeEvents");
2054 if (aReasons & TickReasons::eHasScrollEvents) {
2055 aStr.AppendLiteral(" HasScrollEvents");
2057 if (aReasons & TickReasons::eHasVisualViewportScrollEvents) {
2058 aStr.AppendLiteral(" HasVisualViewportScrollEvents");
2060 if (aReasons & TickReasons::eRootNeedsMoreTicksForUserInput) {
2061 aStr.AppendLiteral(" RootNeedsMoreTicksForUserInput");
2065 bool nsRefreshDriver::
2066 ShouldKeepTimerRunningWhileWaitingForFirstContentfulPaint() {
2067 // On top level content pages keep the timer running initially so that we
2068 // paint the page soon enough.
2069 if (mThrottled || mTestControllingRefreshes || !XRE_IsContentProcess() ||
2070 !mPresContext->Document()->IsTopLevelContentDocument() ||
2071 mPresContext->Document()->IsInitialDocument() ||
2072 gfxPlatform::IsInLayoutAsapMode() ||
2073 mPresContext->HadFirstContentfulPaint() ||
2074 mPresContext->Document()->GetReadyStateEnum() ==
2075 Document::READYSTATE_COMPLETE) {
2076 return false;
2078 if (mBeforeFirstContentfulPaintTimerRunningLimit.IsNull()) {
2079 // Don't let the timer to run forever, so limit to 4s for now.
2080 mBeforeFirstContentfulPaintTimerRunningLimit =
2081 TimeStamp::Now() + TimeDuration::FromSeconds(4.0f);
2084 return TimeStamp::Now() <= mBeforeFirstContentfulPaintTimerRunningLimit;
2087 bool nsRefreshDriver::ShouldKeepTimerRunningAfterPageLoad() {
2088 if (mHasExceededAfterLoadTickPeriod ||
2089 !StaticPrefs::layout_keep_ticking_after_load_ms() || mThrottled ||
2090 mTestControllingRefreshes || !XRE_IsContentProcess() ||
2091 !mPresContext->Document()->IsTopLevelContentDocument() ||
2092 TaskController::Get()->PendingMainthreadTaskCountIncludingSuspended() ==
2093 0 ||
2094 gfxPlatform::IsInLayoutAsapMode()) {
2095 // Make the next check faster.
2096 mHasExceededAfterLoadTickPeriod = true;
2097 return false;
2100 nsPIDOMWindowInner* innerWindow = mPresContext->Document()->GetInnerWindow();
2101 if (!innerWindow) {
2102 return false;
2104 auto* perf =
2105 static_cast<PerformanceMainThread*>(innerWindow->GetPerformance());
2106 if (!perf) {
2107 return false;
2109 nsDOMNavigationTiming* timing = perf->GetDOMTiming();
2110 if (!timing) {
2111 return false;
2113 TimeStamp loadend = timing->LoadEventEnd();
2114 if (!loadend) {
2115 return false;
2117 // Keep ticking after the page load for some time.
2118 const bool retval =
2119 (loadend + TimeDuration::FromMilliseconds(
2120 StaticPrefs::layout_keep_ticking_after_load_ms())) >
2121 TimeStamp::Now();
2122 if (!retval) {
2123 mHasExceededAfterLoadTickPeriod = true;
2125 return retval;
2128 nsRefreshDriver::ObserverArray& nsRefreshDriver::ArrayFor(
2129 FlushType aFlushType) {
2130 switch (aFlushType) {
2131 case FlushType::Event:
2132 return mObservers[0];
2133 case FlushType::Style:
2134 case FlushType::Frames:
2135 return mObservers[1];
2136 case FlushType::Layout:
2137 return mObservers[2];
2138 case FlushType::Display:
2139 return mObservers[3];
2140 default:
2141 MOZ_CRASH("We don't track refresh observers for this flush type");
2146 * nsITimerCallback implementation
2149 void nsRefreshDriver::DoTick() {
2150 MOZ_ASSERT(!IsFrozen(), "Why are we notified while frozen?");
2151 MOZ_ASSERT(mPresContext, "Why are we notified after disconnection?");
2152 MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
2153 "Shouldn't have a JSContext on the stack");
2155 if (mTestControllingRefreshes) {
2156 Tick(VsyncId(), mMostRecentRefresh);
2157 } else {
2158 Tick(VsyncId(), TimeStamp::Now());
2162 struct DocumentFrameCallbacks {
2163 explicit DocumentFrameCallbacks(Document* aDocument) : mDocument(aDocument) {}
2165 RefPtr<Document> mDocument;
2166 nsTArray<FrameRequest> mCallbacks;
2169 static void TakeFrameRequestCallbacksFrom(
2170 Document* aDocument, nsTArray<DocumentFrameCallbacks>& aTarget) {
2171 aTarget.AppendElement(aDocument);
2172 aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks);
2175 void nsRefreshDriver::ScheduleAutoFocusFlush(Document* aDocument) {
2176 MOZ_ASSERT(!mAutoFocusFlushDocuments.Contains(aDocument));
2177 mAutoFocusFlushDocuments.AppendElement(aDocument);
2178 EnsureTimerStarted();
2181 void nsRefreshDriver::FlushAutoFocusDocuments() {
2182 nsTArray<RefPtr<Document>> docs(std::move(mAutoFocusFlushDocuments));
2184 for (const auto& doc : docs) {
2185 MOZ_KnownLive(doc)->FlushAutoFocusCandidates();
2189 void nsRefreshDriver::MaybeIncreaseMeasuredTicksSinceLoading() {
2190 if (mPresContext && mPresContext->IsRoot()) {
2191 mPresContext->MaybeIncreaseMeasuredTicksSinceLoading();
2195 void nsRefreshDriver::CancelFlushAutoFocus(Document* aDocument) {
2196 mAutoFocusFlushDocuments.RemoveElement(aDocument);
2199 // https://fullscreen.spec.whatwg.org/#run-the-fullscreen-steps
2200 void nsRefreshDriver::RunFullscreenSteps() {
2201 // Swap out the current pending events
2202 nsTArray<UniquePtr<PendingFullscreenEvent>> pendings(
2203 std::move(mPendingFullscreenEvents));
2204 for (UniquePtr<PendingFullscreenEvent>& event : pendings) {
2205 event->Dispatch();
2209 void nsRefreshDriver::UpdateIntersectionObservations(TimeStamp aNowTime) {
2210 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Compute intersections", LAYOUT);
2212 AutoTArray<RefPtr<Document>, 32> documents;
2214 if (mPresContext->Document()->HasIntersectionObservers()) {
2215 documents.AppendElement(mPresContext->Document());
2218 mPresContext->Document()->CollectDescendantDocuments(
2219 documents, [](const Document* document) -> bool {
2220 return document->HasIntersectionObservers();
2223 for (const auto& doc : documents) {
2224 doc->UpdateIntersectionObservations(aNowTime);
2225 doc->ScheduleIntersectionObserverNotification();
2228 mNeedToUpdateIntersectionObservations = false;
2231 void nsRefreshDriver::UpdateRelevancyOfContentVisibilityAutoFrames() {
2232 if (!mNeedToUpdateContentRelevancy) {
2233 return;
2236 if (RefPtr<PresShell> topLevelPresShell = mPresContext->GetPresShell()) {
2237 topLevelPresShell->UpdateRelevancyOfContentVisibilityAutoFrames();
2240 mPresContext->Document()->EnumerateSubDocuments([](Document& aSubDoc) {
2241 if (PresShell* presShell = aSubDoc.GetPresShell()) {
2242 presShell->UpdateRelevancyOfContentVisibilityAutoFrames();
2244 return CallState::Continue;
2247 mNeedToUpdateContentRelevancy = false;
2250 void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() {
2251 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Update the rendering: step 14", LAYOUT);
2252 // NotifyResizeObservers might re-schedule us for next tick.
2253 mNeedToUpdateResizeObservers = false;
2255 if (MOZ_UNLIKELY(!mPresContext)) {
2256 return;
2259 auto ShouldCollect = [](const Document* aDocument) {
2260 PresShell* ps = aDocument->GetPresShell();
2261 if (!ps || !ps->DidInitialize()) {
2262 // If there's no shell or it didn't initialize, then we'll run this code
2263 // when the pres shell does the initial reflow.
2264 return false;
2266 return ps->HasContentVisibilityAutoFrames() ||
2267 aDocument->HasResizeObservers() ||
2268 aDocument->HasElementsWithLastRememberedSize();
2271 AutoTArray<RefPtr<Document>, 32> documents;
2272 if (ShouldCollect(mPresContext->Document())) {
2273 documents.AppendElement(mPresContext->Document());
2275 mPresContext->Document()->CollectDescendantDocuments(documents,
2276 ShouldCollect);
2278 for (const RefPtr<Document>& doc : documents) {
2279 MOZ_KnownLive(doc)->DetermineProximityToViewportAndNotifyResizeObservers();
2283 void nsRefreshDriver::DispatchAnimationEvents() {
2284 if (!mPresContext) {
2285 return;
2288 // Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as
2289 // a RefPtr<> array since each AnimationEventDispatcher might be destroyed
2290 // during processing the previous dispatcher.
2291 AutoTArray<RefPtr<AnimationEventDispatcher>, 16> dispatchers;
2292 dispatchers.AppendElements(mAnimationEventFlushObservers);
2293 mAnimationEventFlushObservers.Clear();
2295 for (auto& dispatcher : dispatchers) {
2296 dispatcher->DispatchEvents();
2300 void nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime) {
2301 // Grab all of our frame request callbacks up front.
2302 nsTArray<DocumentFrameCallbacks> frameRequestCallbacks(
2303 mFrameRequestCallbackDocs.Length() +
2304 mThrottledFrameRequestCallbackDocs.Length());
2306 // First, grab throttled frame request callbacks.
2308 nsTArray<Document*> docsToRemove;
2310 // We always tick throttled frame requests if the entire refresh driver is
2311 // throttled, because in that situation throttled frame requests tick at the
2312 // same frequency as non-throttled frame requests.
2313 bool tickThrottledFrameRequests = mThrottled;
2315 if (!tickThrottledFrameRequests &&
2316 aNowTime >= mNextThrottledFrameRequestTick) {
2317 mNextThrottledFrameRequestTick =
2318 aNowTime + mThrottledFrameRequestInterval;
2319 tickThrottledFrameRequests = true;
2322 for (Document* doc : mThrottledFrameRequestCallbackDocs) {
2323 if (tickThrottledFrameRequests) {
2324 // We're ticking throttled documents, so grab this document's requests.
2325 // We don't bother appending to docsToRemove because we're going to
2326 // clear mThrottledFrameRequestCallbackDocs anyway.
2327 TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
2328 } else if (!doc->ShouldThrottleFrameRequests()) {
2329 // This document is no longer throttled, so grab its requests even
2330 // though we're not ticking throttled frame requests right now. If
2331 // this is the first unthrottled document with frame requests, we'll
2332 // enter high precision mode the next time the callback is scheduled.
2333 TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
2334 docsToRemove.AppendElement(doc);
2338 // Remove all the documents we're ticking from
2339 // mThrottledFrameRequestCallbackDocs so they can be readded as needed.
2340 if (tickThrottledFrameRequests) {
2341 mThrottledFrameRequestCallbackDocs.Clear();
2342 } else {
2343 // XXX(seth): We're using this approach to avoid concurrent modification
2344 // of mThrottledFrameRequestCallbackDocs. docsToRemove usually has either
2345 // zero elements or a very small number, so this should be OK in practice.
2346 for (Document* doc : docsToRemove) {
2347 mThrottledFrameRequestCallbackDocs.RemoveElement(doc);
2352 // Now grab unthrottled frame request callbacks.
2353 for (Document* doc : mFrameRequestCallbackDocs) {
2354 TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
2357 // Reset mFrameRequestCallbackDocs so they can be readded as needed.
2358 mFrameRequestCallbackDocs.Clear();
2360 if (!frameRequestCallbacks.IsEmpty()) {
2361 AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint",
2362 "requestAnimationFrame callbacks",
2363 GRAPHICS, GetDocShell(mPresContext));
2364 for (const DocumentFrameCallbacks& docCallbacks : frameRequestCallbacks) {
2365 TimeStamp startTime = TimeStamp::Now();
2367 // XXXbz Bug 863140: GetInnerWindow can return the outer
2368 // window in some cases.
2369 nsPIDOMWindowInner* innerWindow =
2370 docCallbacks.mDocument->GetInnerWindow();
2371 DOMHighResTimeStamp timeStamp = 0;
2372 if (innerWindow) {
2373 if (Performance* perf = innerWindow->GetPerformance()) {
2374 timeStamp = perf->TimeStampToDOMHighResForRendering(aNowTime);
2376 // else window is partially torn down already
2378 for (auto& callback : docCallbacks.mCallbacks) {
2379 if (docCallbacks.mDocument->IsCanceledFrameRequestCallback(
2380 callback.mHandle)) {
2381 continue;
2384 nsCOMPtr<nsIGlobalObject> global(innerWindow ? innerWindow->AsGlobal()
2385 : nullptr);
2386 CallbackDebuggerNotificationGuard guard(
2387 global, DebuggerNotificationType::RequestAnimationFrameCallback);
2389 // MOZ_KnownLive is OK, because the stack array frameRequestCallbacks
2390 // keeps callback alive and the mCallback strong reference can't be
2391 // mutated by the call.
2392 LogFrameRequestCallback::Run run(callback.mCallback);
2393 MOZ_KnownLive(callback.mCallback)->Call(timeStamp);
2396 if (docCallbacks.mDocument->GetReadyStateEnum() ==
2397 Document::READYSTATE_COMPLETE) {
2398 glean::performance_responsiveness::req_anim_frame_callback
2399 .AccumulateRawDuration(TimeStamp::Now() - startTime);
2400 } else {
2401 glean::performance_pageload::req_anim_frame_callback
2402 .AccumulateRawDuration(TimeStamp::Now() - startTime);
2408 static StaticAutoPtr<AutoTArray<RefPtr<Task>, 8>> sPendingIdleTasks;
2410 void nsRefreshDriver::DispatchIdleTaskAfterTickUnlessExists(Task* aTask) {
2411 if (!sPendingIdleTasks) {
2412 sPendingIdleTasks = new AutoTArray<RefPtr<Task>, 8>();
2413 } else {
2414 if (sPendingIdleTasks->Contains(aTask)) {
2415 return;
2419 sPendingIdleTasks->AppendElement(aTask);
2422 void nsRefreshDriver::CancelIdleTask(Task* aTask) {
2423 if (!sPendingIdleTasks) {
2424 return;
2427 sPendingIdleTasks->RemoveElement(aTask);
2429 if (sPendingIdleTasks->IsEmpty()) {
2430 sPendingIdleTasks = nullptr;
2434 static CallState ReduceAnimations(Document& aDocument) {
2435 if (nsPresContext* pc = aDocument.GetPresContext()) {
2436 if (pc->EffectCompositor()->NeedsReducing()) {
2437 pc->EffectCompositor()->ReduceAnimations();
2440 aDocument.EnumerateSubDocuments(ReduceAnimations);
2441 return CallState::Continue;
2444 bool nsRefreshDriver::TickObserverArray(uint32_t aIdx, TimeStamp aNowTime) {
2445 for (RefPtr<nsARefreshObserver> obs : mObservers[aIdx].EndLimitedRange()) {
2446 obs->WillRefresh(aNowTime);
2448 if (!mPresContext || !mPresContext->GetPresShell()) {
2449 return false;
2453 // Any animation timelines updated above may cause animations to queue
2454 // Promise resolution microtasks. We shouldn't run these, however, until we
2455 // have fully updated the animation state.
2457 // As per the "update animations and send events" procedure[1], we should
2458 // remove replaced animations and then run these microtasks before
2459 // dispatching the corresponding animation events.
2461 // [1]
2462 // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events
2463 if (aIdx == 1) {
2464 // This is the FlushType::Style case.
2466 nsAutoMicroTask mt;
2467 ReduceAnimations(*mPresContext->Document());
2470 // Check if running the microtask checkpoint caused the pres context to
2471 // be destroyed.
2472 if (!mPresContext || !mPresContext->GetPresShell()) {
2473 return false;
2476 FlushAutoFocusDocuments();
2477 DispatchScrollEvents();
2478 DispatchVisualViewportScrollEvents();
2479 EvaluateMediaQueriesAndReportChanges();
2480 DispatchAnimationEvents();
2481 RunFullscreenSteps();
2482 RunFrameRequestCallbacks(aNowTime);
2483 MaybeIncreaseMeasuredTicksSinceLoading();
2485 if (mPresContext && mPresContext->GetPresShell()) {
2486 AutoTArray<PresShell*, 16> observers;
2487 observers.AppendElements(mStyleFlushObservers);
2488 for (uint32_t j = observers.Length();
2489 j && mPresContext && mPresContext->GetPresShell(); --j) {
2490 // Make sure to not process observers which might have been removed
2491 // during previous iterations.
2492 PresShell* rawPresShell = observers[j - 1];
2493 if (!mStyleFlushObservers.RemoveElement(rawPresShell)) {
2494 continue;
2497 LogPresShellObserver::Run run(rawPresShell, this);
2499 RefPtr<PresShell> presShell = rawPresShell;
2500 presShell->mObservingStyleFlushes = false;
2501 presShell->FlushPendingNotifications(
2502 ChangesToFlush(FlushType::Style, false));
2503 // Inform the FontFaceSet that we ticked, so that it can resolve its
2504 // ready promise if it needs to (though it might still be waiting on
2505 // a layout flush).
2506 presShell->NotifyFontFaceSetOnRefresh();
2507 mNeedToRecomputeVisibility = true;
2509 // Record the telemetry for events that occurred between ticks.
2510 presShell->PingPerTickTelemetry(FlushType::Style);
2513 } else if (aIdx == 2) {
2514 // This is the FlushType::Layout case.
2515 AutoTArray<PresShell*, 16> observers;
2516 observers.AppendElements(mLayoutFlushObservers);
2517 for (uint32_t j = observers.Length();
2518 j && mPresContext && mPresContext->GetPresShell(); --j) {
2519 // Make sure to not process observers which might have been removed
2520 // during previous iterations.
2521 PresShell* rawPresShell = observers[j - 1];
2522 if (!mLayoutFlushObservers.RemoveElement(rawPresShell)) {
2523 continue;
2526 LogPresShellObserver::Run run(rawPresShell, this);
2528 RefPtr<PresShell> presShell = rawPresShell;
2529 presShell->mObservingLayoutFlushes = false;
2530 presShell->mWasLastReflowInterrupted = false;
2531 const ChangesToFlush ctf(FlushType::InterruptibleLayout, false);
2532 presShell->FlushPendingNotifications(ctf);
2533 if (presShell->FixUpFocus()) {
2534 presShell->FlushPendingNotifications(ctf);
2537 // Inform the FontFaceSet that we ticked, so that it can resolve its
2538 // ready promise if it needs to.
2539 presShell->NotifyFontFaceSetOnRefresh();
2540 mNeedToRecomputeVisibility = true;
2542 // Record the telemetry for events that occurred between ticks.
2543 presShell->PingPerTickTelemetry(FlushType::Layout);
2547 // The pres context may be destroyed during we do the flushing.
2548 if (!mPresContext || !mPresContext->GetPresShell()) {
2549 return false;
2552 return true;
2555 void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
2556 IsExtraTick aIsExtraTick /* = No */) {
2557 MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
2558 "Shouldn't have a JSContext on the stack");
2560 // We're either frozen or we were disconnected (likely in the middle
2561 // of a tick iteration). Just do nothing here, since our
2562 // prescontext went away.
2563 if (IsFrozen() || !mPresContext) {
2564 return;
2567 // We can have a race condition where the vsync timestamp
2568 // is before the most recent refresh due to a forced refresh.
2569 // The underlying assumption is that the refresh driver tick can only
2570 // go forward in time, not backwards. To prevent the refresh
2571 // driver from going back in time, just skip this tick and
2572 // wait until the next tick.
2573 // If this is an 'extra' tick, then we expect it to be using the same
2574 // vsync id and timestamp as the original tick, so also allow those.
2575 if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes &&
2576 aIsExtraTick == IsExtraTick::No) {
2577 return;
2579 auto cleanupInExtraTick = MakeScopeExit([&] { mInNormalTick = false; });
2580 mInNormalTick = aIsExtraTick != IsExtraTick::Yes;
2582 bool isPresentingInVR = false;
2583 #if defined(MOZ_WIDGET_ANDROID)
2584 isPresentingInVR = gfx::VRManagerChild::IsPresenting();
2585 #endif // defined(MOZ_WIDGET_ANDROID)
2587 if (!isPresentingInVR && IsWaitingForPaint(aNowTime)) {
2588 // In immersive VR mode, we do not get notifications when frames are
2589 // presented, so we do not wait for the compositor in that mode.
2591 // We're currently suspended waiting for earlier Tick's to
2592 // be completed (on the Compositor). Mark that we missed the paint
2593 // and keep waiting.
2594 PROFILER_MARKER_UNTYPED(
2595 "RefreshDriverTick waiting for paint", GRAPHICS,
2596 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)));
2597 return;
2600 const TimeStamp previousRefresh = mMostRecentRefresh;
2601 mMostRecentRefresh = aNowTime;
2603 if (mRootRefresh) {
2604 mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
2605 mRootRefresh = nullptr;
2607 mSkippedPaints = false;
2609 RefPtr<PresShell> presShell = mPresContext->GetPresShell();
2610 if (!presShell) {
2611 StopTimer();
2612 return;
2615 TickReasons tickReasons = GetReasonsToTick();
2616 if (tickReasons == TickReasons::eNone) {
2617 // We no longer have any observers.
2618 // Discard composition payloads because there is no paint.
2619 mCompositionPayloads.Clear();
2621 // We don't want to stop the timer when observers are initially
2622 // removed, because sometimes observers can be added and removed
2623 // often depending on what other things are going on and in that
2624 // situation we don't want to thrash our timer. So instead we
2625 // wait until we get a Notify() call when we have no observers
2626 // before stopping the timer.
2627 // On top level content pages keep the timer running initially so that we
2628 // paint the page soon enough.
2629 if (ShouldKeepTimerRunningWhileWaitingForFirstContentfulPaint()) {
2630 PROFILER_MARKER(
2631 "RefreshDriverTick waiting for first contentful paint", GRAPHICS,
2632 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)), Tracing,
2633 "Paint");
2634 } else if (ShouldKeepTimerRunningAfterPageLoad()) {
2635 PROFILER_MARKER(
2636 "RefreshDriverTick after page load", GRAPHICS,
2637 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)), Tracing,
2638 "Paint");
2639 } else {
2640 StopTimer();
2642 return;
2645 if (StaticPrefs::layout_skip_ticks_while_page_suspended()) {
2646 Document* doc = mPresContext->Document();
2647 nsPIDOMWindowInner* win = doc ? doc->GetInnerWindow() : nullptr;
2648 // Synchronous DOM operations mark the document being in such. Window's
2649 // suspend can be used also by external code. So we check here them both
2650 // in order to limit rAF skipping to only those synchronous DOM APIs which
2651 // also suspend window.
2652 if (win && win->IsSuspended() && doc->IsInSyncOperation()) {
2653 return;
2657 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("RefreshDriver tick", LAYOUT);
2659 nsAutoCString profilerStr;
2660 if (profiler_thread_is_being_profiled_for_markers()) {
2661 profilerStr.AppendLiteral("Tick reasons:");
2662 AppendTickReasonsToString(tickReasons, profilerStr);
2664 AUTO_PROFILER_MARKER_TEXT(
2665 "RefreshDriverTick", GRAPHICS,
2666 MarkerOptions(
2667 MarkerStack::TakeBacktrace(std::move(mRefreshTimerStartedCause)),
2668 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext))),
2669 profilerStr);
2671 mResizeSuppressed = false;
2673 bool oldInRefresh = mInRefresh;
2674 auto restoreInRefresh = MakeScopeExit([&] { mInRefresh = oldInRefresh; });
2675 mInRefresh = true;
2677 AutoRestore<TimeStamp> restoreTickStart(mTickStart);
2678 mTickStart = TimeStamp::Now();
2679 mTickVsyncId = aId;
2680 mTickVsyncTime = aNowTime;
2682 gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset();
2684 FlushForceNotifyContentfulPaintPresContext();
2686 AutoTArray<nsCOMPtr<nsIRunnable>, 16> earlyRunners = std::move(mEarlyRunners);
2687 for (auto& runner : earlyRunners) {
2688 runner->Run();
2689 // Early runners might destroy this pres context.
2690 if (!mPresContext || !mPresContext->GetPresShell()) {
2691 StopTimer();
2692 return;
2696 // Resize events should be fired before layout flushes or
2697 // calling animation frame callbacks.
2698 AutoTArray<RefPtr<PresShell>, 16> observers;
2699 observers.AppendElements(mResizeEventFlushObservers);
2700 for (RefPtr<PresShell>& presShell : Reversed(observers)) {
2701 if (!mPresContext || !mPresContext->GetPresShell()) {
2702 StopTimer();
2703 return;
2705 // Make sure to not process observers which might have been removed
2706 // during previous iterations.
2707 if (!mResizeEventFlushObservers.RemoveElement(presShell)) {
2708 continue;
2710 // MOZ_KnownLive because 'observers' is guaranteed to
2711 // keep it alive.
2713 // Fixing https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 on its own
2714 // won't help here, because 'observers' is non-const and we have the
2715 // Reversed() going on too...
2716 MOZ_KnownLive(presShell)->FireResizeEvent();
2718 DispatchVisualViewportResizeEvents();
2721 * The timer holds a reference to |this| while calling |Notify|.
2722 * However, implementations of |WillRefresh| are permitted to destroy
2723 * the pres context, which will cause our |mPresContext| to become
2724 * null. If this happens, TickObserverArray will tell us by returning
2725 * false, and we must stop notifying observers.
2727 // XXXdholbert This would be cleaner as a loop, but for now it's helpful to
2728 // have these calls separated out, so that we can figure out which
2729 // observer-category is involved from the backtrace of crash reports.
2730 bool keepGoing = true;
2731 MOZ_ASSERT(ArrayLength(mObservers) == 4,
2732 "if this changes, then we need to add or remove calls to "
2733 "TickObserverArray below");
2734 keepGoing = keepGoing && TickObserverArray(0, aNowTime);
2735 keepGoing = keepGoing && TickObserverArray(1, aNowTime);
2736 keepGoing = keepGoing && TickObserverArray(2, aNowTime);
2737 keepGoing = keepGoing && TickObserverArray(3, aNowTime);
2738 if (!keepGoing) {
2739 StopTimer();
2740 return;
2743 // Recompute approximate frame visibility if it's necessary and enough time
2744 // has passed since the last time we did it.
2745 if (mNeedToRecomputeVisibility && !mThrottled &&
2746 aNowTime >= mNextRecomputeVisibilityTick &&
2747 !presShell->IsPaintingSuppressed()) {
2748 mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval;
2749 mNeedToRecomputeVisibility = false;
2751 presShell->ScheduleApproximateFrameVisibilityUpdateNow();
2754 // Update any popups that may need to be moved or hidden due to their
2755 // anchor changing.
2756 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
2757 pm->UpdatePopupPositions(this);
2760 // Update the relevancy of the content of any `content-visibility: auto`
2761 // elements. The specification says: "Specifically, such changes will
2762 // take effect between steps 13 and 14 of Update the Rendering step of
2763 // the Processing Model (between “run the animation frame callbacks” and
2764 // “run the update intersection observations steps”)."
2765 // https://drafts.csswg.org/css-contain/#cv-notes
2766 UpdateRelevancyOfContentVisibilityAutoFrames();
2768 // Step 14 (https://html.spec.whatwg.org/#update-the-rendering).
2769 // 1) Initial proximity to the viewport determination for
2770 // content-visibility:auto elements and 2) Resize observers notifications.
2771 DetermineProximityToViewportAndNotifyResizeObservers();
2772 if (MOZ_UNLIKELY(!mPresContext || !mPresContext->GetPresShell())) {
2773 // A resize observer callback apparently destroyed our PresContext.
2774 StopTimer();
2775 return;
2778 UpdateIntersectionObservations(aNowTime);
2780 UpdateAnimatedImages(previousRefresh, aNowTime);
2782 bool dispatchTasksAfterTick = false;
2783 if (mViewManagerFlushIsPending && !mThrottled) {
2784 nsCString transactionId;
2785 if (profiler_thread_is_being_profiled_for_markers()) {
2786 transactionId.AppendLiteral("Transaction ID: ");
2787 transactionId.AppendInt((uint64_t)mNextTransactionId);
2789 AUTO_PROFILER_MARKER_TEXT(
2790 "ViewManagerFlush", GRAPHICS,
2791 MarkerOptions(
2792 MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)),
2793 MarkerStack::TakeBacktrace(std::move(mViewManagerFlushCause))),
2794 transactionId);
2796 // Forward our composition payloads to the layer manager.
2797 if (!mCompositionPayloads.IsEmpty()) {
2798 nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget();
2799 WindowRenderer* renderer = widget ? widget->GetWindowRenderer() : nullptr;
2800 if (renderer && renderer->AsWebRender()) {
2801 renderer->AsWebRender()->RegisterPayloads(mCompositionPayloads);
2803 mCompositionPayloads.Clear();
2806 #ifdef MOZ_DUMP_PAINTING
2807 if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
2808 printf_stderr("Starting ProcessPendingUpdates\n");
2810 #endif
2812 mViewManagerFlushIsPending = false;
2813 RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
2814 const bool skipPaint = isPresentingInVR;
2815 // Skip the paint in immersive VR mode because whatever we paint here will
2816 // not end up on the screen. The screen is displaying WebGL content from a
2817 // single canvas in that mode.
2818 if (!skipPaint) {
2819 PaintTelemetry::AutoRecordPaint record;
2820 vm->ProcessPendingUpdates();
2823 #ifdef MOZ_DUMP_PAINTING
2824 if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
2825 printf_stderr("Ending ProcessPendingUpdates\n");
2827 #endif
2829 dispatchTasksAfterTick = true;
2830 mHasScheduleFlush = false;
2831 } else {
2832 // No paint happened, discard composition payloads.
2833 mCompositionPayloads.Clear();
2836 #ifndef ANDROID /* bug 1142079 */
2837 double totalMs = (TimeStamp::Now() - mTickStart).ToMilliseconds();
2838 mozilla::Telemetry::Accumulate(mozilla::Telemetry::REFRESH_DRIVER_TICK,
2839 static_cast<uint32_t>(totalMs));
2840 #endif
2842 if (mNotifyDOMContentFlushed) {
2843 mNotifyDOMContentFlushed = false;
2844 mPresContext->NotifyDOMContentFlushed();
2847 for (nsAPostRefreshObserver* observer :
2848 mPostRefreshObservers.ForwardRange()) {
2849 observer->DidRefresh();
2852 NS_ASSERTION(mInRefresh, "Still in refresh");
2854 if (mPresContext->IsRoot() && XRE_IsContentProcess() &&
2855 StaticPrefs::gfx_content_always_paint()) {
2856 ScheduleViewManagerFlush();
2859 if (dispatchTasksAfterTick && sPendingIdleTasks) {
2860 UniquePtr<AutoTArray<RefPtr<Task>, 8>> tasks(sPendingIdleTasks.forget());
2861 for (RefPtr<Task>& taskWithDelay : *tasks) {
2862 TaskController::Get()->AddTask(taskWithDelay.forget());
2867 void nsRefreshDriver::UpdateAnimatedImages(TimeStamp aPreviousRefresh,
2868 TimeStamp aNowTime) {
2869 if (mThrottled) {
2870 // Don't do this when throttled, as the compositor might be paused and we
2871 // don't want to queue a lot of paints, see bug 1828587.
2872 return;
2874 // Perform notification to imgIRequests subscribed to listen for refresh
2875 // events.
2876 for (const auto& entry : mStartTable) {
2877 const uint32_t& delay = entry.GetKey();
2878 ImageStartData* data = entry.GetWeak();
2880 if (data->mEntries.IsEmpty()) {
2881 continue;
2884 if (data->mStartTime) {
2885 TimeStamp& start = *data->mStartTime;
2887 if (aPreviousRefresh >= start && aNowTime >= start) {
2888 TimeDuration prev = aPreviousRefresh - start;
2889 TimeDuration curr = aNowTime - start;
2890 uint32_t prevMultiple = uint32_t(prev.ToMilliseconds()) / delay;
2892 // We want to trigger images' refresh if we've just crossed over a
2893 // multiple of the first image's start time. If so, set the animation
2894 // start time to the nearest multiple of the delay and move all the
2895 // images in this table to the main requests table.
2896 if (prevMultiple != uint32_t(curr.ToMilliseconds()) / delay) {
2897 mozilla::TimeStamp desired =
2898 start + TimeDuration::FromMilliseconds(prevMultiple * delay);
2899 BeginRefreshingImages(data->mEntries, desired);
2901 } else {
2902 // Sometimes the start time can be in the future if we spin a nested
2903 // event loop and re-entrantly tick. In that case, setting the
2904 // animation start time to the start time seems like the least bad
2905 // thing we can do.
2906 mozilla::TimeStamp desired = start;
2907 BeginRefreshingImages(data->mEntries, desired);
2909 } else {
2910 // This is the very first time we've drawn images with this time delay.
2911 // Set the animation start time to "now" and move all the images in this
2912 // table to the main requests table.
2913 mozilla::TimeStamp desired = aNowTime;
2914 BeginRefreshingImages(data->mEntries, desired);
2915 data->mStartTime.emplace(aNowTime);
2919 if (!mRequests.IsEmpty()) {
2920 // RequestRefresh may run scripts, so it's not safe to directly call it
2921 // while using a hashtable enumerator to enumerate mRequests in case
2922 // script modifies the hashtable. Instead, we build a (local) array of
2923 // images to refresh, and then we refresh each image in that array.
2924 nsTArray<nsCOMPtr<imgIContainer>> imagesToRefresh(mRequests.Count());
2926 for (const auto& req : mRequests) {
2927 nsCOMPtr<imgIContainer> image;
2928 if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
2929 imagesToRefresh.AppendElement(image.forget());
2933 for (const auto& image : imagesToRefresh) {
2934 image->RequestRefresh(aNowTime);
2939 void nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
2940 mozilla::TimeStamp aDesired) {
2941 for (const auto& req : aEntries) {
2942 mRequests.Insert(req);
2944 nsCOMPtr<imgIContainer> image;
2945 if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
2946 image->SetAnimationStartTime(aDesired);
2949 aEntries.Clear();
2952 void nsRefreshDriver::Freeze() {
2953 StopTimer();
2954 mFreezeCount++;
2957 void nsRefreshDriver::Thaw() {
2958 NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
2960 if (mFreezeCount > 0) {
2961 mFreezeCount--;
2964 if (mFreezeCount == 0) {
2965 if (HasObservers() || HasImageRequests()) {
2966 // FIXME: This isn't quite right, since our EnsureTimerStarted call
2967 // updates our mMostRecentRefresh, but the DoRefresh call won't run
2968 // and notify our observers until we get back to the event loop.
2969 // Thus MostRecentRefresh() will lie between now and the DoRefresh.
2970 RefPtr<nsRunnableMethod<nsRefreshDriver>> event = NewRunnableMethod(
2971 "nsRefreshDriver::DoRefresh", this, &nsRefreshDriver::DoRefresh);
2972 nsPresContext* pc = GetPresContext();
2973 if (pc) {
2974 pc->Document()->Dispatch(event.forget());
2975 EnsureTimerStarted();
2976 } else {
2977 NS_ERROR("Thawing while document is being destroyed");
2983 void nsRefreshDriver::FinishedWaitingForTransaction() {
2984 if (mSkippedPaints && !IsInRefresh() &&
2985 (HasObservers() || HasImageRequests()) && CanDoCatchUpTick()) {
2986 NS_DispatchToCurrentThreadQueue(
2987 NS_NewRunnableFunction(
2988 "nsRefreshDriver::FinishedWaitingForTransaction",
2989 [self = RefPtr{this}]() {
2990 if (self->CanDoCatchUpTick()) {
2991 self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
2992 self->mActiveTimer->MostRecentRefresh());
2995 EventQueuePriority::Vsync);
2997 mWaitingForTransaction = false;
2998 mSkippedPaints = false;
3001 mozilla::layers::TransactionId nsRefreshDriver::GetTransactionId(
3002 bool aThrottle) {
3003 mNextTransactionId = mNextTransactionId.Next();
3004 LOG("[%p] Allocating transaction id %" PRIu64, this, mNextTransactionId.mId);
3006 // If this a paint from within a normal tick, and the caller hasn't explicitly
3007 // asked for it to skip being throttled, then record this transaction as
3008 // pending and maybe disable painting until some transactions are processed.
3009 if (aThrottle && mInNormalTick) {
3010 mPendingTransactions.AppendElement(mNextTransactionId);
3011 if (TooManyPendingTransactions() && !mWaitingForTransaction &&
3012 !mTestControllingRefreshes) {
3013 LOG("[%p] Hit max pending transaction limit, entering wait mode", this);
3014 mWaitingForTransaction = true;
3015 mSkippedPaints = false;
3019 return mNextTransactionId;
3022 mozilla::layers::TransactionId nsRefreshDriver::LastTransactionId() const {
3023 return mNextTransactionId;
3026 void nsRefreshDriver::RevokeTransactionId(
3027 mozilla::layers::TransactionId aTransactionId) {
3028 MOZ_ASSERT(aTransactionId == mNextTransactionId);
3029 LOG("[%p] Revoking transaction id %" PRIu64, this, aTransactionId.mId);
3030 if (AtPendingTransactionLimit() &&
3031 mPendingTransactions.Contains(aTransactionId) && mWaitingForTransaction) {
3032 LOG("[%p] No longer over pending transaction limit, leaving wait state",
3033 this);
3034 MOZ_ASSERT(!mSkippedPaints,
3035 "How did we skip a paint when we're in the middle of one?");
3036 FinishedWaitingForTransaction();
3039 // Notify the pres context so that it can deliver MozAfterPaint for this
3040 // id if any caller was expecting it.
3041 nsPresContext* pc = GetPresContext();
3042 if (pc) {
3043 pc->NotifyRevokingDidPaint(aTransactionId);
3045 // Remove aTransactionId from the set of outstanding transactions since we're
3046 // no longer waiting on it to be completed, but don't revert
3047 // mNextTransactionId since we can't use the id again.
3048 mPendingTransactions.RemoveElement(aTransactionId);
3051 void nsRefreshDriver::ClearPendingTransactions() {
3052 LOG("[%p] ClearPendingTransactions", this);
3053 mPendingTransactions.Clear();
3054 mWaitingForTransaction = false;
3057 void nsRefreshDriver::ResetInitialTransactionId(
3058 mozilla::layers::TransactionId aTransactionId) {
3059 mNextTransactionId = aTransactionId;
3062 mozilla::TimeStamp nsRefreshDriver::GetTransactionStart() { return mTickStart; }
3064 VsyncId nsRefreshDriver::GetVsyncId() { return mTickVsyncId; }
3066 mozilla::TimeStamp nsRefreshDriver::GetVsyncStart() { return mTickVsyncTime; }
3068 void nsRefreshDriver::NotifyTransactionCompleted(
3069 mozilla::layers::TransactionId aTransactionId) {
3070 LOG("[%p] Completed transaction id %" PRIu64, this, aTransactionId.mId);
3071 mPendingTransactions.RemoveElement(aTransactionId);
3072 if (mWaitingForTransaction && !TooManyPendingTransactions()) {
3073 LOG("[%p] No longer over pending transaction limit, leaving wait state",
3074 this);
3075 FinishedWaitingForTransaction();
3079 void nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime) {
3080 mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
3081 mRootRefresh = nullptr;
3082 if (mSkippedPaints) {
3083 DoRefresh();
3087 bool nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime) {
3088 if (mTestControllingRefreshes) {
3089 return false;
3092 if (mWaitingForTransaction) {
3093 LOG("[%p] Over max pending transaction limit when trying to paint, "
3094 "skipping",
3095 this);
3096 mSkippedPaints = true;
3097 return true;
3100 // Try find the 'root' refresh driver for the current window and check
3101 // if that is waiting for a paint.
3102 nsPresContext* pc = GetPresContext();
3103 nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
3104 if (rootContext) {
3105 nsRefreshDriver* rootRefresh = rootContext->RefreshDriver();
3106 if (rootRefresh && rootRefresh != this) {
3107 if (rootRefresh->IsWaitingForPaint(aTime)) {
3108 if (mRootRefresh != rootRefresh) {
3109 if (mRootRefresh) {
3110 mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
3112 rootRefresh->AddRefreshObserver(this, FlushType::Style,
3113 "Waiting for paint");
3114 mRootRefresh = rootRefresh;
3116 mSkippedPaints = true;
3117 return true;
3121 return false;
3124 void nsRefreshDriver::SetActivity(bool aIsActive) {
3125 const bool shouldThrottle = !aIsActive;
3126 if (mThrottled == shouldThrottle) {
3127 return;
3129 mThrottled = shouldThrottle;
3130 if (mActiveTimer || GetReasonsToTick() != TickReasons::eNone) {
3131 // We want to switch our timer type here, so just stop and restart the
3132 // timer.
3133 EnsureTimerStarted(eForceAdjustTimer);
3137 nsPresContext* nsRefreshDriver::GetPresContext() const { return mPresContext; }
3139 void nsRefreshDriver::DoRefresh() {
3140 // Don't do a refresh unless we're in a state where we should be refreshing.
3141 if (!IsFrozen() && mPresContext && mActiveTimer) {
3142 DoTick();
3146 #ifdef DEBUG
3147 bool nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
3148 FlushType aFlushType) {
3149 ObserverArray& array = ArrayFor(aFlushType);
3150 return array.Contains(aObserver);
3152 #endif
3154 void nsRefreshDriver::ScheduleViewManagerFlush() {
3155 NS_ASSERTION(mPresContext && mPresContext->IsRoot(),
3156 "Should only schedule view manager flush on root prescontexts");
3157 mViewManagerFlushIsPending = true;
3158 if (!mViewManagerFlushCause) {
3159 mViewManagerFlushCause = profiler_capture_backtrace();
3161 mHasScheduleFlush = true;
3162 EnsureTimerStarted(eNeverAdjustTimer);
3165 void nsRefreshDriver::ScheduleFrameRequestCallbacks(Document* aDocument) {
3166 NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
3167 mFrameRequestCallbackDocs.NoIndex &&
3168 mThrottledFrameRequestCallbackDocs.IndexOf(aDocument) ==
3169 mThrottledFrameRequestCallbackDocs.NoIndex,
3170 "Don't schedule the same document multiple times");
3171 if (aDocument->ShouldThrottleFrameRequests()) {
3172 mThrottledFrameRequestCallbackDocs.AppendElement(aDocument);
3173 } else {
3174 mFrameRequestCallbackDocs.AppendElement(aDocument);
3177 // make sure that the timer is running
3178 EnsureTimerStarted();
3181 void nsRefreshDriver::RevokeFrameRequestCallbacks(Document* aDocument) {
3182 mFrameRequestCallbackDocs.RemoveElement(aDocument);
3183 mThrottledFrameRequestCallbackDocs.RemoveElement(aDocument);
3184 // No need to worry about restarting our timer in slack mode if it's already
3185 // running; that will happen automatically when it fires.
3188 void nsRefreshDriver::ScheduleFullscreenEvent(
3189 UniquePtr<PendingFullscreenEvent> aEvent) {
3190 mPendingFullscreenEvents.AppendElement(std::move(aEvent));
3191 // make sure that the timer is running
3192 EnsureTimerStarted();
3195 void nsRefreshDriver::CancelPendingFullscreenEvents(Document* aDocument) {
3196 for (auto i : Reversed(IntegerRange(mPendingFullscreenEvents.Length()))) {
3197 if (mPendingFullscreenEvents[i]->Document() == aDocument) {
3198 mPendingFullscreenEvents.RemoveElementAt(i);
3203 void nsRefreshDriver::CancelPendingAnimationEvents(
3204 AnimationEventDispatcher* aDispatcher) {
3205 MOZ_ASSERT(aDispatcher);
3206 aDispatcher->ClearEventQueue();
3207 mAnimationEventFlushObservers.RemoveElement(aDispatcher);
3210 /* static */
3211 TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault,
3212 IdleCheck aCheckType) {
3213 MOZ_ASSERT(NS_IsMainThread());
3214 MOZ_ASSERT(!aDefault.IsNull());
3216 // For computing idleness of refresh drivers we only care about
3217 // sRegularRateTimerList, since we consider refresh drivers attached to
3218 // sThrottledRateTimer to be inactive. This implies that tasks
3219 // resulting from a tick on the sRegularRateTimer counts as being
3220 // busy but tasks resulting from a tick on sThrottledRateTimer
3221 // counts as being idle.
3222 if (sRegularRateTimer) {
3223 TimeStamp retVal = sRegularRateTimer->GetIdleDeadlineHint(aDefault);
3224 if (retVal != aDefault) {
3225 return retVal;
3229 TimeStamp hint = TimeStamp();
3230 if (sRegularRateTimerList) {
3231 for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
3232 TimeStamp newHint = timer->GetIdleDeadlineHint(aDefault);
3233 if (newHint < aDefault && (hint.IsNull() || newHint < hint)) {
3234 hint = newHint;
3239 if (!hint.IsNull()) {
3240 return hint;
3243 if (aCheckType == IdleCheck::AllVsyncListeners && XRE_IsParentProcess()) {
3244 Maybe<TimeDuration> maybeRate =
3245 mozilla::gfx::VsyncSource::GetFastestVsyncRate();
3246 if (maybeRate.isSome()) {
3247 TimeDuration minIdlePeriod =
3248 TimeDuration::FromMilliseconds(StaticPrefs::idle_period_min());
3249 TimeDuration layoutIdleLimit = TimeDuration::FromMilliseconds(
3250 StaticPrefs::layout_idle_period_time_limit());
3251 TimeDuration rate = *maybeRate - layoutIdleLimit;
3253 // If the rate is very short, don't let it affect idle processing in the
3254 // parent process too much.
3255 rate = std::max(rate, minIdlePeriod + minIdlePeriod);
3257 TimeStamp newHint = TimeStamp::Now() + rate;
3258 if (newHint < aDefault) {
3259 return newHint;
3264 return aDefault;
3267 /* static */
3268 Maybe<TimeStamp> nsRefreshDriver::GetNextTickHint() {
3269 MOZ_ASSERT(NS_IsMainThread());
3271 if (sRegularRateTimer) {
3272 return sRegularRateTimer->GetNextTickHint();
3275 Maybe<TimeStamp> hint = Nothing();
3276 if (sRegularRateTimerList) {
3277 for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
3278 if (Maybe<TimeStamp> newHint = timer->GetNextTickHint()) {
3279 if (!hint || newHint.value() < hint.value()) {
3280 hint = newHint;
3285 return hint;
3288 /* static */
3289 bool nsRefreshDriver::IsRegularRateTimerTicking() {
3290 MOZ_ASSERT(NS_IsMainThread());
3292 if (sRegularRateTimer) {
3293 if (sRegularRateTimer->IsTicking()) {
3294 return true;
3298 if (sRegularRateTimerList) {
3299 for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
3300 if (timer->IsTicking()) {
3301 return true;
3306 return false;
3309 void nsRefreshDriver::Disconnect() {
3310 MOZ_ASSERT(NS_IsMainThread());
3312 StopTimer();
3314 mEarlyRunners.Clear();
3316 if (mPresContext) {
3317 mPresContext = nullptr;
3318 if (--sRefreshDriverCount == 0) {
3319 Shutdown();
3324 #undef LOG