Bug 975338 - Implement nsI|ADivertableChannel in HttpChannelChild|Parent r=jduell
☠☠ backed out by 13121198606e ☠ ☠
authorSteve Workman <sworkman@mozilla.com>
Mon, 10 Mar 2014 18:31:21 +0100
changeset 172805 1b4b0a30945324346ca62ea30e7f9bc5dd4127e8
parent 172804 97858e5c1f13d4c9602e436008f0ef01d3638870
child 172806 7a78f199e1cde327d24e522a602352459935481d
push id40832
push usersworkman@mozilla.com
push dateMon, 10 Mar 2014 17:32:47 +0000
treeherdermozilla-inbound@7a78f199e1cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs975338
milestone30.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 975338 - Implement nsI|ADivertableChannel in HttpChannelChild|Parent r=jduell
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/HttpChannelParentListener.cpp
netwerk/protocol/http/HttpChannelParentListener.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -15,16 +15,17 @@
 
 #include "nsStringStream.h"
 #include "nsHttpHandler.h"
 #include "nsNetUtil.h"
 #include "nsSerializationHelper.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/ChannelDiverterChild.h"
 #include "mozilla/net/DNS.h"
 #include "SerializedLoadContext.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
@@ -36,16 +37,19 @@ namespace net {
 HttpChannelChild::HttpChannelChild()
   : HttpAsyncAborter<HttpChannelChild>(MOZ_THIS_IN_INITIALIZER_LIST())
   , mIsFromCache(false)
   , mCacheEntryAvailable(false)
   , mCacheExpirationTime(nsICache::NO_EXPIRATION_TIME)
   , mSendResumeAt(false)
   , mIPCOpen(false)
   , mKeptAlive(false)
+  , mDivertingToParent(false)
+  , mFlushedForDiversion(false)
+  , mSuspendSent(false)
 {
   LOG(("Creating HttpChannelChild @%x\n", this));
 
   mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this));
 }
 
 HttpChannelChild::~HttpChannelChild()
 {
@@ -97,16 +101,17 @@ NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
   NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
   NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
   NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
   NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
   NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
   NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAssociatedContentSecurity, GetAssociatedContentSecurity())
+  NS_INTERFACE_MAP_ENTRY(nsIDivertableChannel)
 NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild::PHttpChannelChild
 //-----------------------------------------------------------------------------
 
 void
 HttpChannelChild::AddIPDLReference()
@@ -221,16 +226,23 @@ HttpChannelChild::RecvOnStartRequest(con
                                      const bool& isFromCache,
                                      const bool& cacheEntryAvailable,
                                      const uint32_t& cacheExpirationTime,
                                      const nsCString& cachedCharset,
                                      const nsCString& securityInfoSerialization,
                                      const NetAddr& selfAddr,
                                      const NetAddr& peerAddr)
 {
+  // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+  // stage, as they are set in the listener's OnStartRequest.
+  MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+    "mFlushedForDiversion should be unset before OnStartRequest!");
+  MOZ_RELEASE_ASSERT(!mDivertingToParent,
+    "mDivertingToParent should be unset before OnStartRequest!");
+
   if (mEventQ->ShouldEnqueue()) {
     mEventQ->Enqueue(new StartRequestEvent(this, responseHead, useResponseHead,
                                           requestHeaders, isFromCache,
                                           cacheEntryAvailable,
                                           cacheExpirationTime, cachedCharset,
                                           securityInfoSerialization, selfAddr,
                                           peerAddr));
   } else {
@@ -250,16 +262,23 @@ HttpChannelChild::OnStartRequest(const n
                                  const uint32_t& cacheExpirationTime,
                                  const nsCString& cachedCharset,
                                  const nsCString& securityInfoSerialization,
                                  const NetAddr& selfAddr,
                                  const NetAddr& peerAddr)
 {
   LOG(("HttpChannelChild::RecvOnStartRequest [this=%p]\n", this));
 
+  // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
+  // stage, as they are set in the listener's OnStartRequest.
+  MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+    "mFlushedForDiversion should be unset before OnStartRequest!");
+  MOZ_RELEASE_ASSERT(!mDivertingToParent,
+    "mDivertingToParent should be unset before OnStartRequest!");
+
   if (useResponseHead && !mCanceled)
     mResponseHead = new nsHttpResponseHead(responseHead);
 
   if (!securityInfoSerialization.IsEmpty()) {
     NS_DeserializeObject(securityInfoSerialization,
                          getter_AddRefs(mSecurityInfo));
   }
 
@@ -281,16 +300,24 @@ HttpChannelChild::OnStartRequest(const n
   mTracingEnabled = false;
 
   nsresult rv = mListener->OnStartRequest(this, mListenerContext);
   if (NS_FAILED(rv)) {
     Cancel(rv);
     return;
   }
 
+  if (mDivertingToParent) {
+    mListener = nullptr;
+    mListenerContext = nullptr;
+    if (mLoadGroup) {
+      mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+    }
+  }
+
   if (mResponseHead)
     SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
 
   rv = ApplyContentConversions();
   if (NS_FAILED(rv))
     Cancel(rv);
 
   mSelfAddr = selfAddr;
@@ -330,36 +357,51 @@ class TransportAndDataEvent : public Cha
 bool
 HttpChannelChild::RecvOnTransportAndData(const nsresult& status,
                                          const uint64_t& progress,
                                          const uint64_t& progressMax,
                                          const nsCString& data,
                                          const uint64_t& offset,
                                          const uint32_t& count)
 {
+  MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+                     "Should not be receiving any more callbacks from parent!");
+
   if (mEventQ->ShouldEnqueue()) {
     mEventQ->Enqueue(new TransportAndDataEvent(this, status, progress,
                                               progressMax, data, offset,
                                               count));
   } else {
+    MOZ_RELEASE_ASSERT(!mDivertingToParent,
+                       "ShouldEnqueue when diverting to parent!");
+
     OnTransportAndData(status, progress, progressMax, data, offset, count);
   }
   return true;
 }
 
 void
 HttpChannelChild::OnTransportAndData(const nsresult& status,
                                      const uint64_t progress,
                                      const uint64_t& progressMax,
                                      const nsCString& data,
                                      const uint64_t& offset,
                                      const uint32_t& count)
 {
   LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this));
 
