bug 296818. discard uncompressed image data after a period of time. original patch from Federico Mena-Quintero <federico@ximian.com>. Changes from me. r=vlad

git-svn-id: svn://10.0.0.236/trunk@237870 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
pavlov%pavlov.net
2007-10-19 00:36:35 +00:00
parent a33f3bf98b
commit afe52b5c68
6 changed files with 815 additions and 69 deletions

View File

@@ -25,6 +25,7 @@
* Asko Tontti <atontti@cc.hut.fi>
* Arron Mogge <paper@animecity.nu>
* Andrew Smith
* Federico Mena-Quintero <federico@novell.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -42,23 +43,47 @@
#include "nsComponentManagerUtils.h"
#include "imgIContainerObserver.h"
#include "ImageErrors.h"
#include "nsIImage.h"
#include "imgILoad.h"
#include "imgIDecoder.h"
#include "imgIDecoderObserver.h"
#include "imgContainer.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsAutoPtr.h"
#include "nsStringStream.h"
#include "prmem.h"
#include "prlog.h"
#include "prenv.h"
#include "gfxContext.h"
/* Accounting for compressed data */
#if defined(PR_LOGGING)
static PRLogModuleInfo *gCompressedImageAccountingLog = PR_NewLogModule ("CompressedImageAccounting");
#else
#define gCompressedImageAccountingLog
#endif
static int num_containers_with_discardable_data;
static PRInt64 num_compressed_image_bytes;
NS_IMPL_ISUPPORTS3(imgContainer, imgIContainer, nsITimerCallback, nsIProperties)
//******************************************************************************
imgContainer::imgContainer() :
mSize(0,0),
mNumFrames(0),
mAnim(nsnull),
mAnimationMode(kNormalAnimMode),
mLoopCount(-1),
mObserver(nsnull)
mObserver(nsnull),
mDiscardable(PR_FALSE),
mDiscarded(PR_FALSE),
mRestoreDataDone(PR_FALSE),
mDiscardTimer(nsnull)
{
}
@@ -67,6 +92,23 @@ imgContainer::~imgContainer()
{
if (mAnim)
delete mAnim;
if (!mRestoreData.IsEmpty()) {
num_containers_with_discardable_data--;
num_compressed_image_bytes -= mRestoreData.Length();
PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: destroying imgContainer %p. "
"Compressed containers: %d, Compressed data bytes: %lld",
this,
num_containers_with_discardable_data,
num_compressed_image_bytes));
}
if (mDiscardTimer) {
mDiscardTimer->Cancel ();
mDiscardTimer = nsnull;
}
}
//******************************************************************************
@@ -124,15 +166,53 @@ NS_IMETHODIMP imgContainer::GetHeight(PRInt32 *aHeight)
return NS_OK;
}
nsresult imgContainer::GetCurrentFrameNoRef(gfxIImageFrame **aFrame)
{
nsresult result;
result = RestoreDiscardedData();
if (NS_FAILED (result)) {
PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: imgContainer::GetCurrentFrameNoRef(): error %d in RestoreDiscardedData(); "
"returning a null frame from imgContainer %p",
result,
this));
*aFrame = nsnull;
return result;
}
if (!mAnim)
*aFrame = mFrames.SafeObjectAt(0);
else if (mAnim->lastCompositedFrameIndex == mAnim->currentAnimationFrameIndex)
*aFrame = mAnim->compositingFrame;
else
*aFrame = mFrames.SafeObjectAt(mAnim->currentAnimationFrameIndex);
if (!*aFrame)
PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: imgContainer::GetCurrentFrameNoRef(): returning null frame from imgContainer %p "
"(no errors when restoring data)",
this));
return NS_OK;
}
//******************************************************************************
/* readonly attribute gfxIImageFrame currentFrame; */
NS_IMETHODIMP imgContainer::GetCurrentFrame(gfxIImageFrame **aCurrentFrame)
{
nsresult result;
NS_ASSERTION(aCurrentFrame, "imgContainer::GetCurrentFrame; Invalid Arg");
if (!aCurrentFrame)
return NS_ERROR_INVALID_POINTER;
if (!(*aCurrentFrame = inlinedGetCurrentFrame()))
result = GetCurrentFrameNoRef (aCurrentFrame);
if (NS_FAILED (result))
return result;
if (!*aCurrentFrame)
return NS_ERROR_FAILURE;
NS_ADDREF(*aCurrentFrame);
@@ -148,7 +228,7 @@ NS_IMETHODIMP imgContainer::GetNumFrames(PRUint32 *aNumFrames)
if (!aNumFrames)
return NS_ERROR_INVALID_ARG;
*aNumFrames = mFrames.Count();
*aNumFrames = mNumFrames;
return NS_OK;
}
@@ -157,16 +237,24 @@ NS_IMETHODIMP imgContainer::GetNumFrames(PRUint32 *aNumFrames)
/* gfxIImageFrame getFrameAt (in unsigned long index); */
NS_IMETHODIMP imgContainer::GetFrameAt(PRUint32 index, gfxIImageFrame **_retval)
{
nsresult result;
NS_ASSERTION(_retval, "imgContainer::GetFrameAt; Invalid Arg");
if (!_retval)
return NS_ERROR_INVALID_POINTER;
if (!mFrames.Count()) {
if (mNumFrames == 0) {
*_retval = nsnull;
return NS_OK;
}
NS_ENSURE_ARG(index < static_cast<PRUint32>(mFrames.Count()));
NS_ENSURE_ARG((int) index < mNumFrames);
result = RestoreDiscardedData ();
if (NS_FAILED (result)) {
*_retval = nsnull;
return result;
}
if (!(*_retval = mFrames[index]))
return NS_ERROR_FAILURE;
@@ -183,16 +271,17 @@ NS_IMETHODIMP imgContainer::AppendFrame(gfxIImageFrame *item)
NS_ASSERTION(item, "imgContainer::AppendFrame; Invalid Arg");
if (!item)
return NS_ERROR_INVALID_ARG;
PRInt32 numFrames = mFrames.Count();
if (numFrames == 0) {
if (mFrames.Count () == 0) {
// This may not be an animated image, don't do all the animation stuff.
mFrames.AppendObject(item);
mNumFrames++;
return NS_OK;
}
if (numFrames == 1) {
if (mFrames.Count () == 1) {
// Now that we got a second frame, initialize animation stuff.
if (!ensureAnimExists())
return NS_ERROR_OUT_OF_MEMORY;
@@ -216,11 +305,13 @@ NS_IMETHODIMP imgContainer::AppendFrame(gfxIImageFrame *item)
itemRect);
mFrames.AppendObject(item);
mNumFrames++;
// If this is our second frame, start the animation.
// Must be called after AppendObject because StartAnimation checks for > 1
// frame
if (numFrames == 1)
if (mFrames.Count () == 1)
StartAnimation();
return NS_OK;
@@ -230,6 +321,7 @@ NS_IMETHODIMP imgContainer::AppendFrame(gfxIImageFrame *item)
/* void removeFrame (in gfxIImageFrame item); */
NS_IMETHODIMP imgContainer::RemoveFrame(gfxIImageFrame *item)
{
/* Remember to decrement mNumFrames if you implement this */
return NS_ERROR_NOT_IMPLEMENTED;
}
@@ -253,7 +345,7 @@ NS_IMETHODIMP imgContainer::DecodingComplete(void)
mAnim->doneDecoding = PR_TRUE;
// If there's only 1 frame, optimize it.
// Optimizing animated images is not supported
if (mFrames.Count() == 1)
if (mNumFrames == 1)
mFrames[0]->SetMutable(PR_FALSE);
return NS_OK;
}
@@ -292,11 +384,11 @@ NS_IMETHODIMP imgContainer::SetAnimationMode(PRUint16 aAnimationMode)
break;
case kNormalAnimMode:
if (mLoopCount != 0 ||
(mAnim && (mAnim->currentAnimationFrameIndex + 1 < mFrames.Count())))
(mAnim && (mAnim->currentAnimationFrameIndex + 1 < mNumFrames)))
StartAnimation();
break;
case kLoopOnceAnimMode:
if (mAnim && (mAnim->currentAnimationFrameIndex + 1 < mFrames.Count()))
if (mAnim && (mAnim->currentAnimationFrameIndex + 1 < mNumFrames))
StartAnimation();
break;
}
@@ -312,12 +404,18 @@ NS_IMETHODIMP imgContainer::StartAnimation()
(mAnim && (mAnim->timer || mAnim->animating)))
return NS_OK;
if (mFrames.Count() > 1) {
if (mNumFrames > 1) {
if (!ensureAnimExists())
return NS_ERROR_OUT_OF_MEMORY;
PRInt32 timeout;
gfxIImageFrame *currentFrame = inlinedGetCurrentFrame();
nsresult result;
gfxIImageFrame *currentFrame;
result = GetCurrentFrameNoRef (&currentFrame);
if (NS_FAILED (result))
return result;
if (currentFrame) {
currentFrame->GetTimeout(&timeout);
if (timeout <= 0) // -1 means display this frame forever
@@ -376,8 +474,15 @@ NS_IMETHODIMP imgContainer::ResetAnimation()
mAnim->currentAnimationFrameIndex = 0;
// Update display
nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
if (observer)
if (observer) {
nsresult result;
result = RestoreDiscardedData ();
if (NS_FAILED (result))
return result;
observer->FrameChanged(this, mFrames[0], &(mAnim->firstFrameRefreshArea));
}
if (oldAnimating)
return StartAnimation();
@@ -411,10 +516,150 @@ NS_IMETHODIMP imgContainer::SetLoopCount(PRInt32 aLoopCount)
return NS_OK;
}
static PRBool
DiscardingEnabled(void)
{
static PRBool inited;
static PRBool enabled;
if (!inited) {
inited = PR_TRUE;
enabled = (PR_GetEnv("MOZ_DISABLE_IMAGE_DISCARD") == nsnull);
}
return enabled;
}
//******************************************************************************
/* void setDiscardable(in string mime_type); */
NS_IMETHODIMP imgContainer::SetDiscardable(const char* aMimeType)
{
NS_ASSERTION(aMimeType, "imgContainer::SetDiscardable() called with null aMimeType");
if (!DiscardingEnabled())
return NS_OK;
if (mDiscardable) {
NS_WARNING ("imgContainer::SetDiscardable(): cannot change an imgContainer which is already discardable");
return NS_ERROR_FAILURE;
}
mDiscardableMimeType.Assign(aMimeType);
mDiscardable = PR_TRUE;
num_containers_with_discardable_data++;
PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: Making imgContainer %p (%s) discardable. "
"Compressed containers: %d, Compressed data bytes: %lld",
this,
aMimeType,
num_containers_with_discardable_data,
num_compressed_image_bytes));
return NS_OK;
}
//******************************************************************************
/* void addRestoreData(in nsIInputStream aInputStream, in unsigned long aCount); */
NS_IMETHODIMP imgContainer::AddRestoreData(const char *aBuffer, PRUint32 aCount)
{
NS_ASSERTION(aBuffer, "imgContainer::AddRestoreData() called with null aBuffer");
if (!DiscardingEnabled ())
return NS_OK;
if (!mDiscardable) {
NS_WARNING ("imgContainer::AddRestoreData() can only be called if SetDiscardable is called first");
return NS_ERROR_FAILURE;
}
if (mRestoreDataDone) {
/* We are being called from the decoder while the data is being restored
* (i.e. we were fully loaded once, then we discarded the image data, then
* we are being restored). We don't want to save the compressed data again,
* since we already have it.
*/
return NS_OK;
}
if (!mRestoreData.AppendElements(aBuffer, aCount))
return NS_ERROR_OUT_OF_MEMORY;
num_compressed_image_bytes += aCount;
PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: Added compressed data to imgContainer %p (%s). "
"Compressed containers: %d, Compressed data bytes: %lld",
this,
mDiscardableMimeType.get(),
num_containers_with_discardable_data,
num_compressed_image_bytes));
return NS_OK;
}
/* Note! buf must be declared as char buf[9]; */
// just used for logging and hashing the header
static void
get_header_str (char *buf, char *data, PRSize data_len)
{
int i;
int n;
static char hex[] = "0123456789abcdef";
n = data_len < 4 ? data_len : 4;
for (i = 0; i < n; i++) {
buf[i * 2] = hex[(data[i] >> 4) & 0x0f];
buf[i * 2 + 1] = hex[data[i] & 0x0f];
}
buf[i * 2] = 0;
}
//******************************************************************************
/* void restoreDataDone(); */
NS_IMETHODIMP imgContainer::RestoreDataDone (void)
{
if (!DiscardingEnabled ())
return NS_OK;
if (mRestoreDataDone)
return NS_OK;
mRestoreData.Compact();
mRestoreDataDone = PR_TRUE;
if (PR_LOG_TEST(gCompressedImageAccountingLog, PR_LOG_DEBUG)) {
char buf[9];
get_header_str(buf, mRestoreData.Elements(), mRestoreData.Length());
PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: imgContainer::RestoreDataDone() - data is done for container %p (%s), %d real frames (cached as %d frames) - header %p is 0x%s (length %d)",
this,
mDiscardableMimeType.get(),
mFrames.Count (),
mNumFrames,
mRestoreData.Elements(),
buf,
mRestoreData.Length()));
}
return ResetDiscardTimer();
}
//******************************************************************************
/* void notify(in nsITimer timer); */
NS_IMETHODIMP imgContainer::Notify(nsITimer *timer)
{
nsresult result;
result = RestoreDiscardedData();
if (NS_FAILED (result))
return result;
// This should never happen since the timer is only set up in StartAnimation()
// after mAnim is checked to exist.
NS_ASSERTION(mAnim, "imgContainer::Notify() called but mAnim is null");
@@ -433,8 +678,7 @@ NS_IMETHODIMP imgContainer::Notify(nsITimer *timer)
return NS_OK;
}
PRInt32 numFrames = mFrames.Count();
if (!numFrames)
if (mNumFrames == 0)
return NS_OK;
gfxIImageFrame *nextFrame = nsnull;
@@ -448,7 +692,7 @@ NS_IMETHODIMP imgContainer::Notify(nsITimer *timer)
// finished decoding (see EndFrameDecode)
if (mAnim->doneDecoding ||
(nextFrameIndex < mAnim->currentDecodingFrameIndex)) {
if (numFrames == nextFrameIndex) {
if (mNumFrames == nextFrameIndex) {
// End of Animation
// If animation mode is "loop once", it's time to stop animating
@@ -906,3 +1150,308 @@ NS_IMETHODIMP imgContainer::GetKeys(PRUint32 *count, char ***keys)
}
return mProperties->GetKeys(count, keys);
}
static int
get_discard_timer_ms (void)
{
/* FIXME: don't hardcode this */
return 45000; /* 45 seconds */
}
void
imgContainer::sDiscardTimerCallback(nsITimer *aTimer, void *aClosure)
{
imgContainer *self = (imgContainer *) aClosure;
int old_frame_count;
NS_ASSERTION(aTimer == self->mDiscardTimer,
"imgContainer::DiscardTimerCallback() got a callback for an unknown timer");
self->mDiscardTimer = nsnull;
old_frame_count = self->mFrames.Count();
if (self->mAnim) {
delete self->mAnim;
self->mAnim = nsnull;
}
self->mFrames.Clear();
self->mDiscarded = PR_TRUE;
PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: discarded uncompressed image data from imgContainer %p (%s) - %d frames (cached count: %d); "
"Compressed containers: %d, Compressed data bytes: %lld",
self,
self->mDiscardableMimeType.get(),
old_frame_count,
self->mNumFrames,
num_containers_with_discardable_data,
num_compressed_image_bytes));
}
nsresult
imgContainer::ResetDiscardTimer (void)
{
if (!DiscardingEnabled())
return NS_OK;
if (!mDiscardTimer) {
mDiscardTimer = do_CreateInstance("@mozilla.org/timer;1");
if (!mDiscardTimer)
return NS_ERROR_OUT_OF_MEMORY;
} else {
if (NS_FAILED(mDiscardTimer->Cancel()))
return NS_ERROR_FAILURE;
}
return mDiscardTimer->InitWithFuncCallback(sDiscardTimerCallback,
(void *) this,
get_discard_timer_ms (),
nsITimer::TYPE_ONE_SHOT);
}
nsresult
imgContainer::RestoreDiscardedData(void)
{
nsresult result;
int num_expected_frames;
if (!mDiscardable)
return NS_OK;
result = ResetDiscardTimer();
if (NS_FAILED (result))
return result;
if (!mDiscarded)
return NS_OK;
num_expected_frames = mNumFrames;
result = ReloadImages ();
if (NS_FAILED (result)) {
PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: imgContainer::RestoreDiscardedData() for container %p failed to ReloadImages()",
this));
return result;
}
mDiscarded = PR_FALSE;
NS_ASSERTION (mNumFrames == mFrames.Count(),
"number of restored image frames doesn't match");
NS_ASSERTION (num_expected_frames == mNumFrames,
"number of restored image frames doesn't match the original number of frames!");
PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
("CompressedImageAccounting: imgContainer::RestoreDiscardedData() restored discarded data "
"for imgContainer %p (%s) - %d image frames. "
"Compressed containers: %d, Compressed data bytes: %lld",
this,
mDiscardableMimeType.get(),
mNumFrames,
num_containers_with_discardable_data,
num_compressed_image_bytes));
return NS_OK;
}
class ContainerLoader : public imgILoad,
public imgIDecoderObserver,
public nsSupportsWeakReference
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IMGILOAD
NS_DECL_IMGIDECODEROBSERVER
NS_DECL_IMGICONTAINEROBSERVER
ContainerLoader(void);
private:
imgIContainer *mContainer;
};
NS_IMPL_ISUPPORTS4 (ContainerLoader, imgILoad, imgIDecoderObserver, imgIContainerObserver, nsISupportsWeakReference)
ContainerLoader::ContainerLoader (void)
{
}
/* Implement imgILoad::image getter */
NS_IMETHODIMP
ContainerLoader::GetImage(imgIContainer **aImage)
{
*aImage = mContainer;
NS_IF_ADDREF (*aImage);
return NS_OK;
}
/* Implement imgILoad::image setter */
NS_IMETHODIMP
ContainerLoader::SetImage(imgIContainer *aImage)
{
mContainer = aImage;
return NS_OK;
}
/* Implement imgILoad::isMultiPartChannel getter */
NS_IMETHODIMP
ContainerLoader::GetIsMultiPartChannel(PRBool *aIsMultiPartChannel)
{
*aIsMultiPartChannel = PR_FALSE; /* FIXME: is this always right? */
return NS_OK;
}
/* Implement imgIDecoderObserver::onStartRequest() */
NS_IMETHODIMP
ContainerLoader::OnStartRequest(imgIRequest *aRequest)
{
return NS_OK;
}
/* Implement imgIDecoderObserver::onStartDecode() */
NS_IMETHODIMP
ContainerLoader::OnStartDecode(imgIRequest *aRequest)
{
return NS_OK;
}
/* Implement imgIDecoderObserver::onStartContainer() */
NS_IMETHODIMP
ContainerLoader::OnStartContainer(imgIRequest *aRequest, imgIContainer *aContainer)
{
return NS_OK;
}
/* Implement imgIDecoderObserver::onStartFrame() */
NS_IMETHODIMP
ContainerLoader::OnStartFrame(imgIRequest *aRequest, gfxIImageFrame *aFrame)
{
return NS_OK;
}
/* Implement imgIDecoderObserver::onDataAvailable() */
NS_IMETHODIMP
ContainerLoader::OnDataAvailable(imgIRequest *aRequest, gfxIImageFrame *aFrame, const nsIntRect * aRect)
{
return NS_OK;
}
/* Implement imgIDecoderObserver::onStopFrame() */
NS_IMETHODIMP
ContainerLoader::OnStopFrame(imgIRequest *aRequest, gfxIImageFrame *aFrame)
{
return NS_OK;
}
/* Implement imgIDecoderObserver::onStopContainer() */
NS_IMETHODIMP
ContainerLoader::OnStopContainer(imgIRequest *aRequest, imgIContainer *aContainer)
{
return NS_OK;
}
/* Implement imgIDecoderObserver::onStopDecode() */
NS_IMETHODIMP
ContainerLoader::OnStopDecode(imgIRequest *aRequest, nsresult status, const PRUnichar *statusArg)
{
return NS_OK;
}
/* Implement imgIDecoderObserver::onStopRequest() */
NS_IMETHODIMP
ContainerLoader::OnStopRequest(imgIRequest *aRequest, PRBool aIsLastPart)
{
return NS_OK;
}
/* implement imgIContainerObserver::frameChanged() */
NS_IMETHODIMP
ContainerLoader::FrameChanged(imgIContainer *aContainer, gfxIImageFrame *aFrame, nsIntRect * aDirtyRect)
{
return NS_OK;
}
nsresult
imgContainer::ReloadImages(void)
{
nsresult result = NS_ERROR_FAILURE;
nsCOMPtr<nsIInputStream> stream;
NS_ASSERTION(!mRestoreData.IsEmpty(),
"imgContainer::ReloadImages(): mRestoreData should not be empty");
NS_ASSERTION(mRestoreDataDone,
"imgContainer::ReloadImages(): mRestoreDataDone shoudl be true!");
mNumFrames = 0;
NS_ASSERTION(mFrames.Count() == 0,
"imgContainer::ReloadImages(): mFrames should be empty");
nsCAutoString decoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;2?type=") + mDiscardableMimeType);
nsCOMPtr<imgIDecoder> decoder = do_CreateInstance(decoderCID.get());
if (!decoder) {
PR_LOG(gCompressedImageAccountingLog, PR_LOG_WARNING,
("CompressedImageAccounting: imgContainer::ReloadImages() could not create decoder for %s",
mDiscardableMimeType.get()));
return NS_IMAGELIB_ERROR_NO_DECODER;
}
nsCOMPtr<imgILoad> loader = new ContainerLoader();
if (!loader) {
PR_LOG(gCompressedImageAccountingLog, PR_LOG_WARNING,
("CompressedImageAccounting: imgContainer::ReloadImages() could not allocate ContainerLoader "
"when reloading the images for container %p",
this));
return NS_ERROR_OUT_OF_MEMORY;
}
loader->SetImage(this);
result = decoder->Init(loader);
if (NS_FAILED(result)) {
PR_LOG(gCompressedImageAccountingLog, PR_LOG_WARNING,
("CompressedImageAccounting: imgContainer::ReloadImages() image container %p "
"failed to initialize the decoder (%s)",
this,
mDiscardableMimeType.get()));
return result;
}
result = NS_NewByteInputStream(getter_AddRefs(stream), mRestoreData.Elements(), mRestoreData.Length(), NS_ASSIGNMENT_DEPEND);
NS_ENSURE_SUCCESS(result, result);
if (PR_LOG_TEST(gCompressedImageAccountingLog, PR_LOG_DEBUG)) {
char buf[9];
get_header_str(buf, mRestoreData.Elements(), mRestoreData.Length());
PR_LOG(gCompressedImageAccountingLog, PR_LOG_WARNING,
("CompressedImageAccounting: imgContainer::ReloadImages() starting to restore images for container %p (%s) - "
"header %p is 0x%s (length %d)",
this,
mDiscardableMimeType.get(),
mRestoreData.Elements(),
buf,
mRestoreData.Length()));
}
PRUint32 written;
result = decoder->WriteFrom(stream, mRestoreData.Length(), &written);
NS_ENSURE_SUCCESS(result, result);
if (NS_FAILED(decoder->Flush()))
return result;
result = decoder->Close();
NS_ENSURE_SUCCESS(result, result);
NS_ASSERTION(mFrames.Count() == mNumFrames,
"imgContainer::ReloadImages(): the restored mFrames.Count() doesn't match mNumFrames!");
return result;
}