Bug 899861 - Animated gifs should not wait to play until fully downloaded. This is a partial backout of 717872 with the intent to re-enable using the separate FrameAnimator class. Hide the new approach behind #define USE_FRAME_ANIMATOR for now. r=bgirard
authorMilan Sreckovic <milan@mozilla.com>
Tue, 13 Aug 2013 18:30:28 -0400
changeset 142666 e054c33d34179f7d166547bffc485ba46b7014f2
parent 142665 7874d6104c5b739e9a2aa4d4361a5ee7b6959625
child 142667 f47dfe10d30192410ed8158a59cd2b74b05bf1ba
push id25104
push useremorley@mozilla.com
push dateThu, 15 Aug 2013 10:56:09 +0000
treeherdermozilla-central@31c08ca022b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgirard
bugs899861, 717872
milestone26.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 899861 - Animated gifs should not wait to play until fully downloaded. This is a partial backout of 717872 with the intent to re-enable using the separate FrameAnimator class. Hide the new approach behind #define USE_FRAME_ANIMATOR for now. r=bgirard
image/src/RasterImage.cpp
image/src/RasterImage.h
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -382,16 +382,19 @@ NS_IMPL_ISUPPORTS3(RasterImage, imgICont
 //******************************************************************************
 RasterImage::RasterImage(imgStatusTracker* aStatusTracker,
                          nsIURI* aURI /* = nullptr */) :
   ImageResource(aStatusTracker, aURI), // invoke superclass's constructor
   mSize(0,0),
   mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
   mMultipartDecodedFrame(nullptr),
   mAnim(nullptr),
+#ifndef USE_FRAME_ANIMATOR
+  mLoopCount(-1),
+#endif
   mLockCount(0),
   mDecodeCount(0),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
   mDecodingMutex("RasterImage"),
   mDecoder(nullptr),
   mBytesDecoded(0),
@@ -519,52 +522,224 @@ RasterImage::Init(const char* aMimeType,
   }
 
   // Mark us as initialized
   mInitialized = true;
 
   return NS_OK;
 }
 