+  // For diversion to parent, just SendDivertOnDataAvailable.
+  if (mDivertingToParent) {
+    MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+      "Should not be processing any more callbacks from parent!");
+
+    SendDivertOnDataAvailable(data, offset, count);
+    return;
+  }
+
   if (mCanceled)
     return;
 
   // cache the progress sink so we don't have to query for it each time.
   if (!mProgressSink)
     GetCallback(mProgressSink);
 
   // Hold queue lock throughout all three calls, else we might process a later
@@ -426,30 +468,43 @@ class StopRequestEvent : public ChannelE
  private:
   HttpChannelChild* mChild;
   nsresult mStatusCode;
 };
 
 bool
 HttpChannelChild::RecvOnStopRequest(const nsresult& statusCode)
 {
+  MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+    "Should not be receiving any more callbacks from parent!");
+
   if (mEventQ->ShouldEnqueue()) {
     mEventQ->Enqueue(new StopRequestEvent(this, statusCode));
   } else {
+    MOZ_ASSERT(!mDivertingToParent, "ShouldEnqueue when diverting to parent!");
+
     OnStopRequest(statusCode);
   }
   return true;
 }
 
 void
 HttpChannelChild::OnStopRequest(const nsresult& statusCode)
 {
   LOG(("HttpChannelChild::OnStopRequest [this=%p status=%x]\n",
            this, statusCode));
 
+  if (mDivertingToParent) {
+    MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+      "Should not be processing any more callbacks from parent!");
+
+    SendDivertOnStopRequest(statusCode);
+    return;
+  }
+
   mIsPending = false;
 
   if (!mCanceled && NS_SUCCEEDED(mStatus))
     mStatus = statusCode;
 
   { // We must flush the queue before we Send__delete__
     // (although we really shouldn't receive any msgs after OnStop),
     // so make sure this goes out of scope before then.
@@ -772,26 +827,68 @@ HttpChannelChild::RecvRedirect3Complete(
   if (mEventQ->ShouldEnqueue()) {
     mEventQ->Enqueue(new Redirect3Event(this));
   } else {
     Redirect3Complete();
   }
   return true;
 }
 
+class HttpFlushedForDiversionEvent : public ChannelEvent
+{
+ public:
+  HttpFlushedForDiversionEvent(HttpChannelChild* aChild)
+  : mChild(aChild)
+  {
+    MOZ_RELEASE_ASSERT(aChild);
+  }
+
+  void Run()
+  {
+    mChild->FlushedForDiversion();
+  }
+ private:
+  HttpChannelChild* mChild;
+};
+
 bool
 HttpChannelChild::RecvFlushedForDiversion()
 {
-  return false;
+  MOZ_RELEASE_ASSERT(mDivertingToParent);
+  MOZ_RELEASE_ASSERT(mEventQ->ShouldEnqueue());
+
+  mEventQ->Enqueue(new HttpFlushedForDiversionEvent(this));
+
+  return true;
+}
+
+void
+HttpChannelChild::FlushedForDiversion()
+{
+  MOZ_RELEASE_ASSERT(mDivertingToParent);
+
+  // Once this is set, it should not be unset before HttpChannelChild is taken
+  // down. After it is set, no OnStart/OnData/OnStop callbacks should be
+  // received from the parent channel, nor dequeued from the ChannelEventQueue.
+  mFlushedForDiversion = true;
+
+  SendDivertComplete();
 }
 
 bool
 HttpChannelChild::RecvDivertMessages()
 {
-  return false;
+  MOZ_RELEASE_ASSERT(mDivertingToParent);
+  MOZ_RELEASE_ASSERT(mSuspendCount > 0);
+
+  // DivertTo() has been called on parent, so we can now start sending queued
+  // IPDL messages back to parent listener.
+  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(Resume()));
+
+  return true;
 }
 
 void
 HttpChannelChild::Redirect3Complete()
 {
   nsresult rv = NS_OK;
 
   // Chrome channel has been AsyncOpen'd.  Reflect this in child.
@@ -950,33 +1047,42 @@ HttpChannelChild::Cancel(nsresult status
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpChannelChild::Suspend()
 {
   NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
-  if (!mSuspendCount++) {
+
+  // SendSuspend only once, when suspend goes from 0 to 1.
+  // Don't SendSuspend at all if we're diverting callbacks to the parent;
+  // suspend will be called at the correct time in the parent itself.
+  if (!mSuspendCount++ && !mDivertingToParent) {
     SendSuspend();
+    mSuspendSent = true;
   }
   mEventQ->Suspend();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpChannelChild::Resume()
 {
   NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
 
   nsresult rv = NS_OK;
 
-  if (!--mSuspendCount) {
+  // SendResume only once, when suspend count drops to 0.
+  // Don't SendResume at all if we're diverting callbacks to the parent (unless
+  // suspend was sent earlier); otherwise, resume will be called at the correct
+  // time in the parent itself.
+  if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) {
     SendResume();
     if (mCallOnResume) {
       AsyncCall(mCallOnResume);
       mCallOnResume = nullptr;
     }
   }
   mEventQ->Resume();
 
@@ -1448,11 +1554,42 @@ NS_IMETHODIMP HttpChannelChild::AddCooki
 }
 
 NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders(RequestHeaderTuples **aRequestHeaders)
 {
   *aRequestHeaders = &mClientSetRequestHeaders;
   return NS_OK;
 }
 
-//------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIDivertableChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild)
+{
+  MOZ_RELEASE_ASSERT(aChild);
+  MOZ_RELEASE_ASSERT(gNeckoChild);
+  MOZ_RELEASE_ASSERT(!mDivertingToParent);
+
+  // We must fail DivertToParent() if there's no parent end of the channel (and
+  // won't be!) due to early failure.
+  if (NS_FAILED(mStatus) && !RemoteChannelExists()) {
+    return mStatus;
+  }
+
+  nsresult rv = Suspend();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Once this is set, it should not be unset before the child is taken down.
+  mDivertingToParent = true;
+
+  PChannelDiverterChild* diverter =
+    gNeckoChild->SendPChannelDiverterConstructor(this);
+  MOZ_RELEASE_ASSERT(diverter);
+
+  *aChild = static_cast<ChannelDiverterChild*>(diverter);
+
+  return NS_OK;
+}
 
 }} // mozilla::net
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -22,42 +22,45 @@
 #include "nsIApplicationCacheChannel.h"
 #include "nsIUploadChannel2.h"
 #include "nsIResumableChannel.h"
 #include "nsIProxiedChannel.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIAssociatedContentSecurity.h"
 #include "nsIChildChannel.h"
 #include "nsIHttpChannelChild.h"
+#include "nsIDivertableChannel.h"
 #include "mozilla/net/DNS.h"
 
 namespace mozilla {
 namespace net {
 
 class HttpChannelChild : public PHttpChannelChild
                        , public HttpBaseChannel
                        , public HttpAsyncAborter<HttpChannelChild>
                        , public nsICacheInfoChannel
                        , public nsIProxiedChannel
                        , public nsIApplicationCacheChannel
                        , public nsIAsyncVerifyRedirectCallback
                        , public nsIAssociatedContentSecurity
                        , public nsIChildChannel
                        , public nsIHttpChannelChild
+                       , public nsIDivertableChannel
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICACHEINFOCHANNEL
   NS_DECL_NSIPROXIEDCHANNEL
   NS_DECL_NSIAPPLICATIONCACHECONTAINER
   NS_DECL_NSIAPPLICATIONCACHECHANNEL
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
   NS_DECL_NSIASSOCIATEDCONTENTSECURITY
   NS_DECL_NSICHILDCHANNEL
   NS_DECL_NSIHTTPCHANNELCHILD
+  NS_DECL_NSIDIVERTABLECHANNEL
 
   HttpChannelChild();
   virtual ~HttpChannelChild();
 
   // Methods HttpBaseChannel didn't implement for us or that we override.
   //
   // nsIRequest
   NS_IMETHOD Cancel(nsresult status);
@@ -85,16 +88,18 @@ public:
   // IPDL holds a reference while the PHttpChannel protocol is live (starting at
   // AsyncOpen, and ending at either OnStopRequest or any IPDL error, either of
   // which call NeckoChild::DeallocPHttpChannelChild()).
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
   bool IsSuspended();
 
+  void FlushedForDiversion();
+
 protected:
   bool RecvOnStartRequest(const nsHttpResponseHead& responseHead,
                           const bool& useResponseHead,
                           const nsHttpHeaderArray& requestHeaders,
                           const bool& isFromCache,
                           const bool& cacheEntryAvailable,
                           const uint32_t& cacheExpirationTime,
                           const nsCString& cachedCharset,
@@ -137,16 +142,25 @@ private:
 
   // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
   bool mSendResumeAt;
 
   bool mIPCOpen;
   bool mKeptAlive;            // IPC kept open, but only for security info
   nsRefPtr<ChannelEventQueue> mEventQ;
 
+  // Once set, OnData and possibly OnStop will be diverted to the parent.
+  bool mDivertingToParent;
+  // Once set, no OnStart/OnData/OnStop callbacks should be received from the
+  // parent channel, nor dequeued from the ChannelEventQueue.
+  bool mFlushedForDiversion;
+  // Set if SendSuspend is called. Determines if SendResume is needed when
+  // diverting callbacks to parent.
+  bool mSuspendSent;
+
   // true after successful AsyncOpen until OnStopRequest completes.
   bool RemoteChannelExists() { return mIPCOpen && !mKeptAlive; }
 
   void AssociateApplicationCache(const nsCString &groupID,
                                  const nsCString &clientID);
   void OnStartRequest(const nsHttpResponseHead& responseHead,
                       const bool& useResponseHead,
                       const nsHttpHeaderArray& requestHeaders,
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -38,16 +38,20 @@ HttpChannelParent::HttpChannelParent(PBr
   , mStoredStatus(NS_OK)
   , mStoredProgress(0)
   , mStoredProgressMax(0)
   , mSentRedirect1Begin(false)
   , mSentRedirect1BeginFailed(false)
   , mReceivedRedirect2Verify(false)
   , mPBOverride(aOverrideStatus)
   , mLoadContext(aLoadContext)
+  , mStatus(NS_OK)
+  , mDivertingFromChild(false)
+  , mDivertedOnStartRequest(false)
+  , mSuspendedForDiversion(false)
 {
   // Ensure gHttpHandler is initialized: we need the atom table up and running.
   nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
     do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
 
   MOZ_ASSERT(gHttpHandler);
   mHttpHandler = gHttpHandler;
 
@@ -174,67 +178,67 @@ HttpChannelParent::DoAsyncOpen(  const U
        this, uriSpec.get()));
 
   nsresult rv;
 
   nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
 
-  rv = NS_NewChannel(getter_AddRefs(mChannel), uri, ios, nullptr, nullptr, loadFlags);
+  nsCOMPtr<nsIChannel> channel;
+  rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, nullptr, nullptr, loadFlags);
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
 
-  nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
+  mChannel = static_cast<nsHttpChannel *>(channel.get());
   if (mPBOverride != kPBOverride_Unset) {
-    httpChan->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
+    mChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
   }
 
   if (doResumeAt)
-    httpChan->ResumeAt(startPos, entityID);
+    mChannel->ResumeAt(startPos, entityID);
 
   if (originalUri)
-    httpChan->SetOriginalURI(originalUri);
+    mChannel->SetOriginalURI(originalUri);
   if (docUri)
-    httpChan->SetDocumentURI(docUri);
+    mChannel->SetDocumentURI(docUri);
   if (referrerUri)
-    httpChan->SetReferrerInternal(referrerUri);
+    mChannel->SetReferrerInternal(referrerUri);
   if (apiRedirectToUri)
-    httpChan->RedirectTo(apiRedirectToUri);
+    mChannel->RedirectTo(apiRedirectToUri);
   if (loadFlags != nsIRequest::LOAD_NORMAL)
-    httpChan->SetLoadFlags(loadFlags);
+    mChannel->SetLoadFlags(loadFlags);
 
   for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
-    httpChan->SetRequestHeader(requestHeaders[i].mHeader,
+    mChannel->SetRequestHeader(requestHeaders[i].mHeader,
                                requestHeaders[i].mValue,
                                requestHeaders[i].mMerge);
   }
 
-  nsRefPtr<HttpChannelParentListener> channelListener =
-      new HttpChannelParentListener(this);
+  mParentListener = new HttpChannelParentListener(this);
 
-  httpChan->SetNotificationCallbacks(channelListener);
+  mChannel->SetNotificationCallbacks(mParentListener);
 
-  httpChan->SetRequestMethod(nsDependentCString(requestMethod.get()));
+  mChannel->SetRequestMethod(nsDependentCString(requestMethod.get()));
 
   nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(uploadStream);
   if (stream) {
-    httpChan->InternalSetUploadStream(stream);
-    httpChan->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
+    mChannel->InternalSetUploadStream(stream);
+    mChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
   }
 
   if (priority != nsISupportsPriority::PRIORITY_NORMAL)
-    httpChan->SetPriority(priority);
-  httpChan->SetRedirectionLimit(redirectionLimit);
-  httpChan->SetAllowPipelining(allowPipelining);
-  httpChan->SetForceAllowThirdPartyCookie(forceAllowThirdPartyCookie);
-  httpChan->SetAllowSpdy(allowSpdy);
+    mChannel->SetPriority(priority);
+  mChannel->SetRedirectionLimit(redirectionLimit);
+  mChannel->SetAllowPipelining(allowPipelining);
+  mChannel->SetForceAllowThirdPartyCookie(forceAllowThirdPartyCookie);
+  mChannel->SetAllowSpdy(allowSpdy);
 
   nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
-    do_QueryInterface(mChannel);
+    do_QueryObject(mChannel);
   nsCOMPtr<nsIApplicationCacheService> appCacheService =
     do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
 
   bool setChooseApplicationCache = chooseApplicationCache;
   if (appCacheChan && appCacheService) {
     // We might potentially want to drop this flag (that is TRUE by default)
     // after we successfully associate the channel with an application cache
     // reported by the channel child.  Dropping it here may be too early.
@@ -268,49 +272,50 @@ HttpChannelParent::DoAsyncOpen(  const U
         // done mPBOverride logic by this point.
         chooseAppCache = NS_ShouldCheckAppCache(principal, NS_UsePrivateBrowsing(mChannel));
       }
 
       appCacheChan->SetChooseApplicationCache(chooseAppCache);
     }
   }
 
-  rv = httpChan->AsyncOpen(channelListener, nullptr);
+  rv = mChannel->AsyncOpen(mParentListener, nullptr);
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
 
   return true;
 }
 
 bool
 HttpChannelParent::ConnectChannel(const uint32_t& channelId)
 {
   nsresult rv;
 
   LOG(("Looking for a registered channel [this=%p, id=%d]", this, channelId));
-  rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(mChannel));
+  nsCOMPtr<nsIChannel> channel;
+  rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel));
+  mChannel = static_cast<nsHttpChannel*>(channel.get());
   LOG(("  found channel %p, rv=%08x", mChannel.get(), rv));
 
   if (mPBOverride != kPBOverride_Unset) {
     // redirected-to channel may not support PB
-    nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(mChannel);
+    nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryObject(mChannel);
     if (pbChannel) {
       pbChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
     }
   }
 
   return true;
 }
 
 bool
 HttpChannelParent::RecvSetPriority(const uint16_t& priority)
 {
   if (mChannel) {
-    nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
-    httpChan->SetPriority(priority);
+    mChannel->SetPriority(priority);
   }
 
   nsCOMPtr<nsISupportsPriority> priorityRedirectChannel =
       do_QueryInterface(mRedirectChannel);
   if (priorityRedirectChannel)
     priorityRedirectChannel->SetPriority(priority);
 
   return true;
@@ -334,18 +339,17 @@ HttpChannelParent::RecvResume()
   return true;
 }
 
 bool
 HttpChannelParent::RecvCancel(const nsresult& status)
 {
   // May receive cancel before channel has been constructed!
   if (mChannel) {
-    nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
-    httpChan->Cancel(status);
+    mChannel->Cancel(status);
   }
   return true;
 }
 
 
 bool
 HttpChannelParent::RecvSetCacheTokenCachedCharset(const nsCString& charset)
 {
@@ -435,40 +439,109 @@ HttpChannelParent::RecvMarkOfflineCacheE
   return true;
 }
 
 bool
 HttpChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
                                              const uint64_t& offset,
                                              const uint32_t& count)
 {
-  return false;
+  MOZ_ASSERT(mParentListener);
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot RecvDivertOnDataAvailable if diverting is not set!");
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return false;
+  }
+
+  // Drop OnDataAvailables if the parent was canceled already.
+  if (NS_FAILED(mStatus)) {
+    return true;
+  }
+
+  nsCOMPtr<nsIInputStream> stringStream;
+  nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
+                                      count, NS_ASSIGNMENT_DEPEND);
+  if (NS_FAILED(rv)) {
+    if (mChannel) {
+      mChannel->Cancel(rv);
+    }
+    mStatus = rv;
+    return true;
+  }
+
+  rv = mParentListener->OnDataAvailable(mChannel, nullptr, stringStream,
+                                        offset, count);
+  stringStream->Close();
+  if (NS_FAILED(rv)) {
+    if (mChannel) {
+      mChannel->Cancel(rv);
+    }
+    mStatus = rv;
+    return true;
+  }
+  return true;
 }
 
 bool
 HttpChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
 {
-  return false;
+  MOZ_ASSERT(mParentListener);
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot RecvDivertOnStopRequest if diverting is not set!");
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return false;
+  }
+
+  // Honor the channel's status even if the underlying transaction completed.
+  nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
+
+  // Reset fake pending status in case OnStopRequest has already been called.
+  if (mChannel) {
+    mChannel->ForcePending(false);
+  }
+
+  mParentListener->OnStopRequest(mChannel, nullptr, status);
+  return true;
 }
 
 bool
 HttpChannelParent::RecvDivertComplete()
 {
-  return false;
+  MOZ_ASSERT(mParentListener);
+  mParentListener = nullptr;
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot RecvDivertComplete if diverting is not set!");
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return false;
+  }
+
+  nsresult rv = ResumeForDiversion();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return false;
+  }
+
+  return true;
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelParent::nsIRequestObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
 {
   LOG(("HttpChannelParent::OnStartRequest [this=%p]\n", this));
 
+  MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+    "Cannot call OnStartRequest if diverting is set!");
+
   nsHttpChannel *chan = static_cast<nsHttpChannel *>(aRequest);
   nsHttpResponseHead *responseHead = chan->GetResponseHead();
   nsHttpRequestHead  *requestHead = chan->GetRequestHead();
   bool isFromCache = false;
   chan->IsFromCache(&isFromCache);
   uint32_t expirationTime = nsICache::NO_EXPIRATION_TIME;
   chan->GetCacheTokenExpirationTime(&expirationTime);
   nsCString cachedCharset;
