diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 81c66603c1..c2b28eee64 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -431,6 +431,7 @@ private: DECL_GFX_PREF(Live, "image.animated.decode-on-demand.threshold-kb", ImageAnimatedDecodeOnDemandThresholdKB, uint32_t, 256*1024); DECL_GFX_PREF(Live, "image.animated.decode-on-demand.batch-size", ImageAnimatedDecodeOnDemandBatchSize, uint32_t, 6); + DECL_GFX_PREF(Live, "image.animated.resume-from-last-displayed", ImageAnimatedResumeFromLastDisplayed, bool, false); DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024); DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500); DECL_GFX_PREF(Live, "image.decode-immediately.enabled", ImageDecodeImmediatelyEnabled, bool, false); diff --git a/image/FrameAnimator.cpp b/image/FrameAnimator.cpp index b9a4aec4a8..0bdecbb346 100644 --- a/image/FrameAnimator.cpp +++ b/image/FrameAnimator.cpp @@ -151,6 +151,21 @@ AnimationState::SetAnimationFrameTime(const TimeStamp& aTime) mCurrentAnimationFrameTime = aTime; } +bool +AnimationState::MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime) +{ + if (!gfxPrefs::ImageAnimatedResumeFromLastDisplayed() || + mCurrentAnimationFrameTime >= aTime) { + return false; + } + + // We are configured to stop an animation when it is out of view, and restart + // it from the same point when it comes back into view. The same applies if it + // was discarded while out of view. + mCurrentAnimationFrameTime = aTime; + return true; +} + uint32_t AnimationState::GetCurrentAnimationFrameIndex() const { @@ -312,6 +327,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, MOZ_ASSERT(currentFrameEndTime.isSome()); aState.mCurrentAnimationFrameTime = *currentFrameEndTime; aState.mCurrentAnimationFrameIndex = nextFrameIndex; + aState.mCompositedFrameRequested = false; aFrames.Advance(nextFrameIndex); return ret; @@ -343,6 +359,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, // Set currentAnimationFrameIndex at the last possible moment aState.mCurrentAnimationFrameIndex = nextFrameIndex; + aState.mCompositedFrameRequested = false; aFrames.Advance(nextFrameIndex); // If we're here, we successfully advanced the frame. @@ -379,6 +396,7 @@ FrameAnimator::RequestRefresh(AnimationState& aState, RefreshResult ret; if (aState.IsDiscarded()) { + aState.MaybeAdvanceAnimationFrameTime(aTime); return ret; } @@ -394,6 +412,10 @@ FrameAnimator::RequestRefresh(AnimationState& aState, aState.UpdateStateInternal(result, aAnimationFinished); if (aState.IsDiscarded() || !result) { + aState.MaybeAdvanceAnimationFrameTime(aTime); + if (!ret.mDirtyRect.IsEmpty()) { + ret.mFrameAdvanced = true; + } return ret; } @@ -407,6 +429,15 @@ FrameAnimator::RequestRefresh(AnimationState& aState, MOZ_ASSERT(aState.mCompositedFrameInvalid); // Nothing we can do but wait for our previous current frame to be decoded // again so we can determine what to do next. + aState.MaybeAdvanceAnimationFrameTime(aTime); + return ret; + } + + // If nothing has accessed the composited frame since the last time we + // advanced, then there is no point in continuing to advance the animation. + // This has the effect of freezing the animation while not in view. + if (!aState.mCompositedFrameRequested && + aState.MaybeAdvanceAnimationFrameTime(aTime)) { return ret; } @@ -443,6 +474,8 @@ FrameAnimator::RequestRefresh(AnimationState& aState, LookupResult FrameAnimator::GetCompositedFrame(AnimationState& aState) { + aState.mCompositedFrameRequested = true; + if (aState.mCompositedFrameInvalid) { MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable()); MOZ_ASSERT(aState.GetHasBeenDecoded()); diff --git a/image/FrameAnimator.h b/image/FrameAnimator.h index fd331da9eb..e245f3797a 100644 --- a/image/FrameAnimator.h +++ b/image/FrameAnimator.h @@ -36,6 +36,7 @@ public: , mHasBeenDecoded(false) , mIsCurrentlyDecoded(false) , mCompositedFrameInvalid(false) + , mCompositedFrameRequested(false) , mDiscarded(false) { } @@ -131,6 +132,13 @@ public: */ void SetAnimationFrameTime(const TimeStamp& aTime); + /** + * Set the animation frame time to @aTime if we are configured to stop the + * animation when not visible and aTime is later than the current time. + * Returns true if the time was updated, else false. + */ + bool MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime); + /** * The current frame we're on, from 0 to (numFrames - 1). */ @@ -225,6 +233,10 @@ private: //! valid to draw to the screen. bool mCompositedFrameInvalid; + //! Whether the composited frame was requested from the animator since the + //! last time we advanced the animation. + bool mCompositedFrameRequested; + //! Whether this image is currently discarded. Only set to true after the //! image has been decoded at least once. bool mDiscarded; diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index e9d26dc6c0..a57f1000e7 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4196,6 +4196,10 @@ pref("image.animated.decode-on-demand.threshold-kb", 262144); // animation's currently displayed frame. pref("image.animated.decode-on-demand.batch-size", 6); +// Resume an animated image from the last displayed frame rather than +// advancing when out of view. +pref("image.animated.resume-from-last-displayed", true); + // The maximum size, in bytes, of the decoded images we cache pref("image.cache.size", 5242880);