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