Bug 497003 - Support delivery of OnDataAvailable on the HTML5 parser thread r=hsivonen r=bholley r=bz
authorSteve Workman <sworkman@mozilla.com>
Mon, 08 Jul 2013 18:45:26 -0700
changeset 149981 056043e8570df862cf2cf43fbcbb2526d5c03438
parent 149980 57ffaa460a6bb8f0507f119a4b5b62e0c430d99a
child 149982 575d402f0dae033e552a799e92eddd6a95eef460
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershsivonen, bholley, bz
bugs497003
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 497003 - Support delivery of OnDataAvailable on the HTML5 parser thread r=hsivonen r=bholley r=bz
netwerk/base/src/nsStreamListenerTee.cpp
parser/html/nsHtml5StreamParser.cpp
parser/html/nsHtml5StreamParser.h
parser/html/nsHtml5TreeOpExecutor.h
uriloader/base/nsURILoader.cpp
xpcom/glue/nsProxyRelease.h
--- a/netwerk/base/src/nsStreamListenerTee.cpp
+++ b/netwerk/base/src/nsStreamListenerTee.cpp
@@ -98,16 +98,26 @@ nsStreamListenerTee::CheckListenerChain(
 {
     NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
     nsresult rv = NS_OK;
     nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
         do_QueryInterface(mListener, &rv);
     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;
 }
 
 NS_IMETHODIMP
 nsStreamListenerTee::Init(nsIStreamListener *listener,
                           nsIOutputStream *sink,
                           nsIRequestObserver *requestObserver)
 {
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -22,16 +22,18 @@
 #include "mozilla/Preferences.h"
 #include "nsHtml5Highlighter.h"
 #include "expat_config.h"
 #include "expat.h"
 #include "nsINestedURI.h"
 #include "nsCharsetSource.h"
 #include "nsIWyciwygChannel.h"
 #include "nsIInputStreamChannel.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsPrintfCString.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 
 using namespace mozilla;
 using mozilla::dom::EncodingUtils;
 
 int32_t nsHtml5StreamParser::sTimerInitialDelay = 120;
 int32_t nsHtml5StreamParser::sTimerSubsequentDelay = 120;
@@ -69,19 +71,20 @@ nsHtml5StreamParser::InitializeStatics()
  * posted from the parser thread to main thread don't need to wrap any
  * runnable-specific data. (In the other direction, the runnables most notably
  * wrap the byte data of the stream.)
  */
 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
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5StreamParser)
   tmp->DropTimer();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mObserver)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequest)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
