Bug 715308 - Part 1: Decode RasterImage::Draw()'n images before other images. r=joe

--HG--
extra : rebase_source : b958a682e5a92767ba1887a7d6df5a53f146fda2
This commit is contained in:
Justin Lebar 2012-01-26 15:54:04 -05:00
parent 8e155b164f
commit dcc95caa8a
2 changed files with 394 additions and 196 deletions

View File

@ -71,6 +71,7 @@
#include "mozilla/StdInt.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/ClearOnShutdown.h"
using namespace mozilla;
using namespace mozilla::image;
@ -176,6 +177,8 @@ DiscardingEnabled()
namespace mozilla {
namespace image {
/* static */ nsRefPtr<RasterImage::DecodeWorker> RasterImage::DecodeWorker::sSingleton;
#ifndef DEBUG
NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
nsISupportsWeakReference)
@ -194,7 +197,7 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
mObserver(nsnull),
mLockCount(0),
mDecoder(nsnull),
mWorker(nsnull),
mDecodeRequest(this),
mBytesDecoded(0),
mDecodeCount(0),
#ifdef DEBUG
@ -207,7 +210,6 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
mHasSourceData(false),
mDecoded(false),
mHasBeenDecoded(false),
mWorkerPending(false),
mInDecoder(false),
mAnimationFinished(false)
{
@ -1496,11 +1498,9 @@ RasterImage::AddSourceData(const char *aBuffer, PRUint32 aCount)
return NS_ERROR_OUT_OF_MEMORY;
// If there's a decoder open, that means we want to do more decoding.
// Wake up the worker if it's not up already
if (mDecoder && !mWorkerPending) {
NS_ABORT_IF_FALSE(mWorker, "We should have a worker here!");
rv = mWorker->Run();
CONTAINER_ENSURE_SUCCESS(rv);
// Wake up the worker.
if (mDecoder) {
DecodeWorker::Singleton()->RequestDecode(this);
}
}
@ -1563,19 +1563,21 @@ RasterImage::SourceDataComplete()
CONTAINER_ENSURE_SUCCESS(rv);
}
// If there's a decoder open, we need to wake up the worker if it's not
// already. This is so the worker can account for the fact that the source
// data is complete. For some decoders, DecodingComplete() is only called
// when the decoder is Close()-ed, and thus the SourceDataComplete() call
// is the only way we can transition to a 'decoded' state. Furthermore,
// it's always possible for any image type to have the data stream stop
// abruptly at any point, in which case we need to trigger an error.
if (mDecoder && !mWorkerPending) {
NS_ABORT_IF_FALSE(mWorker, "We should have a worker here!");
nsresult rv = mWorker->Run();
// If there's a decoder open, synchronously decode the beginning of the image
// to check for errors and get the image's size. (If we already have the
// image's size, this does nothing.) Then kick off an async decode of the
// rest of the image.
if (mDecoder) {
nsresult rv = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this);
CONTAINER_ENSURE_SUCCESS(rv);
}
// If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker
// finish decoding this image.
if (mDecoder) {
DecodeWorker::Singleton()->RequestDecode(this);
}
// Free up any extra space in the backing buffer
mSourceData.Compact();
@ -2281,15 +2283,11 @@ RasterImage::InitDecoder(bool aDoSizeDecode)
mDecoder->Init();
CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
// Create a decode worker
mWorker = new imgDecodeWorker(this);
if (!aDoSizeDecode) {
Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Subtract(mDecodeCount);
mDecodeCount++;
Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(mDecodeCount);
}
CONTAINER_ENSURE_TRUE(mWorker, NS_ERROR_OUT_OF_MEMORY);
return NS_OK;
}
@ -2325,16 +2323,16 @@ RasterImage::ShutdownDecoder(eShutdownIntent aIntent)
decoder->Finish();
mInDecoder = false;
// Kill off our decode request, if it's pending. (If not, this call is
// harmless.)
DecodeWorker::Singleton()->StopDecoding(this);
nsresult decoderStatus = decoder->GetDecoderError();
if (NS_FAILED(decoderStatus)) {
DoError();
return decoderStatus;
}
// Kill off the worker
mWorker = nsnull;
mWorkerPending = false;
// We just shut down the decoder. If we didn't get what we want, but expected
// to, flag an error
bool failed = false;
@ -2468,10 +2466,6 @@ RasterImage::RequestDecode()
CONTAINER_ENSURE_SUCCESS(rv);
}
// If we already have a pending worker, we're done
if (mWorkerPending)
return NS_OK;
// If we've read all the data we have, we're done
if (mBytesDecoded == mSourceData.Length())
return NS_OK;
@ -2483,7 +2477,9 @@ RasterImage::RequestDecode()
// If we get this far, dispatch the worker. We do this instead of starting
// any immediate decoding to guarantee that all our decode notifications are
// dispatched asynchronously, and to ensure we stay responsive.
return mWorker->Dispatch();
DecodeWorker::Singleton()->RequestDecode(this);
return NS_OK;
}
// Synchronously decodes as much data as possible
@ -2590,6 +2586,10 @@ RasterImage::Draw(gfxContext *aContext,
// We use !mDecoded && mHasSourceData to mean discarded.
if (!mDecoded && mHasSourceData) {
mDrawStartTime = TimeStamp::Now();
// We're drawing this image, so indicate that we should decode it as soon
// as possible.
DecodeWorker::Singleton()->MarkAsASAP(this);
}
// If a synchronous draw is requested, flush anything that might be sitting around
@ -2767,143 +2767,7 @@ RasterImage::DoError()
LOG_CONTAINER_ERROR;
}
// Decodes some data, then re-posts itself to the end of the event queue if
// there's more processing to be done
NS_IMETHODIMP
imgDecodeWorker::Run()
{
nsresult rv;
// If we shutdown the decoder in this function, we could lose ourselves
nsCOMPtr<nsIRunnable> kungFuDeathGrip(this);
// The container holds a strong reference to us. Cycles are bad.
nsCOMPtr<imgIContainer> iContainer(do_QueryReferent(mContainer));
if (!iContainer)
return NS_OK;
RasterImage* image = static_cast<RasterImage*>(iContainer.get());
NS_ABORT_IF_FALSE(image->mInitialized,
"Worker active for uninitialized container!");
// If we were pending, we're not anymore
image->mWorkerPending = false;
// If an error is flagged, it probably happened while we were waiting
// in the event queue. Bail early, but no need to bother the run queue
// by returning an error.
if (image->mError)
return NS_OK;
// If we don't have a decoder, we must have finished already (for example,
// a synchronous decode request came while the worker was pending).
if (!image->mDecoder)
return NS_OK;
nsRefPtr<Decoder> decoderKungFuDeathGrip = image->mDecoder;
// Size decodes are cheap and we more or less want them to be
// synchronous. Write all the data in that case, otherwise write a
// chunk
PRUint32 maxBytes = image->mDecoder->IsSizeDecode()
? image->mSourceData.Length() : gDecodeBytesAtATime;
// Loop control
bool haveMoreData = true;
PRInt32 chunkCount = 0;
TimeStamp start = TimeStamp::Now();
TimeStamp deadline = start + TimeDuration::FromMilliseconds(gMaxMSBeforeYield);
// We keep decoding chunks until one of three possible events occur:
// 1) We don't have any data left to decode
// 2) The decode completes
// 3) We hit the deadline and need to yield to keep the UI snappy
while (haveMoreData && !image->IsDecodeFinished() &&
(TimeStamp::Now() < deadline)) {
// Decode a chunk of data
chunkCount++;
rv = image->DecodeSomeData(maxBytes);
if (NS_FAILED(rv)) {
image->DoError();
return rv;
}
// Figure out if we still have more data
haveMoreData =
image->mSourceData.Length() > image->mBytesDecoded;
}
TimeDuration decodeLatency = TimeStamp::Now() - start;
if (chunkCount && !image->mDecoder->IsSizeDecode()) {
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY, PRInt32(decodeLatency.ToMicroseconds()));
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, chunkCount);
}
// accumulate the total decode time
mDecodeTime += decodeLatency;
// Flush invalidations _after_ we've written everything we're going to.
// Furthermore, if we have all of the data, we don't want to do progressive
// display at all. In that case, let Decoder::PostFrameStop() do the
// flush once the whole frame is ready.
if (!image->mHasSourceData) {
image->mInDecoder = true;
image->mDecoder->FlushInvalidations();
image->mInDecoder = false;
}
// If the decode finished, shutdown the decoder
if (image->mDecoder && image->IsDecodeFinished()) {
if (!image->mDecoder->IsSizeDecode()) {
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, PRInt32(mDecodeTime.ToMicroseconds()));
// We only record the speed for some decoders. The rest have SpeedHistogram return HistogramCount.
Telemetry::ID id = image->mDecoder->SpeedHistogram();
if (id < Telemetry::HistogramCount) {
PRInt32 KBps = PRInt32((image->mBytesDecoded/1024.0)/mDecodeTime.ToSeconds());
Telemetry::Accumulate(id, KBps);
}
}
rv = image->ShutdownDecoder(RasterImage::eShutdownIntent_Done);
if (NS_FAILED(rv)) {
image->DoError();
return rv;
}
}
// If Conditions 1 & 2 are still true, then the only reason we bailed was
// because we hit the deadline. Repost ourselves to the end of the event
// queue.
if (image->mDecoder && !image->IsDecodeFinished() && haveMoreData)
return this->Dispatch();
// Otherwise, return success
return NS_OK;
}
// Queues the worker up at the end of the event queue
NS_METHOD imgDecodeWorker::Dispatch()
{
// The container holds a strong reference to us. Cycles are bad.
nsCOMPtr<imgIContainer> iContainer(do_QueryReferent(mContainer));
if (!iContainer)
return NS_OK;
RasterImage* image = static_cast<RasterImage*>(iContainer.get());
// We should not be called if there's already a pending worker
NS_ABORT_IF_FALSE(!image->mWorkerPending,
"Trying to queue up worker with one already pending!");
// Flag that we're pending
image->mWorkerPending = true;
// Dispatch
return NS_DispatchToCurrentThread(this);
}
// nsIInputStream callback to copy the incoming image data directly to the
// nsIInputStream callback to copy the incoming image data directly to the
// RasterImage without processing. The RasterImage is passed as the closure.
// Always reads everything it gets, even if the data is erroneous.
NS_METHOD
@ -2940,7 +2804,6 @@ RasterImage::ShouldAnimate()
!mAnimationFinished;
}
//******************************************************************************
/* readonly attribute PRUint32 framesNotified; */
#ifdef DEBUG
NS_IMETHODIMP
@ -2954,5 +2817,249 @@ RasterImage::GetFramesNotified(PRUint32 *aFramesNotified)
}
#endif
/* static */ RasterImage::DecodeWorker*
RasterImage::DecodeWorker::Singleton()
{
if (!sSingleton) {
sSingleton = new DecodeWorker();
ClearOnShutdown(&sSingleton);
}
return sSingleton;
}
void
RasterImage::DecodeWorker::MarkAsASAP(RasterImage* aImg)
{
DecodeRequest* request = &aImg->mDecodeRequest;
// If we're already an ASAP request, there's nothing to do here.
if (request->mIsASAP) {
return;
}
request->mIsASAP = true;
if (request->isInList()) {
// If the decode request is in a list, it must be in the normal decode
// requests list -- if it had been in the ASAP list, then mIsASAP would
// have been true above. Move the request to the ASAP list.
request->remove();
mASAPDecodeRequests.insertBack(request);
// Since request is in a list, one of the decode worker's lists is
// non-empty, so the worker should be pending in the event loop.
//
// (Note that this invariant only holds while we are not in Run(), because
// DecodeSomeOfImage adds requests to the decode worker using
// AddDecodeRequest, not RequestDecode, and AddDecodeRequest does not call
// EnsurePendingInEventLoop. Therefore, it is an error to call MarkAsASAP
// from within DecodeWorker::Run.)
MOZ_ASSERT(mPendingInEventLoop);
}
}
void
RasterImage::DecodeWorker::AddDecodeRequest(DecodeRequest* aRequest)
{
if (aRequest->isInList()) {
// The image is already in our list of images to decode, so we don't have
// to do anything here.
return;
}
if (aRequest->mIsASAP) {
mASAPDecodeRequests.insertBack(aRequest);
} else {
mNormalDecodeRequests.insertBack(aRequest);
}
}
void
RasterImage::DecodeWorker::RequestDecode(RasterImage* aImg)
{
AddDecodeRequest(&aImg->mDecodeRequest);
EnsurePendingInEventLoop();
}
void
RasterImage::DecodeWorker::EnsurePendingInEventLoop()
{
if (!mPendingInEventLoop) {
mPendingInEventLoop = true;
NS_DispatchToCurrentThread(this);
}
}
void
RasterImage::DecodeWorker::StopDecoding(RasterImage* aImg)
{
DecodeRequest* request = &aImg->mDecodeRequest;
if (request->isInList()) {
request->remove();
}
request->mDecodeTime = TimeDuration(0);
request->mIsASAP = false;
}
NS_IMETHODIMP
RasterImage::DecodeWorker::Run()
{
// We just got called back by the event loop; therefore, we're no longer
// pending.
mPendingInEventLoop = false;
TimeStamp eventStart = TimeStamp::Now();
// Now decode until we either run out of time or run out of images.
do {
// Try to get an ASAP request to handle. If there isn't one, try to get a
// normal request. If no normal request is pending either, then we're done
// here.
DecodeRequest* request = mASAPDecodeRequests.popFirst();
if (!request)
request = mNormalDecodeRequests.popFirst();
if (!request)
break;
RasterImage *image = request->mImage;
DecodeSomeOfImage(image);
// If we aren't yet finished decoding and we have more data in hand, add
// this request to the back of the list.
if (image->mDecoder &&
!image->mError &&
!image->IsDecodeFinished() &&
image->mSourceData.Length() > image->mBytesDecoded) {
AddDecodeRequest(request);
}
} while ((TimeStamp::Now() - eventStart).ToMilliseconds() <= gMaxMSBeforeYield);
// If decode requests are pending, re-post ourself to the event loop.
if (!mASAPDecodeRequests.isEmpty() || !mNormalDecodeRequests.isEmpty()) {
EnsurePendingInEventLoop();
}
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY,
PRUint32((TimeStamp::Now() - eventStart).ToMilliseconds()));
return NS_OK;
}
nsresult
RasterImage::DecodeWorker::DecodeUntilSizeAvailable(RasterImage* aImg)
{
return DecodeSomeOfImage(aImg, DECODE_TYPE_UNTIL_SIZE);
}
nsresult
RasterImage::DecodeWorker::DecodeSomeOfImage(
RasterImage* aImg,
DecodeType aDecodeType /* = DECODE_TYPE_NORMAL */)
{
NS_ABORT_IF_FALSE(aImg->mInitialized,
"Worker active for uninitialized container!");
if (aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize)
return NS_OK;
// If an error is flagged, it probably happened while we were waiting
// in the event queue.
if (aImg->mError)
return NS_OK;
// If we don't have a decoder, we must have finished already (for example,
// a synchronous decode request came while the worker was pending).
if (!aImg->mDecoder)
return NS_OK;
nsRefPtr<Decoder> decoderKungFuDeathGrip = aImg->mDecoder;
PRUint32 maxBytes;
if (aImg->mDecoder->IsSizeDecode()) {
// Decode all available data if we're a size decode; they're cheap, and we
// want them to be more or less synchronous.
maxBytes = aImg->mSourceData.Length();
} else {
// We're only guaranteed to decode this many bytes, so in particular,
// gDecodeBytesAtATime should be set high enough for us to read the size
// from most images.
maxBytes = gDecodeBytesAtATime;
}
// Loop control
PRInt32 chunkCount = 0;
TimeStamp start = TimeStamp::Now();
TimeStamp deadline = start + TimeDuration::FromMilliseconds(gMaxMSBeforeYield);
// We keep decoding chunks until one of events occurs:
// 1) We don't have any data left to decode
// 2) The decode completes
// 3) We're an UNTIL_SIZE decode and we get the size
// 4) We hit the deadline and yield to keep the UI snappy
while (aImg->mSourceData.Length() > aImg->mBytesDecoded &&
!aImg->IsDecodeFinished() &&
TimeStamp::Now() < deadline) {
// Decode a chunk of data.
chunkCount++;
nsresult rv = aImg->DecodeSomeData(maxBytes);
if (NS_FAILED(rv)) {
aImg->DoError();
return rv;
}
// If we're an UNTIL_SIZE decode and we got the image's size, we're done
// here.
if (aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize)
break;
}
aImg->mDecodeRequest.mDecodeTime += (TimeStamp::Now() - start);
if (chunkCount && !aImg->mDecoder->IsSizeDecode()) {
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, chunkCount);
}
// Flush invalidations _after_ we've written everything we're going to.
// Furthermore, if we have all of the data, we don't want to do progressive
// display at all. In that case, let Decoder::PostFrameStop() do the
// flush once the whole frame is ready.
if (!aImg->mHasSourceData) {
aImg->mInDecoder = true;
aImg->mDecoder->FlushInvalidations();
aImg->mInDecoder = false;
}
// If the decode finished, shut down the decoder.
if (aImg->mDecoder && aImg->IsDecodeFinished()) {
// Do some telemetry if this isn't a size decode.
DecodeRequest* request = &aImg->mDecodeRequest;
if (!aImg->mDecoder->IsSizeDecode()) {
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME,
PRInt32(request->mDecodeTime.ToMicroseconds()));
// We record the speed for only some decoders. The rest have
// SpeedHistogram return HistogramCount.
Telemetry::ID id = aImg->mDecoder->SpeedHistogram();
if (id < Telemetry::HistogramCount) {
PRInt32 KBps = PRInt32(request->mImage->mBytesDecoded /
(1024 * request->mDecodeTime.ToSeconds()));
Telemetry::Accumulate(id, KBps);
}
}
nsresult rv = aImg->ShutdownDecoder(RasterImage::eShutdownIntent_Done);
if (NS_FAILED(rv)) {
aImg->DoError();
return rv;
}
}
return NS_OK;
}
} // namespace image
} // namespace mozilla

