Backout 739aff49b8bb (bug 731974) for turning browser_586068-reload.js permaorange on WinXP 3 days ago
authorEd Morley <emorley@mozilla.com>
Mon, 08 Oct 2012 10:57:12 +0100
changeset 109633 9738e5a0190a87202f089315897bf252a99df18f
parent 109632 08bf91d3c0c33a07707b591cda32fa58675b0e1e
child 109652 90c035e4ffd9567240b0b0d56a60757aa3b34740
child 109665 8b78045e153624f66ace247eef2723e742efe86e
push id23640
push useremorley@mozilla.com
push dateMon, 08 Oct 2012 09:59:07 +0000
treeherdermozilla-central@9738e5a0190a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs731974
milestone18.0a1
backs out739aff49b8bb7ab8971c874c8212f5d62a8bcd45
first release with
nightly linux32
9738e5a0190a / 18.0a1 / 20121008031745 / files
nightly linux64
9738e5a0190a / 18.0a1 / 20121008031745 / files
nightly mac
9738e5a0190a / 18.0a1 / 20121008031745 / files
nightly win32
9738e5a0190a / 18.0a1 / 20121008031745 / files
nightly win64
9738e5a0190a / 18.0a1 / 20121008031745 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backout 739aff49b8bb (bug 731974) for turning browser_586068-reload.js permaorange on WinXP 3 days ago
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
layout/build/nsLayoutStatics.cpp
xpcom/ds/TimeStamp.h
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -2,37 +2,21 @@
 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Code to notify things that animate before a refresh, at an appropriate
  * refresh rate.  (Perhaps temporary, until replaced by compositor.)
- *
- * Chrome and each tab have their own RefreshDriver, which in turn
- * hooks into one of a few global timer based on RefreshDriverTimer,
- * defined below.  There are two main global timers -- one for active
- * animations, and one for inactive ones.  These are implemented as
- * subclasses of RefreshDriverTimer; see below for a description of
- * their implementations.  In the future, additional timer types may
- * implement things like blocking on vsync.
  */
 
-#ifdef XP_WIN
-#include <windows.h>
-// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have
-// to manually include it
-#include <mmsystem.h>
-#endif
-
 #include "mozilla/Util.h"
 
 #include "nsRefreshDriver.h"
-#include "nsITimer.h"
 #include "nsPresContext.h"
 #include "nsComponentManagerUtils.h"
 #include "prlog.h"
 #include "nsAutoPtr.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsIDocument.h"
 #include "nsGUIEvent.h"
 #include "nsEventDispatcher.h"
@@ -42,533 +26,148 @@
 #include "nsIViewManager.h"
 #include "sampler.h"
 
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 
 using namespace mozilla;
 
-#ifdef PR_LOGGING
-static PRLogModuleInfo *gLog = nullptr;
-#define LOG(...) PR_LOG(gLog, PR_LOG_NOTICE, (__VA_ARGS__))
-#else
-#define LOG(...) do { } while(0)
-#endif
-
 #define DEFAULT_FRAME_RATE 60
 #define DEFAULT_THROTTLED_FRAME_RATE 1
-// after 10 minutes, stop firing off inactive timers
-#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
 
