Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 |