Bug 630127. Use a precise timer when we have animation frame callbacks. r=dbaron
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 07 Mar 2011 11:58:48 -0500
changeset 63688 fb9913c4e92a435c5c2128a2da50a972b7755a02
parent 63687 f0f1632c19e825546389438f2e507c929afb017c
child 63689 feee7408eacd447d4fdd70e467e58679a2c49ab6
push idunknown
push userunknown
push dateunknown
reviewersdbaron
bugs630127
milestone2.0b13pre
Bug 630127. Use a precise timer when we have animation frame callbacks. r=dbaron
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
layout/build/nsLayoutStatics.cpp
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -53,16 +53,25 @@
 #include "jsapi.h"
 #include "nsContentUtils.h"
 
 using mozilla::TimeStamp;
 
 #define DEFAULT_FRAME_RATE 60
 #define DEFAULT_THROTTLED_FRAME_RATE 1
 
+static PRBool sPrecisePref;
+
+/* static */ void
+nsRefreshDriver::InitializeStatics()
+{
+  nsContentUtils::AddBoolPrefVarCache("layout.frame_rate.precise",
+                                      &sPrecisePref,
+                                      PR_FALSE);
+}
 // Compute the interval to use for the refresh driver timer, in
 // milliseconds
 PRInt32
 nsRefreshDriver::GetRefreshTimerInterval() const
 {
   const char* prefName =
     mThrottled ? "layout.throttled_frame_rate" : "layout.frame_rate";
   PRInt32 rate = nsContentUtils::GetIntPref(prefName, -1);
@@ -80,26 +89,27 @@ nsRefreshDriver::GetRefreshTimerInterval
 }
 
 PRInt32
 nsRefreshDriver::GetRefreshTimerType() const
 {
   if (mThrottled) {
     return nsITimer::TYPE_ONE_SHOT;
   }
-  PRBool precise =
-    nsContentUtils::GetBoolPref("layout.frame_rate.precise", PR_FALSE);
-  return precise ? (PRInt32)nsITimer::TYPE_REPEATING_PRECISE
-                 : (PRInt32)nsITimer::TYPE_REPEATING_SLACK;
+  if (HaveAnimationFrameListeners() || sPrecisePref) {
+    return nsITimer::TYPE_REPEATING_PRECISE;
+  }
+  return nsITimer::TYPE_REPEATING_SLACK;
 }
 
 nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext)
   : mPresContext(aPresContext),
     mFrozen(false),