-namespace mozilla {
-
-/*
- * The base class for all global refresh driver timers.  It takes care
- * of managing the list of refresh drivers attached to them and
- * provides interfaces for querying/setting the rate and actually
- * running a timer 'Tick'.  Subclasses must implement StartTimer(),
- * StopTimer(), and ScheduleNextTick() -- the first two just
- * start/stop whatever timer mechanism is in use, and ScheduleNextTick
- * is called at the start of the Tick() implementation to set a time
- * for the next tick.
- */
-class RefreshDriverTimer {
-public:
-  /*
-   * aRate -- the delay, in milliseconds, requested between timer firings
-   */
-  RefreshDriverTimer(double aRate)
-  {
-    SetRate(aRate);
-  }
-
-  virtual ~RefreshDriverTimer()
-  {
-    NS_ASSERTION(mRefreshDrivers.Length() == 0, "Should have removed all refresh drivers from here by now!");
-  }
-
-  virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
-  {
-    NS_ASSERTION(!mRefreshDrivers.Contains(aDriver), "AddRefreshDriver for a refresh driver that's already in the list!");
-    mRefreshDrivers.AppendElement(aDriver);
-
-    if (mRefreshDrivers.Length() == 1) {
-      StartTimer();
-    }
-  }
-
-  virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver)
-  {
-    NS_ASSERTION(mRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the list!");
-    mRefreshDrivers.RemoveElement(aDriver);
-
-    if (mRefreshDrivers.Length() == 0) {
-      StopTimer();
-    }
-  }
-
-  double GetRate() const
-  {
-    return mRateMilliseconds;
-  }
-
-  // will take effect at next timer tick
-  virtual void SetRate(double aNewRate)
-  {
-    mRateMilliseconds = aNewRate;
-    mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
-  }
-
-  TimeStamp MostRecentRefresh() const { return mLastFireTime; }
-  int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; }
-
-protected:
-  virtual void StartTimer() = 0;
-  virtual void StopTimer() = 0;
-  virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
-
-  /*
-   * Actually runs a tick, poking all the attached RefreshDrivers.
-   * Grabs the "now" time via JS_Now and TimeStamp::Now().
-   */
-  void Tick()
-  {
-    int64_t jsnow = JS_Now();
-    TimeStamp now = TimeStamp::Now();
-
-    ScheduleNextTick(now);
-
-    mLastFireEpoch = jsnow;
-    mLastFireTime = now;
-
-    nsTArray<nsRefPtr<nsRefreshDriver> > drivers(mRefreshDrivers);
-    for (size_t i = 0; i < drivers.Length(); ++i) {
-      // don't poke this driver if it's in test mode
-      if (drivers[i]->IsTestControllingRefreshesEnabled()) {
-        continue;
-      }
-
-      TickDriver(drivers[i], jsnow, now);
-    }
-  }
-
-  static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now)
-  {
-    driver->Tick(jsnow, now);
-  }
-
-  double mRateMilliseconds;
-  TimeDuration mRateDuration;
-
-  int64_t mLastFireEpoch;
-  TimeStamp mLastFireTime;
-  TimeStamp mTargetTime;
-
-  nsTArray<nsRefPtr<nsRefreshDriver> > mRefreshDrivers;
-
-  // useful callback for nsITimer-based derived classes, here
-  // bacause of c++ protected shenanigans
-  static void TimerTick(nsITimer* aTimer, void* aClosure)
-  {
-    RefreshDriverTimer *timer = static_cast<RefreshDriverTimer*>(aClosure);
-    timer->Tick();
-  }
-};
-
-/*
- * A RefreshDriverTimer that uses a nsITimer as the underlying timer.  Note that
- * this is a ONE_SHOT timer, not a repeating one!  Subclasses are expected to
- * implement ScheduleNextTick and intelligently calculate the next time to tick,
- * and to reset mTimer.  Using a repeating nsITimer gets us into a lot of pain
- * with its attempt at intelligent slack removal and such, so we don't do it.
- */
-class SimpleTimerBasedRefreshDriverTimer :
-    public RefreshDriverTimer
-{
-public:
-  SimpleTimerBasedRefreshDriverTimer(double aRate)
-    : RefreshDriverTimer(aRate)
-  {
-    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
-  }
-
-  virtual ~SimpleTimerBasedRefreshDriverTimer()
-  {
-    StopTimer();
-  }
-
-protected:
-
-  virtual void StartTimer()
-  {
-    mLastFireTime = TimeStamp::Now();
-    mTargetTime = mLastFireTime;
-
-    mTimer->InitWithFuncCallback(TimerTick, this, 0, nsITimer::TYPE_ONE_SHOT);
-  }
-
-  virtual void StopTimer()
-  {
-    mTimer->Cancel();
-  }
-
-  nsRefPtr<nsITimer> mTimer;
-};
-
-/*
- * PreciseRefreshDriverTimer schedules ticks based on the current time
- * and when the next tick -should- be sent if we were hitting our
- * rate.  It always schedules ticks on multiples of aRate -- meaning that
- * if some execution takes longer than an alloted slot, the next tick
- * will be delayed instead of triggering instantly.  This might not be
- * desired -- there's an #if 0'd block below that we could put behind
- * a pref to control this behaviour.
- */
-class PreciseRefreshDriverTimer :
-    public SimpleTimerBasedRefreshDriverTimer
-{
-public:
-  PreciseRefreshDriverTimer(double aRate)
-    : SimpleTimerBasedRefreshDriverTimer(aRate)
-  {
-  }
-
-protected:
-  virtual void ScheduleNextTick(TimeStamp aNowTime)
-  {
-    // The number of (whole) elapsed intervals between the last target
-    // time and the actual time.  We want to truncate the double down
-    // to an int number of intervals.
-    int numElapsedIntervals = static_cast<int>((aNowTime - mTargetTime) / mRateDuration);
-
-    // the last "tick" that may or may not have been actually sent was
-    // at this time.  For example, if the rate is 15ms, the target
-    // time is 200ms, and it's now 225ms, the last effective tick
-    // would have been at 215ms.  The next one should then be
-    // scheduled for 5 ms from now.
-    //
-    // We then add another mRateDuration to find the next tick target.
-    TimeStamp newTarget = mTargetTime + mRateDuration * (numElapsedIntervals + 1);
-
-    // the amount of (integer) ms until the next time we should tick
-    uint32_t delay = static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds());
-
-    // Without this block, we'll always schedule on interval ticks;
-    // with it, we'll schedule immediately if we missed our tick target
-    // last time.
-#if 0
-    if (numElapsedIntervals > 0) {
-      // we're late, so reset
-      newTarget = aNowTime;
-      delay = 0;
-    }
-#endif
-
-    // log info & lateness
-    LOG("[%p] precise timer last tick late by %f ms, next tick in %d ms",
-        this,
-        (aNowTime - mTargetTime).ToMilliseconds(),
-        delay);
-
-    // then schedule the timer
-    mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
-
-    mTargetTime = newTarget;
-  }
-};
-
-/*
- * A RefreshDriverTimer for inactive documents.  When a new refresh driver is
- * added, the rate is reset to the base (normally 1s/1fps).  Every time
- * it ticks, a single refresh driver is poked.  Once they have all been poked,
- * the duration between ticks doubles, up to mDisableAfterMilliseconds.  At that point,
- * the timer is quiet and doesn't tick (until something is added to it again).
- *
- * When a timer is removed, there is a possibility of another timer
- * being skipped for one cycle.  We could avoid this by adjusting
- * mNextDriverIndex in RemoveRefreshDriver, but there's little need to
- * add that complexity.  All we want is for inactive drivers to tick
- * at some point, but we don't care too much about how often.
- */
-class InactiveRefreshDriverTimer :
-    public RefreshDriverTimer
-{
-public:
-  InactiveRefreshDriverTimer(double aRate)
-    : RefreshDriverTimer(aRate),
-      mNextTickDuration(aRate),
-      mDisableAfterMilliseconds(-1.0),
-      mNextDriverIndex(0)
-  {
-    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
-  }
-
-  InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
-    : RefreshDriverTimer(aRate),
-      mNextTickDuration(aRate),
-      mDisableAfterMilliseconds(aDisableAfterMilliseconds),
-      mNextDriverIndex(0)
-  {
-    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
-  }
-
-  virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
-  {
-    RefreshDriverTimer::AddRefreshDriver(aDriver);
-
-    LOG("[%p] inactive timer got new refresh driver %p, resetting rate",
-        this, aDriver);
-
-    // reset the timer, and start with the newly added one next time.
-    mNextTickDuration = mRateMilliseconds;
-
-    // we don't really have to start with the newly added one, but we may as well
-    // not tick the old ones at the fastest rate any more than we need to.
-    mNextDriverIndex = mRefreshDrivers.Length() - 1;
-
-    StopTimer();
-    StartTimer();
-  }
-
-protected:
-  virtual void StartTimer()
-  {
-    mLastFireTime = TimeStamp::Now();
-    mTargetTime = mLastFireTime;
-
-    mTimer->InitWithFuncCallback(TimerTickOne, this, 0, nsITimer::TYPE_ONE_SHOT);
-  }
-
-  virtual void StopTimer()
-  {
-    mTimer->Cancel();
-  }
-
-  virtual void ScheduleNextTick(TimeStamp aNowTime)
-  {
-    if (mDisableAfterMilliseconds > 0.0 &&
-        mNextTickDuration > mDisableAfterMilliseconds)
-    {
-      // We hit the time after which we should disable
-      // inactive window refreshes; don't schedule anything
-      // until we get kicked by an AddRefreshDriver call.
-      return;
-    }
-
-    // double the next tick time if we've already gone through all of them once
-    if (mNextDriverIndex >= mRefreshDrivers.Length()) {
-      mNextTickDuration *= 2.0;
-      mNextDriverIndex = 0;
-    }
-
-    // this doesn't need to be precise; do a simple schedule
-    uint32_t delay = static_cast<uint32_t>(mNextTickDuration);
-    mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
-
-    LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration,
-        mNextDriverIndex, mRefreshDrivers.Length());
-  }
-
-  /* Runs just one driver's tick. */
-  void TickOne()
-  {
-    int64_t jsnow = JS_Now();
-    TimeStamp now = TimeStamp::Now();
-
-    ScheduleNextTick(now);
-
-    mLastFireEpoch = jsnow;
-    mLastFireTime = now;
-
-    nsTArray<nsRefPtr<nsRefreshDriver> > drivers(mRefreshDrivers);
-    if (mNextDriverIndex < drivers.Length() &&
-        !drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled())
-    {
-      TickDriver(drivers[mNextDriverIndex], jsnow, now);
-    }
-
-    mNextDriverIndex++;
-  }
-
-  static void TimerTickOne(nsITimer* aTimer, void* aClosure)
-  {
-    InactiveRefreshDriverTimer *timer = static_cast<InactiveRefreshDriverTimer*>(aClosure);
-    timer->TickOne();
-  }
-
-  nsRefPtr<nsITimer> mTimer;
-  double mNextTickDuration;
-  double mDisableAfterMilliseconds;
-  uint32_t mNextDriverIndex;
-};
-
-} // namespace mozilla
-
-static PreciseRefreshDriverTimer *sRegularRateTimer = nullptr;
-static InactiveRefreshDriverTimer *sThrottledRateTimer = nullptr;
-
-static int32_t sHighPrecisionTimerRequests = 0;
+static bool sPrecisePref;
 
 /* static */ void
 nsRefreshDriver::InitializeStatics()
 {
-#ifdef PR_LOGGING
-  if (!gLog) {
-    gLog = PR_NewLogModule("nsRefreshDriver");
-  }
-#endif
-}
-
-/* static */ void
-nsRefreshDriver::Shutdown()
-{
-  // clean up our timers
-  delete sRegularRateTimer;
-  delete sThrottledRateTimer;
-
-  sRegularRateTimer = nullptr;
-  sThrottledRateTimer = nullptr;
+  Preferences::AddBoolVarCache(&sPrecisePref,
+                               "layout.frame_rate.precise",
+                               false);
 }
 
 /* static */ int32_t
 nsRefreshDriver::DefaultInterval()
 {
   return NSToIntRound(1000.0 / DEFAULT_FRAME_RATE);
 }
 
 // Compute the interval to use for the refresh driver timer, in
 // milliseconds
