1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/base/nsRefreshDriver.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1384 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * Code to notify things that animate before a refresh, at an appropriate 1.12 + * refresh rate. (Perhaps temporary, until replaced by compositor.) 1.13 + * 1.14 + * Chrome and each tab have their own RefreshDriver, which in turn 1.15 + * hooks into one of a few global timer based on RefreshDriverTimer, 1.16 + * defined below. There are two main global timers -- one for active 1.17 + * animations, and one for inactive ones. These are implemented as 1.18 + * subclasses of RefreshDriverTimer; see below for a description of 1.19 + * their implementations. In the future, additional timer types may 1.20 + * implement things like blocking on vsync. 1.21 + */ 1.22 + 1.23 +#ifdef XP_WIN 1.24 +#include <windows.h> 1.25 +// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have 1.26 +// to manually include it 1.27 +#include <mmsystem.h> 1.28 +#include "WinUtils.h" 1.29 +#endif 1.30 + 1.31 +#include "mozilla/ArrayUtils.h" 1.32 +#include "mozilla/AutoRestore.h" 1.33 +#include "nsRefreshDriver.h" 1.34 +#include "nsITimer.h" 1.35 +#include "nsLayoutUtils.h" 1.36 +#include "nsPresContext.h" 1.37 +#include "nsComponentManagerUtils.h" 1.38 +#include "prlog.h" 1.39 +#include "nsAutoPtr.h" 1.40 +#include "nsIDocument.h" 1.41 +#include "jsapi.h" 1.42 +#include "nsContentUtils.h" 1.43 +#include "mozilla/Preferences.h" 1.44 +#include "nsViewManager.h" 1.45 +#include "GeckoProfiler.h" 1.46 +#include "nsNPAPIPluginInstance.h" 1.47 +#include "nsPerformance.h" 1.48 +#include "mozilla/dom/WindowBinding.h" 1.49 +#include "RestyleManager.h" 1.50 +#include "Layers.h" 1.51 +#include "imgIContainer.h" 1.52 +#include "nsIFrameRequestCallback.h" 1.53 +#include "mozilla/dom/ScriptSettings.h" 1.54 + 1.55 +using namespace mozilla; 1.56 +using namespace mozilla::widget; 1.57 + 1.58 +#ifdef PR_LOGGING 1.59 +static PRLogModuleInfo *gLog = nullptr; 1.60 +#define LOG(...) PR_LOG(gLog, PR_LOG_NOTICE, (__VA_ARGS__)) 1.61 +#else 1.62 +#define LOG(...) do { } while(0) 1.63 +#endif 1.64 + 1.65 +#define DEFAULT_FRAME_RATE 60 1.66 +#define DEFAULT_THROTTLED_FRAME_RATE 1 1.67 +// after 10 minutes, stop firing off inactive timers 1.68 +#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600 1.69 + 1.70 +namespace mozilla { 1.71 + 1.72 +/* 1.73 + * The base class for all global refresh driver timers. It takes care 1.74 + * of managing the list of refresh drivers attached to them and 1.75 + * provides interfaces for querying/setting the rate and actually 1.76 + * running a timer 'Tick'. Subclasses must implement StartTimer(), 1.77 + * StopTimer(), and ScheduleNextTick() -- the first two just 1.78 + * start/stop whatever timer mechanism is in use, and ScheduleNextTick 1.79 + * is called at the start of the Tick() implementation to set a time 1.80 + * for the next tick. 1.81 + */ 1.82 +class RefreshDriverTimer { 1.83 +public: 1.84 + /* 1.85 + * aRate -- the delay, in milliseconds, requested between timer firings 1.86 + */ 1.87 + RefreshDriverTimer(double aRate) 1.88 + { 1.89 + SetRate(aRate); 1.90 + } 1.91 + 1.92 + virtual ~RefreshDriverTimer() 1.93 + { 1.94 + NS_ASSERTION(mRefreshDrivers.Length() == 0, "Should have removed all refresh drivers from here by now!"); 1.95 + } 1.96 + 1.97 + virtual void AddRefreshDriver(nsRefreshDriver* aDriver) 1.98 + { 1.99 + LOG("[%p] AddRefreshDriver %p", this, aDriver); 1.100 + 1.101 + NS_ASSERTION(!mRefreshDrivers.Contains(aDriver), "AddRefreshDriver for a refresh driver that's already in the list!"); 1.102 + mRefreshDrivers.AppendElement(aDriver); 1.103 + 1.104 + if (mRefreshDrivers.Length() == 1) { 1.105 + StartTimer(); 1.106 + } 1.107 + } 1.108 + 1.109 + virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver) 1.110 + { 1.111 + LOG("[%p] RemoveRefreshDriver %p", this, aDriver); 1.112 + 1.113 + NS_ASSERTION(mRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the list!"); 1.114 + mRefreshDrivers.RemoveElement(aDriver); 1.115 + 1.116 + if (mRefreshDrivers.Length() == 0) { 1.117 + StopTimer(); 1.118 + } 1.119 + } 1.120 + 1.121 + double GetRate() const 1.122 + { 1.123 + return mRateMilliseconds; 1.124 + } 1.125 + 1.126 + // will take effect at next timer tick 1.127 + virtual void SetRate(double aNewRate) 1.128 + { 1.129 + mRateMilliseconds = aNewRate; 1.130 + mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds); 1.131 + } 1.132 + 1.133 + TimeStamp MostRecentRefresh() const { return mLastFireTime; } 1.134 + int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; } 1.135 + 1.136 +protected: 1.137 + virtual void StartTimer() = 0; 1.138 + virtual void StopTimer() = 0; 1.139 + virtual void ScheduleNextTick(TimeStamp aNowTime) = 0; 1.140 + 1.141 + /* 1.142 + * Actually runs a tick, poking all the attached RefreshDrivers. 1.143 + * Grabs the "now" time via JS_Now and TimeStamp::Now(). 1.144 + */ 1.145 + void Tick() 1.146 + { 1.147 + int64_t jsnow = JS_Now(); 1.148 + TimeStamp now = TimeStamp::Now(); 1.149 + 1.150 + ScheduleNextTick(now); 1.151 + 1.152 + mLastFireEpoch = jsnow; 1.153 + mLastFireTime = now; 1.154 + 1.155 + LOG("[%p] ticking drivers...", this); 1.156 + nsTArray<nsRefPtr<nsRefreshDriver> > drivers(mRefreshDrivers); 1.157 + for (size_t i = 0; i < drivers.Length(); ++i) { 1.158 + // don't poke this driver if it's in test mode 1.159 + if (drivers[i]->IsTestControllingRefreshesEnabled()) { 1.160 + continue; 1.161 + } 1.162 + 1.163 + TickDriver(drivers[i], jsnow, now); 1.164 + } 1.165 + LOG("[%p] done.", this); 1.166 + } 1.167 + 1.168 + static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now) 1.169 + { 1.170 + LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow); 1.171 + driver->Tick(jsnow, now); 1.172 + } 1.173 + 1.174 + double mRateMilliseconds; 1.175 + TimeDuration mRateDuration; 1.176 + 1.177 + int64_t mLastFireEpoch; 1.178 + TimeStamp mLastFireTime; 1.179 + TimeStamp mTargetTime; 1.180 + 1.181 + nsTArray<nsRefPtr<nsRefreshDriver> > mRefreshDrivers; 1.182 + 1.183 + // useful callback for nsITimer-based derived classes, here 1.184 + // bacause of c++ protected shenanigans 1.185 + static void TimerTick(nsITimer* aTimer, void* aClosure) 1.186 + { 1.187 + RefreshDriverTimer *timer = static_cast<RefreshDriverTimer*>(aClosure); 1.188 + timer->Tick(); 1.189 + } 1.190 +}; 1.191 + 1.192 +/* 1.193 + * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that 1.194 + * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to 1.195 + * implement ScheduleNextTick and intelligently calculate the next time to tick, 1.196 + * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain 1.197 + * with its attempt at intelligent slack removal and such, so we don't do it. 1.198 + */ 1.199 +class SimpleTimerBasedRefreshDriverTimer : 1.200 + public RefreshDriverTimer 1.201 +{ 1.202 +public: 1.203 + SimpleTimerBasedRefreshDriverTimer(double aRate) 1.204 + : RefreshDriverTimer(aRate) 1.205 + { 1.206 + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); 1.207 + } 1.208 + 1.209 + virtual ~SimpleTimerBasedRefreshDriverTimer() 1.210 + { 1.211 + StopTimer(); 1.212 + } 1.213 + 1.214 +protected: 1.215 + 1.216 + virtual void StartTimer() 1.217 + { 1.218 + // pretend we just fired, and we schedule the next tick normally 1.219 + mLastFireEpoch = JS_Now(); 1.220 + mLastFireTime = TimeStamp::Now(); 1.221 + 1.222 + mTargetTime = mLastFireTime + mRateDuration; 1.223 + 1.224 + uint32_t delay = static_cast<uint32_t>(mRateMilliseconds); 1.225 + mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); 1.226 + } 1.227 + 1.228 + virtual void StopTimer() 1.229 + { 1.230 + mTimer->Cancel(); 1.231 + } 1.232 + 1.233 + nsRefPtr<nsITimer> mTimer; 1.234 +}; 1.235 + 1.236 +/* 1.237 + * PreciseRefreshDriverTimer schedules ticks based on the current time 1.238 + * and when the next tick -should- be sent if we were hitting our 1.239 + * rate. It always schedules ticks on multiples of aRate -- meaning that 1.240 + * if some execution takes longer than an alloted slot, the next tick 1.241 + * will be delayed instead of triggering instantly. This might not be 1.242 + * desired -- there's an #if 0'd block below that we could put behind 1.243 + * a pref to control this behaviour. 1.244 + */ 1.245 +class PreciseRefreshDriverTimer : 1.246 + public SimpleTimerBasedRefreshDriverTimer 1.247 +{ 1.248 +public: 1.249 + PreciseRefreshDriverTimer(double aRate) 1.250 + : SimpleTimerBasedRefreshDriverTimer(aRate) 1.251 + { 1.252 + } 1.253 + 1.254 +protected: 1.255 + virtual void ScheduleNextTick(TimeStamp aNowTime) 1.256 + { 1.257 + // The number of (whole) elapsed intervals between the last target 1.258 + // time and the actual time. We want to truncate the double down 1.259 + // to an int number of intervals. 1.260 + int numElapsedIntervals = static_cast<int>((aNowTime - mTargetTime) / mRateDuration); 1.261 + 1.262 + if (numElapsedIntervals < 0) { 1.263 + // It's possible that numElapsedIntervals is negative (e.g. timer compensation 1.264 + // may result in (aNowTime - mTargetTime) < -1.0/mRateDuration, which will result in 1.265 + // negative numElapsedIntervals), so make sure we don't target the same timestamp. 1.266 + numElapsedIntervals = 0; 1.267 + } 1.268 + 1.269 + // the last "tick" that may or may not have been actually sent was 1.270 + // at this time. For example, if the rate is 15ms, the target 1.271 + // time is 200ms, and it's now 225ms, the last effective tick 1.272 + // would have been at 215ms. The next one should then be 1.273 + // scheduled for 5 ms from now. 1.274 + // 1.275 + // We then add another mRateDuration to find the next tick target. 1.276 + TimeStamp newTarget = mTargetTime + mRateDuration * (numElapsedIntervals + 1); 1.277 + 1.278 + // the amount of (integer) ms until the next time we should tick 1.279 + uint32_t delay = static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds()); 1.280 + 1.281 + // Without this block, we'll always schedule on interval ticks; 1.282 + // with it, we'll schedule immediately if we missed our tick target 1.283 + // last time. 1.284 +#if 0 1.285 + if (numElapsedIntervals > 0) { 1.286 + // we're late, so reset 1.287 + newTarget = aNowTime; 1.288 + delay = 0; 1.289 + } 1.290 +#endif 1.291 + 1.292 + // log info & lateness 1.293 + LOG("[%p] precise timer last tick late by %f ms, next tick in %d ms", 1.294 + this, 1.295 + (aNowTime - mTargetTime).ToMilliseconds(), 1.296 + delay); 1.297 + 1.298 + // then schedule the timer 1.299 + LOG("[%p] scheduling callback for %d ms (2)", this, delay); 1.300 + mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); 1.301 + 1.302 + mTargetTime = newTarget; 1.303 + } 1.304 +}; 1.305 + 1.306 +#ifdef XP_WIN 1.307 +/* 1.308 + * Uses vsync timing on windows with DWM. Falls back dynamically to fixed rate if required. 1.309 + */ 1.310 +class PreciseRefreshDriverTimerWindowsDwmVsync : 1.311 + public PreciseRefreshDriverTimer 1.312 +{ 1.313 +public: 1.314 + // Checks if the vsync API is accessible. 1.315 + static bool IsSupported() 1.316 + { 1.317 + return WinUtils::dwmGetCompositionTimingInfoPtr != nullptr; 1.318 + } 1.319 + 1.320 + PreciseRefreshDriverTimerWindowsDwmVsync(double aRate, bool aPreferHwTiming = false) 1.321 + : PreciseRefreshDriverTimer(aRate) 1.322 + , mPreferHwTiming(aPreferHwTiming) 1.323 + { 1.324 + } 1.325 + 1.326 +protected: 1.327 + // Indicates we should try to adjust to the HW's timing (get rate from the OS or use vsync) 1.328 + // This is typically true if the default refresh-rate value was not modified by the user. 1.329 + bool mPreferHwTiming; 1.330 + 1.331 + nsresult GetVBlankInfo(mozilla::TimeStamp &aLastVBlank, mozilla::TimeDuration &aInterval) 1.332 + { 1.333 + MOZ_ASSERT(WinUtils::dwmGetCompositionTimingInfoPtr, 1.334 + "DwmGetCompositionTimingInfoPtr is unavailable (windows vsync)"); 1.335 + 1.336 + DWM_TIMING_INFO timingInfo; 1.337 + timingInfo.cbSize = sizeof(DWM_TIMING_INFO); 1.338 + HRESULT hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &timingInfo); // For the desktop window instead of a specific one. 1.339 + 1.340 + if (FAILED(hr)) { 1.341 + // This happens first time this is called. 1.342 + return NS_ERROR_NOT_INITIALIZED; 1.343 + } 1.344 + 1.345 + LARGE_INTEGER time, freq; 1.346 + ::QueryPerformanceCounter(&time); 1.347 + ::QueryPerformanceFrequency(&freq); 1.348 + aLastVBlank = TimeStamp::Now(); 1.349 + double secondsPassed = double(time.QuadPart - timingInfo.qpcVBlank) / double(freq.QuadPart); 1.350 + 1.351 + aLastVBlank -= TimeDuration::FromSeconds(secondsPassed); 1.352 + aInterval = TimeDuration::FromSeconds(double(timingInfo.qpcRefreshPeriod) / double(freq.QuadPart)); 1.353 + 1.354 + return NS_OK; 1.355 + } 1.356 + 1.357 + virtual void ScheduleNextTick(TimeStamp aNowTime) 1.358 + { 1.359 + static const TimeDuration kMinSaneInterval = TimeDuration::FromMilliseconds(3); // 330Hz 1.360 + static const TimeDuration kMaxSaneInterval = TimeDuration::FromMilliseconds(44); // 23Hz 1.361 + static const TimeDuration kNegativeMaxSaneInterval = TimeDuration::FromMilliseconds(-44); // Saves conversions for abs interval 1.362 + TimeStamp lastVblank; 1.363 + TimeDuration vblankInterval; 1.364 + 1.365 + if (!mPreferHwTiming || 1.366 + NS_OK != GetVBlankInfo(lastVblank, vblankInterval) || 1.367 + vblankInterval > kMaxSaneInterval || 1.368 + vblankInterval < kMinSaneInterval || 1.369 + (aNowTime - lastVblank) > kMaxSaneInterval || 1.370 + (aNowTime - lastVblank) < kNegativeMaxSaneInterval) { 1.371 + // Use the default timing without vsync 1.372 + PreciseRefreshDriverTimer::ScheduleNextTick(aNowTime); 1.373 + return; 1.374 + } 1.375 + 1.376 + TimeStamp newTarget = lastVblank + vblankInterval; // Base target 1.377 + 1.378 + // However, timer callback might return early (or late, but that wouldn't bother us), and vblankInterval 1.379 + // appears to be slightly (~1%) different on each call (probably the OS measuring recent actual interval[s]) 1.380 + // and since we don't want to re-target the same vsync, we keep advancing in vblank intervals until we find the 1.381 + // next safe target (next vsync, but not within 10% interval of previous target). 1.382 + // This is typically 0 or 1 iteration: 1.383 + // If we're too early, next vsync would be the one we've already targeted (1 iteration). 1.384 + // If the timer returned late, no iteration will be required. 1.385 + 1.386 + const double kSameVsyncThreshold = 0.1; 1.387 + while (newTarget <= mTargetTime + vblankInterval.MultDouble(kSameVsyncThreshold)) { 1.388 + newTarget += vblankInterval; 1.389 + } 1.390 + 1.391 + // To make sure we always hit the same "side" of the signal: 1.392 + // round the delay up (by adding 1, since we later floor) and add a little (10% by default). 1.393 + // Note that newTarget doesn't change (and is the next vblank) as a reference when we're back. 1.394 + static const double kDefaultPhaseShiftPercent = 10; 1.395 + static const double phaseShiftFactor = 0.01 * 1.396 + (Preferences::GetInt("layout.frame_rate.vsync.phasePercentage", kDefaultPhaseShiftPercent) % 100); 1.397 + 1.398 + double phaseDelay = 1.0 + vblankInterval.ToMilliseconds() * phaseShiftFactor; 1.399 + 1.400 + // ms until the next time we should tick 1.401 + double delayMs = (newTarget - aNowTime).ToMilliseconds() + phaseDelay; 1.402 + 1.403 + // Make sure the delay is never negative. 1.404 + uint32_t delay = static_cast<uint32_t>(delayMs < 0 ? 0 : delayMs); 1.405 + 1.406 + // log info & lateness 1.407 + LOG("[%p] precise dwm-vsync timer last tick late by %f ms, next tick in %d ms", 1.408 + this, 1.409 + (aNowTime - mTargetTime).ToMilliseconds(), 1.410 + delay); 1.411 + 1.412 + // then schedule the timer 1.413 + LOG("[%p] scheduling callback for %d ms (2)", this, delay); 1.414 + mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); 1.415 + 1.416 + mTargetTime = newTarget; 1.417 + } 1.418 +}; 1.419 +#endif 1.420 + 1.421 +/* 1.422 + * A RefreshDriverTimer for inactive documents. When a new refresh driver is 1.423 + * added, the rate is reset to the base (normally 1s/1fps). Every time 1.424 + * it ticks, a single refresh driver is poked. Once they have all been poked, 1.425 + * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point, 1.426 + * the timer is quiet and doesn't tick (until something is added to it again). 1.427 + * 1.428 + * When a timer is removed, there is a possibility of another timer 1.429 + * being skipped for one cycle. We could avoid this by adjusting 1.430 + * mNextDriverIndex in RemoveRefreshDriver, but there's little need to 1.431 + * add that complexity. All we want is for inactive drivers to tick 1.432 + * at some point, but we don't care too much about how often. 1.433 + */ 1.434 +class InactiveRefreshDriverTimer : 1.435 + public RefreshDriverTimer 1.436 +{ 1.437 +public: 1.438 + InactiveRefreshDriverTimer(double aRate) 1.439 + : RefreshDriverTimer(aRate), 1.440 + mNextTickDuration(aRate), 1.441 + mDisableAfterMilliseconds(-1.0), 1.442 + mNextDriverIndex(0) 1.443 + { 1.444 + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); 1.445 + } 1.446 + 1.447 + InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds) 1.448 + : RefreshDriverTimer(aRate), 1.449 + mNextTickDuration(aRate), 1.450 + mDisableAfterMilliseconds(aDisableAfterMilliseconds), 1.451 + mNextDriverIndex(0) 1.452 + { 1.453 + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); 1.454 + } 1.455 + 1.456 + virtual void AddRefreshDriver(nsRefreshDriver* aDriver) 1.457 + { 1.458 + RefreshDriverTimer::AddRefreshDriver(aDriver); 1.459 + 1.460 + LOG("[%p] inactive timer got new refresh driver %p, resetting rate", 1.461 + this, aDriver); 1.462 + 1.463 + // reset the timer, and start with the newly added one next time. 1.464 + mNextTickDuration = mRateMilliseconds; 1.465 + 1.466 + // we don't really have to start with the newly added one, but we may as well 1.467 + // not tick the old ones at the fastest rate any more than we need to. 1.468 + mNextDriverIndex = mRefreshDrivers.Length() - 1; 1.469 + 1.470 + StopTimer(); 1.471 + StartTimer(); 1.472 + } 1.473 + 1.474 +protected: 1.475 + virtual void StartTimer() 1.476 + { 1.477 + mLastFireEpoch = JS_Now(); 1.478 + mLastFireTime = TimeStamp::Now(); 1.479 + 1.480 + mTargetTime = mLastFireTime + mRateDuration; 1.481 + 1.482 + uint32_t delay = static_cast<uint32_t>(mRateMilliseconds); 1.483 + mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT); 1.484 + } 1.485 + 1.486 + virtual void StopTimer() 1.487 + { 1.488 + mTimer->Cancel(); 1.489 + } 1.490 + 1.491 + virtual void ScheduleNextTick(TimeStamp aNowTime) 1.492 + { 1.493 + if (mDisableAfterMilliseconds > 0.0 && 1.494 + mNextTickDuration > mDisableAfterMilliseconds) 1.495 + { 1.496 + // We hit the time after which we should disable 1.497 + // inactive window refreshes; don't schedule anything 1.498 + // until we get kicked by an AddRefreshDriver call. 1.499 + return; 1.500 + } 1.501 + 1.502 + // double the next tick time if we've already gone through all of them once 1.503 + if (mNextDriverIndex >= mRefreshDrivers.Length()) { 1.504 + mNextTickDuration *= 2.0; 1.505 + mNextDriverIndex = 0; 1.506 + } 1.507 + 1.508 + // this doesn't need to be precise; do a simple schedule 1.509 + uint32_t delay = static_cast<uint32_t>(mNextTickDuration); 1.510 + mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT); 1.511 + 1.512 + LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration, 1.513 + mNextDriverIndex, mRefreshDrivers.Length()); 1.514 + } 1.515 + 1.516 + /* Runs just one driver's tick. */ 1.517 + void TickOne() 1.518 + { 1.519 + int64_t jsnow = JS_Now(); 1.520 + TimeStamp now = TimeStamp::Now(); 1.521 + 1.522 + ScheduleNextTick(now); 1.523 + 1.524 + mLastFireEpoch = jsnow; 1.525 + mLastFireTime = now; 1.526 + 1.527 + nsTArray<nsRefPtr<nsRefreshDriver> > drivers(mRefreshDrivers); 1.528 + if (mNextDriverIndex < drivers.Length() && 1.529 + !drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled()) 1.530 + { 1.531 + TickDriver(drivers[mNextDriverIndex], jsnow, now); 1.532 + } 1.533 + 1.534 + mNextDriverIndex++; 1.535 + } 1.536 + 1.537 + static void TimerTickOne(nsITimer* aTimer, void* aClosure) 1.538 + { 1.539 + InactiveRefreshDriverTimer *timer = static_cast<InactiveRefreshDriverTimer*>(aClosure); 1.540 + timer->TickOne(); 1.541 + } 1.542 + 1.543 + nsRefPtr<nsITimer> mTimer; 1.544 + double mNextTickDuration; 1.545 + double mDisableAfterMilliseconds; 1.546 + uint32_t mNextDriverIndex; 1.547 +}; 1.548 + 1.549 +} // namespace mozilla 1.550 + 1.551 +static uint32_t 1.552 +GetFirstFrameDelay(imgIRequest* req) 1.553 +{ 1.554 + nsCOMPtr<imgIContainer> container; 1.555 + if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) { 1.556 + return 0; 1.557 + } 1.558 + 1.559 + // If this image isn't animated, there isn't a first frame delay. 1.560 + int32_t delay = container->GetFirstFrameDelay(); 1.561 + if (delay < 0) 1.562 + return 0; 1.563 + 1.564 + return static_cast<uint32_t>(delay); 1.565 +} 1.566 + 1.567 +static PreciseRefreshDriverTimer *sRegularRateTimer = nullptr; 1.568 +static InactiveRefreshDriverTimer *sThrottledRateTimer = nullptr; 1.569 + 1.570 +#ifdef XP_WIN 1.571 +static int32_t sHighPrecisionTimerRequests = 0; 1.572 +// a bare pointer to avoid introducing a static constructor 1.573 +static nsITimer *sDisableHighPrecisionTimersTimer = nullptr; 1.574 +#endif 1.575 + 1.576 +/* static */ void 1.577 +nsRefreshDriver::InitializeStatics() 1.578 +{ 1.579 +#ifdef PR_LOGGING 1.580 + if (!gLog) { 1.581 + gLog = PR_NewLogModule("nsRefreshDriver"); 1.582 + } 1.583 +#endif 1.584 +} 1.585 + 1.586 +/* static */ void 1.587 +nsRefreshDriver::Shutdown() 1.588 +{ 1.589 + // clean up our timers 1.590 + delete sRegularRateTimer; 1.591 + delete sThrottledRateTimer; 1.592 + 1.593 + sRegularRateTimer = nullptr; 1.594 + sThrottledRateTimer = nullptr; 1.595 + 1.596 +#ifdef XP_WIN 1.597 + if (sDisableHighPrecisionTimersTimer) { 1.598 + sDisableHighPrecisionTimersTimer->Cancel(); 1.599 + NS_RELEASE(sDisableHighPrecisionTimersTimer); 1.600 + timeEndPeriod(1); 1.601 + } else if (sHighPrecisionTimerRequests) { 1.602 + timeEndPeriod(1); 1.603 + } 1.604 +#endif 1.605 +} 1.606 + 1.607 +/* static */ int32_t 1.608 +nsRefreshDriver::DefaultInterval() 1.609 +{ 1.610 + return NSToIntRound(1000.0 / DEFAULT_FRAME_RATE); 1.611 +} 1.612 + 1.613 +// Compute the interval to use for the refresh driver timer, in milliseconds. 1.614 +// outIsDefault indicates that rate was not explicitly set by the user 1.615 +// so we might choose other, more appropriate rates (e.g. vsync, etc) 1.616 +// layout.frame_rate=0 indicates "ASAP mode". 1.617 +// In ASAP mode rendering is iterated as fast as possible (typically for stress testing). 1.618 +// A target rate of 10k is used internally instead of special-handling 0. 1.619 +// Backends which block on swap/present/etc should try to not block 1.620 +// when layout.frame_rate=0 - to comply with "ASAP" as much as possible. 1.621 +double 1.622 +nsRefreshDriver::GetRegularTimerInterval(bool *outIsDefault) const 1.623 +{ 1.624 + int32_t rate = Preferences::GetInt("layout.frame_rate", -1); 1.625 + if (rate < 0) { 1.626 + rate = DEFAULT_FRAME_RATE; 1.627 + if (outIsDefault) { 1.628 + *outIsDefault = true; 1.629 + } 1.630 + } else { 1.631 + if (outIsDefault) { 1.632 + *outIsDefault = false; 1.633 + } 1.634 + } 1.635 + 1.636 + if (rate == 0) { 1.637 + rate = 10000; 1.638 + } 1.639 + 1.640 + return 1000.0 / rate; 1.641 +} 1.642 + 1.643 +double 1.644 +nsRefreshDriver::GetThrottledTimerInterval() const 1.645 +{ 1.646 + int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1); 1.647 + if (rate <= 0) { 1.648 + rate = DEFAULT_THROTTLED_FRAME_RATE; 1.649 + } 1.650 + return 1000.0 / rate; 1.651 +} 1.652 + 1.653 +double 1.654 +nsRefreshDriver::GetRefreshTimerInterval() const 1.655 +{ 1.656 + return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval(); 1.657 +} 1.658 + 1.659 +RefreshDriverTimer* 1.660 +nsRefreshDriver::ChooseTimer() const 1.661 +{ 1.662 + if (mThrottled) { 1.663 + if (!sThrottledRateTimer) 1.664 + sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(), 1.665 + DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0); 1.666 + return sThrottledRateTimer; 1.667 + } 1.668 + 1.669 + if (!sRegularRateTimer) { 1.670 + bool isDefault = true; 1.671 + double rate = GetRegularTimerInterval(&isDefault); 1.672 +#ifdef XP_WIN 1.673 + if (PreciseRefreshDriverTimerWindowsDwmVsync::IsSupported()) { 1.674 + sRegularRateTimer = new PreciseRefreshDriverTimerWindowsDwmVsync(rate, isDefault); 1.675 + } 1.676 +#endif 1.677 + if (!sRegularRateTimer) { 1.678 + sRegularRateTimer = new PreciseRefreshDriverTimer(rate); 1.679 + } 1.680 + } 1.681 + return sRegularRateTimer; 1.682 +} 1.683 + 1.684 +nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext) 1.685 + : mActiveTimer(nullptr), 1.686 + mPresContext(aPresContext), 1.687 + mFreezeCount(0), 1.688 + mThrottled(false), 1.689 + mTestControllingRefreshes(false), 1.690 + mViewManagerFlushIsPending(false), 1.691 + mRequestedHighPrecision(false), 1.692 + mInRefresh(false) 1.693 +{ 1.694 + mMostRecentRefreshEpochTime = JS_Now(); 1.695 + mMostRecentRefresh = TimeStamp::Now(); 1.696 +} 1.697 + 1.698 +nsRefreshDriver::~nsRefreshDriver() 1.699 +{ 1.700 + NS_ABORT_IF_FALSE(ObserverCount() == 0, 1.701 + "observers should have unregistered"); 1.702 + NS_ABORT_IF_FALSE(!mActiveTimer, "timer should be gone"); 1.703 + 1.704 + for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { 1.705 + mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); 1.706 + } 1.707 + mPresShellsToInvalidateIfHidden.Clear(); 1.708 +} 1.709 + 1.710 +// Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh 1.711 +// for description. 1.712 +void 1.713 +nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) 1.714 +{ 1.715 + // ensure that we're removed from our driver 1.716 + StopTimer(); 1.717 + 1.718 + if (!mTestControllingRefreshes) { 1.719 + mMostRecentRefreshEpochTime = JS_Now(); 1.720 + mMostRecentRefresh = TimeStamp::Now(); 1.721 + 1.722 + mTestControllingRefreshes = true; 1.723 + } 1.724 + 1.725 + mMostRecentRefreshEpochTime += aMilliseconds * 1000; 1.726 + mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds); 1.727 + 1.728 + mozilla::dom::AutoNoJSAPI nojsapi; 1.729 + DoTick(); 1.730 +} 1.731 + 1.732 +void 1.733 +nsRefreshDriver::RestoreNormalRefresh() 1.734 +{ 1.735 + mTestControllingRefreshes = false; 1.736 + EnsureTimerStarted(false); 1.737 +} 1.738 + 1.739 +TimeStamp 1.740 +nsRefreshDriver::MostRecentRefresh() const 1.741 +{ 1.742 + const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false); 1.743 + 1.744 + return mMostRecentRefresh; 1.745 +} 1.746 + 1.747 +int64_t 1.748 +nsRefreshDriver::MostRecentRefreshEpochTime() const 1.749 +{ 1.750 + const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false); 1.751 + 1.752 + return mMostRecentRefreshEpochTime; 1.753 +} 1.754 + 1.755 +bool 1.756 +nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver, 1.757 + mozFlushType aFlushType) 1.758 +{ 1.759 + ObserverArray& array = ArrayFor(aFlushType); 1.760 + bool success = array.AppendElement(aObserver) != nullptr; 1.761 + EnsureTimerStarted(false); 1.762 + return success; 1.763 +} 1.764 + 1.765 +bool 1.766 +nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver, 1.767 + mozFlushType aFlushType) 1.768 +{ 1.769 + ObserverArray& array = ArrayFor(aFlushType); 1.770 + return array.RemoveElement(aObserver); 1.771 +} 1.772 + 1.773 +void 1.774 +nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) 1.775 +{ 1.776 + mPostRefreshObservers.AppendElement(aObserver); 1.777 +} 1.778 + 1.779 +void 1.780 +nsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) 1.781 +{ 1.782 + mPostRefreshObservers.RemoveElement(aObserver); 1.783 +} 1.784 + 1.785 +bool 1.786 +nsRefreshDriver::AddImageRequest(imgIRequest* aRequest) 1.787 +{ 1.788 + uint32_t delay = GetFirstFrameDelay(aRequest); 1.789 + if (delay == 0) { 1.790 + if (!mRequests.PutEntry(aRequest)) { 1.791 + return false; 1.792 + } 1.793 + } else { 1.794 + ImageStartData* start = mStartTable.Get(delay); 1.795 + if (!start) { 1.796 + start = new ImageStartData(); 1.797 + mStartTable.Put(delay, start); 1.798 + } 1.799 + start->mEntries.PutEntry(aRequest); 1.800 + } 1.801 + 1.802 + EnsureTimerStarted(false); 1.803 + 1.804 + return true; 1.805 +} 1.806 + 1.807 +void 1.808 +nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest) 1.809 +{ 1.810 + // Try to remove from both places, just in case, because we can't tell 1.811 + // whether RemoveEntry() succeeds. 1.812 + mRequests.RemoveEntry(aRequest); 1.813 + uint32_t delay = GetFirstFrameDelay(aRequest); 1.814 + if (delay != 0) { 1.815 + ImageStartData* start = mStartTable.Get(delay); 1.816 + if (start) { 1.817 + start->mEntries.RemoveEntry(aRequest); 1.818 + } 1.819 + } 1.820 +} 1.821 + 1.822 +void 1.823 +nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer) 1.824 +{ 1.825 + if (mTestControllingRefreshes) 1.826 + return; 1.827 + 1.828 + // will it already fire, and no other changes needed? 1.829 + if (mActiveTimer && !aAdjustingTimer) 1.830 + return; 1.831 + 1.832 + if (IsFrozen() || !mPresContext) { 1.833 + // If we don't want to start it now, or we've been disconnected. 1.834 + StopTimer(); 1.835 + return; 1.836 + } 1.837 + 1.838 + // We got here because we're either adjusting the time *or* we're 1.839 + // starting it for the first time. Add to the right timer, 1.840 + // prehaps removing it from a previously-set one. 1.841 + RefreshDriverTimer *newTimer = ChooseTimer(); 1.842 + if (newTimer != mActiveTimer) { 1.843 + if (mActiveTimer) 1.844 + mActiveTimer->RemoveRefreshDriver(this); 1.845 + mActiveTimer = newTimer; 1.846 + mActiveTimer->AddRefreshDriver(this); 1.847 + } 1.848 + 1.849 + mMostRecentRefresh = mActiveTimer->MostRecentRefresh(); 1.850 + mMostRecentRefreshEpochTime = mActiveTimer->MostRecentRefreshEpochTime(); 1.851 +} 1.852 + 1.853 +void 1.854 +nsRefreshDriver::StopTimer() 1.855 +{ 1.856 + if (!mActiveTimer) 1.857 + return; 1.858 + 1.859 + mActiveTimer->RemoveRefreshDriver(this); 1.860 + mActiveTimer = nullptr; 1.861 + 1.862 + if (mRequestedHighPrecision) { 1.863 + SetHighPrecisionTimersEnabled(false); 1.864 + } 1.865 +} 1.866 + 1.867 +#ifdef XP_WIN 1.868 +static void 1.869 +DisableHighPrecisionTimersCallback(nsITimer *aTimer, void *aClosure) 1.870 +{ 1.871 + timeEndPeriod(1); 1.872 + NS_RELEASE(sDisableHighPrecisionTimersTimer); 1.873 +} 1.874 +#endif 1.875 + 1.876 +void 1.877 +nsRefreshDriver::ConfigureHighPrecision() 1.878 +{ 1.879 + bool haveFrameRequestCallbacks = mFrameRequestCallbackDocs.Length() > 0; 1.880 + 1.881 + // if the only change that's needed is that we need high precision, 1.882 + // then just set that 1.883 + if (!mThrottled && !mRequestedHighPrecision && haveFrameRequestCallbacks) { 1.884 + SetHighPrecisionTimersEnabled(true); 1.885 + } else if (mRequestedHighPrecision && !haveFrameRequestCallbacks) { 1.886 + SetHighPrecisionTimersEnabled(false); 1.887 + } 1.888 +} 1.889 + 1.890 +void 1.891 +nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable) 1.892 +{ 1.893 + LOG("[%p] SetHighPrecisionTimersEnabled (%s)", this, aEnable ? "true" : "false"); 1.894 + 1.895 + if (aEnable) { 1.896 + NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!"); 1.897 +#ifdef XP_WIN 1.898 + if (++sHighPrecisionTimerRequests == 1) { 1.899 + // If we had a timer scheduled to disable it, that means that it's already 1.900 + // enabled; just cancel the timer. Otherwise, really enable it. 1.901 + if (sDisableHighPrecisionTimersTimer) { 1.902 + sDisableHighPrecisionTimersTimer->Cancel(); 1.903 + NS_RELEASE(sDisableHighPrecisionTimersTimer); 1.904 + } else { 1.905 + timeBeginPeriod(1); 1.906 + } 1.907 + } 1.908 +#endif 1.909 + mRequestedHighPrecision = true; 1.910 + } else { 1.911 + NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!"); 1.912 +#ifdef XP_WIN 1.913 + if (--sHighPrecisionTimerRequests == 0) { 1.914 + // Don't jerk us around between high precision and low precision 1.915 + // timers; instead, only allow leaving high precision timers 1.916 + // after 90 seconds. This is arbitrary, but hopefully good 1.917 + // enough. 1.918 + NS_ASSERTION(!sDisableHighPrecisionTimersTimer, "We shouldn't have an outstanding disable-high-precision timer !"); 1.919 + 1.920 + nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID); 1.921 + if (timer) { 1.922 + timer.forget(&sDisableHighPrecisionTimersTimer); 1.923 + sDisableHighPrecisionTimersTimer->InitWithFuncCallback(DisableHighPrecisionTimersCallback, 1.924 + nullptr, 1.925 + 90 * 1000, 1.926 + nsITimer::TYPE_ONE_SHOT); 1.927 + } else { 1.928 + // might happen if we're shutting down XPCOM; just drop the time period down 1.929 + // immediately 1.930 + timeEndPeriod(1); 1.931 + } 1.932 + } 1.933 +#endif 1.934 + mRequestedHighPrecision = false; 1.935 + } 1.936 +} 1.937 + 1.938 +uint32_t 1.939 +nsRefreshDriver::ObserverCount() const 1.940 +{ 1.941 + uint32_t sum = 0; 1.942 + for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) { 1.943 + sum += mObservers[i].Length(); 1.944 + } 1.945 + 1.946 + // Even while throttled, we need to process layout and style changes. Style 1.947 + // changes can trigger transitions which fire events when they complete, and 1.948 + // layout changes can affect media queries on child documents, triggering 1.949 + // style changes, etc. 1.950 + sum += mStyleFlushObservers.Length(); 1.951 + sum += mLayoutFlushObservers.Length(); 1.952 + sum += mFrameRequestCallbackDocs.Length(); 1.953 + sum += mViewManagerFlushIsPending; 1.954 + return sum; 1.955 +} 1.956 + 1.957 +/* static */ PLDHashOperator 1.958 +nsRefreshDriver::StartTableRequestCounter(const uint32_t& aKey, 1.959 + ImageStartData* aEntry, 1.960 + void* aUserArg) 1.961 +{ 1.962 + uint32_t *count = static_cast<uint32_t*>(aUserArg); 1.963 + *count += aEntry->mEntries.Count(); 1.964 + 1.965 + return PL_DHASH_NEXT; 1.966 +} 1.967 + 1.968 +uint32_t 1.969 +nsRefreshDriver::ImageRequestCount() const 1.970 +{ 1.971 + uint32_t count = 0; 1.972 + mStartTable.EnumerateRead(nsRefreshDriver::StartTableRequestCounter, &count); 1.973 + return count + mRequests.Count(); 1.974 +} 1.975 + 1.976 +nsRefreshDriver::ObserverArray& 1.977 +nsRefreshDriver::ArrayFor(mozFlushType aFlushType) 1.978 +{ 1.979 + switch (aFlushType) { 1.980 + case Flush_Style: 1.981 + return mObservers[0]; 1.982 + case Flush_Layout: 1.983 + return mObservers[1]; 1.984 + case Flush_Display: 1.985 + return mObservers[2]; 1.986 + default: 1.987 + NS_ABORT_IF_FALSE(false, "bad flush type"); 1.988 + return *static_cast<ObserverArray*>(nullptr); 1.989 + } 1.990 +} 1.991 + 1.992 +/* 1.993 + * nsISupports implementation 1.994 + */ 1.995 + 1.996 +NS_IMPL_ISUPPORTS(nsRefreshDriver, nsISupports) 1.997 + 1.998 +/* 1.999 + * nsITimerCallback implementation 1.1000 + */ 1.1001 + 1.1002 +void 1.1003 +nsRefreshDriver::DoTick() 1.1004 +{ 1.1005 + NS_PRECONDITION(!IsFrozen(), "Why are we notified while frozen?"); 1.1006 + NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?"); 1.1007 + NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(), 1.1008 + "Shouldn't have a JSContext on the stack"); 1.1009 + 1.1010 + if (mTestControllingRefreshes) { 1.1011 + Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh); 1.1012 + } else { 1.1013 + Tick(JS_Now(), TimeStamp::Now()); 1.1014 + } 1.1015 +} 1.1016 + 1.1017 +struct DocumentFrameCallbacks { 1.1018 + DocumentFrameCallbacks(nsIDocument* aDocument) : 1.1019 + mDocument(aDocument) 1.1020 + {} 1.1021 + 1.1022 + nsCOMPtr<nsIDocument> mDocument; 1.1023 + nsIDocument::FrameRequestCallbackList mCallbacks; 1.1024 +}; 1.1025 + 1.1026 +void 1.1027 +nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) 1.1028 +{ 1.1029 + NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(), 1.1030 + "Shouldn't have a JSContext on the stack"); 1.1031 + 1.1032 + if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) { 1.1033 + NS_ERROR("Refresh driver should not run during plugin call!"); 1.1034 + // Try to survive this by just ignoring the refresh tick. 1.1035 + return; 1.1036 + } 1.1037 + 1.1038 + PROFILER_LABEL("nsRefreshDriver", "Tick"); 1.1039 + 1.1040 + // We're either frozen or we were disconnected (likely in the middle 1.1041 + // of a tick iteration). Just do nothing here, since our 1.1042 + // prescontext went away. 1.1043 + if (IsFrozen() || !mPresContext) { 1.1044 + return; 1.1045 + } 1.1046 + 1.1047 + TimeStamp previousRefresh = mMostRecentRefresh; 1.1048 + 1.1049 + mMostRecentRefresh = aNowTime; 1.1050 + mMostRecentRefreshEpochTime = aNowEpoch; 1.1051 + 1.1052 + nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell(); 1.1053 + if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) { 1.1054 + // Things are being destroyed, or we no longer have any observers. 1.1055 + // We don't want to stop the timer when observers are initially 1.1056 + // removed, because sometimes observers can be added and removed 1.1057 + // often depending on what other things are going on and in that 1.1058 + // situation we don't want to thrash our timer. So instead we 1.1059 + // wait until we get a Notify() call when we have no observers 1.1060 + // before stopping the timer. 1.1061 + StopTimer(); 1.1062 + return; 1.1063 + } 1.1064 + 1.1065 + profiler_tracing("Paint", "RD", TRACING_INTERVAL_START); 1.1066 + 1.1067 + AutoRestore<bool> restoreInRefresh(mInRefresh); 1.1068 + mInRefresh = true; 1.1069 + 1.1070 + /* 1.1071 + * The timer holds a reference to |this| while calling |Notify|. 1.1072 + * However, implementations of |WillRefresh| are permitted to destroy 1.1073 + * the pres context, which will cause our |mPresContext| to become 1.1074 + * null. If this happens, we must stop notifying observers. 1.1075 + */ 1.1076 + for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) { 1.1077 + ObserverArray::EndLimitedIterator etor(mObservers[i]); 1.1078 + while (etor.HasMore()) { 1.1079 + nsRefPtr<nsARefreshObserver> obs = etor.GetNext(); 1.1080 + obs->WillRefresh(aNowTime); 1.1081 + 1.1082 + if (!mPresContext || !mPresContext->GetPresShell()) { 1.1083 + StopTimer(); 1.1084 + profiler_tracing("Paint", "RD", TRACING_INTERVAL_END); 1.1085 + return; 1.1086 + } 1.1087 + } 1.1088 + 1.1089 + if (i == 0) { 1.1090 + // Grab all of our frame request callbacks up front. 1.1091 + nsTArray<DocumentFrameCallbacks> 1.1092 + frameRequestCallbacks(mFrameRequestCallbackDocs.Length()); 1.1093 + for (uint32_t i = 0; i < mFrameRequestCallbackDocs.Length(); ++i) { 1.1094 + frameRequestCallbacks.AppendElement(mFrameRequestCallbackDocs[i]); 1.1095 + mFrameRequestCallbackDocs[i]-> 1.1096 + TakeFrameRequestCallbacks(frameRequestCallbacks.LastElement().mCallbacks); 1.1097 + } 1.1098 + // OK, now reset mFrameRequestCallbackDocs so they can be 1.1099 + // readded as needed. 1.1100 + mFrameRequestCallbackDocs.Clear(); 1.1101 + 1.1102 + profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_START); 1.1103 + int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC; 1.1104 + for (uint32_t i = 0; i < frameRequestCallbacks.Length(); ++i) { 1.1105 + const DocumentFrameCallbacks& docCallbacks = frameRequestCallbacks[i]; 1.1106 + // XXXbz Bug 863140: GetInnerWindow can return the outer 1.1107 + // window in some cases. 1.1108 + nsPIDOMWindow* innerWindow = docCallbacks.mDocument->GetInnerWindow(); 1.1109 + DOMHighResTimeStamp timeStamp = 0; 1.1110 + if (innerWindow && innerWindow->IsInnerWindow()) { 1.1111 + nsPerformance* perf = innerWindow->GetPerformance(); 1.1112 + if (perf) { 1.1113 + timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime); 1.1114 + } 1.1115 + // else window is partially torn down already 1.1116 + } 1.1117 + for (uint32_t j = 0; j < docCallbacks.mCallbacks.Length(); ++j) { 1.1118 + const nsIDocument::FrameRequestCallbackHolder& holder = 1.1119 + docCallbacks.mCallbacks[j]; 1.1120 + nsAutoMicroTask mt; 1.1121 + if (holder.HasWebIDLCallback()) { 1.1122 + ErrorResult ignored; 1.1123 + holder.GetWebIDLCallback()->Call(timeStamp, ignored); 1.1124 + } else { 1.1125 + holder.GetXPCOMCallback()->Sample(eventTime); 1.1126 + } 1.1127 + } 1.1128 + } 1.1129 + profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END); 1.1130 + 1.1131 + // This is the Flush_Style case. 1.1132 + if (mPresContext && mPresContext->GetPresShell()) { 1.1133 + nsAutoTArray<nsIPresShell*, 16> observers; 1.1134 + observers.AppendElements(mStyleFlushObservers); 1.1135 + for (uint32_t j = observers.Length(); 1.1136 + j && mPresContext && mPresContext->GetPresShell(); --j) { 1.1137 + // Make sure to not process observers which might have been removed 1.1138 + // during previous iterations. 1.1139 + nsIPresShell* shell = observers[j - 1]; 1.1140 + if (!mStyleFlushObservers.Contains(shell)) 1.1141 + continue; 1.1142 + NS_ADDREF(shell); 1.1143 + mStyleFlushObservers.RemoveElement(shell); 1.1144 + shell->GetPresContext()->RestyleManager()->mObservingRefreshDriver = false; 1.1145 + shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false)); 1.1146 + NS_RELEASE(shell); 1.1147 + } 1.1148 + } 1.1149 + } else if (i == 1) { 1.1150 + // This is the Flush_Layout case. 1.1151 + if (mPresContext && mPresContext->GetPresShell()) { 1.1152 + nsAutoTArray<nsIPresShell*, 16> observers; 1.1153 + observers.AppendElements(mLayoutFlushObservers); 1.1154 + for (uint32_t j = observers.Length(); 1.1155 + j && mPresContext && mPresContext->GetPresShell(); --j) { 1.1156 + // Make sure to not process observers which might have been removed 1.1157 + // during previous iterations. 1.1158 + nsIPresShell* shell = observers[j - 1]; 1.1159 + if (!mLayoutFlushObservers.Contains(shell)) 1.1160 + continue; 1.1161 + NS_ADDREF(shell); 1.1162 + mLayoutFlushObservers.RemoveElement(shell); 1.1163 + shell->mReflowScheduled = false; 1.1164 + shell->mSuppressInterruptibleReflows = false; 1.1165 + shell->FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout, 1.1166 + false)); 1.1167 + NS_RELEASE(shell); 1.1168 + } 1.1169 + } 1.1170 + } 1.1171 + } 1.1172 + 1.1173 + /* 1.1174 + * Perform notification to imgIRequests subscribed to listen 1.1175 + * for refresh events. 1.1176 + */ 1.1177 + 1.1178 + ImageRequestParameters parms = {aNowTime, previousRefresh, &mRequests}; 1.1179 + 1.1180 + mStartTable.EnumerateRead(nsRefreshDriver::StartTableRefresh, &parms); 1.1181 + 1.1182 + if (mRequests.Count()) { 1.1183 + // RequestRefresh may run scripts, so it's not safe to directly call it 1.1184 + // while using a hashtable enumerator to enumerate mRequests in case 1.1185 + // script modifies the hashtable. Instead, we build a (local) array of 1.1186 + // images to refresh, and then we refresh each image in that array. 1.1187 + nsCOMArray<imgIContainer> imagesToRefresh(mRequests.Count()); 1.1188 + mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, 1.1189 + &imagesToRefresh); 1.1190 + 1.1191 + for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) { 1.1192 + imagesToRefresh[i]->RequestRefresh(aNowTime); 1.1193 + } 1.1194 + } 1.1195 + 1.1196 + for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { 1.1197 + mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); 1.1198 + } 1.1199 + mPresShellsToInvalidateIfHidden.Clear(); 1.1200 + 1.1201 + if (mViewManagerFlushIsPending) { 1.1202 +#ifdef MOZ_DUMP_PAINTING 1.1203 + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { 1.1204 + printf_stderr("Starting ProcessPendingUpdates\n"); 1.1205 + } 1.1206 +#endif 1.1207 + 1.1208 + mViewManagerFlushIsPending = false; 1.1209 + nsRefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager(); 1.1210 + vm->ProcessPendingUpdates(); 1.1211 +#ifdef MOZ_DUMP_PAINTING 1.1212 + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { 1.1213 + printf_stderr("Ending ProcessPendingUpdates\n"); 1.1214 + } 1.1215 +#endif 1.1216 + } 1.1217 + 1.1218 + for (uint32_t i = 0; i < mPostRefreshObservers.Length(); ++i) { 1.1219 + mPostRefreshObservers[i]->DidRefresh(); 1.1220 + } 1.1221 + profiler_tracing("Paint", "RD", TRACING_INTERVAL_END); 1.1222 + 1.1223 + NS_ASSERTION(mInRefresh, "Still in refresh"); 1.1224 +} 1.1225 + 1.1226 +/* static */ PLDHashOperator 1.1227 +nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry, 1.1228 + void* aUserArg) 1.1229 +{ 1.1230 + nsCOMArray<imgIContainer>* imagesToRefresh = 1.1231 + static_cast<nsCOMArray<imgIContainer>*> (aUserArg); 1.1232 + imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey()); 1.1233 + NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request"); 1.1234 + nsCOMPtr<imgIContainer> image; 1.1235 + if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) { 1.1236 + imagesToRefresh->AppendElement(image); 1.1237 + } 1.1238 + 1.1239 + return PL_DHASH_NEXT; 1.1240 +} 1.1241 + 1.1242 +/* static */ PLDHashOperator 1.1243 +nsRefreshDriver::BeginRefreshingImages(nsISupportsHashKey* aEntry, 1.1244 + void* aUserArg) 1.1245 +{ 1.1246 + ImageRequestParameters* parms = 1.1247 + static_cast<ImageRequestParameters*> (aUserArg); 1.1248 + 1.1249 + imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey()); 1.1250 + NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request"); 1.1251 + 1.1252 + parms->mRequests->PutEntry(req); 1.1253 + 1.1254 + nsCOMPtr<imgIContainer> image; 1.1255 + if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) { 1.1256 + image->SetAnimationStartTime(parms->mDesired); 1.1257 + } 1.1258 + 1.1259 + return PL_DHASH_REMOVE; 1.1260 +} 1.1261 + 1.1262 +/* static */ PLDHashOperator 1.1263 +nsRefreshDriver::StartTableRefresh(const uint32_t& aDelay, 1.1264 + ImageStartData* aData, 1.1265 + void* aUserArg) 1.1266 +{ 1.1267 + ImageRequestParameters* parms = 1.1268 + static_cast<ImageRequestParameters*> (aUserArg); 1.1269 + 1.1270 + if (!aData->mStartTime.empty()) { 1.1271 + TimeStamp& start = aData->mStartTime.ref(); 1.1272 + TimeDuration prev = parms->mPrevious - start; 1.1273 + TimeDuration curr = parms->mCurrent - start; 1.1274 + uint32_t prevMultiple = static_cast<uint32_t>(prev.ToMilliseconds()) / aDelay; 1.1275 + 1.1276 + // We want to trigger images' refresh if we've just crossed over a multiple 1.1277 + // of the first image's start time. If so, set the animation start time to 1.1278 + // the nearest multiple of the delay and move all the images in this table 1.1279 + // to the main requests table. 1.1280 + if (prevMultiple != static_cast<uint32_t>(curr.ToMilliseconds()) / aDelay) { 1.1281 + parms->mDesired = start + TimeDuration::FromMilliseconds(prevMultiple * aDelay); 1.1282 + aData->mEntries.EnumerateEntries(nsRefreshDriver::BeginRefreshingImages, parms); 1.1283 + } 1.1284 + } else { 1.1285 + // This is the very first time we've drawn images with this time delay. 1.1286 + // Set the animation start time to "now" and move all the images in this 1.1287 + // table to the main requests table. 1.1288 + parms->mDesired = parms->mCurrent; 1.1289 + aData->mEntries.EnumerateEntries(nsRefreshDriver::BeginRefreshingImages, parms); 1.1290 + aData->mStartTime.construct(parms->mCurrent); 1.1291 + } 1.1292 + 1.1293 + return PL_DHASH_NEXT; 1.1294 +} 1.1295 + 1.1296 +void 1.1297 +nsRefreshDriver::Freeze() 1.1298 +{ 1.1299 + StopTimer(); 1.1300 + mFreezeCount++; 1.1301 +} 1.1302 + 1.1303 +void 1.1304 +nsRefreshDriver::Thaw() 1.1305 +{ 1.1306 + NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver"); 1.1307 + 1.1308 + if (mFreezeCount > 0) { 1.1309 + mFreezeCount--; 1.1310 + } 1.1311 + 1.1312 + if (mFreezeCount == 0) { 1.1313 + if (ObserverCount() || ImageRequestCount()) { 1.1314 + // FIXME: This isn't quite right, since our EnsureTimerStarted call 1.1315 + // updates our mMostRecentRefresh, but the DoRefresh call won't run 1.1316 + // and notify our observers until we get back to the event loop. 1.1317 + // Thus MostRecentRefresh() will lie between now and the DoRefresh. 1.1318 + NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh)); 1.1319 + EnsureTimerStarted(false); 1.1320 + } 1.1321 + } 1.1322 +} 1.1323 + 1.1324 +void 1.1325 +nsRefreshDriver::SetThrottled(bool aThrottled) 1.1326 +{ 1.1327 + if (aThrottled != mThrottled) { 1.1328 + mThrottled = aThrottled; 1.1329 + if (mActiveTimer) { 1.1330 + // We want to switch our timer type here, so just stop and 1.1331 + // restart the timer. 1.1332 + EnsureTimerStarted(true); 1.1333 + } 1.1334 + } 1.1335 +} 1.1336 + 1.1337 +void 1.1338 +nsRefreshDriver::DoRefresh() 1.1339 +{ 1.1340 + // Don't do a refresh unless we're in a state where we should be refreshing. 1.1341 + if (!IsFrozen() && mPresContext && mActiveTimer) { 1.1342 + DoTick(); 1.1343 + } 1.1344 +} 1.1345 + 1.1346 +#ifdef DEBUG 1.1347 +bool 1.1348 +nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver, 1.1349 + mozFlushType aFlushType) 1.1350 +{ 1.1351 + ObserverArray& array = ArrayFor(aFlushType); 1.1352 + return array.Contains(aObserver); 1.1353 +} 1.1354 +#endif 1.1355 + 1.1356 +void 1.1357 +nsRefreshDriver::ScheduleViewManagerFlush() 1.1358 +{ 1.1359 + NS_ASSERTION(mPresContext->IsRoot(), 1.1360 + "Should only schedule view manager flush on root prescontexts"); 1.1361 + mViewManagerFlushIsPending = true; 1.1362 + EnsureTimerStarted(false); 1.1363 +} 1.1364 + 1.1365 +void 1.1366 +nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument) 1.1367 +{ 1.1368 + NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) == 1.1369 + mFrameRequestCallbackDocs.NoIndex, 1.1370 + "Don't schedule the same document multiple times"); 1.1371 + mFrameRequestCallbackDocs.AppendElement(aDocument); 1.1372 + 1.1373 + // make sure that the timer is running 1.1374 + ConfigureHighPrecision(); 1.1375 + EnsureTimerStarted(false); 1.1376 +} 1.1377 + 1.1378 +void 1.1379 +nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument) 1.1380 +{ 1.1381 + mFrameRequestCallbackDocs.RemoveElement(aDocument); 1.1382 + ConfigureHighPrecision(); 1.1383 + // No need to worry about restarting our timer in slack mode if it's already 1.1384 + // running; that will happen automatically when it fires. 1.1385 +} 1.1386 + 1.1387 +#undef LOG