+#ifndef USE_FRAME_ANIMATOR
+uint32_t
+RasterImage::GetSingleLoopTime() const
+{
+  if (!mAnim) {
+    return 0;
+  }
+
+  // If we aren't done decoding, we don't know the image's full play time.
+  if (!mHasBeenDecoded) {
+    return 0;
+  }
+
+  // If we're not looping, a single loop time has no meaning
+  if (mLoopCount == 0) {
+    return 0;
+  }
+
+  uint32_t looptime = 0;
+  for (uint32_t i = 0; i < GetNumFrames(); ++i) {
+    int32_t timeout = mFrameBlender.RawGetFrame(i)->GetTimeout();
+    if (timeout > 0) {
+      looptime += static_cast<uint32_t>(timeout);
+    } else {
+      // If we have a frame that never times out, we're probably in an error
+      // case, but let's handle it more gracefully.
+      NS_WARNING("Negative frame timeout - how did this happen?");
+      return 0;
+    }
+  }
+
+  return looptime;
+}
+
+bool
+RasterImage::AdvanceFrame(TimeStamp aTime, nsIntRect* aDirtyRect)
+{
+  NS_ASSERTION(aTime <= TimeStamp::Now(),
+               "Given time appears to be in the future");
+
+  uint32_t currentFrameIndex = mAnim->currentAnimationFrameIndex;
+  uint32_t nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
+  uint32_t timeout = 0;
+
+  // Figure out if we have the next full frame. This is more complicated than
+  // just checking GetNumFrames() because decoders append their frames
+  // before they're filled in.
+  NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= GetNumFrames(),
+                    "How did we get 2 indices too far by incrementing?");
+
+  // If we don't have a decoder, we know we've got everything we're going to
+  // get. If we do, we only display fully-downloaded frames; everything else
+  // gets delayed.
+  bool haveFullNextFrame = (mMultipart && mBytesDecoded == 0) || !mDecoder ||
+                            nextFrameIndex < mDecoder->GetCompleteFrameCount();
+
+  // If we're done decoding the next frame, go ahead and display it now and
+  // reinit with the next frame's delay time.
+  if (haveFullNextFrame) {
+    if (GetNumFrames() == nextFrameIndex) {
+      // End of Animation, unless we are looping forever
+
+      // If animation mode is "loop once", it's time to stop animating
+      if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
+        mAnimationFinished = true;
+        EvaluateAnimation();
+      }
+
+      nextFrameIndex = 0;
+
+      if (mLoopCount > 0) {
+        mLoopCount--;
+      }
+
+      if (!mAnimating) {
+        // break out early if we are actually done animating
+        return false;
+      }
+    }
+
+    timeout = mFrameBlender.GetFrame(nextFrameIndex)->GetTimeout();
+
+  } else {
+    // Uh oh, the frame we want to show is currently being decoded (partial)
+    // Wait until the next refresh driver tick and try again
+    return false;
+  }
+
+  if (!(timeout > 0)) {
+    mAnimationFinished = true;
+    EvaluateAnimation();
+  }
+
+  if (nextFrameIndex == 0) {
+    *aDirtyRect = mAnim->firstFrameRefreshArea;
+  } else {
+    // Change frame
+    if (!mFrameBlender.DoBlend(aDirtyRect, currentFrameIndex, nextFrameIndex)) {
+      // something went wrong, move on to next
+      NS_WARNING("RasterImage::AdvanceFrame(): Compositing of frame failed");
+      mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true);
+      mAnim->currentAnimationFrameTime = GetCurrentImgFrameEndTime();
+      mAnim->currentAnimationFrameIndex = nextFrameIndex;
+      return false;
+    }
+
+    mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false);
+  }
+
+  mAnim->currentAnimationFrameTime = GetCurrentImgFrameEndTime();
+
+  // If we can get closer to the current time by a multiple of the image's loop
+  // time, we should.
+  uint32_t loopTime = GetSingleLoopTime();
+  if (loopTime > 0) {
+    TimeDuration delay = aTime - mAnim->currentAnimationFrameTime;
+    if (delay.ToMilliseconds() > loopTime) {
+      // Explicitly use integer division to get the floor of the number of
+      // loops.
+      uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime;
+      mAnim->currentAnimationFrameTime += TimeDuration::FromMilliseconds(loops * loopTime);
+    }
+  }
+
+  // Set currentAnimationFrameIndex at the last possible moment
+  mAnim->currentAnimationFrameIndex = nextFrameIndex;
+
+  return true;
+}
+#endif
+
 //******************************************************************************
 // [notxpcom] void requestRefresh ([const] in TimeStamp aTime);
 NS_IMETHODIMP_(void)
 RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
 {
   if (!ShouldAnimate()) {
     return;
   }
 
   EvaluateAnimation();
 
+#ifdef USE_FRAME_ANIMATOR
   FrameAnimator::RefreshResult res;
   if (mAnim) {
     res = mAnim->RequestRefresh(aTime);
   }
-
+#else
+  // only advance the frame if the current time is greater than or
+  // equal to the current frame's end time.
+  TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
+  bool frameAdvanced = false;
+
+  // The dirtyRect variable will contain an accumulation of the sub-rectangles
+  // that are dirty for each frame we advance in AdvanceFrame().
+  nsIntRect dirtyRect;
+
+  while (currentFrameEndTime <= aTime) {
+    TimeStamp oldFrameEndTime = currentFrameEndTime;
+    nsIntRect frameDirtyRect;
+    bool didAdvance = AdvanceFrame(aTime, &frameDirtyRect);
+    frameAdvanced = frameAdvanced || didAdvance;
+    currentFrameEndTime = GetCurrentImgFrameEndTime();
+
+    // Accumulate the dirty area.
+    dirtyRect = dirtyRect.Union(frameDirtyRect);
+
+    // if we didn't advance a frame, and our frame end time didn't change,
+    // then we need to break out of this loop & wait for the frame(s)
+    // to finish downloading
+    if (!didAdvance && (currentFrameEndTime == oldFrameEndTime)) {
+      break;
+    }
+  }
+#endif
+
+#ifdef USE_FRAME_ANIMATOR
   if (res.frameAdvanced) {
+#else
+  if (frameAdvanced) {
+#endif
     // Notify listeners that our frame has actually changed, but do this only
     // once for all frames that we've now passed (if AdvanceFrame() was called
     // more than once).
     #ifdef DEBUG
       mFramesNotified++;
     #endif
 
     UpdateImageContainer();
 
     // Explicitly call this on mStatusTracker so we're sure to not interfere
     // with the decoding process
-    if (mStatusTracker)
+#ifdef USE_FRAME_ANIMATOR
+    if (mStatusTracker) {
       mStatusTracker->FrameChanged(&res.dirtyRect);
+    }
   }
 
   if (res.animationFinished) {
     mAnimationFinished = true;
     EvaluateAnimation();
   }
+#else
+    if (mStatusTracker) {
+      mStatusTracker->FrameChanged(&dirtyRect);
+    }
+  }
+#endif
 }
 
 //******************************************************************************
 /* readonly attribute int32_t width; */
 NS_IMETHODIMP
 RasterImage::GetWidth(int32_t *aWidth)
 {
   NS_ENSURE_ARG_POINTER(aWidth);
@@ -682,22 +857,52 @@ RasterImage::GetDrawableImgFrame(uint32_
   }
 
   return frame;
 }
 
 uint32_t
 RasterImage::GetCurrentImgFrameIndex() const
 {
-  if (mAnim)
+  if (mAnim) {
+#ifdef USE_FRAME_ANIMATOR
     return mAnim->GetCurrentAnimationFrameIndex();
+#else
+    return mAnim->currentAnimationFrameIndex;
+#endif
+  }
 
   return 0;
 }
 