-double
-nsRefreshDriver::GetRegularTimerInterval() const
+int32_t
+nsRefreshDriver::GetRefreshTimerInterval() const
 {
-  int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
+  const char* prefName =
+    mThrottled ? "layout.throttled_frame_rate" : "layout.frame_rate";
+  int32_t rate = Preferences::GetInt(prefName, -1);
   if (rate <= 0) {
     // TODO: get the rate from the platform
-    rate = DEFAULT_FRAME_RATE;
+    rate = mThrottled ? DEFAULT_THROTTLED_FRAME_RATE : DEFAULT_FRAME_RATE;
   }
-  return 1000.0 / rate;
-}
-
-double
-nsRefreshDriver::GetThrottledTimerInterval() const
-{
-  int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1);
-  if (rate <= 0) {
-    rate = DEFAULT_THROTTLED_FRAME_RATE;
+  NS_ASSERTION(rate > 0, "Must have positive rate here");
+  int32_t interval = NSToIntRound(1000.0/rate);
+  if (mThrottled) {
+    interval = NS_MAX(interval, mLastTimerInterval * 2);
   }
-  return 1000.0 / rate;
+  mLastTimerInterval = interval;
+  return interval;
 }
 
-double
-nsRefreshDriver::GetRefreshTimerInterval() const
-{
-  return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval();
-}
-
-RefreshDriverTimer*
-nsRefreshDriver::ChooseTimer() const
+int32_t
+nsRefreshDriver::GetRefreshTimerType() const
 {
   if (mThrottled) {
-    if (!sThrottledRateTimer) 
-      sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(),
-                                                           DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0);
-    return sThrottledRateTimer;
+    return nsITimer::TYPE_ONE_SHOT;
   }
