Bug 373266 - base support for animated theme widgets to nsnativetheme. r=roc
authorJim Mathies <jmathies@mozilla.com>
Wed, 07 Mar 2012 09:29:21 -0600
changeset 91322 5d43b850791530388064dee8cfe82b8d0edf753b
parent 91321 44026e95ae904a3e7b59b23968b16a587497c521
child 91323 9b80fa3e587d3ad93e357f0c21a3667a45dd08c7
push id783
push userlsblakk@mozilla.com
push dateTue, 24 Apr 2012 17:33:42 +0000
treeherdermozilla-beta@11faed19f136 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs373266
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 373266 - base support for animated theme widgets to nsnativetheme. r=roc
widget/gtk2/nsNativeThemeGTK.cpp
widget/gtk2/nsNativeThemeGTK.h
widget/xpwidgets/nsNativeTheme.cpp
widget/xpwidgets/nsNativeTheme.h
--- a/widget/gtk2/nsNativeThemeGTK.cpp
+++ b/widget/gtk2/nsNativeThemeGTK.cpp
@@ -62,52 +62,47 @@
 
 #include <gdk/gdkprivate.h>
 #include <gtk/gtk.h>
 
 #include "gfxContext.h"
 #include "gfxPlatformGtk.h"
 #include "gfxGdkNativeRenderer.h"
 
-NS_IMPL_ISUPPORTS_INHERITED2(nsNativeThemeGTK, nsNativeTheme, nsITheme,
-                                                              nsIObserver)
+NS_IMPL_ISUPPORTS_INHERITED1(nsNativeThemeGTK, nsNativeTheme, nsITheme)
 
 static int gLastGdkError;
 
 nsNativeThemeGTK::nsNativeThemeGTK()
 {
   if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
     memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
     return;
   }
 
   // We have to call moz_gtk_shutdown before the event loop stops running.
   nsCOMPtr<nsIObserverService> obsServ =
     mozilla::services::GetObserverService();
-  obsServ->AddObserver(this, "xpcom-shutdown", false);
+  obsServ->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
 
   memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
   memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
 }
 
 nsNativeThemeGTK::~nsNativeThemeGTK() {
 }
 
 NS_IMETHODIMP
 nsNativeThemeGTK::Observe(nsISupports *aSubject, const char *aTopic,
                           const PRUnichar *aData)
 {
-  if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+  if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     moz_gtk_shutdown();
-  } else {
-    NS_NOTREACHED("unexpected topic");
-    return NS_ERROR_UNEXPECTED;
   }