@@ -507,39 +580,41 @@ HttpChannelParent::OnStartRequest(nsIReq
   chan->GetSecurityInfo(getter_AddRefs(secInfoSupp));
   if (secInfoSupp) {
     mAssociatedContentSecurity = do_QueryInterface(secInfoSupp);
     nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp);
     if (secInfoSer)
       NS_SerializeToString(secInfoSer, secInfoSerialization);
   }
 
-  nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
   if (mIPCClosed ||
       !SendOnStartRequest(responseHead ? *responseHead : nsHttpResponseHead(),
                           !!responseHead,
                           requestHead->Headers(),
                           isFromCache,
                           mCacheEntry ? true : false,
                           expirationTime, cachedCharset, secInfoSerialization,
-                          httpChan->GetSelfAddr(), httpChan->GetPeerAddr()))
+                          mChannel->GetSelfAddr(), mChannel->GetPeerAddr()))
   {
     return NS_ERROR_UNEXPECTED;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpChannelParent::OnStopRequest(nsIRequest *aRequest,
                                  nsISupports *aContext,
                                  nsresult aStatusCode)
 {
   LOG(("HttpChannelParent::OnStopRequest: [this=%p status=%x]\n",
        this, aStatusCode));
 
+  MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+    "Cannot call OnStopRequest if diverting is set!");
+
   if (mIPCClosed || !SendOnStopRequest(aStatusCode))
     return NS_ERROR_UNEXPECTED;
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelParent::nsIStreamListener
 //-----------------------------------------------------------------------------
@@ -548,16 +623,19 @@ NS_IMETHODIMP
 HttpChannelParent::OnDataAvailable(nsIRequest *aRequest,
                                    nsISupports *aContext,
                                    nsIInputStream *aInputStream,
                                    uint64_t aOffset,
                                    uint32_t aCount)
 {
   LOG(("HttpChannelParent::OnDataAvailable [this=%p]\n", this));
 
+  MOZ_RELEASE_ASSERT(!mDivertingFromChild,
+    "Cannot call OnDataAvailable if diverting is set!");
+
   nsCString data;
   nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
   if (NS_FAILED(rv))
     return rv;
 
   // OnDataAvailable is always preceded by OnStatus/OnProgress calls that set
   // mStoredStatus/mStoredProgress(Max) to appropriate values, unless
   // LOAD_BACKGROUND set.  In that case, they'll have garbage values, but
@@ -644,18 +722,17 @@ HttpChannelParent::StartRedirect(uint32_
     return NS_BINDING_ABORTED;
 
   nsCOMPtr<nsIURI> newURI;
   newChannel->GetURI(getter_AddRefs(newURI));
 
   URIParams uriParams;
   SerializeURI(newURI, uriParams);
 
-  nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
-  nsHttpResponseHead *responseHead = httpChan->GetResponseHead();
+  nsHttpResponseHead *responseHead = mChannel->GetResponseHead();
   bool result = SendRedirect1Begin(newChannelId, uriParams, redirectFlags,
                                    responseHead ? *responseHead
                                                 : nsHttpResponseHead());
   if (!result) {
     // Bug 621446 investigation
     mSentRedirect1BeginFailed = true;
     return NS_BINDING_ABORTED;
   }
@@ -677,9 +754,201 @@ HttpChannelParent::CompleteRedirect(bool
     // TODO: check return value: assume child dead if failed
     unused << SendRedirect3Complete();
   }
 
   mRedirectChannel = nullptr;
   return NS_OK;
 }
 
+//-----------------------------------------------------------------------------
+// HttpChannelParent::ADivertableParentChannel
+//-----------------------------------------------------------------------------
+nsresult
+HttpChannelParent::SuspendForDiversion()
+{
+  MOZ_ASSERT(mChannel);
+  MOZ_ASSERT(mParentListener);
+  if (NS_WARN_IF(mDivertingFromChild)) {
+    MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Try suspending the channel. Allow it to fail, since OnStopRequest may have
+  // been called and thus the channel may not be pending.
+  DebugOnly<nsresult> rv = mChannel->Suspend();
+  MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
+  mSuspendedForDiversion = NS_SUCCEEDED(rv);
+
+  rv = mParentListener->SuspendForDiversion();
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent
+  // to the child.
+  mDivertingFromChild = true;
+
+  return NS_OK;
+}
+
+/* private, supporting function for ADivertableParentChannel */
+nsresult
+HttpChannelParent::ResumeForDiversion()
+{
+  MOZ_ASSERT(mChannel);
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot ResumeForDiversion if not diverting!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (mSuspendedForDiversion) {
+    // The nsHttpChannel will deliver remaining OnData/OnStop for the transfer.
+    nsresult rv = mChannel->Resume();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      FailDiversion(NS_ERROR_UNEXPECTED, true);
+      return rv;
+    }
+    mSuspendedForDiversion = false;
+  }
+
+  if (NS_WARN_IF(mIPCClosed || !SendDeleteSelf())) {
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return NS_ERROR_UNEXPECTED;
+  }
+  return NS_OK;
+}
+
+void
+HttpChannelParent::DivertTo(nsIStreamListener *aListener)
+{
+  MOZ_ASSERT(mParentListener);
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot DivertTo new listener if diverting is not set!");
+    return;
+  }
+
+  DebugOnly<nsresult> rv = mParentListener->DivertTo(aListener);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  // Call OnStartRequest and SendDivertMessages asynchronously to avoid
+  // reentering client context.
+  NS_DispatchToCurrentThread(
+    NS_NewRunnableMethod(this, &HttpChannelParent::StartDiversion));
+  return;
+}
+
+void
+HttpChannelParent::StartDiversion()
+{
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot StartDiversion if diverting is not set!");
+    return;
+  }
+
+  // Fake pending status in case OnStopRequest has already been called.
+  if (mChannel) {
+    mChannel->ForcePending(true);
+  }
+
+  // Call OnStartRequest for the "DivertTo" listener.
+  nsresult rv = mParentListener->OnStartRequest(mChannel, nullptr);
+  if (NS_FAILED(rv)) {
+    if (mChannel) {
+      mChannel->Cancel(rv);
+    }
+    mStatus = rv;
+  }
+  mDivertedOnStartRequest = true;
+
+  // After OnStartRequest has been called, tell HttpChannelChild to divert the
+  // OnDataAvailables and OnStopRequest to this HttpChannelParent.
+  if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return;
+  }
+}
+
+class HTTPFailDiversionEvent : public nsRunnable
+{
+public:
+  HTTPFailDiversionEvent(HttpChannelParent *aChannelParent,
+                         nsresult aErrorCode,
+                         bool aSkipResume)
+    : mChannelParent(aChannelParent)
+    , mErrorCode(aErrorCode)
+    , mSkipResume(aSkipResume)
+  {
+    MOZ_RELEASE_ASSERT(aChannelParent);
+    MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+  }
+  NS_IMETHOD Run()
+  {
+    mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume);
+    return NS_OK;
+  }
+private:
+  nsRefPtr<HttpChannelParent> mChannelParent;
+  nsresult mErrorCode;
+  bool mSkipResume;
+};
+
+void
+HttpChannelParent::FailDiversion(nsresult aErrorCode,
+                                 bool aSkipResume)
+{
+  MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+  MOZ_RELEASE_ASSERT(mDivertingFromChild);
+  MOZ_RELEASE_ASSERT(mParentListener);
+  MOZ_RELEASE_ASSERT(mChannel);
+
+  NS_DispatchToCurrentThread(
+    new HTTPFailDiversionEvent(this, aErrorCode, aSkipResume));
+}
+
+void
+HttpChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
+                                         bool aSkipResume)
+{
+  MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+  MOZ_RELEASE_ASSERT(mDivertingFromChild);
+  MOZ_RELEASE_ASSERT(mParentListener);
+  MOZ_RELEASE_ASSERT(mChannel);
+
+  mChannel->Cancel(aErrorCode);
+
+  mChannel->ForcePending(false);
+
+  bool isPending = false;
+  nsresult rv = mChannel->IsPending(&isPending);
+  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+  // Resume only if we suspended earlier.
+  if (mSuspendedForDiversion) {
+    mChannel->Resume();
+  }
+  // Channel has already sent OnStartRequest to the child, so ensure that we
+  // call it here if it hasn't already been called.
+  if (!mDivertedOnStartRequest) {
+    mChannel->ForcePending(true);
+    mParentListener->OnStartRequest(mChannel, nullptr);
+    mChannel->ForcePending(false);
+  }
+  // If the channel is pending, it will call OnStopRequest itself; otherwise, do
+  // it here.
+  if (!isPending) {
+    mParentListener->OnStopRequest(mChannel, nullptr, aErrorCode);
+  }
+  mParentListener = nullptr;
+  mChannel = nullptr;
+
+  if (!mIPCClosed) {
+    unused << SendDeleteSelf();
+  }
+}
+
 }} // mozilla::net
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -3,16 +3,17 @@
 
 /* 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/. */
 
 #ifndef mozilla_net_HttpChannelParent_h
 #define mozilla_net_HttpChannelParent_h
 