-
-  if (!sRegularRateTimer)
-    sRegularRateTimer = new PreciseRefreshDriverTimer(GetRegularTimerInterval());
-  return sRegularRateTimer;
+  if (HaveFrameRequestCallbacks() || sPrecisePref) {
+    return nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP;
+  }
+  return nsITimer::TYPE_REPEATING_SLACK;
 }
 
-nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
+nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext)
   : mPresContext(aPresContext),
-    mActiveTimer(nullptr),
     mFrozen(false),
     mThrottled(false),
     mTestControllingRefreshes(false),
+    mTimerIsPrecise(false),
     mViewManagerFlushIsPending(false),
-    mRequestedHighPrecision(false)
+    mLastTimerInterval(0)
 {
-  mMostRecentRefreshEpochTime = JS_Now();
-  mMostRecentRefresh = TimeStamp::Now();
-
   mRequests.Init();
 }
 
 nsRefreshDriver::~nsRefreshDriver()
 {
   NS_ABORT_IF_FALSE(ObserverCount() == 0,
                     "observers should have unregistered");
-  NS_ABORT_IF_FALSE(!mActiveTimer, "timer should be gone");
+  NS_ABORT_IF_FALSE(!mTimer, "timer should be gone");
   
   for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) {
     mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden();
   }
   mPresShellsToInvalidateIfHidden.Clear();
 }
 
 // Method for testing.  See nsIDOMWindowUtils.advanceTimeAndRefresh
 // for description.
 void
 nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds)
 {
-  // ensure that we're removed from our driver
-  StopTimer();
-
-  if (!mTestControllingRefreshes) {
-    mMostRecentRefreshEpochTime = JS_Now();
-    mMostRecentRefresh = TimeStamp::Now();
-
-    mTestControllingRefreshes = true;
-  }
-
+  mTestControllingRefreshes = true;
   mMostRecentRefreshEpochTime += aMilliseconds * 1000;
-  mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds);
-
+  mMostRecentRefresh += TimeDuration::FromMilliseconds(aMilliseconds);
   nsCxPusher pusher;
   if (pusher.PushNull()) {
-    DoTick();
+    Notify(nullptr);
     pusher.Pop();
   }
 }
 
 void
 nsRefreshDriver::RestoreNormalRefresh()
 {
   mTestControllingRefreshes = false;
-  EnsureTimerStarted(false);
+  nsCxPusher pusher;
+  if (pusher.PushNull()) {
+    Notify(nullptr); // will call UpdateMostRecentRefresh()
+    pusher.Pop();
+  }
 }
 
 TimeStamp
 nsRefreshDriver::MostRecentRefresh() const
 {
+  const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false);
+
   return mMostRecentRefresh;
 }
 
 int64_t
 nsRefreshDriver::MostRecentRefreshEpochTime() const
 {
+  const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false);
+
   return mMostRecentRefreshEpochTime;
 }
 
 bool
-nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
+nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver,
                                     mozFlushType aFlushType)
 {
   ObserverArray& array = ArrayFor(aFlushType);
   bool success = array.AppendElement(aObserver) != nullptr;
 
   EnsureTimerStarted(false);
 
   return success;
 }
 
 bool
-nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
+nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver,
                                        mozFlushType aFlushType)
 {
   ObserverArray& array = ArrayFor(aFlushType);
   return array.RemoveElement(aObserver);
 }
 
 bool
 nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
@@ -591,77 +190,57 @@ nsRefreshDriver::RemoveImageRequest(imgI
 void nsRefreshDriver::ClearAllImageRequests()
 {
   mRequests.Clear();
 }
 
 void
 nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer)
 {
-  if (mTestControllingRefreshes)
-    return;
-
-  // if the only change that's needed is that we need high precision,
-  // then just set that
-  if (!mThrottled && !mRequestedHighPrecision && mFrameRequestCallbackDocs.Length() > 0) {
-    SetHighPrecisionTimersEnabled(true);
-  } else if (mRequestedHighPrecision) {
-    SetHighPrecisionTimersEnabled(false);
-  }
-
-  // will it already fire, and no other changes needed?
-  if (mActiveTimer && !aAdjustingTimer)
-    return;
-
-  if (mFrozen || !mPresContext) {
-    // If we don't want to start it now, or we've been disconnected.
-    StopTimer();
+  if (mTimer || mFrozen || !mPresContext) {
+    // It's already been started, or we don't want to start it now or
+    // we've been disconnected.
     return;
   }
 
-  // we got here because we're either adjusting the time *or* we're
-  // starting it for the first time; make sure it's stopped.
-  StopTimer();
+  if (!aAdjustingTimer) {
+    // If we didn't already have a timer and aAdjustingTimer is false,
+    // then we just got our first observer (or an explicit call to
+    // MostRecentRefresh by a caller who's likely to add an observer
+    // shortly).  This means we should fake a most-recent-refresh time
+    // of now so that said observer gets a reasonable refresh time, so
+    // things behave as though the timer had always been running.
+    UpdateMostRecentRefresh();
+  }
 
-  mActiveTimer = ChooseTimer();
-  mActiveTimer->AddRefreshDriver(this);
+  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+  if (!mTimer) {
+    return;
+  }
+
+  int32_t timerType = GetRefreshTimerType();
+  mTimerIsPrecise = (timerType == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
+
+  nsresult rv = mTimer->InitWithCallback(this,
+                                         GetRefreshTimerInterval(),
+                                         timerType);
+  if (NS_FAILED(rv)) {
+    mTimer = nullptr;
+  }
 }
 
 void
 nsRefreshDriver::StopTimer()
 {
-  if (!mActiveTimer)
+  if (!mTimer) {
     return;
-
-  mActiveTimer->RemoveRefreshDriver(this);
-  mActiveTimer = nullptr;
-
-  if (mRequestedHighPrecision) {
-    SetHighPrecisionTimersEnabled(false);
   }
-}
 
-void
-nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable)
-{
-  if (aEnable) {
-    NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!");
-#ifdef XP_WIN
-    if (++sHighPrecisionTimerRequests == 1)
-      timeBeginPeriod(1);
-#endif
-    mRequestedHighPrecision = true;
-  } else {
-    NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!");
-#ifdef XP_WIN
-    if (--sHighPrecisionTimerRequests == 0)
-      timeEndPeriod(1);
-#endif
-    mRequestedHighPrecision = false;
-  }
+  mTimer->Cancel();
+  mTimer = nullptr;
 }
 
 uint32_t
 nsRefreshDriver::ObserverCount() const
 {
   uint32_t sum = 0;
   for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
     sum += mObservers[i].Length();
@@ -679,16 +258,28 @@ nsRefreshDriver::ObserverCount() const
 }
 
 uint32_t
 nsRefreshDriver::ImageRequestCount() const
 {
   return mRequests.Count();
 }
 
+void
+nsRefreshDriver::UpdateMostRecentRefresh()
+{
+  if (mTestControllingRefreshes) {
+    return;
+  }
+
+  // Call JS_Now first, since that can have nonzero latency in some rare cases.
+  mMostRecentRefreshEpochTime = JS_Now();
+  mMostRecentRefresh = TimeStamp::Now();
+}
+
 nsRefreshDriver::ObserverArray&
 nsRefreshDriver::ArrayFor(mozFlushType aFlushType)
 {
   switch (aFlushType) {
     case Flush_Style:
       return mObservers[0];
     case Flush_Layout:
       return mObservers[1];
@@ -699,88 +290,82 @@ nsRefreshDriver::ArrayFor(mozFlushType a
       return *static_cast<ObserverArray*>(nullptr);
   }
 }
 
 /*
  * nsISupports implementation
  */
 
-NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsISupports)
+NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsITimerCallback)
 
 /*
  * nsITimerCallback implementation
  */
 
-void
-nsRefreshDriver::DoTick()
+NS_IMETHODIMP
+nsRefreshDriver::Notify(nsITimer *aTimer)
 {
+  SAMPLE_LABEL("nsRefreshDriver", "Notify");
+
   NS_PRECONDITION(!mFrozen, "Why are we notified while frozen?");
   NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?");
   NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
                   "Shouldn't have a JSContext on the stack");
 
-  if (mTestControllingRefreshes) {
-    Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh);
-  } else {
-    Tick(JS_Now(), TimeStamp::Now());
+  if (mTestControllingRefreshes && aTimer) {
+    // Ignore real refreshes from our timer (but honor the others).
+    return NS_OK;
   }
-}
 
