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