-
-  return NS_OK;
+  return nsNativeTheme::Observe(aSubject, aTopic, aData);
 }
 
 void
 nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame)
 {
   nsIPresShell *shell = GetPresShell(aFrame);
   if (!shell)
     return;
--- a/widget/gtk2/nsNativeThemeGTK.h
+++ b/widget/gtk2/nsNativeThemeGTK.h
@@ -41,22 +41,22 @@
 #include "nsIAtom.h"
 #include "nsIObserver.h"
 #include "nsNativeTheme.h"
 
 #include <gtk/gtk.h>
 #include "gtkdrawing.h"
 
 class nsNativeThemeGTK: private nsNativeTheme,
-                        public nsITheme,
-                        public nsIObserver {
+                        public nsITheme {
 public:
   NS_DECL_ISUPPORTS_INHERITED
-
-  NS_DECL_NSIOBSERVER
+  // Also implemented by nsNativeTheme
+  NS_SCRIPTABLE NS_IMETHOD Observe(nsISupports *aSubject, const char * aTopic,
+                                   const PRUnichar * aData); 
 
   // The nsITheme interface.
   NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext,
                                   nsIFrame* aFrame, PRUint8 aWidgetType,
                                   const nsRect& aRect,
                                   const nsRect& aDirtyRect);
 
   NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
--- a/widget/xpwidgets/nsNativeTheme.cpp
+++ b/widget/xpwidgets/nsNativeTheme.cpp
@@ -49,23 +49,25 @@
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIDOMXULMenuListElement.h"
 #include "nsThemeConstants.h"
 #include "nsIComponentManager.h"
 #include "nsPIDOMWindow.h"
 #include "nsProgressFrame.h"
 #include "nsMenuFrame.h"
 #include "mozilla/dom/Element.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
 
 nsNativeTheme::nsNativeTheme()
 : mAnimatedContentTimeout(PR_UINT32_MAX)
 {
 }
 
-NS_IMPL_ISUPPORTS1(nsNativeTheme, nsITimerCallback)
+NS_IMPL_ISUPPORTS2(nsNativeTheme, nsITimerCallback, nsIObserver)
 
 nsIPresShell *
 nsNativeTheme::GetPresShell(nsIFrame* aFrame)
 {
   if (!aFrame)
     return nsnull;
 
   // this is a workaround for the egcs 1.1.2 not inliningg
@@ -560,37 +562,257 @@ nsNativeTheme::QueueAnimatedContentForRe
   if (!mAnimatedContentList.AppendElement(aContent)) {
     NS_WARNING("Out of memory!");
     return false;
   }
 
   return true;
 }
 
+inline bool
+IsFadeIn(nsNativeTheme::FadeState aState)
+{
+  return (aState == nsNativeTheme::FADE_IN ||
+          aState == nsNativeTheme::FADE_IN_FINISHED);
+}
+
+inline bool
+IsFadeOut(nsNativeTheme::FadeState aState)
+{
+  return (aState == nsNativeTheme::FADE_OUT);
+}
+
+bool
+nsNativeTheme::QueueAnimatedContentRefreshForFade(nsIContent* aContent,
+                                                  FadeState aFadeDirection,
+                                                  PRUint32 aMinimumFrameRate,
+                                                  PRUint32 aMilliseconds,
+                                                  PRUint32 aUserData)
+{
+  NS_ASSERTION(aContent, "Null pointer!");
+  NS_ASSERTION((aFadeDirection == FADE_IN ||
+                aFadeDirection == FADE_OUT), "Bad initial fade direction.");
+
+  // Initialize our hash table and setup an observer for freeing its contents
+  // on shutdown.
+  if (NS_FAILED(InitFadeList()))
+    return false;
+
+  // Note, QueueAnimatedContentForRefresh failures in here can result in
+  // content getting stuck in mAnimatedFadesList until shutdown, so we
+  // warn loudly. Generally this should never happen.
+
+  FadeData* pFade = mAnimatedFadesList.Get(aContent);
+  if (pFade) {
+    // Update the user data
+    pFade->SetUserData(aUserData);
+
+    // Check for direction changes and update our fade data accordingly.
+    if (IsFadeIn(pFade->GetState()) != IsFadeIn(aFadeDirection)) {
+      if (pFade->GetState() != FADE_IN_FINISHED) {
+        // The amount of time we spent getting here equals the amount of
+        // time we spend getting back out.
+        pFade->Reset(pFade->TimeoutUsed(), aFadeDirection);
+      } else {
+        // Reset to transition timeout passed in.
+        //PRUint32 timeout =
+        //  PR_IntervalToMilliseconds(PR_IntervalNow()) + aMilliseconds;
+        //pFade->Reset(timeout, aFadeDirection);
+        pFade->Reset(TimeDuration::FromMilliseconds(aMilliseconds),
+                     aFadeDirection);
+      }
+    }
+
+    // Check for a timeout
+    if (pFade->GetTimeout() < TimeStamp::Now()) {
+      // If timed out and it's a fade up, set state to finished. We keep the
+      // fade data around until a corresponding fade out completes or the
+      // underlying frame is destroyed.
+      if (IsFadeIn(pFade->GetState())) {
+        pFade->FadeInFinished();
+        // Create a heartbeat (1 sec) animation timer so if the underlying
+        // frame is destroyed, Notify will free the content.
+        if (!QueueAnimatedContentForRefresh(aContent, 1)) {
+          NS_WARNING("QueueAnimatedContentForRefresh failed???");
+          return false;
+        }
+      } else if (IsFadeOut(pFade->GetState())) {
+        // If timed out and it's a fade out, clear it, we're done.
+        mAnimatedFadesList.Remove(aContent);
+        // Fire one last time to get the base graphic painted.
+        if (!QueueAnimatedContentForRefresh(aContent, aMinimumFrameRate)) {
+          NS_WARNING("QueueAnimatedContentForRefresh failed???");
+          return false;
+        }
+      }
+    } else {
+      // fading..
+      if (!QueueAnimatedContentForRefresh(aContent, aMinimumFrameRate)) {
+        NS_WARNING("QueueAnimatedContentForRefresh failed???");
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // If we don't have a fade put together a FadeData, store it in
+  // mAnimatedFadesList, and kick things off.
+  TimeStamp timeout = TimeStamp::Now() +
+    TimeDuration::FromMilliseconds(aMilliseconds);
+  nsAutoPtr<FadeData> newFade(new FadeData(timeout, aFadeDirection, aUserData));
+  if (!newFade) {
+    NS_WARNING("Out of memory!");
+    return false;
+  }
+  // Call QueueAnimatedContentForRefresh to kick off the fade animation.
+  if (!QueueAnimatedContentForRefresh(aContent, aMinimumFrameRate)) {
+    NS_WARNING("QueueAnimatedContentForRefresh failed???");
+    return false;
+  }
+  mAnimatedFadesList.Put(aContent, newFade);
+  newFade.forget();
+
+  return true;
+}
+
+// mAnimatedFadesList management
+
+nsresult
+nsNativeTheme::InitFadeList()
+{
+  if (mAnimatedFadesList.IsInitialized())
+    return NS_OK;
+  if (!mAnimatedFadesList.Init())
+    return NS_ERROR_UNEXPECTED;
+  nsCOMPtr<nsIObserverService> obsSvc =
+    mozilla::services::GetObserverService();
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  if (obsSvc) {
+    rv = obsSvc->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
+  }
+  return rv;
+}
+
+NS_IMETHODIMP
+nsNativeTheme::Observe(nsISupports* aSubject, const char* aTopic,
+                       const PRUnichar* aData)
+{
+  if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
+    mAnimatedFadesList.Clear();
+    nsCOMPtr<nsIObserverService> obsSvc =
+      mozilla::services::GetObserverService();
+    nsresult rv = NS_ERROR_UNEXPECTED;
+    if (obsSvc) {
+      rv = obsSvc->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+    }
+    NS_ASSERTION(NS_SUCCEEDED(rv),
+      "nsNativeTheme RemoveObserver failed, this may cause a leak.");
+  }
+  return NS_OK;
+}
+
+// mAnimatedContentTimer callback for QueueAnimatedContentForRefresh
+
 NS_IMETHODIMP
 nsNativeTheme::Notify(nsITimer* aTimer)
 {
   NS_ASSERTION(aTimer == mAnimatedContentTimer, "Wrong timer!");
 
   // XXX Assumes that calling nsIFrame::Invalidate won't reenter
   //     QueueAnimatedContentForRefresh.
 
   PRUint32 count = mAnimatedContentList.Length();
   for (PRUint32 index = 0; index < count; index++) {
     nsIFrame* frame = mAnimatedContentList[index]->GetPrimaryFrame();
     if (frame) {
       frame->InvalidateOverflowRect();
+    } else {
+      // If this content has fade data associated with it, and the
+      // frame has gone away, free the data and cancel the fade.
+      if (mAnimatedFadesList.IsInitialized()) {
+        mAnimatedFadesList.Remove(mAnimatedContentList[index]);
+      }
     }
   }
 
   mAnimatedContentList.Clear();
   mAnimatedContentTimeout = PR_UINT32_MAX;
+
   return NS_OK;
 }
 
+// Fade helpers
+
+nsNativeTheme::FadeData*
+nsNativeTheme::GetFade(nsIContent* aContent)
+{
+  if (!aContent || !mAnimatedFadesList.IsInitialized())
+    return nsnull;
+  return mAnimatedFadesList.Get(reinterpret_cast<nsISupports*>(aContent));
+}
+
+nsNativeTheme::FadeState
+nsNativeTheme::GetFadeState(nsIContent* aContent)
+{
+  FadeData* pFade = GetFade(aContent);
+  if (!pFade)
+    return FADE_NOTACTIVE;
+  return pFade->GetState();
+}
+
+PRUint32
+nsNativeTheme::GetFadeTicks(nsIContent* aContent)
+{
+  FadeData* pFade = GetFade(aContent);
+  if (!pFade)
+    return 0;
+  return pFade->GetTicks();
+}
+
+double
+nsNativeTheme::GetFadeAlpha(nsIContent* aContent)
+{
+  return ((double)GetFadeTicks(aContent))/TICK_MAX;
+}
+
+PRUint32
+nsNativeTheme::GetFadeUserData(nsIContent* aContent)
+{
+  FadeData* pFade = GetFade(aContent);
+  if (!pFade)
+    return 0;
+  return pFade->GetUserData();
+}
+
+void
+nsNativeTheme::SetFadeUserData(nsIContent* aContent, PRUint32 aUserData)
+{
+  FadeData* pFade = GetFade(aContent);
+  if (pFade) {
+    pFade->SetUserData(aUserData);
+  }
+}
+
+void
+nsNativeTheme::CancelFade(nsIContent* aContent)
+{
+  if (aContent && mAnimatedFadesList.IsInitialized()) {
+    mAnimatedFadesList.Remove(reinterpret_cast<nsISupports*>(aContent));
+  }
+}
+
+void
+nsNativeTheme::FinishFadeIn(nsIContent* aContent)
+{
+  FadeData* pFade = GetFade(aContent);
+  if (pFade) {
+    pFade->FadeInFinished();
+  }
+}
+
 nsIFrame*
 nsNativeTheme::GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame,
                                                          bool aNextSibling)
 {
   if (!aFrame)
     return nsnull;
 
   // Find the next visible sibling.
@@ -602,8 +824,9 @@ nsNativeTheme::GetAdjacentSiblingFrameWi
   // Check same appearance and adjacency.
   if (!sibling ||
       sibling->GetStyleDisplay()->mAppearance != aFrame->GetStyleDisplay()->mAppearance ||
       (sibling->GetRect().XMost() != aFrame->GetRect().x &&
        aFrame->GetRect().XMost() != sibling->GetRect().x))
     return nsnull;
   return sibling;
 }
+
--- a/widget/xpwidgets/nsNativeTheme.h
+++ b/widget/xpwidgets/nsNativeTheme.h
@@ -44,28 +44,37 @@
 #include "nsIAtom.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsMargin.h"
 #include "nsGkAtoms.h"
 #include "nsEventStates.h"
 #include "nsTArray.h"
 #include "nsITimer.h"
+#include "nsClassHashtable.h"
+#include "nsIObserver.h"
+#include "mozilla/TimeStamp.h"
 
 class nsIContent;
 class nsIFrame;
 class nsIPresShell;
 class nsPresContext;
 
-class nsNativeTheme : public nsITimerCallback
+class nsNativeTheme :
+  public nsITimerCallback,
+  public nsIObserver
 {
+  typedef mozilla::TimeStamp TimeStamp;
+  typedef mozilla::TimeDuration TimeDuration;
+
  protected:
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSIOBSERVER
 
   enum ScrollbarButtonType {
     eScrollbarButton_UpTop   = 0,
     eScrollbarButton_Down    = 1 << 0,
     eScrollbarButton_Bottom  = 1 << 1
   };
 
   enum TreeSortDirection {
@@ -184,19 +193,177 @@ class nsNativeTheme : public nsITimerCal
 
   nsIPresShell *GetPresShell(nsIFrame* aFrame);
   PRInt32 CheckIntAttr(nsIFrame* aFrame, nsIAtom* aAtom, PRInt32 defaultValue);
   bool CheckBooleanAttr(nsIFrame* aFrame, nsIAtom* aAtom);
 
   bool GetCheckedOrSelected(nsIFrame* aFrame, bool aCheckSelected);
   bool GetIndeterminate(nsIFrame* aFrame);
 
-  bool QueueAnimatedContentForRefresh(nsIContent* aContent,
-                                        PRUint32 aMinimumFrameRate);
-
   nsIFrame* GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame,
                                                       bool aNextSibling);
 
+  // Queue a themed element for a redraw after a set interval.
+  bool QueueAnimatedContentForRefresh(nsIContent* aContent,
+                                      PRUint32 aMinimumFrameRate);
+
+  /*
+   * Simple two phase animations on themed widgets - 'Fades' transition from
+   * a base state to a highlighted state and back to the base state, at which
+   * point data associated with the fade is freed.
+   *
+   * Important notes:
+   *
+   * Consumers are responsible for triggering refresh calls by calling
+   * QueueAnimatedContentRefreshForFade on each redraw.
+   *
+   * Consumers are also responsible for switching fade transitions from
+   * FADE_IN/FADE_IN_FINISHED to FADE_OUT through calls to QACRFF. Failing
+   * to do so keeps content / fade data stored in mAnimatedFadesList until
+   * the content's underlying frame is destroyed or the application closes.
+   */
+
+  // Fade states
+  typedef enum FadeState {
+    FADE_NOTACTIVE = 0,   // Fade state not found, fade complete
+    FADE_IN = 1,          // Fading in
+    FADE_IN_FINISHED = 2, // Fade-in is finished, waiting for fade-out
+    FADE_OUT = 3,         // Fading out
+  };
+
+  /*
+   * QueueAnimatedContentRefreshForFade - creates a new fade or requests a
+   * refresh on an existing fade in progress.
+   *
+   * aContent          The themed content element the animation is associated
+   *                   with.
+   * aFadeDirection    The current direction of the fade. Valid values are
+   *                   FADE_IN or FADE_OUT.
+   * aMinimumFrameRate The minimum frame rate requested (30 is typical). Value
+   *                   is passed to QueueAnimatedContentForRefresh to trigger a
+   *                   refresh.
+   * aMilliseconds     Duration of the fade-in or fade-out transition.
+   * aUserData         Generic consumer data storage for state across rendering
+   *                   of individual frames. Updated on every call.
+   */
+  bool
+  QueueAnimatedContentRefreshForFade(nsIContent* aContent,
+                                     FadeState aFadeDirection,
+                                     PRUint32 aMinimumFrameRate,
+                                     PRUint32 aMilliseconds,
+                                     PRUint32 aUserData = 0);
+
+  // Max ticks returned by FadeData->GetTicks().
+  #define TICK_MAX 100.0
+
+  // Internal data structure for storing fade data
+  class FadeData
+  {
+  public:
+    /*
+     * FadeData()
+     * aTimeout  now + duration
+     * aState    FADE_IN or FADE_OUT
+     * aUserData intial value for user data 
+     */
+    FadeData(TimeStamp aTimeout, FadeState aState, PRUint32 aUserData) :
+      mTimeout(aTimeout),
+      mStartTime(TimeStamp::Now()),
+      mState(aState),
+      mUserData(aUserData) {
+    }
+    ~FadeData() {}
+
+    /*
+     * Reset - resets the to a new timeout value and direction.
+     * aTimeout  msec(now) + duration
+     * aState    FADE_IN or FADE_OUT
+     */
+    void Reset(TimeDuration aTimeout, FadeState aState) { 
+      NS_ASSERTION((aState == FADE_IN || aState == FADE_OUT),
+                   "Bad fade direction.");
+      mStartTime = TimeStamp::Now();
+      mTimeout = TimeStamp::Now() + aTimeout;
+      mState = aState;
+    }
+
+    /*
+     * GetTicks - returns the number of ticks in this animation where
+     * ticks >= 0 && ticks <= TICK_MAX. FADE_IN has increasing ticks,
+     * FADE_OUT decreasing.
+     */
+    PRUint32 GetTicks() {
+      TimeStamp now = TimeStamp::Now();
+      if (now >= mTimeout) {
+        return (mState == FADE_OUT ? 0 : (PRUint32)TICK_MAX);
+      }
+      TimeDuration diff = now - mStartTime;
+      PRUint32 tick =
+        (PRUint32)ceil((diff / (mTimeout - mStartTime)) * TICK_MAX);
+      // we want ticks to ascend and descend according to the direction.
+      if (mState == FADE_OUT) {
+        tick = (PRUint32)abs(tick - TICK_MAX);
+      }
+      return tick;
+    }
+
+    /*
+     * TimeoutUsed - for fades that have not completes, returns the
+     * amount of time used thus far in the current transition in msec.
+     */
+    TimeDuration TimeoutUsed() {
+      TimeDuration used = TimeStamp::Now() - mStartTime;
+      TimeDuration totalTime = mTimeout - mStartTime;
+      return NS_MIN(used, totalTime);
+    }
+
+    /*
+     * Misc. data getters/setters
+     */
+    TimeStamp GetTimeout() { return mTimeout; }
+    FadeState GetState() { return mState; }
+    void FadeInFinished() { mState = FADE_IN_FINISHED; }
+    PRUint32 GetUserData() { return mUserData; }
+    void SetUserData(PRUint32 aUserData) { mUserData = aUserData; }
+
+  private:
+    TimeStamp mTimeout;
+    TimeStamp mStartTime;
+    FadeState mState;
+    PRUint32 mUserData;
+  };
+
+  /*
+   * nsNativeTheme fade data helpers
+   */
+
+  // Retrieves the FadeData object associated with this content, or null.
+  FadeData* GetFade(nsIContent* aContent);
+  // Retrieves the current fade state or FADE_NOTACTIVE.
+  FadeState GetFadeState(nsIContent* aContent);
+  // Retrieves the current tick count for a fade transition or 0. Ticks
+  // range from 0 -> TICK_MAX. For FADE_IN transitions ticks increase,
+  // for FADE_OUT transitions ticks decrease.
+  PRUint32 GetFadeTicks(nsIContent* aContent);
+  // Retrieves the alpha value (0->1) corresponding to the current tick
+  // count for a fade transition, or 0.
+  double GetFadeAlpha(nsIContent* aContent);
+  // Get/set consumer data. Valid across each call to QACRFF.
+  PRUint32 GetFadeUserData(nsIContent* aContent);
+  void SetFadeUserData(nsIContent* aContent, PRUint32 aUserData);
+  // Cancel an active fade and free its resources.
+  void CancelFade(nsIContent* aContent);
+  // Mark a fade as FADE_IN_FINISHED.
+  void FinishFadeIn(nsIContent* aContent);
+
  private:
+  nsresult InitFadeList();
+
   PRUint32 mAnimatedContentTimeout;
   nsCOMPtr<nsITimer> mAnimatedContentTimer;
+  // Render refresh list - nsIContent contains the content
+  // that will be invalidated when mAnimatedContentTimer fires.
+  // Cleared on every call to mAnimatedContentTimer Notify.
   nsAutoTArray<nsCOMPtr<nsIContent>, 20> mAnimatedContentList;
+  // Fade list data - nsISupportsHashKey contains the nsIContent
+  // associated with an active fade.
+  nsClassHashtable<nsISupportsHashKey, FadeData> mAnimatedFadesList;
 };