diff --git a/netwerk/base/public/moz.build b/netwerk/base/public/moz.build index bd384ba86a6a..7410107537c7 100644 --- a/netwerk/base/public/moz.build +++ b/netwerk/base/public/moz.build @@ -12,6 +12,7 @@ XPIDL_SOURCES += [ 'nsIApplicationCacheService.idl', 'nsIArrayBufferInputStream.idl', 'nsIAsyncStreamCopier.idl', + 'nsIAsyncStreamCopier2.idl', 'nsIAsyncVerifyRedirectCallback.idl', 'nsIAuthInformation.idl', 'nsIAuthModule.idl', diff --git a/netwerk/base/public/nsIAsyncStreamCopier.idl b/netwerk/base/public/nsIAsyncStreamCopier.idl index d321a695bb85..633fe72b6e84 100644 --- a/netwerk/base/public/nsIAsyncStreamCopier.idl +++ b/netwerk/base/public/nsIAsyncStreamCopier.idl @@ -9,6 +9,7 @@ interface nsIOutputStream; interface nsIRequestObserver; interface nsIEventTarget; +// You should prefer nsIAsyncStreamCopier2 [scriptable, uuid(5a19ca27-e041-4aca-8287-eb248d4c50c0)] interface nsIAsyncStreamCopier : nsIRequest { @@ -36,7 +37,9 @@ interface nsIAsyncStreamCopier : nsIRequest * @param aCloseSink * true if aSink should be closed after copying. * - * NOTE: at least one of the streams must be buffered. + * NOTE: at least one of the streams must be buffered. If you do not know + * whether your streams are buffered, you should use nsIAsyncStreamCopier2 + * instead. */ void init(in nsIInputStream aSource, in nsIOutputStream aSink, diff --git a/netwerk/base/public/nsIAsyncStreamCopier2.idl b/netwerk/base/public/nsIAsyncStreamCopier2.idl new file mode 100644 index 000000000000..7de793f51e32 --- /dev/null +++ b/netwerk/base/public/nsIAsyncStreamCopier2.idl @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsIRequestObserver; +interface nsIEventTarget; + +[scriptable, uuid(a5b2decf-4ede-4801-8b38-e5fe5db46bf2)] +interface nsIAsyncStreamCopier2 : nsIRequest +{ + /** + * Initialize the stream copier. + * + * If neither the source nor the sink are buffered, buffering will + * be automatically added to the sink. + * + * + * @param aSource + * contains the data to be copied. + * @param aSink + * specifies the destination for the data. + * @param aTarget + * specifies the thread on which the copy will occur. a null value + * is permitted and will cause the copy to occur on an unspecified + * background thread. + * @param aChunkSize + * specifies how many bytes to read/write at a time. this controls + * the granularity of the copying. it should match the segment size + * of the "buffered" streams involved. + * @param aCloseSource + * true if aSource should be closed after copying (this is generally + * the desired behavior). + * @param aCloseSink + * true if aSink should be closed after copying (this is generally + * the desired behavior). + */ + void init(in nsIInputStream aSource, + in nsIOutputStream aSink, + in nsIEventTarget aTarget, + in unsigned long aChunkSize, + in boolean aCloseSource, + in boolean aCloseSink); + + /** + * asyncCopy triggers the start of the copy. The observer will be notified + * when the copy completes. + * + * @param aObserver + * receives notifications. + * @param aObserverContext + * passed to observer methods. + */ + void asyncCopy(in nsIRequestObserver aObserver, + in nsISupports aObserverContext); +}; diff --git a/netwerk/base/src/nsAsyncStreamCopier.cpp b/netwerk/base/src/nsAsyncStreamCopier.cpp index 977704481128..e625654fb9b1 100644 --- a/netwerk/base/src/nsAsyncStreamCopier.cpp +++ b/netwerk/base/src/nsAsyncStreamCopier.cpp @@ -6,6 +6,7 @@ #include "nsIOService.h" #include "nsIEventTarget.h" #include "nsStreamUtils.h" +#include "nsThreadUtils.h" #include "nsNetUtil.h" #include "prlog.h" @@ -20,6 +21,44 @@ static PRLogModuleInfo *gStreamCopierLog = nullptr; #endif #define LOG(args) PR_LOG(gStreamCopierLog, PR_LOG_DEBUG, args) +/** + * An event used to perform initialization off the main thread. + */ +class AsyncApplyBufferingPolicyEvent MOZ_FINAL: public nsRunnable +{ +public: + /** + * @param aCopier + * The nsAsyncStreamCopier requesting the information. + */ + AsyncApplyBufferingPolicyEvent(nsAsyncStreamCopier* aCopier) + : mCopier(aCopier) + , mTarget(NS_GetCurrentThread()) + { } + NS_METHOD Run() + { + nsresult rv = mCopier->ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + return NS_OK; + } + + nsCOMPtr event = NS_NewRunnableMethod(mCopier, &nsAsyncStreamCopier::AsyncCopyInternal); + rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + } + return NS_OK; + } +private: + nsRefPtr mCopier; + nsCOMPtr mTarget; +}; + + + //----------------------------------------------------------------------------- nsAsyncStreamCopier::nsAsyncStreamCopier() @@ -28,6 +67,7 @@ nsAsyncStreamCopier::nsAsyncStreamCopier() , mChunkSize(nsIOService::gDefaultSegmentSize) , mStatus(NS_OK) , mIsPending(false) + , mShouldSniffBuffering(false) { #if defined(PR_LOGGING) if (!gStreamCopierLog) @@ -50,6 +90,12 @@ nsAsyncStreamCopier::IsComplete(nsresult *status) return !mIsPending; } +nsIRequest* +nsAsyncStreamCopier::AsRequest() +{ + return static_cast(static_cast(this)); +} + void nsAsyncStreamCopier::Complete(nsresult status) { @@ -73,7 +119,7 @@ nsAsyncStreamCopier::Complete(nsresult status) if (observer) { LOG((" calling OnStopRequest [status=%x]\n", status)); - observer->OnStopRequest(this, ctx, status); + observer->OnStopRequest(AsRequest(), ctx, status); } } @@ -88,9 +134,19 @@ nsAsyncStreamCopier::OnAsyncCopyComplete(void *closure, nsresult status) //----------------------------------------------------------------------------- // nsISupports -NS_IMPL_ISUPPORTS2(nsAsyncStreamCopier, - nsIRequest, - nsIAsyncStreamCopier) +// We cannot use simply NS_IMPL_ISUPPORTSx as both +// nsIAsyncStreamCopier and nsIAsyncStreamCopier2 implement nsIRequest + +NS_IMPL_ADDREF(nsAsyncStreamCopier) +NS_IMPL_RELEASE(nsAsyncStreamCopier) +NS_INTERFACE_TABLE_HEAD(nsAsyncStreamCopier) +NS_INTERFACE_TABLE_BEGIN +NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier) +NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier2) +NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsIRequest, nsIAsyncStreamCopier) +NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsISupports, nsIAsyncStreamCopier) +NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL //----------------------------------------------------------------------------- // nsIRequest @@ -178,6 +234,38 @@ nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup *aLoadGroup) return NS_OK; } +nsresult +nsAsyncStreamCopier::InitInternal(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + uint32_t chunkSize, + bool closeSource, + bool closeSink) +{ + NS_ASSERTION(!mSource && !mSink, "Init() called more than once"); + if (chunkSize == 0) { + chunkSize = nsIOService::gDefaultSegmentSize; + } + mChunkSize = chunkSize; + + mSource = source; + mSink = sink; + mCloseSource = closeSource; + mCloseSink = closeSink; + + if (target) { + mTarget = target; + } else { + nsresult rv; + mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + //----------------------------------------------------------------------------- // nsIAsyncStreamCopier @@ -192,28 +280,71 @@ nsAsyncStreamCopier::Init(nsIInputStream *source, bool closeSink) { NS_ASSERTION(sourceBuffered || sinkBuffered, "at least one stream must be buffered"); - - if (chunkSize == 0) - chunkSize = nsIOService::gDefaultSegmentSize; - mChunkSize = chunkSize; - - mSource = source; - mSink = sink; - mCloseSource = closeSource; - mCloseSink = closeSink; - mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS : NS_ASYNCCOPY_VIA_WRITESEGMENTS; - if (target) - mTarget = target; - else { - nsresult rv; - mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); - if (NS_FAILED(rv)) return rv; + + return InitInternal(source, sink, target, chunkSize, closeSource, closeSink); +} + +//----------------------------------------------------------------------------- +// nsIAsyncStreamCopier2 + +NS_IMETHODIMP +nsAsyncStreamCopier::Init(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + uint32_t chunkSize, + bool closeSource, + bool closeSink) +{ + mShouldSniffBuffering = true; + + return InitInternal(source, sink, target, chunkSize, closeSource, closeSink); +} + +/** + * Detect whether the input or the output stream is buffered, + * bufferize one of them if neither is buffered. + */ +nsresult +nsAsyncStreamCopier::ApplyBufferingPolicy() +{ + // This function causes I/O, it must not be executed on the main + // thread. + MOZ_ASSERT(!NS_IsMainThread()); + + if (NS_OutputStreamIsBuffered(mSink)) { + // Sink is buffered, no need to perform additional buffering + mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS; + return NS_OK; } + if (NS_InputStreamIsBuffered(mSource)) { + // Source is buffered, no need to perform additional buffering + mMode = NS_ASYNCCOPY_VIA_READSEGMENTS; + return NS_OK; + } + + // No buffering, let's buffer the sink + nsresult rv; + nsCOMPtr sink = + do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + rv = sink->Init(mSink, mChunkSize); + if (NS_FAILED(rv)) { + return rv; + } + + mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS; + mSink = sink; return NS_OK; } +//----------------------------------------------------------------------------- +// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2 + NS_IMETHODIMP nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver *observer, nsISupports *ctx) { @@ -233,11 +364,46 @@ nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver *observer, nsISupports *ctx) mIsPending = true; if (mObserver) { - rv = mObserver->OnStartRequest(this, nullptr); + rv = mObserver->OnStartRequest(AsRequest(), nullptr); if (NS_FAILED(rv)) Cancel(rv); } + if (!mShouldSniffBuffering) { + // No buffer sniffing required, let's proceed + AsyncCopyInternal(); + return NS_OK; + } + + if (NS_IsMainThread()) { + // Don't perform buffer sniffing on the main thread + nsCOMPtr event + = new AsyncApplyBufferingPolicyEvent(this); + rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + Cancel(rv); + } + return NS_OK; + } + + // We're not going to block the main thread, so let's sniff here + rv = ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + Cancel(rv); + } + AsyncCopyInternal(); + return NS_OK; +} + +// Launch async copy. +// All errors are reported through the observer. +void +nsAsyncStreamCopier::AsyncCopyInternal() +{ + MOZ_ASSERT(mMode == NS_ASYNCCOPY_VIA_READSEGMENTS + || mMode == NS_ASYNCCOPY_VIA_WRITESEGMENTS); + + nsresult rv; // we want to receive progress notifications; release happens in // OnAsyncCopyComplete. NS_ADDREF_THIS(); @@ -251,6 +417,6 @@ nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver *observer, nsISupports *ctx) NS_RELEASE_THIS(); Cancel(rv); } - - return NS_OK; } + + diff --git a/netwerk/base/src/nsAsyncStreamCopier.h b/netwerk/base/src/nsAsyncStreamCopier.h index 38c2897e229b..3670de5c0a7d 100644 --- a/netwerk/base/src/nsAsyncStreamCopier.h +++ b/netwerk/base/src/nsAsyncStreamCopier.h @@ -6,6 +6,7 @@ #define nsAsyncStreamCopier_h__ #include "nsIAsyncStreamCopier.h" +#include "nsIAsyncStreamCopier2.h" #include "mozilla/Mutex.h" #include "nsStreamUtils.h" #include "nsCOMPtr.h" @@ -14,13 +15,23 @@ class nsIRequestObserver; //----------------------------------------------------------------------------- -class nsAsyncStreamCopier : public nsIAsyncStreamCopier +class nsAsyncStreamCopier : public nsIAsyncStreamCopier, nsIAsyncStreamCopier2 { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIREQUEST NS_DECL_NSIASYNCSTREAMCOPIER + // nsIAsyncStreamCopier2 + // We declare it by hand instead of NS_DECL_NSIASYNCSTREAMCOPIER2 + // as nsIAsyncStreamCopier2 duplicates methods of nsIAsyncStreamCopier + NS_IMETHOD Init(nsIInputStream *aSource, + nsIOutputStream *aSink, + nsIEventTarget *aTarget, + uint32_t aChunkSize, + bool aCloseSource, + bool aCloseSink); + nsAsyncStreamCopier(); virtual ~nsAsyncStreamCopier(); @@ -31,9 +42,19 @@ public: void Complete(nsresult status); private: + nsresult InitInternal(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + uint32_t chunkSize, + bool closeSource, + bool closeSink); static void OnAsyncCopyComplete(void *, nsresult); + void AsyncCopyInternal(); + nsresult ApplyBufferingPolicy(); + nsIRequest* AsRequest(); + nsCOMPtr mSource; nsCOMPtr mSink; @@ -51,6 +72,10 @@ private: bool mIsPending; bool mCloseSource; bool mCloseSink; + bool mShouldSniffBuffering; + + friend class ProceedWithAsyncCopy; + friend class AsyncApplyBufferingPolicyEvent; }; #endif // !nsAsyncStreamCopier_h__