-    mThrottled(false)
+    mThrottled(false),
+    mTimerIsPrecise(false)
 {
 }
 
 nsRefreshDriver::~nsRefreshDriver()
 {
   NS_ABORT_IF_FALSE(ObserverCount() == 0,
                     "observers should have unregistered");
   NS_ABORT_IF_FALSE(!mTimer, "timer should be gone");
@@ -152,19 +162,22 @@ nsRefreshDriver::EnsureTimerStarted()
 
   UpdateMostRecentRefresh();
 
   mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   if (!mTimer) {
     return;
   }
 
+  PRInt32 timerType = GetRefreshTimerType();
+  mTimerIsPrecise = (timerType == nsITimer::TYPE_REPEATING_PRECISE);
+
   nsresult rv = mTimer->InitWithCallback(this,
                                          GetRefreshTimerInterval(),
-                                         GetRefreshTimerType());
+                                         timerType);
   if (NS_FAILED(rv)) {
     mTimer = nsnull;
   }
 }
 
 void
 nsRefreshDriver::StopTimer()
 {
@@ -333,22 +346,29 @@ nsRefreshDriver::Notify(nsITimer * /* un
           shell->mSuppressInterruptibleReflows = PR_FALSE;
           shell->FlushPendingNotifications(Flush_InterruptibleLayout);
           NS_RELEASE(shell);
         }
       }
     }
   }
 
-  if (mThrottled) {
-    // Stop the timer now and restart it here.  Stopping is 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.  Note
-    // that the EnsureTimerStarted() call here is ok because EnsureTimerStarted
-    // makes sure to not start the timer if it shouldn't be started.
+  if (mThrottled ||
+      (mTimerIsPrecise !=
+       (GetRefreshTimerType() == nsITimer::TYPE_REPEATING_PRECISE))) {
+    // 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();
   }
 
   return NS_OK;
 }
 
 void
@@ -416,22 +436,26 @@ nsRefreshDriver::ScheduleBeforePaintEven
 
 void
 nsRefreshDriver::ScheduleAnimationFrameListeners(nsIDocument* aDocument)
 {
   NS_ASSERTION(mAnimationFrameListenerDocs.IndexOf(aDocument) ==
                mAnimationFrameListenerDocs.NoIndex,
                "Don't schedule the same document multiple times");
   mAnimationFrameListenerDocs.AppendElement(aDocument);
+  // No need to worry about restarting our timer in precise mode if it's
+  // already running; that will happen automatically when it fires.
   EnsureTimerStarted();
 }
 
 void
 nsRefreshDriver::RevokeBeforePaintEvent(nsIDocument* aDocument)
 {
   mBeforePaintTargets.RemoveElement(aDocument);
 }
 
 void
 nsRefreshDriver::RevokeAnimationFrameListeners(nsIDocument* aDocument)
 {
   mAnimationFrameListenerDocs.RemoveElement(aDocument);
+  // No need to worry about restarting our timer in slack mode if it's already
+  // running; that will happen automatically when it fires.
 }
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -74,16 +74,18 @@ public:
   virtual void WillRefresh(mozilla::TimeStamp aTime) = 0;
 };
 
 class nsRefreshDriver : public nsITimerCallback {
 public:
   nsRefreshDriver(nsPresContext *aPresContext);
   ~nsRefreshDriver();
 
+  static void InitializeStatics();
+
   // nsISupports implementation
   NS_DECL_ISUPPORTS
 
   // nsITimerCallback implementation
   NS_DECL_NSITIMERCALLBACK
 
   /**
    * Return the time of the most recent refresh.  This is intended to be
@@ -216,26 +218,34 @@ private:
   void UpdateMostRecentRefresh();
   ObserverArray& ArrayFor(mozFlushType aFlushType);
   // Trigger a refresh immediately, if haven't been disconnected or frozen.
   void DoRefresh();
 
   PRInt32 GetRefreshTimerInterval() const;
   PRInt32 GetRefreshTimerType() const;
 
+  bool HaveAnimationFrameListeners() const {
+    return mAnimationFrameListenerDocs.Length() != 0;
+  }
+
   nsCOMPtr<nsITimer> mTimer;
   mozilla::TimeStamp mMostRecentRefresh; // only valid when mTimer non-null
   PRInt64 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;
+  /* 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;
 
   // separate arrays for each flush type we support
   ObserverArray mObservers[3];
   nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray<nsIDocument*> mBeforePaintTargets;
   // nsTArray on purpose, because we want to be able to swap.
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -123,16 +123,17 @@
 #endif
 
 #include "nsError.h"
 
 #include "nsCycleCollector.h"
 #include "nsJSEnvironment.h"
 #include "nsContentSink.h"
 #include "nsFrameMessageManager.h"
+#include "nsRefreshDriver.h"
 
 extern void NS_ShutdownChainItemPool();
 
 nsrefcnt nsLayoutStatics::sLayoutStaticRefcnt = 0;
 
 nsresult
 nsLayoutStatics::Initialize()
 {
@@ -268,16 +269,17 @@ nsLayoutStatics::Initialize()
 
 #ifdef MOZ_SYDNEYAUDIO
   nsAudioStream::InitLibrary();
 #endif
 
   nsContentSink::InitializeStatics();
   nsHtml5Module::InitializeStatics();
   nsIPresShell::InitializeStatics();
+  nsRefreshDriver::InitializeStatics();
 
   nsCrossSiteListenerProxy::Startup();
 
   rv = nsFrameList::Init();
   if (NS_FAILED(rv)) {
     NS_ERROR("Could not initialize nsFrameList");
     return rv;
   }