+#include "ADivertableParentChannel.h"
 #include "nsHttp.h"
 #include "mozilla/dom/PBrowserParent.h"
 #include "mozilla/net/PHttpChannelParent.h"
 #include "mozilla/net/NeckoCommon.h"
 #include "mozilla/net/NeckoParent.h"
 #include "nsIParentRedirectingChannel.h"
 #include "nsIProgressEventSink.h"
 #include "nsHttpChannel.h"
@@ -29,16 +30,17 @@ class TabParent;
 namespace net {
 
 class HttpChannelParentListener;
 
 class HttpChannelParent : public PHttpChannelParent
                         , public nsIParentRedirectingChannel
                         , public nsIProgressEventSink
                         , public nsIInterfaceRequestor
+                        , public ADivertableParentChannel
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIPARENTCHANNEL
   NS_DECL_NSIPARENTREDIRECTINGCHANNEL
   NS_DECL_NSIPROGRESSEVENTSINK
@@ -46,16 +48,29 @@ public:
 
   HttpChannelParent(mozilla::dom::PBrowserParent* iframeEmbedding,
                     nsILoadContext* aLoadContext,
                     PBOverrideStatus aStatus);
   virtual ~HttpChannelParent();
 
   bool Init(const HttpChannelCreationArgs& aOpenArgs);
 
+  // ADivertableParentChannel functions.
+  void DivertTo(nsIStreamListener *aListener) MOZ_OVERRIDE;
+  nsresult SuspendForDiversion() MOZ_OVERRIDE;
+
+  // Calls OnStartRequest for "DivertTo" listener, then notifies child channel
+  // that it should divert OnDataAvailable and OnStopRequest calls to this
+  // parent channel.
+  void StartDiversion();
+
+  // Handles calling OnStart/Stop if there are errors during diversion.
+  // Called asynchronously from FailDiversion.
+  void NotifyDiversionFailed(nsresult aErrorCode, bool aSkipResume = true);
+
 protected:
   // used to connect redirected-to channel in parent with just created
   // ChildChannel.  Used during redirects.
   bool ConnectChannel(const uint32_t& channelId);
 
   bool DoAsyncOpen(const URIParams&           uri,
                    const OptionalURIParams&   originalUri,
                    const OptionalURIParams&   docUri,
@@ -91,22 +106,27 @@ protected:
   virtual bool RecvMarkOfflineCacheEntryAsForeign() MOZ_OVERRIDE;
   virtual bool RecvDivertOnDataAvailable(const nsCString& data,
                                          const uint64_t& offset,
                                          const uint32_t& count) MOZ_OVERRIDE;
   virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) MOZ_OVERRIDE;
   virtual bool RecvDivertComplete() MOZ_OVERRIDE;
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
-protected:
+  // Supporting function for ADivertableParentChannel.
+  nsresult ResumeForDiversion();
+
+  // Asynchronously calls NotifyDiversionFailed.
+  void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
+
   friend class HttpChannelParentListener;
   nsRefPtr<mozilla::dom::TabParent> mTabParent;
 
 private:
-  nsCOMPtr<nsIChannel>                    mChannel;
+  nsRefPtr<nsHttpChannel>       mChannel;
   nsCOMPtr<nsICacheEntry>       mCacheEntry;
   nsCOMPtr<nsIAssociatedContentSecurity>  mAssociatedContentSecurity;
   bool mIPCClosed;                // PHttpChannel actor has been Closed()
 
   nsCOMPtr<nsIChannel> mRedirectChannel;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
 
   nsAutoPtr<class nsHttpChannel::OfflineCacheEntryAsForeignMarker> mOfflineForeignMarker;
@@ -120,14 +140,27 @@ private:
   bool mSentRedirect1Begin          : 1;
   bool mSentRedirect1BeginFailed    : 1;
   bool mReceivedRedirect2Verify     : 1;
 
   PBOverrideStatus mPBOverride;
 
   nsCOMPtr<nsILoadContext> mLoadContext;
   nsRefPtr<nsHttpHandler>  mHttpHandler;
+
+  nsRefPtr<HttpChannelParentListener> mParentListener;
+  // Set to the canceled status value if the main channel was canceled.
+  nsresult mStatus;
+  // Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it
+  // must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are
+  // received from the child channel.
+  bool mDivertingFromChild;
+
+  // Set if OnStart|StopRequest was called during a diversion from the child.
+  bool mDivertedOnStartRequest;
+
+  bool mSuspendedForDiversion;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_HttpChannelParent_h
--- a/netwerk/protocol/http/HttpChannelParentListener.cpp
+++ b/netwerk/protocol/http/HttpChannelParentListener.cpp
@@ -14,18 +14,19 @@
 #include "nsIHttpEventSink.h"
 
 using mozilla::unused;
 
 namespace mozilla {
 namespace net {
 
 HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitialChannel)
-  : mActiveChannel(aInitialChannel)
+  : mNextListener(aInitialChannel)
   , mRedirectChannelId(0)
+  , mSuspendedForDiversion(false)
 {
 }
 
 HttpChannelParentListener::~HttpChannelParentListener()
 {
 }
 
 //-----------------------------------------------------------------------------
@@ -41,55 +42,64 @@ NS_IMPL_ISUPPORTS5(HttpChannelParentList
 
 //-----------------------------------------------------------------------------
 // HttpChannelParentListener::nsIRequestObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParentListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
 {
-  if (!mActiveChannel)
+  MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+    "Cannot call OnStartRequest if suspended for diversion!");
+
+  if (!mNextListener)
     return NS_ERROR_UNEXPECTED;
 
   LOG(("HttpChannelParentListener::OnStartRequest [this=%p]\n", this));
-  return mActiveChannel->OnStartRequest(aRequest, aContext);
+  return mNextListener->OnStartRequest(aRequest, aContext);
 }
 
 NS_IMETHODIMP
 HttpChannelParentListener::OnStopRequest(nsIRequest *aRequest,
                                           nsISupports *aContext,
                                           nsresult aStatusCode)
 {
-  if (!mActiveChannel)
+  MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+    "Cannot call OnStopRequest if suspended for diversion!");
+
+  if (!mNextListener)
     return NS_ERROR_UNEXPECTED;
 
   LOG(("HttpChannelParentListener::OnStopRequest: [this=%p status=%ul]\n",
        this, aStatusCode));
-  nsresult rv = mActiveChannel->OnStopRequest(aRequest, aContext, aStatusCode);
+  nsresult rv = mNextListener->OnStopRequest(aRequest, aContext, aStatusCode);
 
-  mActiveChannel = nullptr;
+  mNextListener = nullptr;
   return rv;
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelParentListener::nsIStreamListener
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParentListener::OnDataAvailable(nsIRequest *aRequest,
                                             nsISupports *aContext,
                                             nsIInputStream *aInputStream,
                                             uint64_t aOffset,
                                             uint32_t aCount)
 {
-  if (!mActiveChannel)
+  MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
+    "Cannot call OnDataAvailable if suspended for diversion!");
+
+  if (!mNextListener)
     return NS_ERROR_UNEXPECTED;
 
   LOG(("HttpChannelParentListener::OnDataAvailable [this=%p]\n", this));
-  return mActiveChannel->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
+  return mNextListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelParentListener::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParentListener::GetInterface(const nsIID& aIID, void **result)
@@ -97,18 +107,18 @@ HttpChannelParentListener::GetInterface(
   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) ||
       aIID.Equals(NS_GET_IID(nsIHttpEventSink))  ||
       aIID.Equals(NS_GET_IID(nsIRedirectResultListener)))
   {
     return QueryInterface(aIID, result);
   }
 
   nsCOMPtr<nsIInterfaceRequestor> ir;
-  if (mActiveChannel &&
-      NS_SUCCEEDED(CallQueryInterface(mActiveChannel.get(),
+  if (mNextListener &&
+      NS_SUCCEEDED(CallQueryInterface(mNextListener.get(),
                                       getter_AddRefs(ir))))
   {
     return ir->GetInterface(aIID, result);
   }
 
   return NS_NOINTERFACE;
 }
 
@@ -131,17 +141,17 @@ HttpChannelParentListener::AsyncOnChanne
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId));
 
   nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
-      do_QueryInterface(mActiveChannel);
+      do_QueryInterface(mNextListener);
   if (!activeRedirectingChannel) {
     NS_RUNTIMEABORT("Channel got a redirect response, but doesn't implement "
                     "nsIParentRedirectingChannel to handle it.");
   }
 
   return activeRedirectingChannel->StartRedirect(mRedirectChannelId,
                                                  newChannel,
                                                  redirectFlags,
@@ -185,32 +195,75 @@ HttpChannelParentListener::OnRedirectRes
     mRedirectChannelId = 0;
   }
 
   if (!redirectChannel) {
     succeeded = false;
   }
 
   nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
-      do_QueryInterface(mActiveChannel);
+      do_QueryInterface(mNextListener);
   MOZ_ASSERT(activeRedirectingChannel,
     "Channel finished a redirect response, but doesn't implement "
     "nsIParentRedirectingChannel to complete it.");
 
   if (activeRedirectingChannel) {
     activeRedirectingChannel->CompleteRedirect(succeeded);
   } else {
     succeeded = false;
   }
 
   if (succeeded) {
     // Switch to redirect channel and delete the old one.
-    mActiveChannel->Delete();
-    mActiveChannel = redirectChannel;
+    nsCOMPtr<nsIParentChannel> parent;
+    parent = do_QueryInterface(mNextListener);
+    MOZ_ASSERT(parent);
+    parent->Delete();
+    mNextListener = do_QueryInterface(redirectChannel);
+    MOZ_ASSERT(mNextListener);
   } else if (redirectChannel) {
     // Delete the redirect target channel: continue using old channel
     redirectChannel->Delete();
   }
 
   return NS_OK;
 }
 