-void
-nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
-{
-  SAMPLE_LABEL("nsRefreshDriver", "Tick");
-
-  mMostRecentRefresh = aNowTime;
-  mMostRecentRefreshEpochTime = aNowEpoch;
+  UpdateMostRecentRefresh();
 
   nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
   if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
     // Things are being destroyed, or we no longer have any observers.
     // We don't want to stop the timer when observers are initially
     // removed, because sometimes observers can be added and removed
     // often depending on what other things are going on and in that
     // situation we don't want to thrash our timer.  So instead we
     // wait until we get a Notify() call when we have no observers
     // before stopping the timer.
     StopTimer();
-    return;
+    return NS_OK;
   }
 
   /*
    * The timer holds a reference to |this| while calling |Notify|.
    * However, implementations of |WillRefresh| are permitted to destroy
    * the pres context, which will cause our |mPresContext| to become
    * null.  If this happens, we must stop notifying observers.
    */
   for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
     ObserverArray::EndLimitedIterator etor(mObservers[i]);
     while (etor.HasMore()) {
       nsRefPtr<nsARefreshObserver> obs = etor.GetNext();
-      obs->WillRefresh(aNowTime);
+      obs->WillRefresh(mMostRecentRefresh);
       
       if (!mPresContext || !mPresContext->GetPresShell()) {
         StopTimer();
-        return;
+        return NS_OK;
       }
     }
 
     if (i == 0) {
       // Grab all of our frame request callbacks up front.
       nsIDocument::FrameRequestCallbackList frameRequestCallbacks;
       for (uint32_t i = 0; i < mFrameRequestCallbackDocs.Length(); ++i) {
         mFrameRequestCallbackDocs[i]->
           TakeFrameRequestCallbacks(frameRequestCallbacks);
       }
       // OK, now reset mFrameRequestCallbackDocs so they can be
       // readded as needed.
       mFrameRequestCallbackDocs.Clear();
 
-      int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC;
+      int64_t eventTime = mMostRecentRefreshEpochTime / PR_USEC_PER_MSEC;
       for (uint32_t i = 0; i < frameRequestCallbacks.Length(); ++i) {
         nsAutoMicroTask mt;
         frameRequestCallbacks[i]->Sample(eventTime);
       }
 
       // This is the Flush_Style case.
       if (mPresContext && mPresContext->GetPresShell()) {
         nsAutoTArray<nsIPresShell*, 16> observers;
@@ -822,19 +407,20 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
     }
   }
 
   /*
    * Perform notification to imgIRequests subscribed to listen
    * for refresh events.
    */
 
-  ImageRequestParameters parms = {aNowTime};
+  ImageRequestParameters parms = {mMostRecentRefresh};
   if (mRequests.Count()) {
     mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms);
+    EnsureTimerStarted(false);
   }
     
   for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) {
     mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden();
   }
   mPresShellsToInvalidateIfHidden.Clear();
 
   if (mViewManagerFlushIsPending) {
@@ -842,16 +428,35 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
     printf("Starting ProcessPendingUpdates\n");
 #endif
     mViewManagerFlushIsPending = false;
     mPresContext->GetPresShell()->GetViewManager()->ProcessPendingUpdates();
 #ifdef DEBUG_INVALIDATIONS
     printf("Ending ProcessPendingUpdates\n");
 #endif
   }
