Bug 666446, Part 2/18 - Change nsRefreshDriver to accept imgIRequest objects to facilitate refresh driver-based animations. [r=roc]
authorScott Johnson <sjohnson@mozilla.com>
Mon, 03 Oct 2011 13:39:05 -0700
changeset 78453 a48ccb52c13797917f1a5f7193597cce645a57a8
parent 78452 1da3cfb954b12ce0447b2cc49e7bef72fb127fe3
child 78454 a476b7988f63bb5b3dbb05167c8e82347957e30a
push idunknown
push userunknown
push dateunknown
reviewersroc
bugs666446
milestone10.0a1
Bug 666446, Part 2/18 - Change nsRefreshDriver to accept imgIRequest objects to facilitate refresh driver-based animations. [r=roc]
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -107,16 +107,17 @@ nsRefreshDriver::GetRefreshTimerType() c
 nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext)
   : mPresContext(aPresContext),
     mFrozen(false),
     mThrottled(false),
     mTestControllingRefreshes(false),
     mTimerIsPrecise(false),
     mLastTimerInterval(0)
 {
+  mRequests.Init();
 }
 
 nsRefreshDriver::~nsRefreshDriver()
 {
   NS_ABORT_IF_FALSE(ObserverCount() == 0,
                     "observers should have unregistered");
   NS_ABORT_IF_FALSE(!mTimer, "timer should be gone");
 }
@@ -178,16 +179,39 @@ nsRefreshDriver::AddRefreshObserver(nsAR
 bool
 nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver,
                                        mozFlushType aFlushType)
 {
   ObserverArray& array = ArrayFor(aFlushType);
   return array.RemoveElement(aObserver);
 }
 
+bool
+nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
+{
+  if (!mRequests.PutEntry(aRequest)) {
+    return false;
+  }
+
+  EnsureTimerStarted(false);
+
+  return true;
+}
+
+void
+nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
+{
+  mRequests.RemoveEntry(aRequest);
+}
+
+void nsRefreshDriver::ClearAllImageRequests()
+{
+  mRequests.Clear();
+}
+
 void
 nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer)
 {
   if (mTimer || mFrozen || !mPresContext) {
     // It's already been started, or we don't want to start it now or
     // we've been disconnected.
     return;
   }
@@ -231,27 +255,34 @@ nsRefreshDriver::StopTimer()
 
 PRUint32
 nsRefreshDriver::ObserverCount() const
 {
   PRUint32 sum = 0;
   for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(mObservers); ++i) {
     sum += mObservers[i].Length();
   }
+
   // Even while throttled, we need to process layout and style changes.  Style
   // changes can trigger transitions which fire events when they complete, and
   // layout changes can affect media queries on child documents, triggering
   // style changes, etc.
   sum += mStyleFlushObservers.Length();
   sum += mLayoutFlushObservers.Length();
   sum += mBeforePaintTargets.Length();
   sum += mAnimationFrameListenerDocs.Length();
   return sum;
 }
 
+PRUint32
+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.
@@ -296,17 +327,17 @@ nsRefreshDriver::Notify(nsITimer *aTimer
   if (mTestControllingRefreshes && aTimer) {
     // Ignore real refreshes from our timer (but honor the others).
     return NS_OK;
   }
 
   UpdateMostRecentRefresh();
 
   nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
-  if (!presShell || ObserverCount() == 0) {
+  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();
@@ -325,16 +356,17 @@ nsRefreshDriver::Notify(nsITimer *aTimer
       nsRefPtr<nsARefreshObserver> obs = etor.GetNext();
       obs->WillRefresh(mMostRecentRefresh);
       
       if (!mPresContext || !mPresContext->GetPresShell()) {
         StopTimer();
         return NS_OK;
       }
     }
+
     if (i == 0) {
       // Don't just loop while we have things in mBeforePaintTargets,
       // the whole point is that event handlers should readd the
       // target as needed.
       nsTArray< nsCOMPtr<nsIDocument> > targets;
       targets.SwapElements(mBeforePaintTargets);
       for (PRUint32 i = 0; i < targets.Length(); ++i) {
         targets[i]->BeforePaintEventFiring();
@@ -397,16 +429,27 @@ nsRefreshDriver::Notify(nsITimer *aTimer
           shell->mSuppressInterruptibleReflows = PR_FALSE;
           shell->FlushPendingNotifications(Flush_InterruptibleLayout);
           NS_RELEASE(shell);
         }
       }
     }
   }
 
+  /*
+   * Perform notification to imgIRequests subscribed to listen
+   * for refresh events.
+   */
+
+  ImageRequestParameters parms = {mMostRecentRefresh};
+  if (mRequests.Count()) {
+    mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms);
+    EnsureTimerStarted(false);
+  }
+
   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
