Bug 1351163: Part 1 - Support direct stream listener output in nsBaseChannel. draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 31 Mar 2017 15:01:16 -0700
changeset 554556 4c23bded68860e7859bf165edea438c4dee880b7
parent 554527 7c8827728b7f48e593f4c58ccf5d39539071bf91
child 554557 c06640baad7ee2e7e42869032b1ac4c310d0f03e
child 557409 4149737a37ac6030ba147aac22e107f34b99a1a4
push id51983
push usermaglione.k@gmail.com
push dateFri, 31 Mar 2017 22:11:17 +0000
bugs1351163
milestone55.0a1
Bug 1351163: Part 1 - Support direct stream listener output in nsBaseChannel. Several of our simple channel implementations work naturally with direct nsIStreamListener output. Currently, these implementations need to create a pipe, pump data in to that pipe, and then allow nsBaseChannel to pump data out of the other side of that pipe, into its own stream listener. This change allows them to output data directly to nsBaseChannel's stream listener, which removes unnecessary complexity and overhead. MozReview-Commit-ID: JCGKTt6Kn9x
netwerk/base/nsBaseChannel.cpp
netwerk/base/nsBaseChannel.h
netwerk/base/nsInputStreamPump.cpp
--- a/netwerk/base/nsBaseChannel.cpp
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -37,26 +37,27 @@ public:
   ~ScopedRequestSuspender() {
     if (mRequest)
       mRequest->Resume();
   }
 private:
   nsIRequest *mRequest;
 };
 
-// Used to suspend data events from mPump within a function scope.  This is
+// Used to suspend data events from mRequest within a function scope.  This is
 // usually needed when a function makes callbacks that could process events.
 #define SUSPEND_PUMP_FOR_SCOPE() \
-  ScopedRequestSuspender pump_suspender__(mPump)
+  ScopedRequestSuspender pump_suspender__(mRequest)
 
 //-----------------------------------------------------------------------------
 // nsBaseChannel
 
 nsBaseChannel::nsBaseChannel()
-  : mLoadFlags(LOAD_NORMAL)
+  : mPumpingData(false)
+  , mLoadFlags(LOAD_NORMAL)
   , mQueriedProgressSink(true)
   , mSynthProgressEvents(false)
   , mAllowThreadRetargeting(true)
   , mWaitingOnAsyncRedirect(false)
   , mOpenRedirectChannel(false)
   , mStatus(NS_OK)
   , mContentDispositionHint(UINT32_MAX)
   , mContentLength(-1)