+#ifndef USE_FRAME_ANIMATOR
+TimeStamp
+RasterImage::GetCurrentImgFrameEndTime() const
+{
+  imgFrame* currentFrame = mFrameBlender.RawGetFrame(mAnim->currentAnimationFrameIndex);
+  TimeStamp currentFrameTime = mAnim->currentAnimationFrameTime;
+  int64_t timeout = currentFrame->GetTimeout();
+
+  if (timeout < 0) {
+    // We need to return a sentinel value in this case, because our logic
+    // doesn't work correctly if we have a negative timeout value. The reason
+    // this positive infinity was chosen was because it works with the loop in
+    // RequestRefresh() above.
+    return TimeStamp() +
+           TimeDuration::FromMilliseconds(static_cast<double>(UINT64_MAX));
+  }
+
+  TimeDuration durationOfTimeout =
+    TimeDuration::FromMilliseconds(static_cast<double>(timeout));
+  TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
+
+  return currentFrameEndTime;
+}
+#endif
+
 imgFrame*
 RasterImage::GetCurrentImgFrame()
 {
   return GetImgFrame(GetCurrentImgFrameIndex());
 }
 
 //******************************************************************************
 /* [notxpcom] boolean frameIsOpaque(in uint32_t aWhichFrame); */
@@ -1057,16 +1262,17 @@ RasterImage::NonHeapSizeOfDecoded() cons
 
 size_t
 RasterImage::OutOfProcessSizeOfDecoded() const
 {
   return SizeOfDecodedWithComputedFallbackIfHeap(gfxASurface::MEMORY_OUT_OF_PROCESS,
                                                  NULL);
 }
 
+#ifdef USE_FRAME_ANIMATOR
 void
 RasterImage::EnsureAnimExists()
 {
   if (!mAnim) {
 
     // Create the animation context
     mAnim = new FrameAnimator(mFrameBlender);
 
@@ -1080,16 +1286,17 @@ RasterImage::EnsureAnimExists()
     // since we didn't kill the source data in the old world either, locking
     // is acceptable for the moment.
     LockImage();
 
     // Notify our observers that we are starting animation.
     CurrentStatusTracker().RecordImageIsAnimated();
   }
 }
+#endif
 
 nsresult
 RasterImage::InternalAddFrameHelper(uint32_t framenum, imgFrame *aFrame,
                                     uint8_t **imageData, uint32_t *imageLength,
                                     uint32_t **paletteData, uint32_t *paletteLength,
                                     imgFrame** aRetFrame)
 {
   NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Invalid frame index!");
@@ -1161,23 +1368,33 @@ RasterImage::InternalAddFrame(uint32_t f
     EnsureAnimExists();
 
     // If we dispose of the first frame by clearing it, then the
     // First Frame's refresh area is all of itself.
     // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR)
     int32_t frameDisposalMethod = mFrameBlender.RawGetFrame(0)->GetFrameDisposalMethod();
     if (frameDisposalMethod == FrameBlender::kDisposeClear ||
         frameDisposalMethod == FrameBlender::kDisposeRestorePrevious)
+#ifdef USE_FRAME_ANIMATOR
       mAnim->SetFirstFrameRefreshArea(mFrameBlender.RawGetFrame(0)->GetRect());
+#else
+      mAnim->firstFrameRefreshArea = mFrameBlender.RawGetFrame(0)->GetRect();
+#endif
   }
 
   // Calculate firstFrameRefreshArea
   // Some gifs are huge but only have a small area that they animate
   // We only need to refresh that small area when Frame 0 comes around again
+#ifdef USE_FRAME_ANIMATOR
   mAnim->UnionFirstFrameRefreshArea(frame->GetRect());
+#else
+  nsIntRect frameRect = frame->GetRect();
+  mAnim->firstFrameRefreshArea.UnionRect(mAnim->firstFrameRefreshArea,
+                                         frameRect);
+#endif
 
   rv = InternalAddFrameHelper(framenum, frame.forget(), imageData, imageLength,
                               paletteData, paletteLength, aRetFrame);
 
   return rv;
 }
 
 bool