+
+  if (mThrottled ||
+      (mTimerIsPrecise !=
+       (GetRefreshTimerType() == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP))) {
+    // Stop the timer now and restart it here.  Stopping is in the mThrottled
+    // case ok because either it's already one-shot, and it just fired, and all
+    // we need to do is null it out, or it's repeating and we need to reset it
+    // to be one-shot.  Stopping and restarting in the case when we need to
+    // switch from precise to slack timers or vice versa is unfortunately
+    // required.
+
+    // Note that the EnsureTimerStarted() call here is ok because
+    // EnsureTimerStarted makes sure to not start the timer if it shouldn't be
+    // started.
+    StopTimer();
+    EnsureTimerStarted(true);
+  }
+
+  return NS_OK;
 }
 
 PLDHashOperator
 nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
                                         void* aUserArg)
 {
   ImageRequestParameters* parms =
     static_cast<ImageRequestParameters*> (aUserArg);
@@ -890,36 +495,37 @@ nsRefreshDriver::Thaw()
   }
 }
 
 void
 nsRefreshDriver::SetThrottled(bool aThrottled)
 {
   if (aThrottled != mThrottled) {
     mThrottled = aThrottled;
-    if (mActiveTimer) {
+    if (mTimer) {
       // We want to switch our timer type here, so just stop and
       // restart the timer.
+      StopTimer();
       EnsureTimerStarted(true);
     }
   }
 }
 
 void
 nsRefreshDriver::DoRefresh()
 {
   // Don't do a refresh unless we're in a state where we should be refreshing.
-  if (!mFrozen && mPresContext && mActiveTimer) {
-    DoTick();
+  if (!mFrozen && mPresContext && mTimer) {
+    Notify(nullptr);
   }
 }
 
 #ifdef DEBUG
 bool
-nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
+nsRefreshDriver::IsRefreshObserver(nsARefreshObserver *aObserver,
                                    mozFlushType aFlushType)
 {
   ObserverArray& array = ArrayFor(aFlushType);
   return array.Contains(aObserver);
 }
 #endif
 
 void
@@ -933,18 +539,18 @@ nsRefreshDriver::ScheduleViewManagerFlus
 
 void
 nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument)
 {
   NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
                mFrameRequestCallbackDocs.NoIndex,
                "Don't schedule the same document multiple times");
   mFrameRequestCallbackDocs.AppendElement(aDocument);
-
-  // make sure that the timer is running
+  // No need to worry about restarting our timer in precise mode if it's
+  // already running; that will happen automatically when it fires.
   EnsureTimerStarted(false);
 }
 
 void
 nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
 {
   mFrameRequestCallbackDocs.RemoveElement(aDocument);
   // No need to worry about restarting our timer in slack mode if it's already
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -1,24 +1,24 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Code to notify things that animate before a refresh, at an appropriate
  * refresh rate.  (Perhaps temporary, until replaced by compositor.)
  */
 
 #ifndef nsRefreshDriver_h_
 #define nsRefreshDriver_h_
 
 #include "mozilla/TimeStamp.h"
 #include "mozFlushType.h"
+#include "nsITimer.h"
 #include "nsCOMPtr.h"
 #include "nsTObserverArray.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "mozilla/Attributes.h"
 
@@ -27,56 +27,49 @@ class nsIPresShell;
 class nsIDocument;
 class imgIRequest;
 
 /**
  * An abstract base class to be implemented by callers wanting to be
  * notified at refresh times.  When nothing needs to be painted, callers
  * may not be notified.
  */
-namespace mozilla {
-    class RefreshDriverTimer;
-}
-
 class nsARefreshObserver {
 public:
   // AddRef and Release signatures that match nsISupports.  Implementors
   // must implement reference counting, and those that do implement
   // nsISupports will already have methods with the correct signature.
   //
   // The refresh driver does NOT hold references to refresh observers
   // except while it is notifying them.
   NS_IMETHOD_(nsrefcnt) AddRef(void) = 0;
   NS_IMETHOD_(nsrefcnt) Release(void) = 0;
 
   virtual void WillRefresh(mozilla::TimeStamp aTime) = 0;
 };
 
-class nsRefreshDriver MOZ_FINAL : public nsISupports {
+class nsRefreshDriver MOZ_FINAL : public nsITimerCallback {
 public:
   nsRefreshDriver(nsPresContext *aPresContext);
   ~nsRefreshDriver();
 
   static void InitializeStatics();
-  static void Shutdown();
 
   // nsISupports implementation
   NS_DECL_ISUPPORTS
 
+  // nsITimerCallback implementation
+  NS_DECL_NSITIMERCALLBACK
+
   /**
    * Methods for testing, exposed via nsIDOMWindowUtils.  See
    * nsIDOMWindowUtils.advanceTimeAndRefresh for description.
    */
   void AdvanceTimeAndRefresh(int64_t aMilliseconds);
   void RestoreNormalRefresh();
-  void DoTick();
-  bool IsTestControllingRefreshesEnabled() const
-  {
-    return mTestControllingRefreshes;
-  }
 
   /**
    * Return the time of the most recent refresh.  This is intended to be
    * used by callers who want to start an animation now and want to know
    * what time to consider the start of the animation.  (This helps
    * ensure that multiple animations started during the same event off
    * the main event loop have the same start time.)
    */
@@ -228,65 +221,65 @@ public:
    * Default interval the refresh driver uses, in ms.
    */
   static int32_t DefaultInterval();
 
 private:
   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
   typedef nsTHashtable<nsISupportsHashKey> RequestTable;
 
-  void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);
-
   void EnsureTimerStarted(bool aAdjustingTimer);
   void StopTimer();
 
   uint32_t ObserverCount() const;
   uint32_t ImageRequestCount() const;
   static PLDHashOperator ImageRequestEnumerator(nsISupportsHashKey* aEntry,
                                           void* aUserArg);
+  void UpdateMostRecentRefresh();
   ObserverArray& ArrayFor(mozFlushType aFlushType);
   // Trigger a refresh immediately, if haven't been disconnected or frozen.
   void DoRefresh();
 
-  double GetRefreshTimerInterval() const;
-  double GetRegularTimerInterval() const;
-  double GetThrottledTimerInterval() const;
+  int32_t GetRefreshTimerInterval() const;
+  int32_t GetRefreshTimerType() const;
 
   bool HaveFrameRequestCallbacks() const {
     return mFrameRequestCallbackDocs.Length() != 0;
   }
 
-  mozilla::RefreshDriverTimer* ChooseTimer() const;
-  mozilla::RefreshDriverTimer *mActiveTimer;
+  nsCOMPtr<nsITimer> mTimer;
+  mozilla::TimeStamp mMostRecentRefresh; // only valid when mTimer non-null
+  int64_t mMostRecentRefreshEpochTime;   // same thing as mMostRecentRefresh,
+                                         // but in microseconds since the epoch.
 
   nsPresContext *mPresContext; // weak; pres context passed in constructor
                                // and unset in Disconnect
 
   bool mFrozen;
   bool mThrottled;
   bool mTestControllingRefreshes;
+  /* If mTimer is non-null, this boolean indicates whether the timer is
+     a precise timer.  If mTimer is null, this boolean's value can be
+     anything.  */
+  bool mTimerIsPrecise;
   bool mViewManagerFlushIsPending;
-  bool mRequestedHighPrecision;
-
-  int64_t mMostRecentRefreshEpochTime;
-  mozilla::TimeStamp mMostRecentRefresh;
 
   // separate arrays for each flush type we support
   ObserverArray mObservers[3];
   RequestTable mRequests;
 
   nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mPresShellsToInvalidateIfHidden;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray<nsIDocument*> mFrameRequestCallbackDocs;
 
+  // This is the last interval we used for our timer.  May be 0 if we
+  // haven't computed a timer interval yet.
+  mutable int32_t mLastTimerInterval;
+
   // Helper struct for processing image requests
   struct ImageRequestParameters {
       mozilla::TimeStamp ts;
   };
-
-  friend class mozilla::RefreshDriverTimer;
-
-  void SetHighPrecisionTimersEnabled(bool aEnable);
 };
 
 #endif /* !defined(nsRefreshDriver_h_) */
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -356,11 +356,9 @@ nsLayoutStatics::Shutdown()
 
   nsLayoutUtils::Shutdown();
 
   nsHyphenationManager::Shutdown();
   nsEditorSpellCheck::ShutDown();
   nsDOMMutationObserver::Shutdown();
 
   ContentParent::ShutDown();
-
-  nsRefreshDriver::Shutdown();
 }
