Bug 1118087 - Correctly sync decode even if an imgFrame is partially decoded. r=tn

This commit is contained in:
Seth Fowler 2015-01-10 20:47:38 -08:00
parent 13ecf8f345
commit 5955b3c0ef
5 changed files with 154 additions and 50 deletions

View File

@ -240,6 +240,7 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
RawAccessFrameRef ref = frame->RawAccessRef();
if (!ref) {
frame->Abort();
return RawAccessFrameRef();
}
@ -250,6 +251,7 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
aFrameNum),
Lifetime::Persistent);
if (!succeeded) {
ref->Abort();
return RawAccessFrameRef();
}
@ -393,6 +395,10 @@ void
Decoder::PostDataError()
{
mDataError = true;
if (mInFrame && mCurrentFrame) {
mCurrentFrame->Abort();
}
}
void
@ -405,6 +411,10 @@ Decoder::PostDecoderError(nsresult aFailureCode)
// XXXbholley - we should report the image URI here, but imgContainer
// needs to know its URI first
NS_WARNING("Image decoding error - This is probably a bug!");
if (mInFrame && mCurrentFrame) {
mCurrentFrame->Abort();
}
}
} // namespace image

View File

@ -89,7 +89,8 @@ FrameAnimator::AdvanceFrame(TimeStamp aTime)
// If we're done decoding, we know we've got everything we're going to get.
// If we aren't, we only display fully-downloaded frames; everything else
// gets delayed.
bool canDisplay = mDoneDecoding || (nextFrame && nextFrame->ImageComplete());
bool canDisplay = mDoneDecoding ||
(nextFrame && nextFrame->IsImageComplete());
if (!canDisplay) {
// Uh oh, the frame we want to show is currently being decoded (partial)
@ -595,6 +596,8 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
compositingFrameData.mRect,
compositingPrevFrameData.mRawData,
compositingPrevFrameData.mRect);
mCompositingPrevFrame->Finish();
}
// blit next frame into it's correct spot
@ -606,11 +609,7 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
nextFrameData.mBlendMethod);
// Tell the image that it is fully 'downloaded'.
nsresult rv =
mCompositingFrame->ImageUpdated(compositingFrameData.mRect);
if (NS_FAILED(rv)) {
return false;
}
mCompositingFrame->Finish();
mLastCompositedFrameIndex = int32_t(aNextFrameIndex);

View File

