From 895826d5ede12526c69a53f7b9640831f3eae5b4 Mon Sep 17 00:00:00 2001 From: Steve Workman Date: Thu, 13 Jun 2013 10:42:48 -0700 Subject: [PATCH] Bug 497003 - Support delivery of OnDataAvailable on the HTML5 parser thread r=hsivonen r=bholley r=bz --- netwerk/base/src/nsStreamListenerTee.cpp | 10 +++ parser/html/nsHtml5StreamParser.cpp | 110 ++++++++++++++++++----- parser/html/nsHtml5StreamParser.h | 13 ++- parser/html/nsHtml5TreeOpExecutor.h | 1 - uriloader/base/nsURILoader.cpp | 61 ++++++++++--- xpcom/glue/nsProxyRelease.h | 2 + 6 files changed, 160 insertions(+), 37 deletions(-) diff --git a/netwerk/base/src/nsStreamListenerTee.cpp b/netwerk/base/src/nsStreamListenerTee.cpp index 6834f0f8e77c..625cd75efa71 100644 --- a/netwerk/base/src/nsStreamListenerTee.cpp +++ b/netwerk/base/src/nsStreamListenerTee.cpp @@ -103,6 +103,16 @@ nsStreamListenerTee::CheckListenerChain() if (retargetableListener) { rv = retargetableListener->CheckListenerChain(); } + if (NS_FAILED(rv)) { + return rv; + } + if (!mObserver) { + return rv; + } + retargetableListener = do_QueryInterface(mObserver, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } return rv; } diff --git a/parser/html/nsHtml5StreamParser.cpp b/parser/html/nsHtml5StreamParser.cpp index 13400c66a68c..6f00f1c85bff 100644 --- a/parser/html/nsHtml5StreamParser.cpp +++ b/parser/html/nsHtml5StreamParser.cpp @@ -26,6 +26,8 @@ #include "nsINestedURI.h" #include "nsCharsetSource.h" #include "nsIWyciwygChannel.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsPrintfCString.h" #include "mozilla/dom/EncodingUtils.h" @@ -73,9 +75,10 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5StreamParser) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5StreamParser) NS_INTERFACE_TABLE_HEAD(nsHtml5StreamParser) - NS_INTERFACE_TABLE2(nsHtml5StreamParser, + NS_INTERFACE_TABLE3(nsHtml5StreamParser, nsIStreamListener, - nsICharsetDetectionObserver) + nsICharsetDetectionObserver, + nsIThreadRetargetableStreamListener) NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5StreamParser) NS_INTERFACE_MAP_END @@ -926,6 +929,14 @@ nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) mReparseForbidden = true; mFeedChardet = false; // can't restart anyway } + + // Attempt to retarget delivery of data (via OnDataAvailable) to the parser + // thread, rather than through the main thread. + nsCOMPtr threadRetargetableRequest = + do_QueryInterface(mRequest); + if (threadRetargetableRequest) { + threadRetargetableRequest->RetargetDeliveryTo(mThread); + } } if (mCharsetSource == kCharsetFromParentFrame) { @@ -960,6 +971,22 @@ nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) return NS_OK; } +NS_IMETHODIMP +nsHtml5StreamParser::CheckListenerChain() +{ + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); + if (!mObserver) { + return NS_OK; + } + nsresult rv; + nsCOMPtr retargetable = + do_QueryInterface(mObserver, &rv); + if (NS_SUCCEEDED(rv) && retargetable) { + rv = retargetable->CheckListenerChain(); + } + return rv; +} + void nsHtml5StreamParser::DoStopRequest() { @@ -1013,19 +1040,24 @@ nsHtml5StreamParser::OnStopRequest(nsIRequest* aRequest, nsresult status) { NS_ASSERTION(mRequest == aRequest, "Got Stop on wrong stream."); - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(NS_IsMainThread() || IsParserThread(), "Wrong thread!"); if (mObserver) { mObserver->OnStopRequest(aRequest, aContext, status); } - nsCOMPtr stopper = new nsHtml5RequestStopper(this); - if (NS_FAILED(mThread->Dispatch(stopper, nsIThread::DISPATCH_NORMAL))) { - NS_WARNING("Dispatching StopRequest event failed."); + if (NS_IsMainThread()) { + nsCOMPtr stopper = new nsHtml5RequestStopper(this); + if (NS_FAILED(mThread->Dispatch(stopper, nsIThread::DISPATCH_NORMAL))) { + NS_WARNING("Dispatching StopRequest event failed."); + } + } else { + mozilla::MutexAutoLock autoLock(mTokenizerMutex); + DoStopRequest(); } return NS_OK; } void -nsHtml5StreamParser::DoDataAvailable(uint8_t* aBuffer, uint32_t aLength) +nsHtml5StreamParser::DoDataAvailable(const uint8_t* aBuffer, uint32_t aLength) { NS_ASSERTION(IsParserThread(), "Wrong thread!"); NS_PRECONDITION(STREAM_BEING_READ == mStreamState, @@ -1110,24 +1142,58 @@ nsHtml5StreamParser::OnDataAvailable(nsIRequest* aRequest, NS_ASSERTION(mRequest == aRequest, "Got data on wrong stream."); uint32_t totalRead; - const mozilla::fallible_t fallible = mozilla::fallible_t(); - nsAutoArrayPtr data(new (fallible) uint8_t[aLength]); - if (!data) { - return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); + // Main thread to parser thread dispatch requires copying to buffer first. + if (NS_IsMainThread()) { + const mozilla::fallible_t fallible = mozilla::fallible_t(); + nsAutoArrayPtr data(new (fallible) uint8_t[aLength]); + if (!data) { + return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); + } + rv = aInStream->Read(reinterpret_cast(data.get()), + aLength, &totalRead); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(totalRead <= aLength, "Read more bytes than were available?"); + + nsCOMPtr dataAvailable = new nsHtml5DataAvailable(this, + data.forget(), + totalRead); + if (NS_FAILED(mThread->Dispatch(dataAvailable, nsIThread::DISPATCH_NORMAL))) { + NS_WARNING("Dispatching DataAvailable event failed."); + } + return rv; + } else { + NS_ASSERTION(IsParserThread(), "Wrong thread!"); + mozilla::MutexAutoLock autoLock(mTokenizerMutex); + + // Read directly from response buffer. + rv = aInStream->ReadSegments(CopySegmentsToParser, this, aLength, + &totalRead); + if (NS_FAILED(rv)) { + NS_WARNING("Failed reading response data to parser"); + return rv; + } + return NS_OK; } - rv = aInStream->Read(reinterpret_cast(data.get()), - aLength, &totalRead); - NS_ENSURE_SUCCESS(rv, rv); - NS_ASSERTION(totalRead <= aLength, "Read more bytes than were available?"); - nsCOMPtr dataAvailable = new nsHtml5DataAvailable(this, - data.forget(), - totalRead); - if (NS_FAILED(mThread->Dispatch(dataAvailable, nsIThread::DISPATCH_NORMAL))) { - NS_WARNING("Dispatching DataAvailable event failed."); - } - return rv; } +/* static */ +NS_METHOD +nsHtml5StreamParser::CopySegmentsToParser(nsIInputStream *aInStream, + void *aClosure, + const char *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount) +{ + nsHtml5StreamParser* parser = static_cast(aClosure); + + parser->DoDataAvailable((const uint8_t*)aFromSegment, aCount); + // Assume DoDataAvailable consumed all available bytes. + *aWriteCount = aCount; + return NS_OK; +} + + bool nsHtml5StreamParser::PreferredForInternalEncodingDecl(nsACString& aEncoding) { diff --git a/parser/html/nsHtml5StreamParser.h b/parser/html/nsHtml5StreamParser.h index a0776de5c626..c7dc36981531 100644 --- a/parser/html/nsHtml5StreamParser.h +++ b/parser/html/nsHtml5StreamParser.h @@ -20,6 +20,7 @@ #include "nsHtml5Speculation.h" #include "nsITimer.h" #include "nsICharsetDetector.h" +#include "nsIThreadRetargetableStreamListener.h" class nsHtml5Parser; @@ -101,6 +102,7 @@ enum eHtml5StreamState { }; class nsHtml5StreamParser : public nsIStreamListener, + public nsIThreadRetargetableStreamListener, public nsICharsetDetectionObserver { friend class nsHtml5RequestStopper; @@ -125,6 +127,8 @@ class nsHtml5StreamParser : public nsIStreamListener, NS_DECL_NSIREQUESTOBSERVER // nsIStreamListener methods: NS_DECL_NSISTREAMLISTENER + // nsIThreadRetargetableStreamListener methods: + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER // nsICharsetDetectionObserver /** @@ -239,7 +243,14 @@ class nsHtml5StreamParser : public nsIStreamListener, void DoStopRequest(); - void DoDataAvailable(uint8_t* aBuffer, uint32_t aLength); + void DoDataAvailable(const uint8_t* aBuffer, uint32_t aLength); + + static NS_METHOD CopySegmentsToParser(nsIInputStream *aInStream, + void *aClosure, + const char *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount); bool IsTerminatedOrInterrupted() { mozilla::MutexAutoLock autoLock(mTerminatedMutex); diff --git a/parser/html/nsHtml5TreeOpExecutor.h b/parser/html/nsHtml5TreeOpExecutor.h index c01d802901b6..dce1a088d6e2 100644 --- a/parser/html/nsHtml5TreeOpExecutor.h +++ b/parser/html/nsHtml5TreeOpExecutor.h @@ -228,7 +228,6 @@ class nsHtml5TreeOpExecutor : public nsContentSink, * value if broken. */ inline nsresult IsBroken() { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return mBroken; } diff --git a/uriloader/base/nsURILoader.cpp b/uriloader/base/nsURILoader.cpp index a03930dcb48f..ebbbf7f16a74 100644 --- a/uriloader/base/nsURILoader.cpp +++ b/uriloader/base/nsURILoader.cpp @@ -6,6 +6,7 @@ #include "nsURILoader.h" #include "nsAutoPtr.h" +#include "nsProxyRelease.h" #include "nsIURIContentListener.h" #include "nsIContentHandler.h" #include "nsILoadGroup.h" @@ -30,10 +31,12 @@ #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIDocShellTreeOwner.h" +#include "nsIThreadRetargetableStreamListener.h" #include "nsXPIDLString.h" #include "nsString.h" #include "nsNetUtil.h" +#include "nsThreadUtils.h" #include "nsReadableUtils.h" #include "nsError.h" @@ -63,6 +66,7 @@ PRLogModuleInfo* nsURILoader::mLog = nullptr; * (or aborted). */ class nsDocumentOpenInfo MOZ_FINAL : public nsIStreamListener + , public nsIThreadRetargetableStreamListener { public: // Needed for nsCOMPtr to work right... Don't call this! @@ -110,6 +114,8 @@ public: // nsIStreamListener methods: NS_DECL_NSISTREAMLISTENER + // nsIThreadRetargetableStreamListener + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER protected: ~nsDocumentOpenInfo(); @@ -124,7 +130,7 @@ protected: * The stream listener to forward nsIStreamListener notifications * to. This is set once the load is dispatched. */ - nsCOMPtr m_targetStreamListener; + nsMainThreadPtrHandle m_targetStreamListener; /** * A pointer to the entity that originated the load. We depend on getting @@ -159,6 +165,7 @@ NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener) NS_INTERFACE_MAP_END_THREADSAFE nsDocumentOpenInfo::nsDocumentOpenInfo() @@ -266,6 +273,22 @@ NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupport return rv; } +NS_IMETHODIMP +nsDocumentOpenInfo::CheckListenerChain() +{ + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); + nsresult rv = NS_OK; + nsCOMPtr retargetableListener = + do_QueryInterface(m_targetStreamListener, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } + LOG(("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv %x", + this, (NS_SUCCEEDED(rv) ? "success" : "failure"), + (nsIStreamListener*)m_targetStreamListener, rv)); + return rv; +} + NS_IMETHODIMP nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * inStr, @@ -288,7 +311,7 @@ NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports if ( m_targetStreamListener) { - nsCOMPtr listener(m_targetStreamListener); + nsMainThreadPtrHandle listener = m_targetStreamListener; // If this is a multipart stream, we could get another // OnStartRequest after this... reset state. @@ -514,11 +537,15 @@ nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT)); } + nsCOMPtr listener; rv = helperAppService->DoContent(mContentType, request, m_originalContext, false, - getter_AddRefs(m_targetStreamListener)); + getter_AddRefs(listener)); + // Passing false here to allow off main thread use. + m_targetStreamListener + = new nsMainThreadPtrHolder(listener, false); if (NS_FAILED(rv)) { request->SetLoadFlags(loadFlags); m_targetStreamListener = nullptr; @@ -558,7 +585,7 @@ nsDocumentOpenInfo::ConvertData(nsIRequest *request, // stream is split up into multiple destination streams. This // intermediate instance is used to target these "decoded" streams... // - nsCOMPtr nextLink = + nsRefPtr nextLink = new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader); if (!nextLink) return NS_ERROR_OUT_OF_MEMORY; @@ -581,11 +608,16 @@ nsDocumentOpenInfo::ConvertData(nsIRequest *request, // stream converter and sets the output end of the stream converter to // nextLink. As we pump data into m_targetStreamListener the stream // converter will convert it and pass the converted data to nextLink. - return StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(), - PromiseFlatCString(aOutContentType).get(), - nextLink, - request, - getter_AddRefs(m_targetStreamListener)); + nsCOMPtr listener; + rv = StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(), + PromiseFlatCString(aOutContentType).get(), + nextLink, + request, + getter_AddRefs(listener)); + // Passing false here to allow off main thread use. + m_targetStreamListener + = new nsMainThreadPtrHolder(listener, false); + return rv; } bool @@ -630,7 +662,7 @@ nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener, // m_targetStreamListener is now the input end of the converter, and we can // just pump the data in there, if it exists. If it does not, we need to // try other nsIURIContentListeners. - return m_targetStreamListener != nullptr; + return m_targetStreamListener.get() != nullptr; } // At this point, aListener wants data of type mContentType. Let 'em have @@ -652,12 +684,15 @@ nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener, bool abort = false; bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0; + nsCOMPtr listener; nsresult rv = aListener->DoContent(mContentType.get(), isPreferred, aChannel, - getter_AddRefs(m_targetStreamListener), + getter_AddRefs(listener), &abort); - + // Passing false here to allow off main thread use. + m_targetStreamListener + = new nsMainThreadPtrHolder(listener, false); if (NS_FAILED(rv)) { LOG_ERROR((" DoContent failed")); @@ -812,7 +847,7 @@ nsresult nsURILoader::OpenChannel(nsIChannel* channel, // we need to create a DocumentOpenInfo object which will go ahead and open // the url and discover the content type.... - nsCOMPtr loader = + nsRefPtr loader = new nsDocumentOpenInfo(aWindowContext, aFlags, this); if (!loader) return NS_ERROR_OUT_OF_MEMORY; diff --git a/xpcom/glue/nsProxyRelease.h b/xpcom/glue/nsProxyRelease.h index b1ce72fe2661..3ba9faab256f 100644 --- a/xpcom/glue/nsProxyRelease.h +++ b/xpcom/glue/nsProxyRelease.h @@ -203,6 +203,8 @@ class nsMainThreadPtrHandle operator T*() { return get(); } T* operator->() { return get(); } + operator bool() { return get(); } + // These are safe to call on other threads with appropriate external locking. bool operator==(const nsMainThreadPtrHandle& aOther) const { if (!mPtr || !aOther.mPtr)