+//-----------------------------------------------------------------------------
+
+nsresult
+HttpChannelParentListener::SuspendForDiversion()
+{
+  if (NS_WARN_IF(mSuspendedForDiversion)) {
+    MOZ_ASSERT(!mSuspendedForDiversion, "Cannot SuspendForDiversion twice!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // While this is set, no OnStart/OnData/OnStop callbacks should be forwarded
+  // to mNextListener.
+  mSuspendedForDiversion = true;
+
+  return NS_OK;
+}
+
+nsresult
+HttpChannelParentListener::ResumeForDiversion()
+{
+  MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
+
+  // Allow OnStart/OnData/OnStop callbacks to be forwarded to mNextListener.
+  mSuspendedForDiversion = false;
+
+  return NS_OK;
+}
+
+nsresult
+HttpChannelParentListener::DivertTo(nsIStreamListener* aListener)
+{
+  MOZ_ASSERT(aListener);
+  MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
+
+  mNextListener = aListener;
+
+  return ResumeForDiversion();
+}
+
 }} // mozilla::net
--- a/netwerk/protocol/http/HttpChannelParentListener.h
+++ b/netwerk/protocol/http/HttpChannelParentListener.h
@@ -30,17 +30,30 @@ public:
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIREDIRECTRESULTLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
 
   HttpChannelParentListener(HttpChannelParent* aInitialChannel);
   virtual ~HttpChannelParentListener();
 
