From 91b79bc5a5ba44d8fdc00685b32e039f41880543 Mon Sep 17 00:00:00 2001 From: "jst%mozilla.jstenback.com" Date: Sun, 11 Apr 2004 05:33:17 +0000 Subject: [PATCH] Fixing bug 237076 (and bug 89270 too). Make our plugin stream code deal with cases where we're getting data off of the network faster than the plugin can handle it. Suspend the incoming request when the plugin says it can't handle more data, then keep feeding the plugin that we got so far until it's done with it, and then resume the network request. r=brendan@mozilla.org, sr=darin@meer.net, a=asa@mozilla.org git-svn-id: svn://10.0.0.236/trunk@154683 18797224-902f-48f8-a5cc-f745e15eee43 --- .../modules/plugin/base/src/ns4xPlugin.cpp | 95 ++-- .../plugin/base/src/ns4xPluginInstance.cpp | 500 ++++++++++++------ .../base/src/ns4xPluginStreamListener.h | 55 +- .../plugin/base/src/nsPluginHostImpl.cpp | 116 ++-- 4 files changed, 488 insertions(+), 278 deletions(-) diff --git a/mozilla/modules/plugin/base/src/ns4xPlugin.cpp b/mozilla/modules/plugin/base/src/ns4xPlugin.cpp index 4c877a2c92d..5a6ebecf505 100644 --- a/mozilla/modules/plugin/base/src/ns4xPlugin.cpp +++ b/mozilla/modules/plugin/base/src/ns4xPlugin.cpp @@ -1,4 +1,4 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * @@ -89,10 +89,7 @@ enum eNPPStreamTypeInternal { static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID); static NS_DEFINE_IID(kCPluginManagerCID, NS_PLUGINMANAGER_CID); static NS_DEFINE_IID(kPluginManagerCID, NS_PLUGINMANAGER_CID); -static NS_DEFINE_IID(kIPluginManagerIID, NS_IPLUGINMANAGER_IID); static NS_DEFINE_IID(kMemoryCID, NS_MEMORY_CID); -static NS_DEFINE_IID(kIMemoryIID, NS_IMEMORY_IID); -static NS_DEFINE_IID(kIPluginStreamListenerIID, NS_IPLUGINSTREAMLISTENER_IID); PR_BEGIN_EXTERN_C @@ -461,9 +458,9 @@ ns4xPlugin::CreatePlugin(nsIServiceManagerObsolete* aServiceMgr, CheckClassInitialized(); // set up the MemAllocator service now because it might be used by the plugin - if (aServiceMgr != nsnull) { - if (nsnull == gMalloc) - aServiceMgr->GetService(kMemoryCID, kIMemoryIID, (nsISupports**)&gMalloc); + if (aServiceMgr && !gMalloc) { + aServiceMgr->GetService(kMemoryCID, NS_GET_IID(nsIMemory), + (nsISupports**)&gMalloc); } #if defined(XP_UNIX) && !defined(XP_MACOSX) @@ -703,9 +700,9 @@ ns4xPlugin::CreatePlugin(nsIServiceManagerObsolete* aServiceMgr, //---------------- //Creates a ns4xPluginInstance object. -nsresult ns4xPlugin :: CreateInstance(nsISupports *aOuter, - const nsIID &aIID, - void **aResult) +nsresult ns4xPlugin::CreateInstance(nsISupports *aOuter, + const nsIID &aIID, + void **aResult) { if (aResult == NULL) return NS_ERROR_NULL_POINTER; @@ -713,23 +710,18 @@ nsresult ns4xPlugin :: CreateInstance(nsISupports *aOuter, *aResult = NULL; // XXX This is suspicuous! - ns4xPluginInstance *inst = new ns4xPluginInstance(&fCallbacks, fLibrary); + nsRefPtr inst = + new ns4xPluginInstance(&fCallbacks, fLibrary); if (inst == NULL) return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(inst); // Stabilize - - nsresult res = inst->QueryInterface(aIID, aResult); - - NS_RELEASE(inst); // Destabilize and avoid leaks. Avoid calling delete - - return res; + return inst->QueryInterface(aIID, aResult); } //////////////////////////////////////////////////////////////////////// -nsresult ns4xPlugin :: LockFactory(PRBool aLock) +nsresult ns4xPlugin::LockFactory(PRBool aLock) { // Not implemented in simplest case. return NS_OK; @@ -737,10 +729,10 @@ nsresult ns4xPlugin :: LockFactory(PRBool aLock) //////////////////////////////////////////////////////////////////////// -NS_METHOD ns4xPlugin :: CreatePluginInstance(nsISupports *aOuter, - REFNSIID aIID, - const char *aPluginMIMEType, - void **aResult) +NS_METHOD ns4xPlugin::CreatePluginInstance(nsISupports *aOuter, + REFNSIID aIID, + const char *aPluginMIMEType, + void **aResult) { return CreateInstance(aOuter, aIID, aResult); } @@ -1031,31 +1023,38 @@ NPError NP_EXPORT _destroystream(NPP npp, NPStream *pstream, NPError reason) { NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, - ("NPN_DestroyStream: npp=%p, url=%s, reason=%d\n", (void*)npp, pstream->url, (int)reason)); + ("NPN_DestroyStream: npp=%p, url=%s, reason=%d\n", (void*)npp, + pstream->url, (int)reason)); - if(!npp) + if (!npp) return NPERR_INVALID_INSTANCE_ERROR; - nsISupports* stream = (nsISupports*) pstream->ndata; - nsIPluginStreamListener* listener; + nsCOMPtr listener = + do_QueryInterface((nsISupports *)pstream->ndata); // DestroyStream can kill two kinds of streams: NPP derived and NPN derived. // check to see if they're trying to kill a NPP stream - if(stream->QueryInterface(kIPluginStreamListenerIID, (void**)&listener) == NS_OK) { - // XXX we should try to kill this listener here somehow - NS_RELEASE(listener); - return NPERR_NO_ERROR; + if (listener) { + // Tell the stream listner that the stream is now gone. + listener->OnStopBinding(nsnull, NS_BINDING_ABORTED); + + // FIXME: http://bugzilla.mozilla.org/show_bug.cgi?id=240131 + // + // Is it ok to leave pstream->ndata set here, and who releases it + // (or is it even properly ref counted)? And who closes the stream + // etc? + } else { + ns4xStreamWrapper* wrapper = (ns4xStreamWrapper *)pstream->ndata; + NS_ASSERTION(wrapper != NULL, "null wrapper"); + + if (wrapper == NULL) + return NPERR_INVALID_PARAM; + + // This will release the wrapped nsIOutputStream. + delete wrapper; + pstream->ndata = nsnull; } - ns4xStreamWrapper* wrapper = (ns4xStreamWrapper*) pstream->ndata; - NS_ASSERTION(wrapper != NULL, "null wrapper"); - - if (wrapper == NULL) - return NPERR_INVALID_PARAM; - - // This will release the wrapped nsIOutputStream. - delete wrapper; - pstream->ndata = nsnull; return NPERR_NO_ERROR; } @@ -1112,12 +1111,9 @@ _reloadplugins(NPBool reloadPages) if(gServiceMgr == nsnull) return; - nsIPluginManager * pm; - gServiceMgr->GetService(kPluginManagerCID, kIPluginManagerIID, (nsISupports**)&pm); + nsCOMPtr pm(do_GetService(kPluginManagerCID)); pm->ReloadPlugins(reloadPages); - - NS_RELEASE(pm); } @@ -1491,14 +1487,11 @@ _useragent(NPP npp) return NULL; char *retstr; - - nsIPluginManager * pm; - gServiceMgr->GetService(kPluginManagerCID, kIPluginManagerIID, (nsISupports**)&pm); - + + nsCOMPtr pm(do_GetService(kPluginManagerCID)); + pm->UserAgent((const char **)&retstr); - - NS_RELEASE(pm); - + return retstr; } diff --git a/mozilla/modules/plugin/base/src/ns4xPluginInstance.cpp b/mozilla/modules/plugin/base/src/ns4xPluginInstance.cpp index 8e843ffb04a..508105a7908 100644 --- a/mozilla/modules/plugin/base/src/ns4xPluginInstance.cpp +++ b/mozilla/modules/plugin/base/src/ns4xPluginInstance.cpp @@ -1,4 +1,4 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * @@ -78,31 +78,30 @@ static NS_DEFINE_IID(kIPluginStreamListenerIID, NS_IPLUGINSTREAMLISTENER_IID); /////////////////////////////////////////////////////////////////////////////// // ns4xPluginStreamListener Methods -NS_IMPL_ISUPPORTS1(ns4xPluginStreamListener, nsIPluginStreamListener) +NS_IMPL_ISUPPORTS2(ns4xPluginStreamListener, nsIPluginStreamListener, + nsITimerCallback) /////////////////////////////////////////////////////////////////////////////// ns4xPluginStreamListener::ns4xPluginStreamListener(nsIPluginInstance* inst, void* notifyData, const char* aURL) - : mNotifyData(notifyData), - mStreamBuffer(nsnull), - mNotifyURL(nsnull), - mStreamStarted(PR_FALSE), - mStreamCleanedUp(PR_FALSE), - mCallNotify(PR_FALSE), - mStreamInfo(nsnull) + : mNotifyData(notifyData), + mStreamBuffer(nsnull), + mNotifyURL(aURL ? PL_strdup(aURL) : nsnull), + mInst((ns4xPluginInstance *)inst), + mStreamBufferSize(0), + mStreamBufferByteCount(0), + mStreamType(nsPluginStreamType_Normal), + mStreamStarted(PR_FALSE), + mStreamCleanedUp(PR_FALSE), + mCallNotify(PR_FALSE), + mIsSuspended(PR_FALSE) { - mInst = (ns4xPluginInstance*) inst; - mPosition = 0; - mStreamBufferSize = 0; // Initialize the 4.x interface structure memset(&mNPStream, 0, sizeof(mNPStream)); NS_IF_ADDREF(mInst); - - if (aURL) - mNotifyURL = PL_strdup(aURL); } @@ -127,9 +126,10 @@ ns4xPluginStreamListener::~ns4xPluginStreamListener(void) } } - // For those cases when NewStream is never called, we still may need to fire a - // notification callback. Return network error as fallback reason because for other - // cases, notify should have already been called for other reasons elsewhere. + // For those cases when NewStream is never called, we still may need + // to fire a notification callback. Return network error as fallback + // reason because for other cases, notify should have already been + // called for other reasons elsewhere. CallURLNotify(NPRES_NETWORK_ERR); // lets get rid of the buffer @@ -139,7 +139,6 @@ ns4xPluginStreamListener::~ns4xPluginStreamListener(void) mStreamBuffer=nsnull; } - NS_IF_RELEASE(inst); if (mNotifyURL) @@ -186,6 +185,8 @@ nsresult ns4xPluginStreamListener::CleanUpStream(NPReason reason) mStreamCleanedUp = PR_TRUE; mStreamStarted = PR_FALSE; + StopDataPump(); + // fire notification back to plugin, just like before CallURLNotify(reason); @@ -261,7 +262,6 @@ ns4xPluginStreamListener::OnStartBinding(nsIPluginStreamInfo* pluginInfo) mStreamInfo = pluginInfo; - NS_TRY_SAFE_CALL_RETURN(error, CallNPP_NewStreamProc(callbacks->newstream, npp, (char *)contentType, @@ -299,172 +299,322 @@ ns4xPluginStreamListener::OnStartBinding(nsIPluginStreamInfo* pluginInfo) return NS_OK; } +nsresult +ns4xPluginStreamListener::SuspendRequest() +{ + NS_ASSERTION(!mIsSuspended, + "Suspending a request that's already suspended!"); + + nsCOMPtr pluginInfo4x = + do_QueryInterface(mStreamInfo); + nsIRequest *request; + + if (!pluginInfo4x || !(request = pluginInfo4x->GetRequest())) { + NS_ERROR("Trying to suspend a non-suspendable stream!"); + + return NS_ERROR_FAILURE; + } + + nsresult rv = StartDataPump(); + NS_ENSURE_SUCCESS(rv, rv); + + mIsSuspended = PR_TRUE; + + return request->Suspend(); +} + +void +ns4xPluginStreamListener::ResumeRequest() +{ + nsCOMPtr pluginInfo4x = + do_QueryInterface(mStreamInfo); + + nsIRequest *request = pluginInfo4x->GetRequest(); + + // request can be null if the network stream is done. + if (request) { + request->Resume(); + } + + mIsSuspended = PR_FALSE; +} + +nsresult +ns4xPluginStreamListener::StartDataPump() +{ + nsresult rv; + mDataPumpTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Start pumping data to the plugin every 100ms until it obeys and + // eats the data. + return mDataPumpTimer->InitWithCallback(this, 100, + nsITimer::TYPE_REPEATING_SLACK); +} + +void +ns4xPluginStreamListener::StopDataPump() +{ + if (mDataPumpTimer) { + mDataPumpTimer->Cancel(); + + mDataPumpTimer = nsnull; + } +} + /////////////////////////////////////////////////////////////////////////////// + +// This method is called when there's more data available off the +// network, but it's also called from our data pump when we're feeding +// the plugin data that we already got off the network, but the plugin +// was unable to consume it at the point it arrived. In the case when +// the plugin pump calls this method, the input argument will be null, +// and the length will be the number of bytes available in our +// internal buffer. NS_IMETHODIMP ns4xPluginStreamListener::OnDataAvailable(nsIPluginStreamInfo* pluginInfo, nsIInputStream* input, PRUint32 length) { - nsresult rv = NS_ERROR_FAILURE; if (!mInst || !mInst->IsStarted()) - return rv; + return NS_ERROR_FAILURE; + + // Just in case the caller switches plugin info on us. + mStreamInfo = pluginInfo; const NPPluginFuncs *callbacks = nsnull; mInst->GetCallbacks(&callbacks); // check out if plugin implements NPP_Write call if(!callbacks || !callbacks->write || !length) - return rv; // it'll cancel necko transaction + return NS_ERROR_FAILURE; // it'll cancel necko transaction if (!mStreamBuffer) { - // to optimize the mem usage & performance we have to allocate mStreamBuffer here - // in first ODA when length of data available in input stream is known. - // mStreamBuffer will be freed in DTOR. - // we also have to remember the size of that buff - // to make safe consecutive Read() calls form input stream into our buff. - if (length >= MAX_PLUGIN_NECKO_BUFFER) { - // ">" is rare case for decoded stream, but lets eat it all - mStreamBufferSize = length; - } else { - PRUint32 contentLength; - pluginInfo->GetLength(&contentLength); - if (contentLength < MAX_PLUGIN_NECKO_BUFFER) { - // this is most common case for contentLength < 16k - mStreamBufferSize = length < contentLength ? contentLength:length; - } else { - mStreamBufferSize = MAX_PLUGIN_NECKO_BUFFER; - } - } + // To optimize the mem usage & performance we have to allocate + // mStreamBuffer here in first ODA when length of data available + // in input stream is known. mStreamBuffer will be freed in DTOR. + // we also have to remember the size of that buff to make safe + // consecutive Read() calls form input stream into our buff. + + PRUint32 contentLength; + pluginInfo->GetLength(&contentLength); + + mStreamBufferSize = PR_MAX(length, contentLength); + + // Limit the size of the initial buffer to MAX_PLUGIN_NECKO_BUFFER + // (16k). This buffer will grow if needed, as in the case where + // we're getting data faster than the plugin can process it. + mStreamBufferSize = PR_MIN(mStreamBufferSize, MAX_PLUGIN_NECKO_BUFFER); + mStreamBuffer = (char*) PR_Malloc(mStreamBufferSize); if (!mStreamBuffer) return NS_ERROR_OUT_OF_MEMORY; } // prepare NPP_ calls params - NPP npp; + NPP npp; mInst->GetNPP(&npp); - PRInt32 streamOffset; - pluginInfo->GetStreamOffset(&streamOffset); - mPosition = streamOffset; - streamOffset += length; - // Set new stream offset for the next ODA call - // regardless of how following NPP_Write call will behave - // we pretend to consume all data from the input stream. - // It's possible that current steam position will be overwritten - // from NPP_RangeRequest call made from NPP_Write, so - // we cannot call SetStreamOffset after NPP_Write. - // Note: there is a special case when data flow - // should be temporarily stopped if NPP_WriteReady returns 0 (bug #89270) - pluginInfo->SetStreamOffset(streamOffset); + PRInt32 streamPosition; + pluginInfo->GetStreamOffset(&streamPosition); + PRInt32 streamOffset = streamPosition; - // set new end in case the content is compressed - // initial end is less than end of decompressed stream - // and some plugins (e.g. acrobat) can fail. - if ((PRInt32)mNPStream.end < streamOffset) - mNPStream.end = streamOffset; + if (input) { + streamOffset += length; - do - { - PRUint32 bytesToRead = PR_MIN(length, mStreamBufferSize); - PRInt32 amountRead = 0; - rv = input->Read(mStreamBuffer, bytesToRead, (PRUint32*)&amountRead); - if (amountRead == 0 || NS_FAILED(rv)) { - NS_WARNING("input->Read() returns no data, it's almost impossible to get here"); - break; + // Set new stream offset for the next ODA call regardless of how + // following NPP_Write call will behave we pretend to consume all + // data from the input stream. It's possible that current steam + // position will be overwritten from NPP_RangeRequest call made + // from NPP_Write, so we cannot call SetStreamOffset after + // NPP_Write. + // + // Note: there is a special case when data flow should be + // temporarily stopped if NPP_WriteReady returns 0 (bug #89270) + pluginInfo->SetStreamOffset(streamOffset); + + // set new end in case the content is compressed + // initial end is less than end of decompressed stream + // and some plugins (e.g. acrobat) can fail. + if ((PRInt32)mNPStream.end < streamOffset) + mNPStream.end = streamOffset; + } + + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv) && length > 0) { + if (input && length) { + if (mStreamBufferSize < mStreamBufferByteCount + length && + mIsSuspended) { + // We're in the ::OnDataAvailable() call that we might get + // after suspending a request, or we suspended the request + // from within this ::OnDataAvailable() call while there's + // still data in the input, and we don't have enough space to + // store what we got off the network. Reallocate our internal + // buffer. + mStreamBufferSize = mStreamBufferByteCount + length; + char *buf = (char *)PR_Realloc(mStreamBuffer, mStreamBufferSize); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + + mStreamBuffer = buf; + } + + PRUint32 bytesToRead = + PR_MIN(length, mStreamBufferSize - mStreamBufferByteCount); + + PRUint32 amountRead = 0; + rv = input->Read(mStreamBuffer + mStreamBufferByteCount, bytesToRead, + &amountRead); + NS_ENSURE_SUCCESS(rv, rv); + + if (amountRead == 0) { + NS_NOTREACHED("input->Read() returns no data, it's almost impossible " + "to get here"); + + break; + } + + mStreamBufferByteCount += amountRead; + length -= amountRead; + } else { + // No input, nothing to read. Set length to 0 so that we don't + // keep iterating through this outer loop any more. + + length = 0; } - // this loop in general case will end on length <= 0, without extra input->Read() call - length -= amountRead; - - char *ptrStreamBuffer = mStreamBuffer; // tmp ptr + // Temporary pointer to the beginning of the data we're writing as + // we loop and feed the plugin data. + char *ptrStreamBuffer = mStreamBuffer; - // it is possible plugin's NPP_Write() returns 0 byte consumed - // we use zeroBytesWriteCount to count situation like this - // and break the loop + // it is possible plugin's NPP_Write() returns 0 byte consumed. We + // use zeroBytesWriteCount to count situation like this and break + // the loop PRInt32 zeroBytesWriteCount = 0; - - // amountRead tells us how many bytes were put in the buffer - // WriteReady returns to us how many bytes the plugin is - // ready to handle - we have to keep calling WriteReady and - // Write until amountRead > 0 - for(;;) - { // it breaks on (amountRead <= 0) or on error - PRInt32 numtowrite; - if (callbacks->writeready) - { + + // mStreamBufferByteCount tells us how many bytes there are in the + // buffer. WriteReady returns to us how many bytes the plugin is + // ready to handle. + while (mStreamBufferByteCount > 0) { + PRInt32 numtowrite; + if (callbacks->writeready) { NS_TRY_SAFE_CALL_RETURN(numtowrite, - CallNPP_WriteReadyProc(callbacks->writeready, npp, &mNPStream), - mInst->fLibrary, mInst); - + CallNPP_WriteReadyProc(callbacks->writeready, + npp, &mNPStream), + mInst->fLibrary, mInst); + NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY, - ("NPP WriteReady called: this=%p, npp=%p, return(towrite)=%d, url=%s\n", - this, npp, numtowrite, mNPStream.url)); - - // if WriteReady returned 0, the plugin is not ready to handle - // the data, return FAILURE for now + ("NPP WriteReady called: this=%p, npp=%p, " + "return(towrite)=%d, url=%s\n", + this, npp, numtowrite, mNPStream.url)); + + // if WriteReady returned 0, the plugin is not ready to handle + // the data, suspend the stream (if it isn't already + // suspended). if (numtowrite <= 0) { - NS_ASSERTION(numtowrite,"WriteReady returned Zero"); - rv = NS_ERROR_FAILURE; + if (!mIsSuspended) { + rv = SuspendRequest(); + } + + // Break out of the inner loop, but keep going through the + // outer loop in case there's more data to read from the + // input stream. + break; } - if (numtowrite > amountRead) - numtowrite = amountRead; + + numtowrite = PR_MIN(numtowrite, mStreamBufferByteCount); + } else { + // if WriteReady is not supported by the plugin, just write + // the whole buffer + numtowrite = mStreamBufferByteCount; } - else - { - // if WriteReady is not supported by the plugin, - // just write the whole buffer - numtowrite = amountRead; - } - - PRInt32 writeCount = 0; // bytes consumed by plugin instance - + + PRInt32 writeCount = 0; // bytes consumed by plugin instance NS_TRY_SAFE_CALL_RETURN(writeCount, - CallNPP_WriteProc(callbacks->write, npp, &mNPStream, mPosition, numtowrite, (void *)ptrStreamBuffer), - mInst->fLibrary, mInst); - + CallNPP_WriteProc(callbacks->write, npp, + &mNPStream, streamPosition, + numtowrite, + ptrStreamBuffer), + mInst->fLibrary, mInst); + NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY, - ("NPP Write called: this=%p, npp=%p, pos=%d, len=%d, buf=%s, return(written)=%d, url=%s\n", - this, npp, mPosition, numtowrite, (char *)ptrStreamBuffer, writeCount, mNPStream.url)); - - if (writeCount > 0) - { - mPosition += writeCount; - amountRead -= writeCount; - if (amountRead <= 0) - break; // in common case we'll break for(;;) loop here - + ("NPP Write called: this=%p, npp=%p, pos=%d, len=%d, " + "buf=%s, return(written)=%d, url=%s\n", + this, npp, streamPosition, numtowrite, + ptrStreamBuffer, writeCount, mNPStream.url)); + + if (writeCount > 0) { + NS_ASSERTION(writeCount <= mStreamBufferByteCount, + "Plugin read past the end of the available data!"); + + writeCount = PR_MIN(writeCount, mStreamBufferByteCount); + mStreamBufferByteCount -= writeCount; + + streamPosition += writeCount; + zeroBytesWriteCount = 0; - if (writeCount % sizeof(long)) { - // memmove will take care about alignment - memmove(ptrStreamBuffer,ptrStreamBuffer+writeCount,amountRead); - } else { - // if aligned we can use ptrStreamBuffer += to eliminate memmove() - ptrStreamBuffer += writeCount; + + if (mStreamBufferByteCount > 0) { + // This alignment code is most likely bogus, but we'll leave + // it in for now in case it matters for some plugins on some + // architectures. Who knows... + if (writeCount % sizeof(PRWord)) { + // memmove will take care about alignment + memmove(mStreamBuffer, ptrStreamBuffer + writeCount, + mStreamBufferByteCount); + ptrStreamBuffer = mStreamBuffer; + } else { + // if aligned we can use ptrStreamBuffer += to eliminate + // memmove() + ptrStreamBuffer += writeCount; + } } - } - else if (writeCount == 0) - { - // if NPP_Write() returns writeCount == 0 lets say 3 times in a raw - // lets consider this as end of ODA call (plugin isn't hungry, or broken) without an error. - if (++zeroBytesWriteCount == 3) - { - length = 0; // break do{}while - rv = NS_OK; + } else if (writeCount == 0) { + // if NPP_Write() returns writeCount == 0 lets say 3 times in + // a row, suspend the request and continue feeding the plugin + // the data we got so far. Once that data is consumed, we'll + // resume the request. + if (mIsSuspended || ++zeroBytesWriteCount == 3) { + if (!mIsSuspended) { + rv = SuspendRequest(); + } + + // Break out of the for loop, but keep going through the + // while loop in case there's more data to read from the + // input stream. + break; } - } - else - { - length = 0; // break do{}while + } else { + // Something's really wrong, kill the stream. rv = NS_ERROR_FAILURE; + break; } - } // end of for(;;) - } while ((PRInt32)(length) > 0); + } // end of inner while loop - return rv == NS_BASE_STREAM_WOULD_BLOCK ? NS_OK:rv; + if (mStreamBufferByteCount && mStreamBuffer != ptrStreamBuffer) { + memmove(mStreamBuffer, ptrStreamBuffer, mStreamBufferByteCount); + } + } + + if (streamPosition != streamOffset) { + // The plugin didn't consume all available data, or consumed some + // of our cached data while we're pumping cached data. Adjust the + // plugin info's stream offset to match reality, except if the + // plugin info's stream offset was set by a re-entering + // NPN_RequestRead() call. + + PRInt32 postWriteStreamPosition; + pluginInfo->GetStreamOffset(&postWriteStreamPosition); + + if (postWriteStreamPosition == streamOffset) { + pluginInfo->SetStreamOffset(streamPosition); + } + } + + return rv; } /////////////////////////////////////////////////////////////////////////////// @@ -504,6 +654,20 @@ NS_IMETHODIMP ns4xPluginStreamListener::OnStopBinding(nsIPluginStreamInfo* pluginInfo, nsresult status) { + StopDataPump(); + + if (mIsSuspended && NS_FAILED(status)) { + // We're suspended, and the stream was destroyed, or died for some + // reason. Make sure we cancel the suspended request. + nsCOMPtr pluginInfo4x = + do_QueryInterface(mStreamInfo); + + nsIRequest *request; + if (pluginInfo4x && (request = pluginInfo4x->GetRequest())) { + request->Cancel(status); + } + } + if(!mInst || !mInst->IsStarted()) return NS_ERROR_FAILURE; @@ -518,7 +682,7 @@ ns4xPluginStreamListener::OnStopBinding(nsIPluginStreamInfo* pluginInfo, rv = CleanUpStream(reason); } - + if(rv != NPERR_NO_ERROR) return NS_ERROR_FAILURE; @@ -532,6 +696,39 @@ ns4xPluginStreamListener::GetStreamType(nsPluginStreamType *result) return NS_OK; } +NS_IMETHODIMP +ns4xPluginStreamListener::Notify(nsITimer *aTimer) +{ + NS_ASSERTION(aTimer == mDataPumpTimer, "Uh, wrong timer?"); + + PRInt32 oldStreamBufferByteCount = mStreamBufferByteCount; + + nsresult rv = OnDataAvailable(mStreamInfo, nsnull, mStreamBufferByteCount); + + if (NS_FAILED(rv)) { + // We ran into an error, no need to keep firing this timer then. + + aTimer->Cancel(); + + return NS_OK; + } + + if (mStreamBufferByteCount != oldStreamBufferByteCount && + ((mStreamStarted && mStreamBufferByteCount < 1024) || + mStreamBufferByteCount == 0)) { + // The plugin read some data and we've got less than 1024 bytes in + // our buffer (or its empty and the stream is already + // done). Resume the request so that we get more data off the + // network. + + ResumeRequest(); + + // Necko will pump data now that we've resumed the request. + StopDataPump(); + } + + return NS_OK; +} /////////////////////////////////////////////////////////////////////////////// nsInstanceStream::nsInstanceStream() @@ -553,7 +750,8 @@ NS_IMPL_ISUPPORTS2(ns4xPluginInstance, nsIPluginInstance, nsIScriptablePlugin) /////////////////////////////////////////////////////////////////////////////// -ns4xPluginInstance :: ns4xPluginInstance(NPPluginFuncs* callbacks, PRLibrary* aLibrary) +ns4xPluginInstance::ns4xPluginInstance(NPPluginFuncs* callbacks, + PRLibrary* aLibrary) : fCallbacks(callbacks) { NS_ASSERTION(fCallbacks != NULL, "null callbacks"); @@ -575,7 +773,7 @@ ns4xPluginInstance :: ns4xPluginInstance(NPPluginFuncs* callbacks, PRLibrary* aL /////////////////////////////////////////////////////////////////////////////// -ns4xPluginInstance :: ~ns4xPluginInstance(void) +ns4xPluginInstance::~ns4xPluginInstance(void) { PLUGIN_LOG(PLUGIN_LOG_BASIC, ("ns4xPluginInstance dtor: this=%p\n",this)); @@ -599,7 +797,7 @@ ns4xPluginInstance :: ~ns4xPluginInstance(void) /////////////////////////////////////////////////////////////////////////////// PRBool -ns4xPluginInstance :: IsStarted(void) +ns4xPluginInstance::IsStarted(void) { return mStarted; } @@ -1220,8 +1418,8 @@ nsresult ns4xPluginInstance::GetValueInternal(NPPVariable variable, void* value) //////////////////////////////////////////////////////////////////////// -NS_IMETHODIMP ns4xPluginInstance :: GetValue(nsPluginInstanceVariable variable, - void *value) +NS_IMETHODIMP ns4xPluginInstance::GetValue(nsPluginInstanceVariable variable, + void *value) { nsresult res = NS_OK; @@ -1275,7 +1473,7 @@ nsresult ns4xPluginInstance::GetCallbacks(const NPPluginFuncs ** aCallbacks) //////////////////////////////////////////////////////////////////////// -nsresult ns4xPluginInstance :: SetWindowless(PRBool aWindowless) +nsresult ns4xPluginInstance::SetWindowless(PRBool aWindowless) { mWindowless = aWindowless; return NS_OK; @@ -1283,7 +1481,7 @@ nsresult ns4xPluginInstance :: SetWindowless(PRBool aWindowless) //////////////////////////////////////////////////////////////////////// -nsresult ns4xPluginInstance :: SetTransparent(PRBool aTransparent) +nsresult ns4xPluginInstance::SetTransparent(PRBool aTransparent) { mTransparent = aTransparent; return NS_OK; @@ -1291,7 +1489,7 @@ nsresult ns4xPluginInstance :: SetTransparent(PRBool aTransparent) //////////////////////////////////////////////////////////////////////// /* readonly attribute nsQIResult scriptablePeer; */ -NS_IMETHODIMP ns4xPluginInstance :: GetScriptablePeer(void * *aScriptablePeer) +NS_IMETHODIMP ns4xPluginInstance::GetScriptablePeer(void * *aScriptablePeer) { if (!aScriptablePeer) return NS_ERROR_NULL_POINTER; @@ -1303,7 +1501,7 @@ NS_IMETHODIMP ns4xPluginInstance :: GetScriptablePeer(void * *aScriptablePeer) //////////////////////////////////////////////////////////////////////// /* readonly attribute nsIIDPtr scriptableInterface; */ -NS_IMETHODIMP ns4xPluginInstance :: GetScriptableInterface(nsIID * *aScriptableInterface) +NS_IMETHODIMP ns4xPluginInstance::GetScriptableInterface(nsIID * *aScriptableInterface) { if (!aScriptableInterface) return NS_ERROR_NULL_POINTER; diff --git a/mozilla/modules/plugin/base/src/ns4xPluginStreamListener.h b/mozilla/modules/plugin/base/src/ns4xPluginStreamListener.h index db6ab78b70e..84b8c882dee 100644 --- a/mozilla/modules/plugin/base/src/ns4xPluginStreamListener.h +++ b/mozilla/modules/plugin/base/src/ns4xPluginStreamListener.h @@ -40,28 +40,47 @@ #include "nsIPluginStreamListener.h" #include "nsIPluginStreamInfo.h" +#include "nsIRequest.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" #define MAX_PLUGIN_NECKO_BUFFER 16384 -class ns4xPluginStreamListener : public nsIPluginStreamListener +class ns4xPluginInstance; +class nsI4xPluginStreamInfo; + +class ns4xPluginStreamListener : public nsIPluginStreamListener, + public nsITimerCallback { public: NS_DECL_ISUPPORTS // from nsIPluginStreamListener: NS_IMETHOD OnStartBinding(nsIPluginStreamInfo* pluginInfo); - NS_IMETHOD OnDataAvailable(nsIPluginStreamInfo* pluginInfo, nsIInputStream* input, PRUint32 length); - NS_IMETHOD OnFileAvailable( nsIPluginStreamInfo* pluginInfo, const char* fileName); + NS_IMETHOD OnDataAvailable(nsIPluginStreamInfo* pluginInfo, + nsIInputStream* input, PRUint32 length); + NS_IMETHOD OnFileAvailable( nsIPluginStreamInfo* pluginInfo, + const char* fileName); NS_IMETHOD OnStopBinding(nsIPluginStreamInfo* pluginInfo, nsresult status); NS_IMETHOD GetStreamType(nsPluginStreamType *result); + NS_DECL_NSITIMERCALLBACK + // ns4xPluginStreamListener specific methods: - ns4xPluginStreamListener(nsIPluginInstance* inst, void* notifyData, const char* aURL); + ns4xPluginStreamListener(nsIPluginInstance* inst, void* notifyData, + const char* aURL); virtual ~ns4xPluginStreamListener(); PRBool IsStarted(); nsresult CleanUpStream(NPReason reason); void CallURLNotify(NPReason reason); - void SetCallNotify(PRBool aCallNotify) { mCallNotify = aCallNotify; } + void SetCallNotify(PRBool aCallNotify) + { + mCallNotify = aCallNotify; + } + nsresult SuspendRequest(); + void ResumeRequest(); + nsresult StartDataPump(); + void StopDataPump(); protected: void* mNotifyData; @@ -69,16 +88,38 @@ protected: char* mNotifyURL; ns4xPluginInstance* mInst; NPStream mNPStream; - PRUint32 mPosition; PRUint32 mStreamBufferSize; + PRInt32 mStreamBufferByteCount; nsPluginStreamType mStreamType; PRPackedBool mStreamStarted; PRPackedBool mStreamCleanedUp; PRPackedBool mCallNotify; + PRPackedBool mIsSuspended; + nsCOMPtr mDataPumpTimer; public: - nsIPluginStreamInfo * mStreamInfo; + nsCOMPtr mStreamInfo; +}; + +// nsI4xPluginStreamInfo is an internal helper interface that exposes +// the underlying necko request to consumers of nsIPluginStreamInfo's. +#define NS_I4XPLUGINSTREAMINFO_IID \ +{ 0x097fdaaa, 0xa2a3, 0x49c2, \ + {0x91, 0xee, 0xeb, 0xc5, 0x7d, 0x6c, 0x9c, 0x97} } + +class nsI4xPluginStreamInfo : public nsIPluginStreamInfo +{ +public: + NS_DEFINE_STATIC_IID_ACCESSOR(NS_I4XPLUGINSTREAMINFO_IID) + + nsIRequest *GetRequest() + { + return mRequest; + } + +protected: + nsCOMPtr mRequest; }; #endif diff --git a/mozilla/modules/plugin/base/src/nsPluginHostImpl.cpp b/mozilla/modules/plugin/base/src/nsPluginHostImpl.cpp index d6e885c9536..75d0b8abbab 100644 --- a/mozilla/modules/plugin/base/src/nsPluginHostImpl.cpp +++ b/mozilla/modules/plugin/base/src/nsPluginHostImpl.cpp @@ -45,6 +45,7 @@ #include "prio.h" #include "prmem.h" #include "ns4xPlugin.h" +#include "ns4xPluginStreamListener.h" #include "nsPluginInstancePeer.h" #include "nsIPlugin.h" #ifdef OJI @@ -1093,7 +1094,7 @@ PRBool nsPluginTag::Equals(nsPluginTag *aPluginTag) //////////////////////////////////////////////////////////////////////// class nsPluginStreamListenerPeer; -class nsPluginStreamInfo : public nsIPluginStreamInfo +class nsPluginStreamInfo : public nsI4xPluginStreamInfo { public: nsPluginStreamInfo(); @@ -1159,6 +1160,12 @@ public: void SetStreamComplete(const PRBool complete); + void + SetRequest(nsIRequest *request) + { + mRequest = request; + } + private: char* mContentType; @@ -1216,7 +1223,7 @@ private: nsIPluginInstanceOwner *mOwner; nsIPluginInstance *mInstance; nsIPluginStreamListener *mPStreamListener; - nsPluginStreamInfo *mPluginStreamInfo; + nsRefPtr mPluginStreamInfo; // Set to PR_TRUE if we request failed (like with a HTTP response of 404) PRPackedBool mRequestFailed; @@ -1297,37 +1304,10 @@ nsPluginStreamInfo::~nsPluginStreamInfo() } //////////////////////////////////////////////////////////////////////// -NS_IMPL_ADDREF(nsPluginStreamInfo) -NS_IMPL_RELEASE(nsPluginStreamInfo) +NS_IMPL_ISUPPORTS2(nsPluginStreamInfo, nsIPluginStreamInfo, + nsI4xPluginStreamInfo) //////////////////////////////////////////////////////////////////////// -nsresult nsPluginStreamInfo::QueryInterface(const nsIID& aIID, - void** aInstancePtrResult) -{ - NS_PRECONDITION(nsnull != aInstancePtrResult, "null pointer"); - - if (nsnull == aInstancePtrResult) - return NS_ERROR_NULL_POINTER; - - if (aIID.Equals(kIPluginStreamInfoIID)) - { - *aInstancePtrResult = (void *)((nsIPluginStreamInfo *)this); - AddRef(); - return NS_OK; - } - - if (aIID.Equals(kISupportsIID)) - { - *aInstancePtrResult = (void *)((nsISupports *)((nsIStreamListener *)this)); - AddRef(); - return NS_OK; - } - - return NS_NOINTERFACE; -} - - -//////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsPluginStreamInfo::GetContentType(nsMIMEType* result) { @@ -1335,8 +1315,6 @@ nsPluginStreamInfo::GetContentType(nsMIMEType* result) return NS_OK; } - -//////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsPluginStreamInfo::IsSeekable(PRBool* result) { @@ -1344,8 +1322,6 @@ nsPluginStreamInfo::IsSeekable(PRBool* result) return NS_OK; } - -//////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsPluginStreamInfo::GetLength(PRUint32* result) { @@ -1353,8 +1329,6 @@ nsPluginStreamInfo::GetLength(PRUint32* result) return NS_OK; } - -//////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsPluginStreamInfo::GetLastModified(PRUint32* result) { @@ -1362,8 +1336,6 @@ nsPluginStreamInfo::GetLastModified(PRUint32* result) return NS_OK; } - -//////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsPluginStreamInfo::GetURL(const char** result) { @@ -1371,9 +1343,6 @@ nsPluginStreamInfo::GetURL(const char** result) return NS_OK; } - - -//////////////////////////////////////////////////////////////////////// void nsPluginStreamInfo::MakeByteRangeString(nsByteRange* aRangeList, nsACString &rangeRequest, PRInt32 *numRequests) { @@ -1647,7 +1616,6 @@ nsPluginStreamListenerPeer::nsPluginStreamListenerPeer() mOwner = nsnull; mInstance = nsnull; mPStreamListener = nsnull; - mPluginStreamInfo = nsnull; mHost = nsnull; mStreamType = nsPluginStreamType_Normal; mStartBinding = PR_FALSE; @@ -1677,7 +1645,6 @@ nsPluginStreamListenerPeer::~nsPluginStreamListenerPeer() NS_IF_RELEASE(mInstance); NS_IF_RELEASE(mPStreamListener); NS_IF_RELEASE(mHost); - NS_IF_RELEASE(mPluginStreamInfo); // close FD of mFileCacheOutputStream if it's still open // or we won't be able to remove the cache file @@ -1747,7 +1714,6 @@ nsresult nsPluginStreamListenerPeer::Initialize(nsIURI *aURL, if (!mPluginStreamInfo) return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(mPluginStreamInfo); mPluginStreamInfo->SetPluginInstance(aInstance); mPluginStreamInfo->SetPluginStreamListenerPeer(this); @@ -1802,7 +1768,6 @@ nsresult nsPluginStreamListenerPeer::InitializeEmbeded(nsIURI *aURL, if (!mPluginStreamInfo) return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(mPluginStreamInfo); mPluginStreamInfo->SetPluginInstance(aInstance); mPluginStreamInfo->SetPluginStreamListenerPeer(this); @@ -1829,7 +1794,6 @@ nsresult nsPluginStreamListenerPeer::InitializeFullPage(nsIPluginInstance *aInst if (!mPluginStreamInfo) return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(mPluginStreamInfo); mPluginStreamInfo->SetPluginInstance(aInstance); mPluginStreamInfo->SetPluginStreamListenerPeer(this); @@ -2023,6 +1987,8 @@ nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request, nsISupports* aCo mPluginStreamInfo->SetLength(length); } + mPluginStreamInfo->SetRequest(request); + nsCAutoString aContentType; rv = channel->GetContentType(aContentType); if (NS_FAILED(rv)) @@ -2176,6 +2142,8 @@ NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable(nsIRequest *request, if(!mPStreamListener || !mPluginStreamInfo) return NS_ERROR_FAILURE; + mPluginStreamInfo->SetRequest(request); + const char * url = nsnull; mPluginStreamInfo->GetURL(&url); @@ -2190,21 +2158,25 @@ NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable(nsIRequest *request, // get the absolute offset of the request, if one exists. nsCOMPtr brr = do_QueryInterface(request); PRInt32 absoluteOffset = 0; - PRInt32 amtForwardToPlugin = 0; if (brr) { - brr->GetStartRange(&absoluteOffset); - - // we need to track how much data we have forward on to the plugin. - nsPRUintKey key(absoluteOffset); + if (!mDataForwardToRequest) + return NS_ERROR_FAILURE; - if (!mDataForwardToRequest) - return NS_ERROR_FAILURE; + brr->GetStartRange(&absoluteOffset); - if (mDataForwardToRequest->Exists(&key)) - amtForwardToPlugin = NS_PTR_TO_INT32(mDataForwardToRequest->Remove(&key)); - - mDataForwardToRequest->Put(&key, (void*) (amtForwardToPlugin+aLength)); - mPluginStreamInfo->SetStreamOffset(absoluteOffset + amtForwardToPlugin); + // we need to track how much data we have forwarded to the + // plugin. + + // FIXME: http://bugzilla.mozilla.org/show_bug.cgi?id=240130 + // + // Why couldn't this be tracked on the plugin info, and not in a + // *hash table*? + nsPRUintKey key(absoluteOffset); + PRInt32 amtForwardToPlugin = + NS_PTR_TO_INT32(mDataForwardToRequest->Get(&key)); + mDataForwardToRequest->Put(&key, NS_INT32_TO_PTR(amtForwardToPlugin + aLength)); + + mPluginStreamInfo->SetStreamOffset(absoluteOffset + amtForwardToPlugin); } nsCOMPtr stream = aIStream; @@ -2219,9 +2191,9 @@ NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable(nsIRequest *request, return rv; } - rv = mPStreamListener->OnDataAvailable((nsIPluginStreamInfo*)mPluginStreamInfo, - stream, - aLength); + rv = mPStreamListener->OnDataAvailable(mPluginStreamInfo, + stream, + aLength); // if a plugin returns an error, the peer must kill the stream // else the stream and PluginStreamListener leak @@ -2317,7 +2289,7 @@ NS_IMETHODIMP nsPluginStreamListenerPeer::OnStopRequest(nsIRequest *request, if (NS_FAILED(aStatus)) { // on error status cleanup the stream // and return w/o OnFileAvailable() - mPStreamListener->OnStopBinding((nsIPluginStreamInfo*)mPluginStreamInfo, aStatus); + mPStreamListener->OnStopBinding(mPluginStreamInfo, aStatus); return NS_OK; } @@ -2345,13 +2317,13 @@ NS_IMETHODIMP nsPluginStreamListenerPeer::OnStopRequest(nsIRequest *request, if (mStartBinding) { // On start binding has been called - mPStreamListener->OnStopBinding((nsIPluginStreamInfo*)mPluginStreamInfo, aStatus); + mPStreamListener->OnStopBinding(mPluginStreamInfo, aStatus); } else { // OnStartBinding hasn't been called, so complete the action. - mPStreamListener->OnStartBinding((nsIPluginStreamInfo*)mPluginStreamInfo); - mPStreamListener->OnStopBinding((nsIPluginStreamInfo*)mPluginStreamInfo, aStatus); + mPStreamListener->OnStartBinding(mPluginStreamInfo); + mPStreamListener->OnStopBinding(mPluginStreamInfo, aStatus); } if (NS_SUCCEEDED(aStatus)) @@ -2449,7 +2421,7 @@ nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest *request, } } - rv = mPStreamListener->OnStartBinding((nsIPluginStreamInfo*)mPluginStreamInfo); + rv = mPStreamListener->OnStartBinding(mPluginStreamInfo); mStartBinding = PR_TRUE; @@ -2495,7 +2467,7 @@ nsPluginStreamListenerPeer::OnFileAvailable(nsIFile* aFile) return NS_OK; } - rv = mPStreamListener->OnFileAvailable((nsIPluginStreamInfo*)mPluginStreamInfo, path.get()); + rv = mPStreamListener->OnFileAvailable(mPluginStreamInfo, path.get()); return rv; } @@ -6594,7 +6566,7 @@ nsresult nsPluginStreamListenerPeer::ServeStreamAsFile(nsIRequest *request, } mPluginStreamInfo->SetSeekable(0); - mPStreamListener->OnStartBinding((nsIPluginStreamInfo*)mPluginStreamInfo); + mPStreamListener->OnStartBinding(mPluginStreamInfo); mPluginStreamInfo->SetStreamOffset(0); // force the plugin use stream as file @@ -6740,4 +6712,10 @@ void nsPluginStreamInfo::SetStreamComplete(const PRBool complete) { mStreamComplete = complete; + + if (complete) { + // We're done, release the request. + + SetRequest(nsnull); + } }