@@ -1390,31 +1607,35 @@ RasterImage::DecodingComplete()
       // complexity and it's not really needed since we already are smart about
       // not displaying the still-decoding frame of an animated image. We may
       // have already stored an extra frame, though, so we'll release it here.
       delete mMultipartDecodedFrame;
       mMultipartDecodedFrame = nullptr;
     }
   }
 
+#ifdef USE_FRAME_ANIMATOR
   if (mAnim) {
     mAnim->SetDoneDecoding(true);
   }
+#endif
 
   return NS_OK;
 }
 
+#ifdef USE_FRAME_ANIMATOR
 NS_IMETHODIMP
 RasterImage::SetAnimationMode(uint16_t aAnimationMode)
 {
   if (mAnim) {
     mAnim->SetAnimationMode(aAnimationMode);
   }
   return SetAnimationModeInternal(aAnimationMode);
 }
+#endif
 
 //******************************************************************************
 /* void StartAnimation () */
 nsresult
 RasterImage::StartAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
@@ -1427,17 +1648,23 @@ RasterImage::StartAnimation()
   if (currentFrame) {
     if (currentFrame->GetTimeout() < 0) { // -1 means display this frame forever
       mAnimationFinished = true;
       return NS_ERROR_ABORT;
     }
 
     // We need to set the time that this initial frame was first displayed, as
     // this is used in AdvanceFrame().
+#ifdef USE_FRAME_ANIMATOR
     mAnim->InitAnimationFrameTimeIfNecessary();
+#else
+    if (mAnim->currentAnimationFrameTime.IsNull()) {
+      mAnim->currentAnimationFrameTime = TimeStamp::Now();
+    }
+#endif
   }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void stopAnimation (); */
 nsresult
@@ -1454,39 +1681,53 @@ RasterImage::StopAnimation()
 //******************************************************************************
 /* void resetAnimation (); */
 NS_IMETHODIMP
 RasterImage::ResetAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
+#ifdef USE_FRAME_ANIMATOR
   if (mAnimationMode == kDontAnimMode ||
       !mAnim || mAnim->GetCurrentAnimationFrameIndex() == 0)
     return NS_OK;
+#else
+  if (mAnimationMode == kDontAnimMode ||
+      !mAnim || mAnim->currentAnimationFrameIndex == 0)
+    return NS_OK;
+#endif
 
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
   mFrameBlender.ResetAnimation();
+#ifdef USE_FRAME_ANIMATOR
   if (mAnim) {
     mAnim->ResetAnimation();
   }