@ -204,7 +204,7 @@ public:
if (succeeded) {
// Mark the frame as complete and discardable.
mDstRef->ImageUpdated(mDstRef->GetRect());
MOZ_ASSERT(mDstRef->ImageComplete(),
MOZ_ASSERT(mDstRef->IsImageComplete(),
"Incomplete, but just updated the entire frame");
}
@ -551,7 +551,8 @@ RasterImage::LookupFrame(uint32_t aFrameNum,
mFrameCount = 0;
WantDecodedFrames(aFlags, aShouldSyncNotify);
// See if we managed to redecode enough to get the frame we want.
// If we were able to sync decode, we should already have the frame. If we
// had to decode asynchronously, maybe we've gotten lucky.
ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
if (!ref) {
@ -566,6 +567,14 @@ RasterImage::LookupFrame(uint32_t aFrameNum,
MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "Should not have paletted frame");
// Sync decoding guarantees that we got the frame, but if it's owned by an
// async decoder that's currently running, the contents of the frame may not
// be available yet. Make sure we get everything.
if (ref && mHasSourceData && aShouldSyncNotify &&
(aFlags & FLAG_SYNC_DECODE)) {
ref->WaitUntilComplete();
}
return ref;
}
@ -1918,7 +1927,7 @@ RasterImage::RequestScale(imgFrame* aFrame,
const nsIntSize& aSize)
{
// We don't scale frames which aren't fully decoded.
if (!aFrame->ImageComplete()) {
if (!aFrame->IsImageComplete()) {
return;
}
@ -1967,7 +1976,7 @@ RasterImage::DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
// to aFrameRef below.
RequestScale(aFrameRef.get(), aFlags, aSize);
}
if (frameRef && !frameRef->ImageComplete()) {
if (frameRef && !frameRef->IsImageComplete()) {
frameRef.reset(); // We're still scaling, so we can't use this yet.
}
}
@ -2474,7 +2483,7 @@ RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame,
DecodeFlags(aFlags),
0));
if (frameRef && frameRef->ImageComplete()) {
if (frameRef && frameRef->IsImageComplete()) {
return destSize; // We have an existing HQ scale for this size.
}
if (!frameRef) {

View File

@ -130,13 +130,14 @@ static bool AllowedImageAndFrameDimensions(const nsIntSize& aImageSize,
imgFrame::imgFrame()
: mMutex("imgFrame")
: mMonitor("imgFrame")
, mDecoded(0, 0, 0, 0)
, mLockCount(0)
, mTimeout(100)
, mDisposalMethod(DisposalMethod::NOT_SPECIFIED)
, mBlendMethod(BlendMethod::OVER)
, mHasNoAlpha(false)
, mAborted(false)
, mPalettedImageData(nullptr)
, mPaletteDepth(0)
, mNonPremult(false)
@ -155,6 +156,11 @@ imgFrame::imgFrame()
imgFrame::~imgFrame()
{
#ifdef DEBUG
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mAborted || IsImageCompleteInternal());
#endif
moz_free(mPalettedImageData);
mPalettedImageData = nullptr;
}
@ -170,6 +176,7 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
// warn for properties related to bad content.
if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) {
NS_WARNING("Should have legal image size");
mAborted = true;
return NS_ERROR_FAILURE;
}
@ -186,6 +193,7 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
if (aPaletteDepth > 8) {
NS_WARNING("Should have legal palette depth");
NS_ERROR("This Depth is not supported");
mAborted = true;
return NS_ERROR_FAILURE;
}
@ -202,6 +210,7 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
mVBuf = AllocateBufferForImage(mSize, mFormat);
if (!mVBuf) {
mAborted = true;
return NS_ERROR_OUT_OF_MEMORY;
}
if (mVBuf->OnHeap()) {
@ -213,6 +222,7 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
if (!mImageSurface) {
NS_WARNING("Failed to create VolatileDataSourceSurface");
mAborted = true;
return NS_ERROR_OUT_OF_MEMORY;
}
}
@ -231,6 +241,7 @@ imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
// warn for properties related to bad content.
if (!AllowedImageSize(aSize.width, aSize.height)) {
NS_WARNING("Should have legal image size");
mAborted = true;
return NS_ERROR_FAILURE;
}
@ -253,12 +264,14 @@ imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
mVBuf = AllocateBufferForImage(mSize, mFormat);
if (!mVBuf) {
mAborted = true;
return NS_ERROR_OUT_OF_MEMORY;
}
int32_t stride = VolatileSurfaceStride(mSize, mFormat);
VolatileBufferPtr<uint8_t> ptr(mVBuf);
if (!ptr) {
mAborted = true;
return NS_ERROR_OUT_OF_MEMORY;
}
if (mVBuf->OnHeap()) {
@ -280,6 +293,7 @@ imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
}
if (!target) {
mAborted = true;
return NS_ERROR_OUT_OF_MEMORY;
}
@ -292,6 +306,7 @@ imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
if (canUseDataSurface && !mImageSurface) {
NS_WARNING("Failed to create VolatileDataSourceSurface");
mAborted = true;
return NS_ERROR_OUT_OF_MEMORY;
}
@ -301,13 +316,17 @@ imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
mOptSurface = target->Snapshot();
}
// If we reach this point, we should regard ourselves as complete.
mDecoded = GetRect();
MOZ_ASSERT(IsImageComplete());
return NS_OK;
}
nsresult imgFrame::Optimize()
{
MOZ_ASSERT(NS_IsMainThread());
mMutex.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
MOZ_ASSERT(mLockCount == 1,
"Should only optimize when holding the lock exclusively");
@ -455,7 +474,7 @@ imgFrame::SurfaceForDrawing(bool aDoPadding,
SourceSurface* aSurface)
{
MOZ_ASSERT(NS_IsMainThread());
mMutex.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height()));
if (!aDoPadding && !aDoPartialDecode) {
@ -516,7 +535,7 @@ bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
"We must be allowed to sample *some* source pixels!");
NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
nsIntMargin padding(mOffset.y,
mImageSize.width - (mOffset.x + mSize.width),
@ -524,7 +543,7 @@ bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
mOffset.x);
bool doPadding = padding != nsIntMargin(0,0,0,0);
bool doPartialDecode = !ImageCompleteInternal();
bool doPartialDecode = !IsImageCompleteInternal();
if (mSinglePixel && !doPadding && !doPartialDecode) {
if (mSinglePixelColor.a == 0.0) {
@ -569,14 +588,14 @@ bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
nsresult
imgFrame::ImageUpdated(const nsIntRect& aUpdateRect)
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
return ImageUpdatedInternal(aUpdateRect);
}
nsresult
imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect)
{
mMutex.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
mDecoded.UnionRect(mDecoded, aUpdateRect);
@ -585,14 +604,21 @@ imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect)
nsIntRect boundsRect(mOffset, nsIntSize(mSize.width, mSize.height));
mDecoded.IntersectRect(mDecoded, boundsRect);
// If the image is now complete, wake up anyone who's waiting.
if (IsImageCompleteInternal()) {
mMonitor.NotifyAll();
}
return NS_OK;
}
void
imgFrame::Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
int32_t aRawTimeout, BlendMethod aBlendMethod)
imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */,
DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
int32_t aRawTimeout /* = 0 */,
BlendMethod aBlendMethod /* = BlendMethod::OVER */)
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
if (aFrameOpacity == Opacity::OPAQUE) {
@ -613,7 +639,7 @@ nsIntRect imgFrame::GetRect() const
int32_t
imgFrame::GetStride() const
{
mMutex.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
if (mImageSurface) {
return mImageSurface->Stride();
@ -624,13 +650,13 @@ imgFrame::GetStride() const
SurfaceFormat imgFrame::GetFormat() const
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
return mFormat;
}
uint32_t imgFrame::GetImageBytesPerRow() const
{
mMutex.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
if (mVBuf)
return mSize.width * BytesPerPixel(mFormat);
@ -649,14 +675,14 @@ uint32_t imgFrame::GetImageDataLength() const
void
imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
GetImageDataInternal(aData, aLength);
}
void
imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const
{
mMutex.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
if (mImageSurface) {
@ -710,7 +736,7 @@ uint32_t* imgFrame::GetPaletteData() const
nsresult
imgFrame::LockImageData()
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks");
if (mLockCount < 0) {
@ -742,7 +768,7 @@ nsresult
imgFrame::Deoptimize()
{
MOZ_ASSERT(NS_IsMainThread());
mMutex.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
MOZ_ASSERT(!mImageSurface);
if (!mImageSurface) {
@ -811,7 +837,7 @@ void
imgFrame::AssertImageDataLocked() const
{
#ifdef DEBUG
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
#endif
}
@ -834,13 +860,16 @@ private:
nsresult
imgFrame::UnlockImageData()
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!");
if (mLockCount <= 0) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(mLockCount > 1 || IsImageCompleteInternal() || mAborted,
"Should have marked complete or aborted before unlocking");
// If we're about to become unlocked, we don't need to hold on to our data
// surface anymore. (But we don't need to do anything for paletted images,
// which don't have surfaces.)
@ -898,14 +927,14 @@ imgFrame::IsSinglePixel() const
TemporaryRef<SourceSurface>
imgFrame::GetSurface()
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
return GetSurfaceInternal();
}
TemporaryRef<SourceSurface>
imgFrame::GetSurfaceInternal()
{
mMutex.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
if (mOptSurface) {
if (mOptSurface->IsValid())
@ -930,7 +959,7 @@ imgFrame::GetSurfaceInternal()
TemporaryRef<DrawTarget>
imgFrame::GetDrawTarget()
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
uint8_t* data;
uint32_t length;
@ -947,7 +976,7 @@ imgFrame::GetDrawTarget()
AnimationData
imgFrame::GetAnimationData() const
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
uint8_t* data;
@ -967,7 +996,7 @@ imgFrame::GetAnimationData() const
ScalingData
imgFrame::GetScalingData() const
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
MOZ_ASSERT(!GetIsPaletted(), "GetScalingData can't handle paletted images");
@ -978,17 +1007,46 @@ imgFrame::GetScalingData() const
return ScalingData(data, mSize, GetImageBytesPerRow(), mFormat);
}
bool
imgFrame::ImageComplete() const
void
imgFrame::Abort()
{
MutexAutoLock lock(mMutex);
return ImageCompleteInternal();
MonitorAutoLock lock(mMonitor);
mAborted = true;
// Wake up anyone who's waiting.
if (IsImageCompleteInternal()) {
mMonitor.NotifyAll();
}
}
bool
imgFrame::ImageCompleteInternal() const
imgFrame::IsImageComplete() const
{
mMutex.AssertCurrentThreadOwns();
MonitorAutoLock lock(mMonitor);
return IsImageCompleteInternal();
}
void
imgFrame::WaitUntilComplete() const
{
MonitorAutoLock lock(mMonitor);
while (true) {
// Return if we're aborted or complete.
if (mAborted || IsImageCompleteInternal()) {
return;
}
// Not complete yet, so we'll have to wait.
mMonitor.Wait();
}
}
bool
imgFrame::IsImageCompleteInternal() const
{
mMonitor.AssertCurrentThreadOwns();
return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y,
mSize.width, mSize.height));
}
@ -1009,7 +1067,7 @@ size_t
imgFrame::SizeOfExcludingThis(gfxMemoryLocation aLocation,
MallocSizeOf aMallocSizeOf) const
{
MutexAutoLock lock(mMutex);
MonitorAutoLock lock(mMonitor);
// aMallocSizeOf is only used if aLocation==gfxMemoryLocation::IN_PROCESS_HEAP. It
// should be nullptr otherwise.

View File

@ -8,8 +8,8 @@
#define imgFrame_h
#include "mozilla/MemoryReporting.h"
#include "mozilla/Monitor.h"
#include "mozilla/Move.h"
#include "mozilla/Mutex.h"
#include "mozilla/TypedEnum.h"
#include "mozilla/VolatileBuffer.h"
#include "gfxDrawable.h"
@ -182,6 +182,9 @@ public:
/**
* Mark this imgFrame as completely decoded, and set final options.
*
* You must always call either Finish() or Abort() before releasing the last
* RawAccessFrameRef pointing to an imgFrame.
*
* @param aFrameOpacity Whether this imgFrame is opaque.
* @param aDisposalMethod For animation frames, how this imgFrame is cleared
* from the compositing frame before the next frame is
@ -193,8 +196,34 @@ public:
* @param aBlendMethod For animation frames, a blending method to be used
* when compositing this frame.
*/
void Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
int32_t aRawTimeout, BlendMethod aBlendMethod);
void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
int32_t aRawTimeout = 0,
BlendMethod aBlendMethod = BlendMethod::OVER);
/**
* Mark this imgFrame as aborted. This informs the imgFrame that if it isn't
* completely decoded now, it never will be.
*
* You must always call either Finish() or Abort() before releasing the last
* RawAccessFrameRef pointing to an imgFrame.
*/
void Abort();
/**
* Returns true if this imgFrame is completely decoded.
*/
bool IsImageComplete() const;
/**
* Blocks until this imgFrame is either completely decoded, or is marked as
* aborted.
*
* Note that calling this on the main thread _blocks the main thread_. Be very
* careful in your use of this method to avoid excessive main thread jank or
* deadlock.
*/
void WaitUntilComplete() const;
IntSize GetImageSize() { return mImageSize; }
nsIntRect GetRect() const;
@ -218,8 +247,6 @@ public:
AnimationData GetAnimationData() const;
ScalingData GetScalingData() const;
bool ImageComplete() const;
bool GetCompositingFailed() const;
void SetCompositingFailed(bool val);
@ -245,7 +272,7 @@ private: // methods
void AssertImageDataLocked() const;
bool ImageCompleteInternal() const;
bool IsImageCompleteInternal() const;
nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect);
void GetImageDataInternal(uint8_t **aData, uint32_t *length) const;
uint32_t GetImageBytesPerRow() const;
@ -283,10 +310,10 @@ private: // data
friend class UnlockImageDataRunnable;
//////////////////////////////////////////////////////////////////////////////
// Thread-safe mutable data, protected by mMutex.
// Thread-safe mutable data, protected by mMonitor.
//////////////////////////////////////////////////////////////////////////////
mutable Mutex mMutex;
mutable Monitor mMonitor;
RefPtr<DataSourceSurface> mImageSurface;
RefPtr<SourceSurface> mOptSurface;
@ -307,6 +334,7 @@ private: // data
SurfaceFormat mFormat;
bool mHasNoAlpha;
bool mAborted;
//////////////////////////////////////////////////////////////////////////////