+  // For channel diversion from child to parent.
+  nsresult DivertTo(nsIStreamListener *aListener);
+  nsresult SuspendForDiversion();
+
 private:
-  nsCOMPtr<nsIParentChannel> mActiveChannel;
+  // Private partner function to SuspendForDiversion.
+  nsresult ResumeForDiversion();
+
+  // Can be the original HttpChannelParent that created this object (normal
+  // case), a different {HTTP|FTP}ChannelParent that we've been redirected to,
+  // or some other listener that we have been diverted to via
+  // nsIDivertableChannel.
+  nsCOMPtr<nsIStreamListener> mNextListener;
   uint32_t mRedirectChannelId;
+  // When set, no OnStart/OnData/OnStop calls should be received.
+  bool mSuspendedForDiversion;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_HttpChannelParent_h
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -215,16 +215,17 @@ nsHttpChannel::nsHttpChannel()
     , mCacheEntryIsReadOnly(false)
     , mCacheEntryIsWriteOnly(false)
     , mCacheEntriesToWaitFor(0)
     , mHasQueryString(0)
     , mConcurentCacheAccess(0)
     , mIsPartialRequest(0)
     , mHasAutoRedirectVetoNotifier(0)
     , mDidReval(false)
+    , mForcePending(false)
 {
     LOG(("Creating nsHttpChannel [this=%p]\n", this));
     mChannelCreationTime = PR_Now();
     mChannelCreationTimestamp = TimeStamp::Now();
 }
 
 nsHttpChannel::~nsHttpChannel()
 {
@@ -6195,9 +6196,27 @@ nsHttpChannel::SetNotificationCallbacks(
 
     nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks);
     if (NS_SUCCEEDED(rv)) {
         UpdateAggregateCallbacks();
     }
     return rv;
 }
 
