layout/base/nsRefreshDriver.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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()
  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());
  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)
  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;
  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;
  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;
  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;
  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);
  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);
  1112           // else window is partially torn down already
  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);
  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);
  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);
  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);
  1193   for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) {
  1194     mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden();
  1196   mPresShellsToInvalidateIfHidden.Clear();
  1198   if (mViewManagerFlushIsPending) {
  1199 #ifdef MOZ_DUMP_PAINTING
  1200     if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
  1201       printf_stderr("Starting ProcessPendingUpdates\n");
  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");
  1212 #endif
  1215   for (uint32_t i = 0; i < mPostRefreshObservers.Length(); ++i) {
  1216     mPostRefreshObservers[i]->DidRefresh();
  1218   profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
  1220   NS_ASSERTION(mInRefresh, "Still in refresh");
  1223 /* static */ PLDHashOperator
  1224 nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
  1225                                         void* aUserArg)
  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);
  1236   return PL_DHASH_NEXT;
  1239 /* static */ PLDHashOperator
  1240 nsRefreshDriver::BeginRefreshingImages(nsISupportsHashKey* aEntry,
  1241                                        void* aUserArg)
  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);
  1256   return PL_DHASH_REMOVE;
  1259 /* static */ PLDHashOperator
  1260 nsRefreshDriver::StartTableRefresh(const uint32_t& aDelay,
  1261                                    ImageStartData* aData,
  1262                                    void* aUserArg)
  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);
  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);
  1290   return PL_DHASH_NEXT;
  1293 void
  1294 nsRefreshDriver::Freeze()
  1296   StopTimer();
  1297   mFreezeCount++;
  1300 void
  1301 nsRefreshDriver::Thaw()
  1303   NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
  1305   if (mFreezeCount > 0) {
  1306     mFreezeCount--;
  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);
  1321 void
  1322 nsRefreshDriver::SetThrottled(bool aThrottled)
  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);
  1334 void
  1335 nsRefreshDriver::DoRefresh()
  1337   // Don't do a refresh unless we're in a state where we should be refreshing.
  1338   if (!IsFrozen() && mPresContext && mActiveTimer) {
  1339     DoTick();
  1343 #ifdef DEBUG
  1344 bool
  1345 nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
  1346                                    mozFlushType aFlushType)
  1348   ObserverArray& array = ArrayFor(aFlushType);
  1349   return array.Contains(aObserver);
  1351 #endif
  1353 void
  1354 nsRefreshDriver::ScheduleViewManagerFlush()
  1356   NS_ASSERTION(mPresContext->IsRoot(),
  1357                "Should only schedule view manager flush on root prescontexts");
  1358   mViewManagerFlushIsPending = true;
  1359   EnsureTimerStarted(false);
  1362 void
  1363 nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument)
  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);
  1375 void
  1376 nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
  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.
  1384 #undef LOG

mercurial