diff --git a/image/FrameAnimator.cpp b/image/FrameAnimator.cpp index 273a6ce203da..700a0169b844 100644 --- a/image/FrameAnimator.cpp +++ b/image/FrameAnimator.cpp @@ -71,12 +71,7 @@ AnimationState::UpdateStateInternal(LookupResult& aResult, if (mHasBeenDecoded) { Maybe frameCount = FrameCount(); MOZ_ASSERT(frameCount.isSome()); - if (NS_SUCCEEDED(aResult.Surface().Seek(*frameCount - 1)) && - aResult.Surface()->IsFinished()) { - mIsCurrentlyDecoded = true; - } else { - mIsCurrentlyDecoded = false; - } + mIsCurrentlyDecoded = aResult.Surface().IsFullyDecoded(); } } @@ -303,8 +298,12 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, // failure) we would have discarded all the old frames and may not yet have // the new ones. if (!nextFrame || !nextFrame->IsFinished()) { - // Uh oh, the frame we want to show is currently being decoded (partial) - // Wait until the next refresh driver tick and try again + // Uh oh, the frame we want to show is currently being decoded (partial). + // Similar to the above case, we could be blocked by network or decoding, + // and so we should advance our current time rather than risk jumping + // through the animation. We will wait until the next refresh driver tick + // and try again. + aState.mCurrentAnimationFrameTime = aTime; return ret; } @@ -330,6 +329,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, MOZ_ASSERT(currentFrameEndTime.isSome()); aState.mCurrentAnimationFrameTime = *currentFrameEndTime; aState.mCurrentAnimationFrameIndex = nextFrameIndex; + aFrames.Advance(nextFrameIndex); return ret; } @@ -375,6 +375,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, // Set currentAnimationFrameIndex at the last possible moment aState.mCurrentAnimationFrameIndex = nextFrameIndex; + aFrames.Advance(nextFrameIndex); // If we're here, we successfully advanced the frame. ret.mFrameAdvanced = true; @@ -382,6 +383,25 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, return ret; } +void +FrameAnimator::ResetAnimation(AnimationState& aState) +{ + aState.ResetAnimation(); + + // Our surface provider is synchronized to our state, so we need to reset its + // state as well, if we still have one. + LookupResult result = + SurfaceCache::Lookup(ImageKey(mImage), + RasterSurfaceKey(mSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + if (!result) { + return; + } + + result.Surface().Reset(); +} + RefreshResult FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime, diff --git a/image/FrameAnimator.h b/image/FrameAnimator.h index 9a5fa39f7416..a59986d0be50 100644 --- a/image/FrameAnimator.h +++ b/image/FrameAnimator.h @@ -289,6 +289,12 @@ public: MOZ_COUNT_DTOR(FrameAnimator); } + /** + * Call when you need to re-start animating. Ensures we start from the first + * frame. + */ + void ResetAnimation(AnimationState& aState); + /** * Re-evaluate what frame we're supposed to be on, and do whatever blending * is necessary to get us to that frame. diff --git a/image/ISurfaceProvider.h b/image/ISurfaceProvider.h index f1d46b8e26b1..852072eb34db 100644 --- a/image/ISurfaceProvider.h +++ b/image/ISurfaceProvider.h @@ -53,6 +53,12 @@ public: /// @return true if DrawableRef() will return a completely decoded surface. virtual bool IsFinished() const = 0; + /// @return true if the underlying decoder is currently fully decoded. For + /// animated images, this means that at least every frame has been decoded + /// at least once. It does not guarantee that all of the frames are present, + /// as the surface provider has the option to discard as it deems necessary. + virtual bool IsFullyDecoded() const { return IsFinished(); } + /// @return the number of bytes of memory this ISurfaceProvider is expected to /// require. Optimizations may result in lower real memory usage. Trivial /// overhead is ignored. Because this value is used in bookkeeping, it's @@ -76,6 +82,9 @@ public: aNonHeapSizeOut, aExtHandlesOut); } + virtual void Reset() { } + virtual void Advance(size_t aFrame) { } + /// @return the availability state of this ISurfaceProvider, which indicates /// whether DrawableRef() could successfully return a surface. Should only be /// called from SurfaceCache code as it relies on SurfaceCache for @@ -190,6 +199,36 @@ public: return mDrawableRef ? NS_OK : NS_ERROR_FAILURE; } + void Reset() + { + if (!mProvider) { + MOZ_ASSERT_UNREACHABLE("Trying to reset a static DrawableSurface?"); + return; + } + + mProvider->Reset(); + } + + void Advance(size_t aFrame) + { + if (!mProvider) { + MOZ_ASSERT_UNREACHABLE("Trying to advance a static DrawableSurface?"); + return; + } + + mProvider->Advance(aFrame); + } + + bool IsFullyDecoded() const + { + if (!mProvider) { + MOZ_ASSERT_UNREACHABLE("Trying to check decoding state of a static DrawableSurface?"); + return false; + } + + return mProvider->IsFullyDecoded(); + } + explicit operator bool() const { return mHaveSurface; } imgFrame* operator->() { return DrawableRef().get(); } diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index 276e43556bc4..d1221ad61565 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -869,7 +869,8 @@ RasterImage::ResetAnimation() } MOZ_ASSERT(mAnimationState, "Should have AnimationState"); - mAnimationState->ResetAnimation(); + MOZ_ASSERT(mFrameAnimator, "Should have FrameAnimator"); + mFrameAnimator->ResetAnimation(*mAnimationState); NotifyProgress(NoProgress, mAnimationState->FirstFrameRefreshArea());