@@ -226,20 +227,28 @@ nsBaseChannel::PushStreamConverter(const
     }
   }
   return rv;
 }
 
 nsresult
 nsBaseChannel::BeginPumpingData()
 {
+  nsresult rv = BeginAsyncRead(this, getter_AddRefs(mRequest));
+  if (NS_SUCCEEDED(rv)) {
+    mPumpingData = true;
+    return rv;
+  }
+  if (rv != NS_ERROR_NOT_IMPLEMENTED)
+    return rv;
+
   nsCOMPtr<nsIInputStream> stream;
   nsCOMPtr<nsIChannel> channel;
-  nsresult rv = OpenContentStream(true, getter_AddRefs(stream),
-                                  getter_AddRefs(channel));
+  rv = OpenContentStream(true, getter_AddRefs(stream),
+                         getter_AddRefs(channel));
   if (NS_FAILED(rv))
     return rv;
 
   NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?");
 
   if (channel) {
       rv = NS_DispatchToCurrentThread(new RedirectRunnable(this, channel));
       if (NS_SUCCEEDED(rv))
@@ -250,26 +259,29 @@ nsBaseChannel::BeginPumpingData()
   // By assigning mPump, we flag this channel as pending (see Pending).  It's
   // important that the pending flag is set when we call into the stream (the
   // call to AsyncRead results in the stream's AsyncWait method being called)
   // and especially when we call into the loadgroup.  Our caller takes care to
   // release mPump if we return an error.
  
   rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, -1, -1, 0, 0,
                                  true);
-  if (NS_SUCCEEDED(rv))
+  if (NS_SUCCEEDED(rv)) {
+    mPumpingData = true;
+    mRequest = mPump;
     rv = mPump->AsyncRead(this, nullptr);
+  }
 
   return rv;
 }
 
 void
 nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel)
 {
-  NS_ASSERTION(!mPump, "Shouldn't have gotten here");
+  NS_ASSERTION(!mPumpingData, "Shouldn't have gotten here");
 
   nsresult rv = mStatus;
   if (NS_SUCCEEDED(mStatus)) {
     rv = Redirect(newChannel,
                   nsIChannelEventSink::REDIRECT_TEMPORARY,
                   true);
     if (NS_SUCCEEDED(rv)) {
       // OnRedirectVerifyCallback will be called asynchronously
@@ -356,51 +368,53 @@ nsBaseChannel::IsPending(bool *result)
 {
   *result = Pending();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseChannel::GetStatus(nsresult *status)
 {
-  if (mPump && NS_SUCCEEDED(mStatus)) {
-    mPump->GetStatus(status);
+  if (mRequest && NS_SUCCEEDED(mStatus)) {
+    mRequest->GetStatus(status);
   } else {
     *status = mStatus;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseChannel::Cancel(nsresult status)
 {
   // Ignore redundant cancelation
   if (NS_FAILED(mStatus))
     return NS_OK;
 
   mStatus = status;
 
-  if (mPump)
-    mPump->Cancel(status);
+  if (mRequest)
+    mRequest->Cancel(status);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseChannel::Suspend()
 {
-  NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
-  return mPump->Suspend();
+  NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED);
+  NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED);
+  return mRequest->Suspend();
 }
 
 NS_IMETHODIMP
 nsBaseChannel::Resume()
 {
-  NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
-  return mPump->Resume();
+  NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED);
+  NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED);
+  return mRequest->Resume();
 }
 
 NS_IMETHODIMP
 nsBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
 {
   *aLoadFlags = mLoadFlags;
   return NS_OK;
 }
@@ -599,17 +613,17 @@ nsBaseChannel::SetContentLength(int64_t 
   mContentLength = aContentLength;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseChannel::Open(nsIInputStream **result)
 {
   NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
-  NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS);
+  NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS);
   NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
 
   nsCOMPtr<nsIChannel> chan;
   nsresult rv = OpenContentStream(false, result, getter_AddRefs(chan));
   NS_ASSERTION(!chan || !*result, "Got both a channel and a stream?");
   if (NS_SUCCEEDED(rv) && chan) {
       rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false);
       if (NS_FAILED(rv))
@@ -641,17 +655,17 @@ nsBaseChannel::AsyncOpen(nsIStreamListen
   MOZ_ASSERT(!mLoadInfo ||
              mLoadInfo->GetSecurityMode() == 0 ||
              mLoadInfo->GetInitialSecurityCheckDone() ||
              (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
               nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
              "security flags in loadInfo but asyncOpen2() not called");
 
   NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
-  NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS);
+  NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS);
   NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
   NS_ENSURE_ARG(listener);
 
   // Skip checking for chrome:// sub-resources.
   nsAutoCString scheme;
   mURI->GetScheme(scheme);
   if (!scheme.EqualsLiteral("file")) {
     NS_CompareLoadInfoAndLoadContext(this);
@@ -672,16 +686,18 @@ nsBaseChannel::AsyncOpen(nsIStreamListen
   mListener = listener;
   mListenerContext = ctxt;
 
   // This method assigns mPump as a side-effect.  We need to clear mPump if
   // this method fails.
   rv = BeginPumpingData();
   if (NS_FAILED(rv)) {
     mPump = nullptr;
+    mRequest = nullptr;
+    mPumpingData = false;
     ChannelDone();
     mCallbacks = nullptr;
     return rv;
   }
 
   // At this point, we are going to return success no matter what.
 
   mWasOpened = true;
@@ -712,17 +728,17 @@ nsBaseChannel::AsyncOpen2(nsIStreamListe
 // nsBaseChannel::nsITransportEventSink
 
 NS_IMETHODIMP
 nsBaseChannel::OnTransportStatus(nsITransport *transport, nsresult status,
                                  int64_t progress, int64_t progressMax)
 {
   // In some cases, we may wish to suppress transport-layer status events.
 
-  if (!mPump || NS_FAILED(mStatus)) {
+  if (!mPumpingData || NS_FAILED(mStatus)) {
     return NS_OK;
   }
 
   SUSPEND_PUMP_FOR_SCOPE();
 
   // Lazily fetch mProgressSink
   if (!mProgressSink) {
     if (mQueriedProgressSink) {
@@ -788,30 +804,32 @@ CallUnknownTypeSniffer(void *aClosure, c
   nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected);
   if (NS_SUCCEEDED(rv))
     chan->SetContentType(detected);
 }
 
 NS_IMETHODIMP
 nsBaseChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
 {
-  MOZ_ASSERT(request == mPump);
+  MOZ_ASSERT_IF(mRequest, request == mRequest);
 
-  // If our content type is unknown, use the content type
-  // sniffer. If the sniffer is not available for some reason, then we just keep
-  // going as-is.
-  if (NS_SUCCEEDED(mStatus) &&
-      mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
-    mPump->PeekStream(CallUnknownTypeSniffer, static_cast<nsIChannel*>(this));
+  if (mPump) {
+    // If our content type is unknown, use the content type
+    // sniffer. If the sniffer is not available for some reason, then we just keep
+    // going as-is.
+    if (NS_SUCCEEDED(mStatus) &&
+        mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+      mPump->PeekStream(CallUnknownTypeSniffer, static_cast<nsIChannel*>(this));
+    }
+
+    // Now, the general type sniffers. Skip this if we have none.
+    if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS)
+      mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
   }
 
-  // Now, the general type sniffers. Skip this if we have none.
-  if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS)
-    mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
-
   SUSPEND_PUMP_FOR_SCOPE();
 
   if (mListener) // null in case of redirect
       return mListener->OnStartRequest(this, mListenerContext);
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -820,16 +838,18 @@ nsBaseChannel::OnStopRequest(nsIRequest 
 {
   // If both mStatus and status are failure codes, we keep mStatus as-is since
   // that is consistent with our GetStatus and Cancel methods.
   if (NS_SUCCEEDED(mStatus))
     mStatus = status;
 
   // Cause Pending to return false.
   mPump = nullptr;
+  mRequest = nullptr;
+  mPumpingData = false;
 
   if (mListener) // null in case of redirect
       mListener->OnStopRequest(this, mListenerContext, mStatus);
   ChannelDone();
 
   // No need to suspend pump in this scope since we will not be receiving
   // any more events from it.
 
@@ -908,23 +928,26 @@ nsBaseChannel::OnRedirectVerifyCallback(
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseChannel::RetargetDeliveryTo(nsIEventTarget* aEventTarget)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
+  NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_INITIALIZED);
 
-  if (!mAllowThreadRetargeting) {
-    return NS_ERROR_NOT_IMPLEMENTED;
+  nsCOMPtr<nsIThreadRetargetableRequest> req;
+  if (mAllowThreadRetargeting) {
+    req = do_QueryInterface(mRequest);
   }
 
-  return mPump->RetargetDeliveryTo(aEventTarget);
+  NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED);
+
+  return req->RetargetDeliveryTo(aEventTarget);
 }
 
 NS_IMETHODIMP
 nsBaseChannel::CheckListenerChain()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mAllowThreadRetargeting) {
--- a/netwerk/base/nsBaseChannel.h
+++ b/netwerk/base/nsBaseChannel.h
@@ -87,16 +87,29 @@ private:
   // terms of AsyncOpen (see NS_ImplementChannelOpen).
   // A callee is allowed to return an nsIChannel instead of an nsIInputStream.
   // That case will be treated as a redirect to the new channel.  By default
   // *channel will be set to null by the caller, so callees who don't want to
   // return one an just not touch it.
   virtual nsresult OpenContentStream(bool async, nsIInputStream **stream,
                                      nsIChannel** channel) = 0;
 
+  // Implemented by subclass to begin pumping data for an async channel, in
+  // lieu of returning a stream. If implemented, OpenContentStream will never
+  // be called for async channels. If not implemented, AsyncOpen2 will fall
+  // back to OpenContentStream.
+  //
+  // On success, the callee must begin pumping data to the stream listener,
+  // and at some point call OnStartRequest followed by OnStopRequest.
+  // Additionally, it may provide a request object which may be used to
+  // suspend, resume, and cancel the underlying request.
+  virtual nsresult BeginAsyncRead(nsIStreamListener* listener, nsIRequest** request) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   // The basechannel calls this method from its OnTransportStatus method to
   // determine whether to call nsIProgressEventSink::OnStatus in addition to
   // nsIProgressEventSink::OnProgress.  This method may be overriden by the
   // subclass to enable nsIProgressEventSink::OnStatus events.  If this method
   // returns true, then the statusArg out param specifies the "statusArg" value
   // to pass to the OnStatus method.  By default, OnStatus messages are
   // suppressed.  The status parameter passed to this method is the status value
   // from the OnTransportStatus method.
@@ -159,17 +172,17 @@ public:
 
   // Test the load flags
   bool HasLoadFlag(uint32_t flag) {
     return (mLoadFlags & flag) != 0;
   }
 
   // This is a short-cut to calling nsIRequest::IsPending()
   virtual bool Pending() const {
-    return mPump || mWaitingOnAsyncRedirect;
+    return mPumpingData || mWaitingOnAsyncRedirect;
  }
 
   // Helper function for querying the channel's notification callbacks.
   template <class T> void GetCallback(nsCOMPtr<T> &result) {
     GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result));
   }
 
   // Helper function for calling QueryInterface on this.
@@ -261,16 +274,18 @@ private:
 
   private:
     RefPtr<nsBaseChannel> mChannel;
     nsCOMPtr<nsIChannel> mNewChannel;
   };
   friend class RedirectRunnable;
 
   RefPtr<nsInputStreamPump>         mPump;
+  RefPtr<nsIRequest>                  mRequest;
+  bool                                mPumpingData;
   nsCOMPtr<nsIProgressEventSink>      mProgressSink;
   nsCOMPtr<nsIURI>                    mOriginalURI;
   nsCOMPtr<nsISupports>               mOwner;
   nsCOMPtr<nsISupports>               mSecurityInfo;
   nsCOMPtr<nsIChannel>                mRedirectChannel;
   nsCString                           mContentType;
   nsCString                           mContentCharset;
   uint32_t                            mLoadFlags;
--- a/netwerk/base/nsInputStreamPump.cpp
+++ b/netwerk/base/nsInputStreamPump.cpp
@@ -235,17 +235,17 @@ NS_IMETHODIMP
 nsInputStreamPump::Resume()
 {
     ReentrantMonitorAutoEnter mon(mMonitor);
 
     LOG(("nsInputStreamPump::Resume [this=%p]\n", this));
     NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
     NS_ENSURE_TRUE(mState != STATE_IDLE, NS_ERROR_UNEXPECTED);
 
-    if (--mSuspendCount == 0)
+    if (--mSuspendCount == 0 && mAsyncStream)
         EnsureWaiting();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsInputStreamPump::GetLoadFlags(nsLoadFlags *aLoadFlags)
 {
     ReentrantMonitorAutoEnter mon(mMonitor);