layout/base/nsRefreshDriver.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /*
michael@0 8 * Code to notify things that animate before a refresh, at an appropriate
michael@0 9 * refresh rate. (Perhaps temporary, until replaced by compositor.)
michael@0 10 *
michael@0 11 * Chrome and each tab have their own RefreshDriver, which in turn
michael@0 12 * hooks into one of a few global timer based on RefreshDriverTimer,
michael@0 13 * defined below. There are two main global timers -- one for active
michael@0 14 * animations, and one for inactive ones. These are implemented as
michael@0 15 * subclasses of RefreshDriverTimer; see below for a description of
michael@0 16 * their implementations. In the future, additional timer types may
michael@0 17 * implement things like blocking on vsync.
michael@0 18 */
michael@0 19
michael@0 20 #ifdef XP_WIN
michael@0 21 #include <windows.h>
michael@0 22 // mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have
michael@0 23 // to manually include it
michael@0 24 #include <mmsystem.h>
michael@0 25 #include "WinUtils.h"
michael@0 26 #endif
michael@0 27
michael@0 28 #include "mozilla/ArrayUtils.h"
michael@0 29 #include "mozilla/AutoRestore.h"
michael@0 30 #include "nsRefreshDriver.h"
michael@0 31 #include "nsITimer.h"
michael@0 32 #include "nsLayoutUtils.h"
michael@0 33 #include "nsPresContext.h"
michael@0 34 #include "nsComponentManagerUtils.h"
michael@0 35 #include "prlog.h"
michael@0 36 #include "nsAutoPtr.h"
michael@0 37 #include "nsIDocument.h"
michael@0 38 #include "jsapi.h"
michael@0 39 #include "nsContentUtils.h"
michael@0 40 #include "mozilla/Preferences.h"
michael@0 41 #include "nsViewManager.h"
michael@0 42 #include "GeckoProfiler.h"
michael@0 43 #include "nsNPAPIPluginInstance.h"
michael@0 44 #include "nsPerformance.h"
michael@0 45 #include "mozilla/dom/WindowBinding.h"
michael@0 46 #include "RestyleManager.h"
michael@0 47 #include "Layers.h"
michael@0 48 #include "imgIContainer.h"
michael@0 49 #include "nsIFrameRequestCallback.h"
michael@0 50 #include "mozilla/dom/ScriptSettings.h"
michael@0 51
michael@0 52 using namespace mozilla;
michael@0 53 using namespace mozilla::widget;
michael@0 54
michael@0 55 #ifdef PR_LOGGING
michael@0 56 static PRLogModuleInfo *gLog = nullptr;
michael@0 57 #define LOG(...) PR_LOG(gLog, PR_LOG_NOTICE, (__VA_ARGS__))
michael@0 58 #else
michael@0 59 #define LOG(...) do { } while(0)
michael@0 60 #endif
michael@0 61
michael@0 62 #define DEFAULT_FRAME_RATE 60
michael@0 63 #define DEFAULT_THROTTLED_FRAME_RATE 1
michael@0 64 // after 10 minutes, stop firing off inactive timers
michael@0 65 #define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
michael@0 66
michael@0 67 namespace mozilla {
michael@0 68
michael@0 69 /*
michael@0 70 * The base class for all global refresh driver timers. It takes care
michael@0 71 * of managing the list of refresh drivers attached to them and
michael@0 72 * provides interfaces for querying/setting the rate and actually
michael@0 73 * running a timer 'Tick'. Subclasses must implement StartTimer(),
michael@0 74 * StopTimer(), and ScheduleNextTick() -- the first two just
michael@0 75 * start/stop whatever timer mechanism is in use, and ScheduleNextTick
michael@0 76 * is called at the start of the Tick() implementation to set a time
michael@0 77 * for the next tick.
michael@0 78 */
michael@0 79 class RefreshDriverTimer {
michael@0 80 public:
michael@0 81 /*
michael@0 82 * aRate -- the delay, in milliseconds, requested between timer firings
michael@0 83 */
michael@0 84 RefreshDriverTimer(double aRate)
michael@0 85 {
michael@0 86 SetRate(aRate);
michael@0 87 }
michael@0 88
michael@0 89 virtual ~RefreshDriverTimer()
michael@0 90 {
michael@0 91 NS_ASSERTION(mRefreshDrivers.Length() == 0, "Should have removed all refresh drivers from here by now!");
michael@0 92 }
michael@0 93
michael@0 94 virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
michael@0 95 {
michael@0 96 LOG("[%p] AddRefreshDriver %p", this, aDriver);
michael@0 97
michael@0 98 NS_ASSERTION(!mRefreshDrivers.Contains(aDriver), "AddRefreshDriver for a refresh driver that's already in the list!");
michael@0 99 mRefreshDrivers.AppendElement(aDriver);
michael@0 100
michael@0 101 if (mRefreshDrivers.Length() == 1) {
michael@0 102 StartTimer();
michael@0 103 }
michael@0 104 }
michael@0 105
michael@0 106 virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver)
michael@0 107 {
michael@0 108 LOG("[%p] RemoveRefreshDriver %p", this, aDriver);
michael@0 109
michael@0 110 NS_ASSERTION(mRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the list!");
michael@0 111 mRefreshDrivers.RemoveElement(aDriver);
michael@0 112
michael@0 113 if (mRefreshDrivers.Length() == 0) {
michael@0 114 StopTimer();
michael@0 115 }
michael@0 116 }
michael@0 117
michael@0 118 double GetRate() const
michael@0 119 {
michael@0 120 return mRateMilliseconds;
michael@0 121 }
michael@0 122
michael@0 123 // will take effect at next timer tick
michael@0 124 virtual void SetRate(double aNewRate)
michael@0 125 {
michael@0 126 mRateMilliseconds = aNewRate;
michael@0 127 mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
michael@0 128 }
michael@0 129
michael@0 130 TimeStamp MostRecentRefresh() const { return mLastFireTime; }
michael@0 131 int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; }
michael@0 132
michael@0 133 protected:
michael@0 134 virtual void StartTimer() = 0;
michael@0 135 virtual void StopTimer() = 0;
michael@0 136 virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
michael@0 137
michael@0 138 /*
michael@0 139 * Actually runs a tick, poking all the attached RefreshDrivers.
michael@0 140 * Grabs the "now" time via JS_Now and TimeStamp::Now().
michael@0 141 */
michael@0 142 void Tick()
michael@0 143 {
michael@0 144 int64_t jsnow = JS_Now();
michael@0 145 TimeStamp now = TimeStamp::Now();
michael@0 146
michael@0 147 ScheduleNextTick(now);
michael@0 148
michael@0 149 mLastFireEpoch = jsnow;
michael@0 150 mLastFireTime = now;
michael@0 151
michael@0 152 LOG("[%p] ticking drivers...", this);
michael@0 153 nsTArray<nsRefPtr<nsRefreshDriver> > drivers(mRefreshDrivers);
michael@0 154 for (size_t i = 0; i < drivers.Length(); ++i) {
michael@0 155 // don't poke this driver if it's in test mode
michael@0 156 if (drivers[i]->IsTestControllingRefreshesEnabled()) {
michael@0 157 continue;
michael@0 158 }
michael@0 159
michael@0 160 TickDriver(drivers[i], jsnow, now);
michael@0 161 }
michael@0 162 LOG("[%p] done.", this);
michael@0 163 }
michael@0 164
michael@0 165 static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now)
michael@0 166 {
michael@0 167 LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow);
michael@0 168 driver->Tick(jsnow, now);
michael@0 169 }
michael@0 170
michael@0 171 double mRateMilliseconds;
michael@0 172 TimeDuration mRateDuration;
michael@0 173
michael@0 174 int64_t mLastFireEpoch;
michael@0 175 TimeStamp mLastFireTime;
michael@0 176 TimeStamp mTargetTime;
michael@0 177
michael@0 178 nsTArray<nsRefPtr<nsRefreshDriver> > mRefreshDrivers;
michael@0 179
michael@0 180 // useful callback for nsITimer-based derived classes, here
michael@0 181 // bacause of c++ protected shenanigans
michael@0 182 static void TimerTick(nsITimer* aTimer, void* aClosure)
michael@0 183 {
michael@0 184 RefreshDriverTimer *timer = static_cast<RefreshDriverTimer*>(aClosure);
michael@0 185 timer->Tick();
michael@0 186 }
michael@0 187 };
michael@0 188
michael@0 189 /*
michael@0 190 * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that
michael@0 191 * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to
michael@0 192 * implement ScheduleNextTick and intelligently calculate the next time to tick,
michael@0 193 * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain
michael@0 194 * with its attempt at intelligent slack removal and such, so we don't do it.
michael@0 195 */
michael@0 196 class SimpleTimerBasedRefreshDriverTimer :
michael@0 197 public RefreshDriverTimer
michael@0 198 {
michael@0 199 public:
michael@0 200 SimpleTimerBasedRefreshDriverTimer(double aRate)
michael@0 201 : RefreshDriverTimer(aRate)
michael@0 202 {
michael@0 203 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
michael@0 204 }
michael@0 205
michael@0 206 virtual ~SimpleTimerBasedRefreshDriverTimer()
michael@0 207 {
michael@0 208 StopTimer();
michael@0 209 }
michael@0 210
michael@0 211 protected:
michael@0 212
michael@0 213 virtual void StartTimer()
michael@0 214 {
michael@0 215 // pretend we just fired, and we schedule the next tick normally
michael@0 216 mLastFireEpoch = JS_Now();
michael@0 217 mLastFireTime = TimeStamp::Now();
michael@0 218
michael@0 219 mTargetTime = mLastFireTime + mRateDuration;
michael@0 220
michael@0 221 uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
michael@0 222 mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
michael@0 223 }
michael@0 224
michael@0 225 virtual void StopTimer()
michael@0 226 {
michael@0 227 mTimer->Cancel();
michael@0 228 }
michael@0 229
michael@0 230 nsRefPtr<nsITimer> mTimer;
michael@0 231 };
michael@0 232
michael@0 233 /*
michael@0 234 * PreciseRefreshDriverTimer schedules ticks based on the current time
michael@0 235 * and when the next tick -should- be sent if we were hitting our
michael@0 236 * rate. It always schedules ticks on multiples of aRate -- meaning that
michael@0 237 * if some execution takes longer than an alloted slot, the next tick
michael@0 238 * will be delayed instead of triggering instantly. This might not be
michael@0 239 * desired -- there's an #if 0'd block below that we could put behind
michael@0 240 * a pref to control this behaviour.
michael@0 241 */
michael@0 242 class PreciseRefreshDriverTimer :
michael@0 243 public SimpleTimerBasedRefreshDriverTimer
michael@0 244 {
michael@0 245 public:
michael@0 246 PreciseRefreshDriverTimer(double aRate)
michael@0 247 : SimpleTimerBasedRefreshDriverTimer(aRate)
michael@0 248 {
michael@0 249 }
michael@0 250
michael@0 251 protected:
michael@0 252 virtual void ScheduleNextTick(TimeStamp aNowTime)
michael@0 253 {
michael@0 254 // The number of (whole) elapsed intervals between the last target
michael@0 255 // time and the actual time. We want to truncate the double down
michael@0 256 // to an int number of intervals.
michael@0 257 int numElapsedIntervals = static_cast<int>((aNowTime - mTargetTime) / mRateDuration);
michael@0 258
michael@0 259 if (numElapsedIntervals < 0) {
michael@0 260 // It's possible that numElapsedIntervals is negative (e.g. timer compensation
michael@0 261 // may result in (aNowTime - mTargetTime) < -1.0/mRateDuration, which will result in
michael@0 262 // negative numElapsedIntervals), so make sure we don't target the same timestamp.
michael@0 263 numElapsedIntervals = 0;
michael@0 264 }
michael@0 265
michael@0 266 // the last "tick" that may or may not have been actually sent was
michael@0 267 // at this time. For example, if the rate is 15ms, the target
michael@0 268 // time is 200ms, and it's now 225ms, the last effective tick
michael@0 269 // would have been at 215ms. The next one should then be
michael@0 270 // scheduled for 5 ms from now.
michael@0 271 //
michael@0 272 // We then add another mRateDuration to find the next tick target.
michael@0 273 TimeStamp newTarget = mTargetTime + mRateDuration * (numElapsedIntervals + 1);
michael@0 274
michael@0 275 // the amount of (integer) ms until the next time we should tick
michael@0 276 uint32_t delay = static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds());
michael@0 277
michael@0 278 // Without this block, we'll always schedule on interval ticks;
michael@0 279 // with it, we'll schedule immediately if we missed our tick target
michael@0 280 // last time.
michael@0 281 #if 0
michael@0 282 if (numElapsedIntervals > 0) {
michael@0 283 // we're late, so reset
michael@0 284 newTarget = aNowTime;
michael@0 285 delay = 0;
michael@0 286 }
michael@0 287 #endif
michael@0 288
michael@0 289 // log info & lateness
michael@0 290 LOG("[%p] precise timer last tick late by %f ms, next tick in %d ms",
michael@0 291 this,
michael@0 292 (aNowTime - mTargetTime).ToMilliseconds(),
michael@0 293 delay);
michael@0 294
michael@0 295 // then schedule the timer
michael@0 296 LOG("[%p] scheduling callback for %d ms (2)", this, delay);
michael@0 297 mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
michael@0 298
michael@0 299 mTargetTime = newTarget;
michael@0 300 }
michael@0 301 };
michael@0 302
michael@0 303 #ifdef XP_WIN
michael@0 304 /*
michael@0 305 * Uses vsync timing on windows with DWM. Falls back dynamically to fixed rate if required.
michael@0 306 */
michael@0 307 class PreciseRefreshDriverTimerWindowsDwmVsync :
michael@0 308 public PreciseRefreshDriverTimer
michael@0 309 {
michael@0 310 public:
michael@0 311 // Checks if the vsync API is accessible.
michael@0 312 static bool IsSupported()
michael@0 313 {
michael@0 314 return WinUtils::dwmGetCompositionTimingInfoPtr != nullptr;
michael@0 315 }
michael@0 316
michael@0 317 PreciseRefreshDriverTimerWindowsDwmVsync(double aRate, bool aPreferHwTiming = false)
michael@0 318 : PreciseRefreshDriverTimer(aRate)
michael@0 319 , mPreferHwTiming(aPreferHwTiming)
michael@0 320 {
michael@0 321 }
michael@0 322
michael@0 323 protected:
michael@0 324 // Indicates we should try to adjust to the HW's timing (get rate from the OS or use vsync)
michael@0 325 // This is typically true if the default refresh-rate value was not modified by the user.
michael@0 326 bool mPreferHwTiming;
michael@0 327
michael@0 328 nsresult GetVBlankInfo(mozilla::TimeStamp &aLastVBlank, mozilla::TimeDuration &aInterval)
michael@0 329 {
michael@0 330 MOZ_ASSERT(WinUtils::dwmGetCompositionTimingInfoPtr,
michael@0 331 "DwmGetCompositionTimingInfoPtr is unavailable (windows vsync)");
michael@0 332
michael@0 333 DWM_TIMING_INFO timingInfo;
michael@0 334 timingInfo.cbSize = sizeof(DWM_TIMING_INFO);
michael@0 335 HRESULT hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &timingInfo); // For the desktop window instead of a specific one.
michael@0 336
michael@0 337 if (FAILED(hr)) {
michael@0 338 // This happens first time this is called.
michael@0 339 return NS_ERROR_NOT_INITIALIZED;
michael@0 340 }
michael@0 341
michael@0 342 LARGE_INTEGER time, freq;
michael@0 343 ::QueryPerformanceCounter(&time);
michael@0 344 ::QueryPerformanceFrequency(&freq);
michael@0 345 aLastVBlank = TimeStamp::Now();
michael@0 346 double secondsPassed = double(time.QuadPart - timingInfo.qpcVBlank) / double(freq.QuadPart);
michael@0 347
michael@0 348 aLastVBlank -= TimeDuration::FromSeconds(secondsPassed);
michael@0 349 aInterval = TimeDuration::FromSeconds(double(timingInfo.qpcRefreshPeriod) / double(freq.QuadPart));
michael@0 350
michael@0 351 return NS_OK;
michael@0 352 }
michael@0 353
michael@0 354 virtual void ScheduleNextTick(TimeStamp aNowTime)
michael@0 355 {
michael@0 356 static const TimeDuration kMinSaneInterval = TimeDuration::FromMilliseconds(3); // 330Hz
michael@0 357 static const TimeDuration kMaxSaneInterval = TimeDuration::FromMilliseconds(44); // 23Hz
michael@0 358 static const TimeDuration kNegativeMaxSaneInterval = TimeDuration::FromMilliseconds(-44); // Saves conversions for abs interval
michael@0 359 TimeStamp lastVblank;
michael@0 360 TimeDuration vblankInterval;
michael@0 361
michael@0 362 if (!mPreferHwTiming ||
michael@0 363 NS_OK != GetVBlankInfo(lastVblank, vblankInterval) ||
michael@0 364 vblankInterval > kMaxSaneInterval ||
michael@0 365 vblankInterval < kMinSaneInterval ||
michael@0 366 (aNowTime - lastVblank) > kMaxSaneInterval ||
michael@0 367 (aNowTime - lastVblank) < kNegativeMaxSaneInterval) {
michael@0 368 // Use the default timing without vsync
michael@0 369 PreciseRefreshDriverTimer::ScheduleNextTick(aNowTime);
michael@0 370 return;
michael@0 371 }
michael@0 372
michael@0 373 TimeStamp newTarget = lastVblank + vblankInterval; // Base target
michael@0 374
michael@0 375 // However, timer callback might return early (or late, but that wouldn't bother us), and vblankInterval
michael@0 376 // appears to be slightly (~1%) different on each call (probably the OS measuring recent actual interval[s])
michael@0 377 // and since we don't want to re-target the same vsync, we keep advancing in vblank intervals until we find the
michael@0 378 // next safe target (next vsync, but not within 10% interval of previous target).
michael@0 379 // This is typically 0 or 1 iteration:
michael@0 380 // If we're too early, next vsync would be the one we've already targeted (1 iteration).
michael@0 381 // If the timer returned late, no iteration will be required.
michael@0 382
michael@0 383 const double kSameVsyncThreshold = 0.1;
michael@0 384 while (newTarget <= mTargetTime + vblankInterval.MultDouble(kSameVsyncThreshold)) {
michael@0 385 newTarget += vblankInterval;
michael@0 386 }
michael@0 387
michael@0 388 // To make sure we always hit the same "side" of the signal:
michael@0 389 // round the delay up (by adding 1, since we later floor) and add a little (10% by default).
michael@0 390 // Note that newTarget doesn't change (and is the next vblank) as a reference when we're back.
michael@0 391 static const double kDefaultPhaseShiftPercent = 10;
michael@0 392 static const double phaseShiftFactor = 0.01 *
michael@0 393 (Preferences::GetInt("layout.frame_rate.vsync.phasePercentage", kDefaultPhaseShiftPercent) % 100);
michael@0 394
michael@0 395 double phaseDelay = 1.0 + vblankInterval.ToMilliseconds() * phaseShiftFactor;
michael@0 396
michael@0 397 // ms until the next time we should tick
michael@0 398 double delayMs = (newTarget - aNowTime).ToMilliseconds() + phaseDelay;
michael@0 399
michael@0 400 // Make sure the delay is never negative.
michael@0 401 uint32_t delay = static_cast<uint32_t>(delayMs < 0 ? 0 : delayMs);
michael@0 402
michael@0 403 // log info & lateness
michael@0 404 LOG("[%p] precise dwm-vsync timer last tick late by %f ms, next tick in %d ms",
michael@0 405 this,
michael@0 406 (aNowTime - mTargetTime).ToMilliseconds(),
michael@0 407 delay);
michael@0 408
michael@0 409 // then schedule the timer
michael@0 410 LOG("[%p] scheduling callback for %d ms (2)", this, delay);
michael@0 411 mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
michael@0 412
michael@0 413 mTargetTime = newTarget;
michael@0 414 }
michael@0 415 };
michael@0 416 #endif
michael@0 417
michael@0 418 /*
michael@0 419 * A RefreshDriverTimer for inactive documents. When a new refresh driver is
michael@0 420 * added, the rate is reset to the base (normally 1s/1fps). Every time
michael@0 421 * it ticks, a single refresh driver is poked. Once they have all been poked,
michael@0 422 * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point,
michael@0 423 * the timer is quiet and doesn't tick (until something is added to it again).
michael@0 424 *
michael@0 425 * When a timer is removed, there is a possibility of another timer
michael@0 426 * being skipped for one cycle. We could avoid this by adjusting
michael@0 427 * mNextDriverIndex in RemoveRefreshDriver, but there's little need to
michael@0 428 * add that complexity. All we want is for inactive drivers to tick
michael@0 429 * at some point, but we don't care too much about how often.
michael@0 430 */
michael@0 431 class InactiveRefreshDriverTimer :
michael@0 432 public RefreshDriverTimer
michael@0 433 {
michael@0 434 public:
michael@0 435 InactiveRefreshDriverTimer(double aRate)
michael@0 436 : RefreshDriverTimer(aRate),
michael@0 437 mNextTickDuration(aRate),
michael@0 438 mDisableAfterMilliseconds(-1.0),
michael@0 439 mNextDriverIndex(0)
michael@0 440 {
michael@0 441 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
michael@0 442 }
michael@0 443
michael@0 444 InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
michael@0 445 : RefreshDriverTimer(aRate),
michael@0 446 mNextTickDuration(aRate),
michael@0 447 mDisableAfterMilliseconds(aDisableAfterMilliseconds),
michael@0 448 mNextDriverIndex(0)
michael@0 449 {
michael@0 450 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
michael@0 451 }
michael@0 452
michael@0 453 virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
michael@0 454 {
michael@0 455 RefreshDriverTimer::AddRefreshDriver(aDriver);
michael@0 456
michael@0 457 LOG("[%p] inactive timer got new refresh driver %p, resetting rate",
michael@0 458 this, aDriver);
michael@0 459
michael@0 460 // reset the timer, and start with the newly added one next time.
michael@0 461 mNextTickDuration = mRateMilliseconds;
michael@0 462
michael@0 463 // we don't really have to start with the newly added one, but we may as well
michael@0 464 // not tick the old ones at the fastest rate any more than we need to.
michael@0 465 mNextDriverIndex = mRefreshDrivers.Length() - 1;
michael@0 466
michael@0 467 StopTimer();
michael@0 468 StartTimer();
michael@0 469 }
michael@0 470
michael@0 471 protected:
michael@0 472 virtual void StartTimer()
michael@0 473 {
michael@0 474 mLastFireEpoch = JS_Now();
michael@0 475 mLastFireTime = TimeStamp::Now();
michael@0 476
michael@0 477 mTargetTime = mLastFireTime + mRateDuration;
michael@0 478
michael@0 479 uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
michael@0 480 mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
michael@0 481 }
michael@0 482
michael@0 483 virtual void StopTimer()
michael@0 484 {
michael@0 485 mTimer->Cancel();
michael@0 486 }
michael@0 487
michael@0 488 virtual void ScheduleNextTick(TimeStamp aNowTime)
michael@0 489 {
michael@0 490 if (mDisableAfterMilliseconds > 0.0 &&
michael@0 491 mNextTickDuration > mDisableAfterMilliseconds)
michael@0 492 {
michael@0 493 // We hit the time after which we should disable
michael@0 494 // inactive window refreshes; don't schedule anything
michael@0 495 // until we get kicked by an AddRefreshDriver call.
michael@0 496 return;
michael@0 497 }
michael@0 498
michael@0 499 // double the next tick time if we've already gone through all of them once
michael@0 500 if (mNextDriverIndex >= mRefreshDrivers.Length()) {
michael@0 501 mNextTickDuration *= 2.0;
michael@0 502 mNextDriverIndex = 0;
michael@0 503 }
michael@0 504
michael@0 505 // this doesn't need to be precise; do a simple schedule
michael@0 506 uint32_t delay = static_cast<uint32_t>(mNextTickDuration);
michael@0 507 mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
michael@0 508
michael@0 509 LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration,
michael@0 510 mNextDriverIndex, mRefreshDrivers.Length());
michael@0 511 }
michael@0 512
michael@0 513 /* Runs just one driver's tick. */
michael@0 514 void TickOne()
michael@0 515 {
michael@0 516 int64_t jsnow = JS_Now();
michael@0 517 TimeStamp now = TimeStamp::Now();
michael@0 518
michael@0 519 ScheduleNextTick(now);
michael@0 520
michael@0 521 mLastFireEpoch = jsnow;
michael@0 522 mLastFireTime = now;
michael@0 523
michael@0 524 nsTArray<nsRefPtr<nsRefreshDriver> > drivers(mRefreshDrivers);
michael@0 525 if (mNextDriverIndex < drivers.Length() &&
michael@0 526 !drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled())
michael@0 527 {
michael@0 528 TickDriver(drivers[mNextDriverIndex], jsnow, now);
michael@0 529 }
michael@0 530
michael@0 531 mNextDriverIndex++;
michael@0 532 }
michael@0 533
michael@0 534 static void TimerTickOne(nsITimer* aTimer, void* aClosure)
michael@0 535 {
michael@0 536 InactiveRefreshDriverTimer *timer = static_cast<InactiveRefreshDriverTimer*>(aClosure);
michael@0 537 timer->TickOne();
michael@0 538 }
michael@0 539
michael@0 540 nsRefPtr<nsITimer> mTimer;
michael@0 541 double mNextTickDuration;
michael@0 542 double mDisableAfterMilliseconds;
michael@0 543 uint32_t mNextDriverIndex;
michael@0 544 };
michael@0 545
michael@0 546 } // namespace mozilla
michael@0 547
michael@0 548 static uint32_t
michael@0 549 GetFirstFrameDelay(imgIRequest* req)
michael@0 550 {
michael@0 551 nsCOMPtr<imgIContainer> container;
michael@0 552 if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) {
michael@0 553 return 0;
michael@0 554 }
michael@0 555
michael@0 556 // If this image isn't animated, there isn't a first frame delay.
michael@0 557 int32_t delay = container->GetFirstFrameDelay();
michael@0 558 if (delay < 0)
michael@0 559 return 0;
michael@0 560
michael@0 561 return static_cast<uint32_t>(delay);
michael@0 562 }
michael@0 563
michael@0 564 static PreciseRefreshDriverTimer *sRegularRateTimer = nullptr;
michael@0 565 static InactiveRefreshDriverTimer *sThrottledRateTimer = nullptr;
michael@0 566
michael@0 567 #ifdef XP_WIN
michael@0 568 static int32_t sHighPrecisionTimerRequests = 0;
michael@0 569 // a bare pointer to avoid introducing a static constructor
michael@0 570 static nsITimer *sDisableHighPrecisionTimersTimer = nullptr;
michael@0 571 #endif
michael@0 572
michael@0 573 /* static */ void
michael@0 574 nsRefreshDriver::InitializeStatics()
michael@0 575 {
michael@0 576 #ifdef PR_LOGGING
michael@0 577 if (!gLog) {
michael@0 578 gLog = PR_NewLogModule("nsRefreshDriver");
michael@0 579 }
michael@0 580 #endif
michael@0 581 }
michael@0 582
michael@0 583 /* static */ void
michael@0 584 nsRefreshDriver::Shutdown()
michael@0 585 {
michael@0 586 // clean up our timers
michael@0 587 delete sRegularRateTimer;
michael@0 588 delete sThrottledRateTimer;
michael@0 589
michael@0 590 sRegularRateTimer = nullptr;
michael@0 591 sThrottledRateTimer = nullptr;
michael@0 592
michael@0 593 #ifdef XP_WIN
michael@0 594 if (sDisableHighPrecisionTimersTimer) {
michael@0 595 sDisableHighPrecisionTimersTimer->Cancel();
michael@0 596 NS_RELEASE(sDisableHighPrecisionTimersTimer);
michael@0 597 timeEndPeriod(1);
michael@0 598 } else if (sHighPrecisionTimerRequests) {
michael@0 599 timeEndPeriod(1);
michael@0 600 }
michael@0 601 #endif
michael@0 602 }
michael@0 603
michael@0 604 /* static */ int32_t
michael@0 605 nsRefreshDriver::DefaultInterval()
michael@0 606 {
michael@0 607 return NSToIntRound(1000.0 / DEFAULT_FRAME_RATE);
michael@0 608 }
michael@0 609
michael@0 610 // Compute the interval to use for the refresh driver timer, in milliseconds.
michael@0 611 // outIsDefault indicates that rate was not explicitly set by the user
michael@0 612 // so we might choose other, more appropriate rates (e.g. vsync, etc)
michael@0 613 // layout.frame_rate=0 indicates "ASAP mode".
michael@0 614 // In ASAP mode rendering is iterated as fast as possible (typically for stress testing).
michael@0 615 // A target rate of 10k is used internally instead of special-handling 0.
michael@0 616 // Backends which block on swap/present/etc should try to not block
michael@0 617 // when layout.frame_rate=0 - to comply with "ASAP" as much as possible.
michael@0 618 double
michael@0 619 nsRefreshDriver::GetRegularTimerInterval(bool *outIsDefault) const
michael@0 620 {
michael@0 621 int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
michael@0 622 if (rate < 0) {
michael@0 623 rate = DEFAULT_FRAME_RATE;
michael@0 624 if (outIsDefault) {
michael@0 625 *outIsDefault = true;
michael@0 626 }
michael@0 627 } else {
michael@0 628 if (outIsDefault) {
michael@0 629 *outIsDefault = false;
michael@0 630 }
michael@0 631 }
michael@0 632
michael@0 633 if (rate == 0) {
michael@0 634 rate = 10000;
michael@0 635 }
michael@0 636
michael@0 637 return 1000.0 / rate;
michael@0 638 }
michael@0 639
michael@0 640 double
michael@0 641 nsRefreshDriver::GetThrottledTimerInterval() const
michael@0 642 {
michael@0 643 int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1);
michael@0 644 if (rate <= 0) {
michael@0 645 rate = DEFAULT_THROTTLED_FRAME_RATE;
michael@0 646 }
michael@0 647 return 1000.0 / rate;
michael@0 648 }
michael@0 649
michael@0 650 double
michael@0 651 nsRefreshDriver::GetRefreshTimerInterval() const
michael@0 652 {
michael@0 653 return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval();
michael@0 654 }
michael@0 655
michael@0 656 RefreshDriverTimer*
michael@0 657 nsRefreshDriver::ChooseTimer() const
michael@0 658 {
michael@0 659 if (mThrottled) {
michael@0 660 if (!sThrottledRateTimer)
michael@0 661 sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(),
michael@0 662 DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0);
michael@0 663 return sThrottledRateTimer;
michael@0 664 }
michael@0 665
michael@0 666 if (!sRegularRateTimer) {
michael@0 667 bool isDefault = true;
michael@0 668 double rate = GetRegularTimerInterval(&isDefault);
michael@0 669 #ifdef XP_WIN
michael@0 670 if (PreciseRefreshDriverTimerWindowsDwmVsync::IsSupported()) {
michael@0 671 sRegularRateTimer = new PreciseRefreshDriverTimerWindowsDwmVsync(rate, isDefault);
michael@0 672 }
michael@0 673 #endif
michael@0 674 if (!sRegularRateTimer) {
michael@0 675 sRegularRateTimer = new PreciseRefreshDriverTimer(rate);
michael@0 676 }
michael@0 677 }
michael@0 678 return sRegularRateTimer;
michael@0 679 }
michael@0 680
michael@0 681 nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
michael@0 682 : mActiveTimer(nullptr),
michael@0 683 mPresContext(aPresContext),
michael@0 684 mFreezeCount(0),
michael@0 685 mThrottled(false),
michael@0 686 mTestControllingRefreshes(false),
michael@0 687 mViewManagerFlushIsPending(false),
michael@0 688 mRequestedHighPrecision(false),
michael@0 689 mInRefresh(false)
michael@0 690 {
michael@0 691 mMostRecentRefreshEpochTime = JS_Now();
michael@0 692 mMostRecentRefresh = TimeStamp::Now();
michael@0 693 }
michael@0 694
michael@0 695 nsRefreshDriver::~nsRefreshDriver()
michael@0 696 {
michael@0 697 NS_ABORT_IF_FALSE(ObserverCount() == 0,
michael@0 698 "observers should have unregistered");
michael@0 699 NS_ABORT_IF_FALSE(!mActiveTimer, "timer should be gone");
michael@0 700
michael@0 701 for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) {
michael@0 702 mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden();
michael@0 703 }
michael@0 704 mPresShellsToInvalidateIfHidden.Clear();
michael@0 705 }
michael@0 706
michael@0 707 // Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh
michael@0 708 // for description.
michael@0 709 void
michael@0 710 nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds)
michael@0 711 {
michael@0 712 // ensure that we're removed from our driver
michael@0 713 StopTimer();
michael@0 714
michael@0 715 if (!mTestControllingRefreshes) {
michael@0 716 mMostRecentRefreshEpochTime = JS_Now();
michael@0 717 mMostRecentRefresh = TimeStamp::Now();
michael@0 718
michael@0 719 mTestControllingRefreshes = true;
michael@0 720 }
michael@0 721
michael@0 722 mMostRecentRefreshEpochTime += aMilliseconds * 1000;
michael@0 723 mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds);
michael@0 724
michael@0 725 mozilla::dom::AutoNoJSAPI nojsapi;
michael@0 726 DoTick();
michael@0 727 }
michael@0 728
michael@0 729 void
michael@0 730 nsRefreshDriver::RestoreNormalRefresh()
michael@0 731 {
michael@0 732 mTestControllingRefreshes = false;
michael@0 733 EnsureTimerStarted(false);
michael@0 734 }
michael@0 735
michael@0 736 TimeStamp
michael@0 737 nsRefreshDriver::MostRecentRefresh() const
michael@0 738 {
michael@0 739 const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false);
michael@0 740
michael@0 741 return mMostRecentRefresh;
michael@0 742 }
michael@0 743
michael@0 744 int64_t
michael@0 745 nsRefreshDriver::MostRecentRefreshEpochTime() const
michael@0 746 {
michael@0 747 const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false);
michael@0 748
michael@0 749 return mMostRecentRefreshEpochTime;
michael@0 750 }
michael@0 751
michael@0 752 bool
michael@0 753 nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
michael@0 754 mozFlushType aFlushType)
michael@0 755 {
michael@0 756 ObserverArray& array = ArrayFor(aFlushType);
michael@0 757 bool success = array.AppendElement(aObserver) != nullptr;
michael@0 758 EnsureTimerStarted(false);
michael@0 759 return success;
michael@0 760 }
michael@0 761
michael@0 762 bool
michael@0 763 nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
michael@0 764 mozFlushType aFlushType)
michael@0 765 {
michael@0 766 ObserverArray& array = ArrayFor(aFlushType);
michael@0 767 return array.RemoveElement(aObserver);
michael@0 768 }
michael@0 769
michael@0 770 void
michael@0 771 nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
michael@0 772 {
michael@0 773 mPostRefreshObservers.AppendElement(aObserver);
michael@0 774 }
michael@0 775
michael@0 776 void
michael@0 777 nsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver)
michael@0 778 {
michael@0 779 mPostRefreshObservers.RemoveElement(aObserver);
michael@0 780 }
michael@0 781
michael@0 782 bool
michael@0 783 nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
michael@0 784 {
michael@0 785 uint32_t delay = GetFirstFrameDelay(aRequest);
michael@0 786 if (delay == 0) {
michael@0 787 if (!mRequests.PutEntry(aRequest)) {
michael@0 788 return false;
michael@0 789 }
michael@0 790 } else {
michael@0 791 ImageStartData* start = mStartTable.Get(delay);
michael@0 792 if (!start) {
michael@0 793 start = new ImageStartData();
michael@0 794 mStartTable.Put(delay, start);
michael@0 795 }
michael@0 796 start->mEntries.PutEntry(aRequest);
michael@0 797 }
michael@0 798
michael@0 799 EnsureTimerStarted(false);
michael@0 800
michael@0 801 return true;
michael@0 802 }
michael@0 803
michael@0 804 void
michael@0 805 nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
michael@0 806 {
michael@0 807 // Try to remove from both places, just in case, because we can't tell
michael@0 808 // whether RemoveEntry() succeeds.
michael@0 809 mRequests.RemoveEntry(aRequest);
michael@0 810 uint32_t delay = GetFirstFrameDelay(aRequest);
michael@0 811 if (delay != 0) {
michael@0 812 ImageStartData* start = mStartTable.Get(delay);
michael@0 813 if (start) {
michael@0 814 start->mEntries.RemoveEntry(aRequest);
michael@0 815 }
michael@0 816 }
michael@0 817 }
michael@0 818
michael@0 819 void
michael@0 820 nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer)
michael@0 821 {
michael@0 822 if (mTestControllingRefreshes)
michael@0 823 return;
michael@0 824
michael@0 825 // will it already fire, and no other changes needed?
michael@0 826 if (mActiveTimer && !aAdjustingTimer)
michael@0 827 return;
michael@0 828
michael@0 829 if (IsFrozen() || !mPresContext) {
michael@0 830 // If we don't want to start it now, or we've been disconnected.
michael@0 831 StopTimer();
michael@0 832 return;
michael@0 833 }
michael@0 834
michael@0 835 // We got here because we're either adjusting the time *or* we're
michael@0 836 // starting it for the first time. Add to the right timer,
michael@0 837 // prehaps removing it from a previously-set one.
michael@0 838 RefreshDriverTimer *newTimer = ChooseTimer();
michael@0 839 if (newTimer != mActiveTimer) {
michael@0 840 if (mActiveTimer)
michael@0 841 mActiveTimer->RemoveRefreshDriver(this);
michael@0 842 mActiveTimer = newTimer;
michael@0 843 mActiveTimer->AddRefreshDriver(this);
michael@0 844 }
michael@0 845
michael@0 846 mMostRecentRefresh = mActiveTimer->MostRecentRefresh();
michael@0 847 mMostRecentRefreshEpochTime = mActiveTimer->MostRecentRefreshEpochTime();
michael@0 848 }
michael@0 849
michael@0 850 void
michael@0 851 nsRefreshDriver::StopTimer()
michael@0 852 {
michael@0 853 if (!mActiveTimer)
michael@0 854 return;
michael@0 855
michael@0 856 mActiveTimer->RemoveRefreshDriver(this);
michael@0 857 mActiveTimer = nullptr;
michael@0 858
michael@0 859 if (mRequestedHighPrecision) {
michael@0 860 SetHighPrecisionTimersEnabled(false);
michael@0 861 }
michael@0 862 }
michael@0 863
michael@0 864 #ifdef XP_WIN
michael@0 865 static void
michael@0 866 DisableHighPrecisionTimersCallback(nsITimer *aTimer, void *aClosure)
michael@0 867 {
michael@0 868 timeEndPeriod(1);
michael@0 869 NS_RELEASE(sDisableHighPrecisionTimersTimer);
michael@0 870 }
michael@0 871 #endif
michael@0 872
michael@0 873 void
michael@0 874 nsRefreshDriver::ConfigureHighPrecision()
michael@0 875 {
michael@0 876 bool haveFrameRequestCallbacks = mFrameRequestCallbackDocs.Length() > 0;
michael@0 877
michael@0 878 // if the only change that's needed is that we need high precision,
michael@0 879 // then just set that
michael@0 880 if (!mThrottled && !mRequestedHighPrecision && haveFrameRequestCallbacks) {
michael@0 881 SetHighPrecisionTimersEnabled(true);
michael@0 882 } else if (mRequestedHighPrecision && !haveFrameRequestCallbacks) {
michael@0 883 SetHighPrecisionTimersEnabled(false);
michael@0 884 }
michael@0 885 }
michael@0 886
michael@0 887 void
michael@0 888 nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable)
michael@0 889 {
michael@0 890 LOG("[%p] SetHighPrecisionTimersEnabled (%s)", this, aEnable ? "true" : "false");
michael@0 891
michael@0 892 if (aEnable) {
michael@0 893 NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!");
michael@0 894 #ifdef XP_WIN
michael@0 895 if (++sHighPrecisionTimerRequests == 1) {
michael@0 896 // If we had a timer scheduled to disable it, that means that it's already
michael@0 897 // enabled; just cancel the timer. Otherwise, really enable it.
michael@0 898 if (sDisableHighPrecisionTimersTimer) {
michael@0 899 sDisableHighPrecisionTimersTimer->Cancel();
michael@0 900 NS_RELEASE(sDisableHighPrecisionTimersTimer);
michael@0 901 } else {
michael@0 902 timeBeginPeriod(1);
michael@0 903 }
michael@0 904 }
michael@0 905 #endif
michael@0 906 mRequestedHighPrecision = true;
michael@0 907 } else {
michael@0 908 NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!");
michael@0 909 #ifdef XP_WIN
michael@0 910 if (--sHighPrecisionTimerRequests == 0) {
michael@0 911 // Don't jerk us around between high precision and low precision
michael@0 912 // timers; instead, only allow leaving high precision timers
michael@0 913 // after 90 seconds. This is arbitrary, but hopefully good
michael@0 914 // enough.
michael@0 915 NS_ASSERTION(!sDisableHighPrecisionTimersTimer, "We shouldn't have an outstanding disable-high-precision timer !");
michael@0 916
michael@0 917 nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
michael@0 918 if (timer) {
michael@0 919 timer.forget(&sDisableHighPrecisionTimersTimer);
michael@0 920 sDisableHighPrecisionTimersTimer->InitWithFuncCallback(DisableHighPrecisionTimersCallback,
michael@0 921 nullptr,
michael@0 922 90 * 1000,
michael@0 923 nsITimer::TYPE_ONE_SHOT);
michael@0 924 } else {
michael@0 925 // might happen if we're shutting down XPCOM; just drop the time period down
michael@0 926 // immediately
michael@0 927 timeEndPeriod(1);
michael@0 928 }
michael@0 929 }
michael@0 930 #endif
michael@0 931 mRequestedHighPrecision = false;
michael@0 932 }
michael@0 933 }
michael@0 934
michael@0 935 uint32_t
michael@0 936 nsRefreshDriver::ObserverCount() const
michael@0 937 {
michael@0 938 uint32_t sum = 0;
michael@0 939 for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
michael@0 940 sum += mObservers[i].Length();
michael@0 941 }
michael@0 942
michael@0 943 // Even while throttled, we need to process layout and style changes. Style
michael@0 944 // changes can trigger transitions which fire events when they complete, and
michael@0 945 // layout changes can affect media queries on child documents, triggering
michael@0 946 // style changes, etc.
michael@0 947 sum += mStyleFlushObservers.Length();
michael@0 948 sum += mLayoutFlushObservers.Length();
michael@0 949 sum += mFrameRequestCallbackDocs.Length();
michael@0 950 sum += mViewManagerFlushIsPending;
michael@0 951 return sum;
michael@0 952 }
michael@0 953
michael@0 954 /* static */ PLDHashOperator
michael@0 955 nsRefreshDriver::StartTableRequestCounter(const uint32_t& aKey,
michael@0 956 ImageStartData* aEntry,
michael@0 957 void* aUserArg)
michael@0 958 {
michael@0 959 uint32_t *count = static_cast<uint32_t*>(aUserArg);
michael@0 960 *count += aEntry->mEntries.Count();
michael@0 961
michael@0 962 return PL_DHASH_NEXT;
michael@0 963 }
michael@0 964
michael@0 965 uint32_t
michael@0 966 nsRefreshDriver::ImageRequestCount() const
michael@0 967 {
michael@0 968 uint32_t count = 0;
michael@0 969 mStartTable.EnumerateRead(nsRefreshDriver::StartTableRequestCounter, &count);
michael@0 970 return count + mRequests.Count();
michael@0 971 }
michael@0 972
michael@0 973 nsRefreshDriver::ObserverArray&
michael@0 974 nsRefreshDriver::ArrayFor(mozFlushType aFlushType)
michael@0 975 {
michael@0 976 switch (aFlushType) {
michael@0 977 case Flush_Style:
michael@0 978 return mObservers[0];
michael@0 979 case Flush_Layout:
michael@0 980 return mObservers[1];
michael@0 981 case Flush_Display:
michael@0 982 return mObservers[2];
michael@0 983 default:
michael@0 984 NS_ABORT_IF_FALSE(false, "bad flush type");
michael@0 985 return *static_cast<ObserverArray*>(nullptr);
michael@0 986 }
michael@0 987 }
michael@0 988
michael@0 989 /*
michael@0 990 * nsISupports implementation
michael@0 991 */
michael@0 992
michael@0 993 NS_IMPL_ISUPPORTS(nsRefreshDriver, nsISupports)
michael@0 994
michael@0 995 /*
michael@0 996 * nsITimerCallback implementation
michael@0 997 */
michael@0 998
michael@0 999 void
michael@0 1000 nsRefreshDriver::DoTick()
michael@0 1001 {
michael@0 1002 NS_PRECONDITION(!IsFrozen(), "Why are we notified while frozen?");
michael@0 1003 NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?");
michael@0 1004 NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
michael@0 1005 "Shouldn't have a JSContext on the stack");
michael@0 1006
michael@0 1007 if (mTestControllingRefreshes) {
michael@0 1008 Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh);
michael@0 1009 } else {
michael@0 1010 Tick(JS_Now(), TimeStamp::Now());
michael@0 1011 }
michael@0 1012 }
michael@0 1013
michael@0 1014 struct DocumentFrameCallbacks {
michael@0 1015 DocumentFrameCallbacks(nsIDocument* aDocument) :
michael@0 1016 mDocument(aDocument)
michael@0 1017 {}
michael@0 1018
michael@0 1019 nsCOMPtr<nsIDocument> mDocument;
michael@0 1020 nsIDocument::FrameRequestCallbackList mCallbacks;
michael@0 1021 };
michael@0 1022
michael@0 1023 void
michael@0 1024 nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
michael@0 1025 {
michael@0 1026 NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
michael@0 1027 "Shouldn't have a JSContext on the stack");
michael@0 1028
michael@0 1029 if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) {
michael@0 1030 NS_ERROR("Refresh driver should not run during plugin call!");
michael@0 1031 // Try to survive this by just ignoring the refresh tick.
michael@0 1032 return;
michael@0 1033 }
michael@0 1034
michael@0 1035 PROFILER_LABEL("nsRefreshDriver", "Tick");
michael@0 1036
michael@0 1037 // We're either frozen or we were disconnected (likely in the middle
michael@0 1038 // of a tick iteration). Just do nothing here, since our
michael@0 1039 // prescontext went away.
michael@0 1040 if (IsFrozen() || !mPresContext) {
michael@0 1041 return;
michael@0 1042 }
michael@0 1043
michael@0 1044 TimeStamp previousRefresh = mMostRecentRefresh;
michael@0 1045
michael@0 1046 mMostRecentRefresh = aNowTime;
michael@0 1047 mMostRecentRefreshEpochTime = aNowEpoch;
michael@0 1048
michael@0 1049 nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
michael@0 1050 if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
michael@0 1051 // Things are being destroyed, or we no longer have any observers.
michael@0 1052 // We don't want to stop the timer when observers are initially
michael@0 1053 // removed, because sometimes observers can be added and removed
michael@0 1054 // often depending on what other things are going on and in that
michael@0 1055 // situation we don't want to thrash our timer. So instead we
michael@0 1056 // wait until we get a Notify() call when we have no observers
michael@0 1057 // before stopping the timer.
michael@0 1058 StopTimer();
michael@0 1059 return;
michael@0 1060 }
michael@0 1061
michael@0 1062 profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
michael@0 1063
michael@0 1064 AutoRestore<bool> restoreInRefresh(mInRefresh);
michael@0 1065 mInRefresh = true;
michael@0 1066
michael@0 1067 /*
michael@0 1068 * The timer holds a reference to |this| while calling |Notify|.
michael@0 1069 * However, implementations of |WillRefresh| are permitted to destroy
michael@0 1070 * the pres context, which will cause our |mPresContext| to become
michael@0 1071 * null. If this happens, we must stop notifying observers.
michael@0 1072 */
michael@0 1073 for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
michael@0 1074 ObserverArray::EndLimitedIterator etor(mObservers[i]);
michael@0 1075 while (etor.HasMore()) {
michael@0 1076 nsRefPtr<nsARefreshObserver> obs = etor.GetNext();
michael@0 1077 obs->WillRefresh(aNowTime);
michael@0 1078
michael@0 1079 if (!mPresContext || !mPresContext->GetPresShell()) {
michael@0 1080 StopTimer();
michael@0 1081 profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
michael@0 1082 return;
michael@0 1083 }
michael@0 1084 }
michael@0 1085
michael@0 1086 if (i == 0) {
michael@0 1087 // Grab all of our frame request callbacks up front.
michael@0 1088 nsTArray<DocumentFrameCallbacks>
michael@0 1089 frameRequestCallbacks(mFrameRequestCallbackDocs.Length());
michael@0 1090 for (uint32_t i = 0; i < mFrameRequestCallbackDocs.Length(); ++i) {
michael@0 1091 frameRequestCallbacks.AppendElement(mFrameRequestCallbackDocs[i]);
michael@0 1092 mFrameRequestCallbackDocs[i]->
michael@0 1093 TakeFrameRequestCallbacks(frameRequestCallbacks.LastElement().mCallbacks);
michael@0 1094 }
michael@0 1095 // OK, now reset mFrameRequestCallbackDocs so they can be
michael@0 1096 // readded as needed.
michael@0 1097 mFrameRequestCallbackDocs.Clear();
michael@0 1098
michael@0 1099 profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_START);
michael@0 1100 int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC;
michael@0 1101 for (uint32_t i = 0; i < frameRequestCallbacks.Length(); ++i) {
michael@0 1102 const DocumentFrameCallbacks& docCallbacks = frameRequestCallbacks[i];
michael@0 1103 // XXXbz Bug 863140: GetInnerWindow can return the outer
michael@0 1104 // window in some cases.
michael@0 1105 nsPIDOMWindow* innerWindow = docCallbacks.mDocument->GetInnerWindow();
michael@0 1106 DOMHighResTimeStamp timeStamp = 0;
michael@0 1107 if (innerWindow && innerWindow->IsInnerWindow()) {
michael@0 1108 nsPerformance* perf = innerWindow->GetPerformance();
michael@0 1109 if (perf) {
michael@0 1110 timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime);
michael@0 1111 }
michael@0 1112 // else window is partially torn down already
michael@0 1113 }
michael@0 1114 for (uint32_t j = 0; j < docCallbacks.mCallbacks.Length(); ++j) {
michael@0 1115 const nsIDocument::FrameRequestCallbackHolder& holder =
michael@0 1116 docCallbacks.mCallbacks[j];
michael@0 1117 nsAutoMicroTask mt;
michael@0 1118 if (holder.HasWebIDLCallback()) {
michael@0 1119 ErrorResult ignored;
michael@0 1120 holder.GetWebIDLCallback()->Call(timeStamp, ignored);
michael@0 1121 } else {
michael@0 1122 holder.GetXPCOMCallback()->Sample(eventTime);
michael@0 1123 }
michael@0 1124 }
michael@0 1125 }
michael@0 1126 profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END);
michael@0 1127
michael@0 1128 // This is the Flush_Style case.
michael@0 1129 if (mPresContext && mPresContext->GetPresShell()) {
michael@0 1130 nsAutoTArray<nsIPresShell*, 16> observers;
michael@0 1131 observers.AppendElements(mStyleFlushObservers);
michael@0 1132 for (uint32_t j = observers.Length();
michael@0 1133 j && mPresContext && mPresContext->GetPresShell(); --j) {
michael@0 1134 // Make sure to not process observers which might have been removed
michael@0 1135 // during previous iterations.
michael@0 1136 nsIPresShell* shell = observers[j - 1];
michael@0 1137 if (!mStyleFlushObservers.Contains(shell))
michael@0 1138 continue;
michael@0 1139 NS_ADDREF(shell);
michael@0 1140 mStyleFlushObservers.RemoveElement(shell);
michael@0 1141 shell->GetPresContext()->RestyleManager()->mObservingRefreshDriver = false;
michael@0 1142 shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false));
michael@0 1143 NS_RELEASE(shell);
michael@0 1144 }
michael@0 1145 }
michael@0 1146 } else if (i == 1) {
michael@0 1147 // This is the Flush_Layout case.
michael@0 1148 if (mPresContext && mPresContext->GetPresShell()) {
michael@0 1149 nsAutoTArray<nsIPresShell*, 16> observers;
michael@0 1150 observers.AppendElements(mLayoutFlushObservers);
michael@0 1151 for (uint32_t j = observers.Length();
michael@0 1152 j && mPresContext && mPresContext->GetPresShell(); --j) {
michael@0 1153 // Make sure to not process observers which might have been removed
michael@0 1154 // during previous iterations.
michael@0 1155 nsIPresShell* shell = observers[j - 1];
michael@0 1156 if (!mLayoutFlushObservers.Contains(shell))
michael@0 1157 continue;
michael@0 1158 NS_ADDREF(shell);
michael@0 1159 mLayoutFlushObservers.RemoveElement(shell);
michael@0 1160 shell->mReflowScheduled = false;
michael@0 1161 shell->mSuppressInterruptibleReflows = false;
michael@0 1162 shell->FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout,
michael@0 1163 false));
michael@0 1164 NS_RELEASE(shell);
michael@0 1165 }
michael@0 1166 }
michael@0 1167 }
michael@0 1168 }
michael@0 1169
michael@0 1170 /*
michael@0 1171 * Perform notification to imgIRequests subscribed to listen
michael@0 1172 * for refresh events.
michael@0 1173 */
michael@0 1174
michael@0 1175 ImageRequestParameters parms = {aNowTime, previousRefresh, &mRequests};
michael@0 1176
michael@0 1177 mStartTable.EnumerateRead(nsRefreshDriver::StartTableRefresh, &parms);
michael@0 1178
michael@0 1179 if (mRequests.Count()) {
michael@0 1180 // RequestRefresh may run scripts, so it's not safe to directly call it
michael@0 1181 // while using a hashtable enumerator to enumerate mRequests in case
michael@0 1182 // script modifies the hashtable. Instead, we build a (local) array of
michael@0 1183 // images to refresh, and then we refresh each image in that array.
michael@0 1184 nsCOMArray<imgIContainer> imagesToRefresh(mRequests.Count());
michael@0 1185 mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator,
michael@0 1186 &imagesToRefresh);
michael@0 1187
michael@0 1188 for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) {
michael@0 1189 imagesToRefresh[i]->RequestRefresh(aNowTime);
michael@0 1190 }
michael@0 1191 }
michael@0 1192
michael@0 1193 for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) {
michael@0 1194 mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden();
michael@0 1195 }
michael@0 1196 mPresShellsToInvalidateIfHidden.Clear();
michael@0 1197
michael@0 1198 if (mViewManagerFlushIsPending) {
michael@0 1199 #ifdef MOZ_DUMP_PAINTING
michael@0 1200 if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
michael@0 1201 printf_stderr("Starting ProcessPendingUpdates\n");
michael@0 1202 }
michael@0 1203 #endif
michael@0 1204
michael@0 1205 mViewManagerFlushIsPending = false;
michael@0 1206 nsRefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
michael@0 1207 vm->ProcessPendingUpdates();
michael@0 1208 #ifdef MOZ_DUMP_PAINTING
michael@0 1209 if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
michael@0 1210 printf_stderr("Ending ProcessPendingUpdates\n");
michael@0 1211 }
michael@0 1212 #endif
michael@0 1213 }
michael@0 1214
michael@0 1215 for (uint32_t i = 0; i < mPostRefreshObservers.Length(); ++i) {
michael@0 1216 mPostRefreshObservers[i]->DidRefresh();
michael@0 1217 }
michael@0 1218 profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
michael@0 1219
michael@0 1220 NS_ASSERTION(mInRefresh, "Still in refresh");
michael@0 1221 }
michael@0 1222
michael@0 1223 /* static */ PLDHashOperator
michael@0 1224 nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
michael@0 1225 void* aUserArg)
michael@0 1226 {
michael@0 1227 nsCOMArray<imgIContainer>* imagesToRefresh =
michael@0 1228 static_cast<nsCOMArray<imgIContainer>*> (aUserArg);
michael@0 1229 imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey());
michael@0 1230 NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request");
michael@0 1231 nsCOMPtr<imgIContainer> image;
michael@0 1232 if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
michael@0 1233 imagesToRefresh->AppendElement(image);
michael@0 1234 }
michael@0 1235
michael@0 1236 return PL_DHASH_NEXT;
michael@0 1237 }
michael@0 1238
michael@0 1239 /* static */ PLDHashOperator
michael@0 1240 nsRefreshDriver::BeginRefreshingImages(nsISupportsHashKey* aEntry,
michael@0 1241 void* aUserArg)
michael@0 1242 {
michael@0 1243 ImageRequestParameters* parms =
michael@0 1244 static_cast<ImageRequestParameters*> (aUserArg);
michael@0 1245
michael@0 1246 imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey());
michael@0 1247 NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request");
michael@0 1248
michael@0 1249 parms->mRequests->PutEntry(req);
michael@0 1250
michael@0 1251 nsCOMPtr<imgIContainer> image;
michael@0 1252 if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
michael@0 1253 image->SetAnimationStartTime(parms->mDesired);
michael@0 1254 }
michael@0 1255
michael@0 1256 return PL_DHASH_REMOVE;
michael@0 1257 }
michael@0 1258
michael@0 1259 /* static */ PLDHashOperator
michael@0 1260 nsRefreshDriver::StartTableRefresh(const uint32_t& aDelay,
michael@0 1261 ImageStartData* aData,
michael@0 1262 void* aUserArg)
michael@0 1263 {
michael@0 1264 ImageRequestParameters* parms =
michael@0 1265 static_cast<ImageRequestParameters*> (aUserArg);
michael@0 1266
michael@0 1267 if (!aData->mStartTime.empty()) {
michael@0 1268 TimeStamp& start = aData->mStartTime.ref();
michael@0 1269 TimeDuration prev = parms->mPrevious - start;
michael@0 1270 TimeDuration curr = parms->mCurrent - start;
michael@0 1271 uint32_t prevMultiple = static_cast<uint32_t>(prev.ToMilliseconds()) / aDelay;
michael@0 1272
michael@0 1273 // We want to trigger images' refresh if we've just crossed over a multiple
michael@0 1274 // of the first image's start time. If so, set the animation start time to
michael@0 1275 // the nearest multiple of the delay and move all the images in this table
michael@0 1276 // to the main requests table.
michael@0 1277 if (prevMultiple != static_cast<uint32_t>(curr.ToMilliseconds()) / aDelay) {
michael@0 1278 parms->mDesired = start + TimeDuration::FromMilliseconds(prevMultiple * aDelay);
michael@0 1279 aData->mEntries.EnumerateEntries(nsRefreshDriver::BeginRefreshingImages, parms);
michael@0 1280 }
michael@0 1281 } else {
michael@0 1282 // This is the very first time we've drawn images with this time delay.
michael@0 1283 // Set the animation start time to "now" and move all the images in this
michael@0 1284 // table to the main requests table.
michael@0 1285 parms->mDesired = parms->mCurrent;
michael@0 1286 aData->mEntries.EnumerateEntries(nsRefreshDriver::BeginRefreshingImages, parms);
michael@0 1287 aData->mStartTime.construct(parms->mCurrent);
michael@0 1288 }
michael@0 1289
michael@0 1290 return PL_DHASH_NEXT;
michael@0 1291 }
michael@0 1292
michael@0 1293 void
michael@0 1294 nsRefreshDriver::Freeze()
michael@0 1295 {
michael@0 1296 StopTimer();
michael@0 1297 mFreezeCount++;
michael@0 1298 }
michael@0 1299
michael@0 1300 void
michael@0 1301 nsRefreshDriver::Thaw()
michael@0 1302 {
michael@0 1303 NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
michael@0 1304
michael@0 1305 if (mFreezeCount > 0) {
michael@0 1306 mFreezeCount--;
michael@0 1307 }
michael@0 1308
michael@0 1309 if (mFreezeCount == 0) {
michael@0 1310 if (ObserverCount() || ImageRequestCount()) {
michael@0 1311 // FIXME: This isn't quite right, since our EnsureTimerStarted call
michael@0 1312 // updates our mMostRecentRefresh, but the DoRefresh call won't run
michael@0 1313 // and notify our observers until we get back to the event loop.
michael@0 1314 // Thus MostRecentRefresh() will lie between now and the DoRefresh.
michael@0 1315 NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
michael@0 1316 EnsureTimerStarted(false);
michael@0 1317 }
michael@0 1318 }
michael@0 1319 }
michael@0 1320
michael@0 1321 void
michael@0 1322 nsRefreshDriver::SetThrottled(bool aThrottled)
michael@0 1323 {
michael@0 1324 if (aThrottled != mThrottled) {
michael@0 1325 mThrottled = aThrottled;
michael@0 1326 if (mActiveTimer) {
michael@0 1327 // We want to switch our timer type here, so just stop and
michael@0 1328 // restart the timer.
michael@0 1329 EnsureTimerStarted(true);
michael@0 1330 }
michael@0 1331 }
michael@0 1332 }
michael@0 1333
michael@0 1334 void
michael@0 1335 nsRefreshDriver::DoRefresh()
michael@0 1336 {
michael@0 1337 // Don't do a refresh unless we're in a state where we should be refreshing.
michael@0 1338 if (!IsFrozen() && mPresContext && mActiveTimer) {
michael@0 1339 DoTick();
michael@0 1340 }
michael@0 1341 }
michael@0 1342
michael@0 1343 #ifdef DEBUG
michael@0 1344 bool
michael@0 1345 nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
michael@0 1346 mozFlushType aFlushType)
michael@0 1347 {
michael@0 1348 ObserverArray& array = ArrayFor(aFlushType);
michael@0 1349 return array.Contains(aObserver);
michael@0 1350 }
michael@0 1351 #endif
michael@0 1352
michael@0 1353 void
michael@0 1354 nsRefreshDriver::ScheduleViewManagerFlush()
michael@0 1355 {
michael@0 1356 NS_ASSERTION(mPresContext->IsRoot(),
michael@0 1357 "Should only schedule view manager flush on root prescontexts");
michael@0 1358 mViewManagerFlushIsPending = true;
michael@0 1359 EnsureTimerStarted(false);
michael@0 1360 }
michael@0 1361
michael@0 1362 void
michael@0 1363 nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument)
michael@0 1364 {
michael@0 1365 NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
michael@0 1366 mFrameRequestCallbackDocs.NoIndex,
michael@0 1367 "Don't schedule the same document multiple times");
michael@0 1368 mFrameRequestCallbackDocs.AppendElement(aDocument);
michael@0 1369
michael@0 1370 // make sure that the timer is running
michael@0 1371 ConfigureHighPrecision();
michael@0 1372 EnsureTimerStarted(false);
michael@0 1373 }
michael@0 1374
michael@0 1375 void
michael@0 1376 nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
michael@0 1377 {
michael@0 1378 mFrameRequestCallbackDocs.RemoveElement(aDocument);
michael@0 1379 ConfigureHighPrecision();
michael@0 1380 // No need to worry about restarting our timer in slack mode if it's already
michael@0 1381 // running; that will happen automatically when it fires.
michael@0 1382 }
michael@0 1383
michael@0 1384 #undef LOG

mercurial