--- a/xpcom/ds/TimeStamp.h
+++ b/xpcom/ds/TimeStamp.h
@@ -78,28 +78,16 @@ public:
   TimeDuration& operator+=(const TimeDuration& aOther) {
     mValue += aOther.mValue;
     return *this;
   }
   TimeDuration& operator-=(const TimeDuration& aOther) {
     mValue -= aOther.mValue;
     return *this;
   }
-  TimeDuration operator*(const double aMultiplier) const {
-    return TimeDuration::FromTicks(mValue * int64_t(aMultiplier));
-  }
-  TimeDuration operator*(const int32_t aMultiplier) const {
-    return TimeDuration::FromTicks(mValue * int64_t(aMultiplier));
-  }
-  TimeDuration operator*(const uint32_t aMultiplier) const {
-    return TimeDuration::FromTicks(mValue * int64_t(aMultiplier));
-  }
-  TimeDuration operator*(const int64_t aMultiplier) const {
-    return TimeDuration::FromTicks(mValue * int64_t(aMultiplier));
-  }
   double operator/(const TimeDuration& aOther) {
     return static_cast<double>(mValue) / aOther.mValue;
   }
 
   bool operator<(const TimeDuration& aOther) const {
     return mValue < aOther.mValue;
   }
   bool operator<=(const TimeDuration& aOther) const {