layout/base/nsRefreshDriver.cpp

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

mercurial