View File

@ -66,6 +66,7 @@
#include "DiscardTracker.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Telemetry.h"
#include "mozilla/LinkedList.h"
#ifdef DEBUG
#include "imgIContainerDebug.h"
#endif
@ -164,7 +165,6 @@ class ImageContainer;
}
namespace image {
class imgDecodeWorker;
class Decoder;
class RasterImage : public Image
@ -376,6 +376,123 @@ private:
~Anim() {}
};
/**
* DecodeWorker keeps a linked list of DecodeRequests to keep track of the
* images it needs to decode.
*
* Each RasterImage has a single DecodeRequest member.
*/
struct DecodeRequest : public LinkedListElement<DecodeRequest>
{
DecodeRequest(RasterImage* aImage)
: mImage(aImage)
, mIsASAP(false)
{
}
RasterImage* const mImage;
/* Keeps track of how much time we've burned decoding this particular decode
* request. */
TimeDuration mDecodeTime;
/* True if we need to handle this decode as soon as possible. */
bool mIsASAP;
};
/*
* DecodeWorker is a singleton class we use when decoding large images.
*
* When we wish to decode an image larger than
* image.mem.max_bytes_for_sync_decode, we call DecodeWorker::RequestDecode()
* for the image. This adds the image to a queue of pending requests and posts
* the DecodeWorker singleton to the event queue, if it's not already pending
* there.
*
* When the DecodeWorker is run from the event queue, it decodes the image (and
* all others it's managing) in chunks, periodically yielding control back to
* the event loop.
*
* An image being decoded may have one of two priorities: normal or ASAP. ASAP
* images are always decoded before normal images. (We currently give ASAP
* priority to images which appear onscreen but are not yet decoded.)
*/
class DecodeWorker : public nsRunnable
{
public:
static DecodeWorker* Singleton();
/**
* Ask the DecodeWorker to asynchronously decode this image.
*/
void RequestDecode(RasterImage* aImg);
/**
* Give this image ASAP priority; it will be decoded before all non-ASAP
* images. You can call MarkAsASAP before or after you call RequestDecode
* for the image, but if you MarkAsASAP before you call RequestDecode, you
* still need to call RequestDecode.
*
* StopDecoding() resets the image's ASAP flag.
*/
void MarkAsASAP(RasterImage* aImg);
/**
* Ask the DecodeWorker to stop decoding this image. Internally, we also
* call this function when we finish decoding an image.
*
* Since the DecodeWorker keeps raw pointers to RasterImages, make sure you
* call this before a RasterImage is destroyed!
*/
void StopDecoding(RasterImage* aImg);
/**
* Synchronously decode the beginning of the image until we run out of
* bytes or we get the image's size. Note that this done on a best-effort
* basis; if the size is burried too deep in the image, we'll give up.
*
* @return NS_ERROR if an error is encountered, and NS_OK otherwise. (Note
* that we return NS_OK even when the size was not found.)
*/
nsresult DecodeUntilSizeAvailable(RasterImage* aImg);
NS_IMETHOD Run();
private: /* statics */
static nsRefPtr<DecodeWorker> sSingleton;
private: /* methods */
DecodeWorker()
: mPendingInEventLoop(false)
{}
/* Post ourselves to the event loop if we're not currently pending. */
void EnsurePendingInEventLoop();
/* Add the given request to the appropriate list of decode requests, but
* don't ensure that we're pending in the event loop. */
void AddDecodeRequest(DecodeRequest* aRequest);
enum DecodeType {
DECODE_TYPE_NORMAL,
DECODE_TYPE_UNTIL_SIZE
};
/* Decode some chunks of the given image. If aDecodeType is UNTIL_SIZE,
* decode until we have the image's size, then stop. */
nsresult DecodeSomeOfImage(RasterImage* aImg,
DecodeType aDecodeType = DECODE_TYPE_NORMAL);
private: /* members */
LinkedList<DecodeRequest> mASAPDecodeRequests;
LinkedList<DecodeRequest> mNormalDecodeRequests;
/* True if we've posted ourselves to the event loop and expect Run() to
* be called sometime in the future. */
bool mPendingInEventLoop;
};
/**
* Advances the animation. Typically, this will advance a single frame, but it
* may advance multiple frames. This may happen if we have infrequently
@ -524,12 +641,11 @@ private: // data
nsCString mSourceDataMimeType;
nsCString mURIString;
friend class imgDecodeWorker;
friend class DiscardTracker;
// Decoder and friends
nsRefPtr<Decoder> mDecoder;
nsRefPtr<imgDecodeWorker> mWorker;
DecodeRequest mDecodeRequest;
PRUint32 mBytesDecoded;
// How many times we've decoded this image.
@ -554,8 +670,6 @@ private: // data
bool mDecoded:1;
bool mHasBeenDecoded:1;
// Helpers for decoder
bool mWorkerPending:1;
bool mInDecoder:1;
// Whether the animation can stop, due to running out
@ -591,29 +705,6 @@ protected:
bool ShouldAnimate();
};
// XXXdholbert These helper classes should move to be inside the
// scope of the RasterImage class.
// Decoding Helper Class
//
// We use this class to mimic the interactivity benefits of threading
// in a single-threaded event loop. We want to progressively decode
// and keep a responsive UI while we're at it, so we have a runnable
// class that does a bit of decoding, and then "yields" by dispatching
// itself to the end of the event queue.
class imgDecodeWorker : public nsRunnable
{
public:
imgDecodeWorker(imgIContainer* aContainer) {
mContainer = do_GetWeakReference(aContainer);
}
NS_IMETHOD Run();
NS_METHOD Dispatch();
private:
nsWeakPtr mContainer;
TimeDuration mDecodeTime; // the default constructor initializes to 0
};
// Asynchronous Decode Requestor
//
// We use this class when someone calls requestDecode() from within a decode