@@ -417,30 +460,48 @@ nsRefreshDriver::Notify(nsITimer *aTimer
     // started.
     StopTimer();
     EnsureTimerStarted(true);
   }
 
   return NS_OK;
 }
 
+PLDHashOperator
+nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
+                                        void* aUserArg)
+{
+  ImageRequestParameters* parms =
+    static_cast<ImageRequestParameters*> (aUserArg);
+  mozilla::TimeStamp mostRecentRefresh = parms->ts;
+  imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey());
+  NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request");
+  nsCOMPtr<imgIContainer> image;
+  req->GetImage(getter_AddRefs(image));
+  if (image) {
+    image->RequestRefresh(mostRecentRefresh);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 void
 nsRefreshDriver::Freeze()
 {
   NS_ASSERTION(!mFrozen, "Freeze called on already-frozen refresh driver");
   StopTimer();
   mFrozen = true;
 }
 
 void
 nsRefreshDriver::Thaw()
 {
   NS_ASSERTION(mFrozen, "Thaw called on an unfrozen refresh driver");
   mFrozen = false;
-  if (ObserverCount()) {
+  if (ObserverCount() || ImageRequestCount()) {
     // FIXME: This isn't quite right, since our EnsureTimerStarted call
     // updates our mMostRecentRefresh, but the DoRefresh call won't run
     // and notify our observers until we get back to the event loop.
     // Thus MostRecentRefresh() will lie between now and the DoRefresh.
     NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
     EnsureTimerStarted(false);
   }
 }
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -45,20 +45,23 @@
 
 #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"
 
 class nsPresContext;
 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.
  */
 class nsARefreshObserver {
 public:
@@ -124,16 +127,34 @@ public:
    * they must remove themselves before they are destroyed.
    */
   bool AddRefreshObserver(nsARefreshObserver *aObserver,
                             mozFlushType aFlushType);
   bool RemoveRefreshObserver(nsARefreshObserver *aObserver,
                                mozFlushType aFlushType);
 
   /**
+   * Add/Remove imgIRequest versions of observers.
+   *
+   * These are used for hooking into the refresh driver for
+   * controlling animated images.
+   *
+   * @note The refresh driver owns a reference to these listeners.
+   *
+   * @note Technically, imgIRequest objects are not nsARefreshObservers, but
+   * for controlling animated image repaint events, we subscribe the
+   * imgIRequests to the nsRefreshDriver for notification of paint events.
+   *
+   * @returns whether the operation succeeded, or void in the case of removal.
+   */
+  bool AddImageRequest(imgIRequest* aRequest);
+  void RemoveImageRequest(imgIRequest* aRequest);
+  void ClearAllImageRequests();
+
+  /**
    * Add / remove presshells that we should flush style and layout on
    */
   bool AddStyleFlushObserver(nsIPresShell* aShell) {
     NS_ASSERTION(!mStyleFlushObservers.Contains(aShell),
 		 "Double-adding style flush observer");
     bool appended = mStyleFlushObservers.AppendElement(aShell) != nsnull;
     EnsureTimerStarted(false);
     return appended;
@@ -213,20 +234,25 @@ public:
    * Check whether the given observer is an observer for the given flush type
    */
   bool IsRefreshObserver(nsARefreshObserver *aObserver,
 			   mozFlushType aFlushType);
 #endif
 
 private:
   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
+  typedef nsTHashtable<nsISupportsHashKey> RequestTable;
 
   void EnsureTimerStarted(bool aAdjustingTimer);
   void StopTimer();
+
   PRUint32 ObserverCount() const;
+  PRUint32 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();
 
   PRInt32 GetRefreshTimerInterval() const;
   PRInt32 GetRefreshTimerType() const;
 
@@ -247,21 +273,28 @@ private:
   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;
 
   // separate arrays for each flush type we support
   ObserverArray mObservers[3];
+  RequestTable mRequests;
+
   nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray< nsCOMPtr<nsIDocument> > mBeforePaintTargets;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray<nsIDocument*> mAnimationFrameListenerDocs;
 
   // This is the last interval we used for our timer.  May be 0 if we
   // haven't computed a timer interval yet.
   mutable PRInt32 mLastTimerInterval;
+
+  // Helper struct for processing image requests
+  struct ImageRequestParameters {
+      mozilla::TimeStamp ts;
+  };
 };
 
 #endif /* !defined(nsRefreshDriver_h_) */