-
+#else
+
+  mAnim->currentAnimationFrameIndex = 0;
+#endif
   UpdateImageContainer();
 
   // Note - We probably want to kick off a redecode somewhere around here when
   // we fix bug 500402.
 
   // Update display if we were animating before
   if (mAnimating && mStatusTracker) {
+#ifdef USE_FRAME_ANIMATOR
     nsIntRect rect = mAnim->GetFirstFrameRefreshArea();
     mStatusTracker->FrameChanged(&rect);
+#else
+    mStatusTracker->FrameChanged(&(mAnim->firstFrameRefreshArea));
+#endif
   }
 
   if (ShouldAnimate()) {
     StartAnimation();
     // The animation may not have been running before, if mAnimationFinished
     // was false (before we changed it to true in this function). So, mark the
     // animation as running.
     mAnimating = true;
@@ -1498,37 +1739,55 @@ RasterImage::ResetAnimation()
 //******************************************************************************
 // [notxpcom] void requestRefresh ([const] in TimeStamp aTime);
 NS_IMETHODIMP_(void)
 RasterImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime)
 {
   if (mError || mAnimating || !mAnim)
     return;
 
+#ifdef USE_FRAME_ANIMATOR
   mAnim->SetAnimationFrameTime(aTime);
+#else
+  mAnim->currentAnimationFrameTime = aTime;
+#endif
 }
 
 NS_IMETHODIMP_(float)
 RasterImage::GetFrameIndex(uint32_t aWhichFrame)
 {
   MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
+#ifdef USE_FRAME_ANIMATOR
   return (aWhichFrame == FRAME_FIRST || !mAnim)
          ? 0.0f
          : mAnim->GetCurrentAnimationFrameIndex();
+#else
+  return (aWhichFrame == FRAME_FIRST || !mAnim)
+         ? 0.0f
+         : mAnim->currentAnimationFrameIndex;
+#endif
 }
 
 void
 RasterImage::SetLoopCount(int32_t aLoopCount)
 {
   if (mError)
     return;
 
+#ifdef USE_FRAME_ANIMATOR
   if (mAnim) {
     mAnim->SetLoopCount(aLoopCount);
   }
+#else
+  // -1  infinite
+  //  0  no looping, one iteration
+  //  1  one loop, two iterations
+  //  ...
+  mLoopCount = aLoopCount;
+#endif
 }
 
 nsresult
 RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount)
 {
   MutexAutoLock lock(mDecodingMutex);
 
   if (mError)
@@ -1790,19 +2049,21 @@ RasterImage::OnNewSourceData()
 
   // Reset some flags
   mDecoded = false;
   mHasSourceData = false;
   mHasSize = false;
   mWantFullDecode = true;
   mDecodeRequest = nullptr;
 
+#ifdef USE_FRAME_ANIMATOR
   if (mAnim) {
     mAnim->SetDoneDecoding(false);
   }
+#endif
 
   // We always need the size first.
   rv = InitDecoder(/* aDoSizeDecode = */ true);
   CONTAINER_ENSURE_SUCCESS(rv);
 
   return NS_OK;
 }
 
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -36,16 +36,20 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/Mutex.h"
 #include "gfx2DGlue.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
 
+// This will enable FrameAnimator approach to image animation.  Before doing
+// so, make sure bug 899861 symptoms are gone.
+// #define USE_FRAME_ANIMATOR 1
+
 class nsIInputStream;
 class nsIThreadPool;
 
 #define NS_RASTERIMAGE_CID \
 { /* 376ff2c1-9bf6-418a-b143-3340c00112f7 */         \
      0x376ff2c1,                                     \
      0x9bf6,                                         \
      0x418a,                                         \
@@ -316,16 +320,32 @@ private:
       return *mDecodeRequest->mStatusTracker;
     } else {
       return *mStatusTracker;
     }
   }
 
   nsresult OnImageDataCompleteCore(nsIRequest* aRequest, nsISupports*, nsresult aStatus);
 
+#ifndef USE_FRAME_ANIMATOR
+  struct Anim
+  {
+    //! Area of the first frame that needs to be redrawn on subsequent loops.
+    nsIntRect                  firstFrameRefreshArea;
+    uint32_t                   currentAnimationFrameIndex; // 0 to numFrames-1
+
+    // the time that the animation advanced to the current frame
+    TimeStamp                  currentAnimationFrameTime;
+
+    Anim() :
+      currentAnimationFrameIndex(0)
+    {}
+  };
+#endif
+
   /**
    * Each RasterImage has a pointer to one or zero heap-allocated
    * DecodeRequests.
    */
   struct DecodeRequest
   {
     DecodeRequest(RasterImage* aImage)
       : mImage(aImage)
@@ -523,36 +543,93 @@ private:
                                     const gfxRect &aFill,
                                     const nsIntRect &aSubimage,
                                     uint32_t aFlags);
 
   nsresult CopyFrame(uint32_t aWhichFrame,
                      uint32_t aFlags,
                      gfxImageSurface **_retval);
 