+void
+nsHttpChannel::ForcePending(bool aForcePending)
+{
+    // Set true here so IsPending will return true.
+    // Required for callback diversion from child back to parent. In such cases
+    // OnStopRequest can be called in the parent before callbacks are diverted
+    // back from the child to the listener in the parent.
+    mForcePending = aForcePending;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::IsPending(bool *aIsPending)
+{
+    NS_ENSURE_ARG_POINTER(aIsPending);
+    *aIsPending = mIsPending || mForcePending;
+    return NS_OK;
+}
+
 } } // namespace mozilla::net
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -100,16 +100,17 @@ public:
                           nsIURI *aProxyURI);
 
     // Methods HttpBaseChannel didn't implement for us or that we override.
     //
     // nsIRequest
     NS_IMETHOD Cancel(nsresult status);
     NS_IMETHOD Suspend();
     NS_IMETHOD Resume();
+    NS_IMETHOD IsPending(bool *aIsPending);
     // nsIChannel
     NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
     NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext);
     // nsIHttpChannelInternal
     NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey);
     // nsISupportsPriority
     NS_IMETHOD SetPriority(int32_t value);
     // nsIResumableChannel
@@ -177,16 +178,18 @@ public: /* internal necko use only */
         mChannel->mCacheEntriesToWaitFor &= mKeep;
       }
 
     private:
       nsHttpChannel* mChannel;
       uint32_t mKeep : 2;
     };
 
+    void ForcePending(bool aForcePending);
+
 private:
     typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
 
     bool     RequestIsConditional();
     nsresult BeginConnect();
     nsresult Connect();
     nsresult ContinueConnect();
     void     SpeculativeConnect();
@@ -419,13 +422,14 @@ protected:
     virtual void DoNotifyListenerCleanup();
 
 private: // cache telemetry
     bool mDidReval;
 
 private:
     nsIPrincipal *GetPrincipal();
     nsCOMPtr<nsIPrincipal> mPrincipal;
+    bool mForcePending;
 };
 
 } } // namespace mozilla::net
 
 #endif // nsHttpChannel_h__