@@ -923,16 +926,24 @@ nsHtml5StreamParser::OnStartRequest(nsIR
     // XXX does Necko have a way to renavigate POST, etc. without hitting
     // the network?
     if (!method.EqualsLiteral("GET")) {
       // This is the old Gecko behavior but the HTML5 spec disagrees.
       // Don't reparse on POST.
       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<nsIThreadRetargetableRequest> threadRetargetableRequest =
+      do_QueryInterface(mRequest);
+    if (threadRetargetableRequest) {
+      threadRetargetableRequest->RetargetDeliveryTo(mThread);
+    }
   }
 
   if (mCharsetSource == kCharsetFromParentFrame) {
     // Remember this in case chardet overwrites mCharsetSource
     mInitialEncodingWasFromParentFrame = true;
   }
 
   if (mCharsetSource >= kCharsetFromAutoDetection) {
@@ -957,16 +968,32 @@ nsHtml5StreamParser::OnStartRequest(nsIR
   // if we failed to get a decoder, there will be fallback, so don't propagate
   //  the error.
   if (NS_FAILED(rv)) {
     mCharsetSource = kCharsetFromWeakDocTypeDefault;
   }
   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<nsIThreadRetargetableStreamListener> retargetable =
+    do_QueryInterface(mObserver, &rv);
+  if (NS_SUCCEEDED(rv) && retargetable) {
+    rv = retargetable->CheckListenerChain();
+  }
+  return rv;
+}
+
 void
 nsHtml5StreamParser::DoStopRequest()
 {
   NS_ASSERTION(IsParserThread(), "Wrong thread!");
   NS_PRECONDITION(STREAM_BEING_READ == mStreamState,
                   "Stream ended without being open.");
   mTokenizerMutex.AssertCurrentThreadOwns();
 
@@ -1022,17 +1049,17 @@ nsHtml5StreamParser::OnStopRequest(nsIRe
   nsCOMPtr<nsIRunnable> stopper = new nsHtml5RequestStopper(this);
   if (NS_FAILED(mThread->Dispatch(stopper, nsIThread::DISPATCH_NORMAL))) {
     NS_WARNING("Dispatching StopRequest event failed.");
   }
   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,
                   "DoDataAvailable called when stream not open.");
   mTokenizerMutex.AssertCurrentThreadOwns();
 
   if (IsTerminated()) {
     return;
@@ -1107,32 +1134,65 @@ nsHtml5StreamParser::OnDataAvailable(nsI
 {
   nsresult rv;
   if (NS_FAILED(rv = mExecutor->IsBroken())) {
     return rv;
   }
 
   NS_ASSERTION(mRequest == aRequest, "Got data on wrong stream.");
   uint32_t totalRead;
-  const mozilla::fallible_t fallible = mozilla::fallible_t();
-  nsAutoArrayPtr<uint8_t> 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<uint8_t> data(new (fallible) uint8_t[aLength]);
+    if (!data) {
+      return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+    }
+    rv = aInStream->Read(reinterpret_cast<char*>(data.get()),
+                         aLength, &totalRead);
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ASSERTION(totalRead <= aLength, "Read more bytes than were available?");
+
+    nsCOMPtr<nsIRunnable> 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<char*>(data.get()),
-  aLength, &totalRead);
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ASSERTION(totalRead <= aLength, "Read more bytes than were available?");
-  nsCOMPtr<nsIRunnable> 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<nsHtml5StreamParser*>(aClosure);
+
+  parser->DoDataAvailable((const uint8_t*)aFromSegment, aCount);
+  // Assume DoDataAvailable consumed all available bytes.
+  *aWriteCount = aCount;
+  return NS_OK;
 }
 
 bool
 nsHtml5StreamParser::PreferredForInternalEncodingDecl(nsACString& aEncoding)
 {
   nsAutoCString newEncoding;
   if (!EncodingUtils::FindEncodingForLabel(aEncoding, newEncoding)) {
     // the encoding name is bogus
--- a/parser/html/nsHtml5StreamParser.h
+++ b/parser/html/nsHtml5StreamParser.h
@@ -15,16 +15,17 @@
 #include "nsHtml5TreeOpExecutor.h"
 #include "nsHtml5OwningUTF16Buffer.h"
 #include "nsIInputStream.h"
 #include "mozilla/Mutex.h"
 #include "nsHtml5AtomTable.h"
 #include "nsHtml5Speculation.h"
 #include "nsITimer.h"
 #include "nsICharsetDetector.h"
+#include "nsIThreadRetargetableStreamListener.h"
 
 class nsHtml5Parser;
 
 #define NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE 1024
 #define NS_HTML5_STREAM_PARSER_SNIFFING_BUFFER_SIZE 1024
 
 enum eParserMode {
   /**
@@ -96,16 +97,17 @@ enum eBomState {
 
 enum eHtml5StreamState {
   STREAM_NOT_STARTED = 0,
   STREAM_BEING_READ = 1,
   STREAM_ENDED = 2
 };
 
 class nsHtml5StreamParser : public nsIStreamListener,
+                            public nsIThreadRetargetableStreamListener,
                             public nsICharsetDetectionObserver {
 
   friend class nsHtml5RequestStopper;
   friend class nsHtml5DataAvailable;
   friend class nsHtml5StreamParserContinuation;
   friend class nsHtml5TimerKungFu;
 
   public:
@@ -120,16 +122,18 @@ class nsHtml5StreamParser : public nsISt
                         eParserMode aMode);
                         
     virtual ~nsHtml5StreamParser();
 
     // nsIRequestObserver methods:
     NS_DECL_NSIREQUESTOBSERVER
     // nsIStreamListener methods:
     NS_DECL_NSISTREAMLISTENER
+    // nsIThreadRetargetableStreamListener methods:
+    NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
     
     // nsICharsetDetectionObserver
     /**
      * Chardet calls this to report the detection result
      */
     NS_IMETHOD Notify(const char* aCharset, nsDetectionConfident aConf);
 
     // EncodingDeclarationHandler
@@ -236,17 +240,24 @@ class nsHtml5StreamParser : public nsISt
      * timer.
      */
     void FlushTreeOpsAndDisarmTimer();
 
     void ParseAvailableData();
     
     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);
       return mTerminated || mInterrupted;
     }
 
     bool IsTerminated() {
       mozilla::MutexAutoLock autoLock(mTerminatedMutex);
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -223,17 +223,16 @@ class nsHtml5TreeOpExecutor : public nsC
      */
     nsresult MarkAsBroken(nsresult aReason);
 
     /**
      * Checks if this parser is broken. Returns a non-NS_OK (i.e. non-0)
      * value if broken.
      */
     inline nsresult IsBroken() {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
       return mBroken;
     }
 
     inline void BeginDocUpdate() {
       NS_PRECONDITION(mFlushState == eInFlush, "Tried to double-open update.");
       NS_PRECONDITION(mParser, "Started update without parser.");
       mFlushState = eInDocUpdate;
       mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
--- a/uriloader/base/nsURILoader.cpp
+++ b/uriloader/base/nsURILoader.cpp
@@ -25,20 +25,22 @@
 #include "nsWeakReference.h"
 #include "nsIHttpChannel.h"
 #include "nsIMultiPartChannel.h"
 #include "netCore.h"
 #include "nsCRT.h"
 #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"
 
 #include "nsICategoryManager.h"
 #include "nsCExternalHandlerService.h" // contains contractids for the helper app service
 
 #include "nsIMIMEHeaderParam.h"
 #include "nsNetCID.h"
@@ -58,16 +60,17 @@ PRLogModuleInfo* nsURILoader::mLog = nul
 
 /**
  * The nsDocumentOpenInfo contains the state required when a single
  * document is being opened in order to discover the content type...
  * Each instance remains alive until its target URL has been loaded
  * (or aborted).
  */
 class nsDocumentOpenInfo MOZ_FINAL : public nsIStreamListener
+                                   , public nsIThreadRetargetableStreamListener
 {
 public:
   // Needed for nsCOMPtr to work right... Don't call this!
   nsDocumentOpenInfo();
 
   // Real constructor
   // aFlags is a combination of the flags on nsIURILoader
   nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
@@ -105,16 +108,18 @@ public:
                             nsIChannel* aChannel);
 
   // nsIRequestObserver methods:
   NS_DECL_NSIREQUESTOBSERVER
 
   // nsIStreamListener methods:
   NS_DECL_NSISTREAMLISTENER
 
+  // nsIThreadRetargetableStreamListener
+  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
 protected:
   ~nsDocumentOpenInfo();
 
 protected:
   /**
    * The first content listener to try dispatching data to.  Typically
    * the listener associated with the entity that originated the load.
    */
@@ -154,16 +159,17 @@ protected:
 
 NS_IMPL_THREADSAFE_ADDREF(nsDocumentOpenInfo)
 NS_IMPL_THREADSAFE_RELEASE(nsDocumentOpenInfo)
 
 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()
 {
   NS_NOTREACHED("This should never be called\n");
 }
 
 nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
@@ -262,16 +268,32 @@ NS_IMETHODIMP nsDocumentOpenInfo::OnStar
     rv = m_targetStreamListener->OnStartRequest(request, aCtxt);
 
   LOG(("  OnStartRequest returning: 0x%08X", rv));
   
   return rv;
 }
 
 NS_IMETHODIMP
+nsDocumentOpenInfo::CheckListenerChain()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIThreadRetargetableStreamListener> 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,
                                     uint64_t sourceOffset, uint32_t count)
 {
   // if we have retarged to the end stream listener, then forward the call....
   // otherwise, don't do anything
 
   nsresult rv = NS_OK;
@@ -553,17 +575,17 @@ nsDocumentOpenInfo::ConvertData(nsIReque
   // When applying stream decoders, it is necessary to "insert" an 
   // intermediate nsDocumentOpenInfo instance to handle the targeting of
   // the "final" stream or streams.
   //
   // For certain content types (ie. multi-part/x-mixed-replace) the input
   // stream is split up into multiple destination streams.  This
   // intermediate instance is used to target these "decoded" streams...
   //
-  nsCOMPtr<nsDocumentOpenInfo> nextLink =
+  nsRefPtr<nsDocumentOpenInfo> nextLink =
     new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader);
   if (!nextLink) return NS_ERROR_OUT_OF_MEMORY;
 
   LOG(("  Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
   
   // Make sure nextLink starts with the contentListener that said it wanted the
   // results of this decode.
   nextLink->m_contentListener = aListener;
@@ -807,17 +829,17 @@ nsresult nsURILoader::OpenChannel(nsICha
         LOG(("  OnStartURIOpen aborted load"));
         return NS_ERROR_WONT_HANDLE_CONTENT;
       }
     }
   }
 
   // we need to create a DocumentOpenInfo object which will go ahead and open
   // the url and discover the content type....
-  nsCOMPtr<nsDocumentOpenInfo> loader =
+  nsRefPtr<nsDocumentOpenInfo> loader =
     new nsDocumentOpenInfo(aWindowContext, aFlags, this);
 
   if (!loader) return NS_ERROR_OUT_OF_MEMORY;
 
   // Set the correct loadgroup on the channel
   nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
 
   if (!loadGroup) {
--- a/xpcom/glue/nsProxyRelease.h
+++ b/xpcom/glue/nsProxyRelease.h
@@ -198,16 +198,18 @@ class nsMainThreadPtrHandle
       return mPtr.get()->get();
     }
     return nullptr;
   }
 
   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<T>& aOther) const {
     if (!mPtr || !aOther.mPtr)
       return mPtr == aOther.mPtr;
     return *mPtr == *aOther.mPtr;
   }
   bool operator!() { return !mPtr; }
 };