+#ifndef USE_FRAME_ANIMATOR
+  /**
+   * Advances the animation. Typically, this will advance a single frame, but it
+   * may advance multiple frames. This may happen if we have infrequently
+   * "ticking" refresh drivers (e.g. in background tabs), or extremely short-
+   * lived animation frames.
+   *
+   * @param aTime the time that the animation should advance to. This will
+   *              typically be <= TimeStamp::Now().
+   *
+   * @param [out] aDirtyRect a pointer to an nsIntRect which encapsulates the
+   *        area to be repainted after the frame is advanced.
+   *
+   * @returns true, if the frame was successfully advanced, false if it was not
+   *          able to be advanced (e.g. the frame to which we want to advance is
+   *          still decoding). Note: If false is returned, then aDirtyRect will
+   *          remain unmodified.
+   */
+  bool AdvanceFrame(mozilla::TimeStamp aTime, nsIntRect* aDirtyRect);
+
+  /**
+   * Gets the length of a single loop of this image, in milliseconds.
+   *
+   * If this image is not finished decoding, is not animated, or it is animated
+   * but does not loop, returns 0.
+   */
+  uint32_t GetSingleLoopTime() const;
+#endif
+
   /**
    * Deletes and nulls out the frame in mFrames[framenum].
    *
    * Does not change the size of mFrames.
    *
    * @param framenum The index of the frame to be deleted.
    *                 Must lie in [0, mFrames.Length() )
    */
   void DeleteImgFrame(uint32_t framenum);
 
   imgFrame* GetImgFrameNoDecode(uint32_t framenum);
   imgFrame* GetImgFrame(uint32_t framenum);
   imgFrame* GetDrawableImgFrame(uint32_t framenum);
   imgFrame* GetCurrentImgFrame();
   uint32_t GetCurrentImgFrameIndex() const;
+#ifndef USE_FRAME_ANIMATOR
+  mozilla::TimeStamp GetCurrentImgFrameEndTime() const;
+#endif
 
   size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxASurface::MemoryLocation aLocation,
                                                  mozilla::MallocSizeOf aMallocSizeOf) const;
 
+#ifdef USE_FRAME_ANIMATOR
   void EnsureAnimExists();
+#else
+  inline void EnsureAnimExists()
+  {
+    if (!mAnim) {
+
+      // Create the animation context
+      mAnim = new Anim();
+
+      // We don't support discarding animated images (See bug 414259).
+      // Lock the image and throw away the key.
+      //
+      // Note that this is inefficient, since we could get rid of the source
+      // data too. However, doing this is actually hard, because we're probably
+      // calling ensureAnimExists mid-decode, and thus we're decoding out of
+      // the source buffer. Since we're going to fix this anyway later, and
+      // since we didn't kill the source data in the old world either, locking
+      // is acceptable for the moment.
+      LockImage();
+
+      // Notify our observers that we are starting animation.
+      CurrentStatusTracker().RecordImageIsAnimated();
+    }
+  }
+#endif
 
   nsresult InternalAddFrameHelper(uint32_t framenum, imgFrame *frame,
                                   uint8_t **imageData, uint32_t *imageLength,
                                   uint32_t **paletteData, uint32_t *paletteLength,
                                   imgFrame** aRetFrame);
   nsresult InternalAddFrame(uint32_t framenum, int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight,
                             gfxASurface::gfxImageFormat aFormat, uint8_t aPaletteDepth,
                             uint8_t **imageData, uint32_t *imageLength,
@@ -600,17 +677,24 @@ private: // data
   // The last frame we decoded for multipart images.
   imgFrame*                  mMultipartDecodedFrame;
 
   nsCOMPtr<nsIProperties>    mProperties;
 
   // IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure
   // that the frames actually exist (they may have been discarded to save memory, or
   // we maybe decoding on draw).
+#ifdef USE_FRAME_ANIMATOR
   FrameAnimator* mAnim;
+#else
+  RasterImage::Anim*        mAnim;
+
+  //! # loops remaining before animation stops (-1 no stop)
+  int32_t                    mLoopCount;
+#endif
 
   // Discard members
   uint32_t                   mLockCount;
   DiscardTracker::Node       mDiscardTrackerNode;
 
   // Source data members
   nsCString                  mSourceDataMimeType;
 
@@ -717,16 +801,22 @@ protected:
 
   friend class ImageFactory;
 };
 
 inline NS_IMETHODIMP RasterImage::GetAnimationMode(uint16_t *aAnimationMode) {
   return GetAnimationModeInternal(aAnimationMode);
 }
 
+#ifndef USE_FRAME_ANIMATOR
+inline NS_IMETHODIMP RasterImage::SetAnimationMode(uint16_t aAnimationMode) {
+  return SetAnimationModeInternal(aAnimationMode);
+}
+#endif
+
 // Asynchronous Decode Requestor
 //
 // We use this class when someone calls requestDecode() from within a decode
 // notification. Since requestDecode() involves modifying the decoder's state
 // (for example, possibly shutting down a header-only decode and starting a
 // full decode), we don't want to do this from inside a decoder.
 class imgDecodeRequestor : public nsRunnable
 {