Bug 975338 - Enable diverting of HTTP and FTP channels back to parent process from child r=jduell
authorSteve Workman <sworkman@mozilla.com>
Mon, 10 Mar 2014 23:04:28 +0100
changeset 172872 760131490c817d3ff167e708f516da5a16761c91
parent 172871 6e7f5c8e3e83f89de43e1035a17a1127b6ca0cc5
child 172873 79d8037bf2ed299ca51529229a2f32c31a098697
push id40844
push usersworkman@mozilla.com
push dateMon, 10 Mar 2014 22:05:12 +0000
treeherdermozilla-inbound@760131490c81 [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 - Enable diverting of HTTP and FTP channels back to parent process from child r=jduell
netwerk/base/public/moz.build
netwerk/base/public/nsIDivertableChannel.idl
netwerk/base/src/ADivertableParentChannel.h
netwerk/base/src/ChannelDiverterChild.cpp
netwerk/base/src/ChannelDiverterChild.h
netwerk/base/src/ChannelDiverterParent.cpp
netwerk/base/src/ChannelDiverterParent.h
netwerk/base/src/moz.build
netwerk/base/src/nsBaseChannel.cpp
netwerk/base/src/nsBaseChannel.h
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/ipc/NeckoChild.cpp
netwerk/ipc/NeckoChild.h
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PChannelDiverter.ipdl
netwerk/ipc/PNecko.ipdl
netwerk/ipc/moz.build
netwerk/protocol/file/nsFileChannel.cpp
netwerk/protocol/ftp/FTPChannelChild.cpp
netwerk/protocol/ftp/FTPChannelChild.h
netwerk/protocol/ftp/FTPChannelParent.cpp
netwerk/protocol/ftp/FTPChannelParent.h
netwerk/protocol/ftp/PFTPChannel.ipdl
netwerk/protocol/ftp/nsFTPChannel.cpp
netwerk/protocol/ftp/nsFTPChannel.h
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/PHttpChannel.ipdl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
--- a/netwerk/base/public/moz.build
+++ b/netwerk/base/public/moz.build
@@ -31,16 +31,17 @@ XPIDL_SOURCES += [
     'nsIChannelPolicy.idl',
     'nsIChildChannel.idl',
     'nsIContentSniffer.idl',
     'nsICryptoFIPSInfo.idl',
     'nsICryptoHash.idl',
     'nsICryptoHMAC.idl',
     'nsIDashboard.idl',
     'nsIDashboardEventNotifier.idl',
+    'nsIDivertableChannel.idl',
     'nsIDownloader.idl',
     'nsIEncodedChannel.idl',
     'nsIExternalProtocolHandler.idl',
     'nsIFileStreams.idl',
     'nsIFileURL.idl',
     'nsIIncrementalDownload.idl',
     'nsIInputStreamChannel.idl',
     'nsIInputStreamPump.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsIDivertableChannel.idl
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+//#include "mozilla/net/ChannelDiverterChild.h"
+namespace mozilla {
+namespace net {
+class ChannelDiverterChild;
+}
+}
+%}
+
+[ptr] native ChannelDiverterChild(mozilla::net::ChannelDiverterChild);
+
+interface nsIStreamListener;
+
+/**
+ * A channel implementing this interface allows diverting from an
+ * nsIStreamListener in the child process to one in the parent.
+ */
+[uuid(4430e0d0-ff70-45f5-99dc-b5fd06943fc1)]
+interface nsIDivertableChannel : nsISupports
+{
+  /**
+   * CHILD ONLY.
+   * Called by Necko client in child process during OnStartRequest to divert
+   * nsIStreamListener and nsIRequest callbacks to the parent process.
+   *
+   * The process should look like the following:
+   *
+   * 1) divertToParent is called in the child process.  It can only be called
+   *    during OnStartRequest().
+   *
+   * 2) The ChannelDiverterChild that is returned is an IPDL object. It should
+   *    be passed via some other IPDL method of the client's choosing to the
+   *    parent.  On the parent the ChannelDiverterParent's divertTo() function
+   *    should be called with an nsIStreamListener that will then receive the
+   *    OnStartRequest/OnDataAvailable/OnStopRequest for the channel.  The
+   *    ChannelDiverterParent can then be deleted (which will also destroy the
+   *    ChannelDiverterChild in the child).
+   *
+   *    After divertToParent() has been called, NO further function calls
+   *    should be made on the channel.  It is a dead object for all purposes.
+   *    The reference that the channel holds to the listener in the child is
+   *    released is once OnStartRequest completes, and no other
+   *    nsIStreamListener calls (OnDataAvailable, OnStopRequest) will be made
+   *    to it.
+   *
+   * @return ChannelDiverterChild IPDL actor to be passed to parent process by
+   *         client IPDL message, e.g. PClient.DivertUsing(PDiverterChild).
+   *
+   * @throws exception if the channel was canceled early. Throws status code of
+   *         canceled channel.
+   */
+  ChannelDiverterChild divertToParent();
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ADivertableParentChannel.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 _adivertablechannelparent_h_
+#define _adivertablechannelparent_h_
+
+#include "nsISupports.h"
+
+class nsIStreamListener;
+
+namespace mozilla {
+namespace net {
+
+// To be implemented by a channel's parent actors, e.g. HttpChannelParent
+// and FTPChannelParent. Used by ChannelDiverterParent to divert
+// nsIStreamListener callbacks from the child process to a new
+// listener in the parent process.
+class ADivertableParentChannel : public nsISupports
+{
+public:
+  // Called by ChannelDiverterParent::DivertTo(nsIStreamListener*).
+  // The listener should now be used to received nsIStreamListener callbacks,
+  // i.e. OnStartRequest, OnDataAvailable and OnStopRequest, as if it had been
+  // passed to AsyncOpen for the channel. A reference to the listener will be
+  // added and kept until OnStopRequest has completed.
+  virtual void DivertTo(nsIStreamListener *aListener) = 0;
+
+  // Called to suspend parent channel in ChannelDiverterParent constructor.
+  virtual nsresult SuspendForDiversion() = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ChannelDiverterChild.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/ChannelDiverterChild.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+#include "mozilla/net/PHttpChannelChild.h"
+#include "mozilla/net/PFTPChannelChild.h"
+#include "nsIDivertableChannel.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelDiverterChild::ChannelDiverterChild()
+{
+}
+
+ChannelDiverterChild::~ChannelDiverterChild()
+{
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ChannelDiverterChild.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 _channeldiverterchild_h_
+#define _channeldiverterchild_h_
+
+#include "mozilla/net/PChannelDiverterChild.h"
+
+class nsIDivertableChannel;
+
+namespace mozilla {
+namespace net {
+
+class ChannelDiverterArgs;
+
+class ChannelDiverterChild :
+  public PChannelDiverterChild
+{
+public:
+  ChannelDiverterChild();
+  virtual ~ChannelDiverterChild();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* _channeldiverterchild_h_ */
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ChannelDiverterParent.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/ChannelDiverterParent.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/FTPChannelParent.h"
+#include "mozilla/net/PHttpChannelParent.h"
+#include "mozilla/net/PFTPChannelParent.h"
+#include "ADivertableParentChannel.h"
+
+namespace mozilla {
+namespace net {
+
+ChannelDiverterParent::ChannelDiverterParent()
+{
+}
+
+ChannelDiverterParent::~ChannelDiverterParent()
+{
+}
+
+bool
+ChannelDiverterParent::Init(const ChannelDiverterArgs& aChannel)
+{
+  switch (aChannel.type()) {
+  case ChannelDiverterArgs::TPHttpChannelParent:
+  {
+    mDivertableChannelParent = static_cast<ADivertableParentChannel*>(
+      static_cast<HttpChannelParent*>(aChannel.get_PHttpChannelParent()));
+    break;
+  }
+  case ChannelDiverterArgs::TPFTPChannelParent:
+  {
+    mDivertableChannelParent = static_cast<ADivertableParentChannel*>(
+      static_cast<FTPChannelParent*>(aChannel.get_PFTPChannelParent()));
+    break;
+  }
+  default:
+    NS_NOTREACHED("unknown ChannelDiverterArgs type");
+    return false;
+  }
+  MOZ_ASSERT(mDivertableChannelParent);
+
+  nsresult rv = mDivertableChannelParent->SuspendForDiversion();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  return true;
+}
+
+void
+ChannelDiverterParent::DivertTo(nsIStreamListener* newListener)
+{
+  MOZ_ASSERT(newListener);
+  MOZ_ASSERT(mDivertableChannelParent);
+
+  mDivertableChannelParent->DivertTo(newListener);
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ChannelDiverterParent.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 _channeldiverterparent_h_
+#define _channeldiverterparent_h_
+
+#include "mozilla/net/PChannelDiverterParent.h"
+
+class nsIStreamListener;
+
+namespace mozilla {
+namespace net {
+
+class ChannelDiverterArgs;
+class ADivertableParentChannel;
+
+class ChannelDiverterParent :
+  public PChannelDiverterParent
+{
+public:
+  ChannelDiverterParent();
+  virtual ~ChannelDiverterParent();
+
+  bool Init(const ChannelDiverterArgs& aChannel);
+
+  void DivertTo(nsIStreamListener* newListener);
+private:
+  nsRefPtr<ADivertableParentChannel> mDivertableChannelParent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* _channeldiverterparent_h_ */
--- a/netwerk/base/src/moz.build
+++ b/netwerk/base/src/moz.build
@@ -7,23 +7,27 @@
 EXPORTS += [
     'nsFileStreams.h',
     'nsMIMEInputStream.h',
     'nsTemporaryFileInputStream.h',
     'nsURLHelper.h',
 ]
 
 EXPORTS.mozilla.net += [
+    'ChannelDiverterChild.h',
+    'ChannelDiverterParent.h',
     'Dashboard.h',
     'DashboardTypes.h',
 ]
 
 UNIFIED_SOURCES += [
     'ArrayBufferInputStream.cpp',
     'BackgroundFileSaver.cpp',
+    'ChannelDiverterChild.cpp',
+    'ChannelDiverterParent.cpp',
     'Dashboard.cpp',
     'EventTokenBucket.cpp',
     'LoadContextInfo.cpp',
     'NetworkActivityMonitor.cpp',
     'nsAsyncStreamCopier.cpp',
     'nsAuthInformationHolder.cpp',
     'nsBase64Encoder.cpp',
     'nsBaseChannel.cpp',
@@ -108,16 +112,17 @@ FAIL_ON_WARNINGS = True
 
 MSVC_ENABLE_PGO = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'necko'
 LOCAL_INCLUDES += [
     '/dom/base',
+    '/netwerk/protocol/http'
 ]
 
 if 'rtsp' in CONFIG['NECKO_PROTOCOLS']:
     LOCAL_INCLUDES += [
         '/netwerk/protocol/rtsp/controller',
         '/netwerk/protocol/rtsp/rtsp',
     ]
 
--- a/netwerk/base/src/nsBaseChannel.cpp
+++ b/netwerk/base/src/nsBaseChannel.cpp
@@ -151,17 +151,17 @@ nsBaseChannel::ContinueRedirect()
   ChannelDone();
 
   return NS_OK;
 }
 
 bool
 nsBaseChannel::HasContentTypeHint() const
 {
-  NS_ASSERTION(!IsPending(), "HasContentTypeHint called too late");
+  NS_ASSERTION(!Pending(), "HasContentTypeHint called too late");
   return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE);
 }
 
 nsresult
 nsBaseChannel::PushStreamConverter(const char *fromType,
                                    const char *toType,
                                    bool invalidatesContentLength,
                                    nsIStreamListener **result)
@@ -203,17 +203,17 @@ nsBaseChannel::BeginPumpingData()
 
   if (channel) {
       rv = NS_DispatchToCurrentThread(new RedirectRunnable(this, channel));
       if (NS_SUCCEEDED(rv))
           mWaitingOnAsyncRedirect = true;
       return rv;
   }
 
-  // By assigning mPump, we flag this channel as pending (see IsPending).  It's
+  // 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))
@@ -307,17 +307,17 @@ nsBaseChannel::GetName(nsACString &resul
     return NS_OK;
   }
   return mURI->GetSpec(result);
 }
 
 NS_IMETHODIMP
 nsBaseChannel::IsPending(bool *result)
 {
-  *result = IsPending();
+  *result = Pending();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseChannel::GetStatus(nsresult *status)
 {
   if (mPump && NS_SUCCEEDED(mStatus)) {
     mPump->GetStatus(status);
@@ -720,17 +720,17 @@ NS_IMETHODIMP
 nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
                              nsresult status)
 {
   // 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 IsPending to return false.
+  // Cause Pending to return false.
   mPump = nullptr;
 
   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.
--- a/netwerk/base/src/nsBaseChannel.h
+++ b/netwerk/base/src/nsBaseChannel.h
@@ -152,19 +152,19 @@ public:
   }
 
   // Test the load flags
   bool HasLoadFlag(uint32_t flag) {
     return (mLoadFlags & flag) != 0;
   }
 
   // This is a short-cut to calling nsIRequest::IsPending()
-  bool IsPending() const {
+  virtual bool Pending() const {
     return mPump || 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.
   nsQueryInterface do_QueryInterface() {
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -1,15 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et tw=80 ft=c: */
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+include protocol PHttpChannel;
+include protocol PFTPChannel;
 include URIParams;
 include InputStreamParams;
 
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h";
 using struct nsHttpAtom from "nsHttp.h";
 
 namespace mozilla {
@@ -75,10 +77,16 @@ struct FTPChannelConnectArgs
 };
 
 union FTPChannelCreationArgs
 {
   FTPChannelOpenArgs;      // For AsyncOpen: the common case.
   FTPChannelConnectArgs;   // Used for redirected-to channels
 };
 
+union ChannelDiverterArgs
+{
+  PHttpChannel;
+  PFTPChannel;
+};
+
 } // namespace ipc
 } // namespace mozilla
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/net/HttpChannelChild.h"
 #include "mozilla/net/CookieServiceChild.h"
 #include "mozilla/net/WyciwygChannelChild.h"
 #include "mozilla/net/FTPChannelChild.h"
 #include "mozilla/net/WebSocketChannelChild.h"
 #include "mozilla/net/DNSRequestChild.h"
 #include "mozilla/net/RemoteOpenFileChild.h"
+#include "mozilla/net/ChannelDiverterChild.h"
 #include "mozilla/dom/network/TCPSocketChild.h"
 #include "mozilla/dom/network/TCPServerSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #ifdef NECKO_PROTOCOL_rtsp
 #include "mozilla/net/RtspControllerChild.h"
 #endif
 #include "SerializedLoadContext.h"
 
@@ -261,10 +262,23 @@ NeckoChild::AllocPRemoteOpenFileChild(co
 bool
 NeckoChild::DeallocPRemoteOpenFileChild(PRemoteOpenFileChild* aChild)
 {
   RemoteOpenFileChild *p = static_cast<RemoteOpenFileChild*>(aChild);
   p->ReleaseIPDLReference();
   return true;
 }
 
+PChannelDiverterChild*
+NeckoChild::AllocPChannelDiverterChild(const ChannelDiverterArgs& channel)
+{
+  return new ChannelDiverterChild();;
+}
+
+bool
+NeckoChild::DeallocPChannelDiverterChild(PChannelDiverterChild* child)
+{
+  delete static_cast<ChannelDiverterChild*>(child);
+  return true;
+}
+
 }} // mozilla::net
 
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -57,16 +57,20 @@ protected:
                                                   const uint32_t& aFlags) MOZ_OVERRIDE;
   virtual bool DeallocPDNSRequestChild(PDNSRequestChild*) MOZ_OVERRIDE;
   virtual PRemoteOpenFileChild*
     AllocPRemoteOpenFileChild(const URIParams&,
                               const OptionalURIParams&) MOZ_OVERRIDE;
   virtual bool DeallocPRemoteOpenFileChild(PRemoteOpenFileChild*) MOZ_OVERRIDE;
   virtual PRtspControllerChild* AllocPRtspControllerChild() MOZ_OVERRIDE;
   virtual bool DeallocPRtspControllerChild(PRtspControllerChild*) MOZ_OVERRIDE;
+  virtual PChannelDiverterChild*
+  AllocPChannelDiverterChild(const ChannelDiverterArgs& channel) MOZ_OVERRIDE;
+  virtual bool
+  DeallocPChannelDiverterChild(PChannelDiverterChild* actor) MOZ_OVERRIDE;
 };
 
 /**
  * Reference to the PNecko Child protocol.
  * Null if this is not a content process.
  */
 extern PNeckoChild *gNeckoChild;
 
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/net/WyciwygChannelParent.h"
 #include "mozilla/net/FTPChannelParent.h"
 #include "mozilla/net/WebSocketChannelParent.h"
 #ifdef NECKO_PROTOCOL_rtsp
 #include "mozilla/net/RtspControllerParent.h"
 #endif
 #include "mozilla/net/DNSRequestParent.h"
 #include "mozilla/net/RemoteOpenFileParent.h"
+#include "mozilla/net/ChannelDiverterParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/network/TCPSocketParent.h"
 #include "mozilla/dom/network/TCPServerSocketParent.h"
 #include "mozilla/dom/network/UDPSocketParent.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/AppProcessChecker.h"
@@ -603,16 +604,38 @@ bool
 NeckoParent::RecvCancelHTMLDNSPrefetch(const nsString& hostname,
                                  const uint16_t& flags,
                                  const nsresult& reason)
 {
   nsHTMLDNSPrefetch::CancelPrefetch(hostname, flags, reason);
   return true;
 }
 
+PChannelDiverterParent*
+NeckoParent::AllocPChannelDiverterParent(const ChannelDiverterArgs& channel)
+{
+  return new ChannelDiverterParent();
+}
+
+bool
+NeckoParent::RecvPChannelDiverterConstructor(PChannelDiverterParent* actor,
+                                             const ChannelDiverterArgs& channel)
+{
+  auto parent = static_cast<ChannelDiverterParent*>(actor);
+  parent->Init(channel);
+  return true;
+}
+
+bool
+NeckoParent::DeallocPChannelDiverterParent(PChannelDiverterParent* parent)
+{
+  delete static_cast<ChannelDiverterParent*>(parent);
+  return true;
+}
+
 void
 NeckoParent::CloneManagees(ProtocolBase* aSource,
                          mozilla::ipc::ProtocolCloneContext* aCtx)
 {
   aCtx->SetNeckoParent(this); // For cloning protocols managed by this.
   PNeckoParent::CloneManagees(aSource, aCtx);
 }
 
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -139,16 +139,24 @@ protected:
                                          const nsresult& reason) MOZ_OVERRIDE;
 
   virtual mozilla::ipc::IProtocol*
   CloneProtocol(Channel* aChannel,
                 mozilla::ipc::ProtocolCloneContext* aCtx) MOZ_OVERRIDE;
   virtual PRtspControllerParent* AllocPRtspControllerParent() MOZ_OVERRIDE;
   virtual bool DeallocPRtspControllerParent(PRtspControllerParent*) MOZ_OVERRIDE;
 
+  virtual PChannelDiverterParent*
+  AllocPChannelDiverterParent(const ChannelDiverterArgs& channel) MOZ_OVERRIDE;
+  virtual bool
+  RecvPChannelDiverterConstructor(PChannelDiverterParent* actor,
+                                  const ChannelDiverterArgs& channel) MOZ_OVERRIDE;
+  virtual bool DeallocPChannelDiverterParent(PChannelDiverterParent* actor)
+                                                                MOZ_OVERRIDE;
+
 private:
   nsCString mCoreAppsBasePath;
   nsCString mWebAppsBasePath;
 };
 
 } // namespace net
 } // namespace mozilla
 
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/PChannelDiverter.ipdl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80 ft=cpp: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PFTPChannel;
+include protocol PHttpChannel;
+include protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+// Used when diverting necko channels from child back to the parent.
+// See nsIDivertableChannel.
+async protocol PChannelDiverter
+{
+  manager PNecko;
+
+child:
+  __delete__();
+};
+
+}// namespace net
+}// namespace mozilla
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -12,16 +12,17 @@ include protocol PBrowser;
 include protocol PWyciwygChannel;
 include protocol PFTPChannel;
 include protocol PWebSocket;
 include protocol PTCPSocket;
 include protocol PTCPServerSocket;
 include protocol PUDPSocket;
 include protocol PRemoteOpenFile;
 include protocol PDNSRequest;
+include protocol PChannelDiverter;
 include protocol PBlob; //FIXME: bug #792908
 
 include protocol PRtspController;
 include URIParams;
 include InputStreamParams;
 include NeckoChannelParams;
 
 
@@ -40,16 +41,17 @@ sync protocol PNecko
   manages PFTPChannel;
   manages PWebSocket;
   manages PTCPSocket;
   manages PTCPServerSocket;
   manages PUDPSocket;
   manages PDNSRequest;
   manages PRemoteOpenFile;
   manages PRtspController;
+  manages PChannelDiverter;
 
 parent:
   __delete__();
 
   PCookieService();
   PHttpChannel(nullable PBrowser browser,
                SerializedLoadContext loadContext,
                HttpChannelCreationArgs args);
@@ -63,16 +65,17 @@ parent:
 
   PDNSRequest(nsCString hostName, uint32_t flags);
 
   PRemoteOpenFile(URIParams fileuri, OptionalURIParams appuri);
 
   HTMLDNSPrefetch(nsString hostname, uint16_t flags);
   CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason);
   PRtspController();
+  PChannelDiverter(ChannelDiverterArgs channel);
 
 both:
   PTCPSocket();
 };
 
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/ipc/moz.build
+++ b/netwerk/ipc/moz.build
@@ -26,16 +26,17 @@ UNIFIED_SOURCES += [
     'NeckoCommon.cpp',
     'NeckoParent.cpp',
     'RemoteOpenFileChild.cpp',
     'RemoteOpenFileParent.cpp',
 ]
 
 IPDL_SOURCES = [
     'NeckoChannelParams.ipdlh',
+    'PChannelDiverter.ipdl',
     'PNecko.ipdl',
     'PRemoteOpenFile.ipdl',
     'PRtspController.ipdl',
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
--- a/netwerk/protocol/file/nsFileChannel.cpp
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -426,17 +426,17 @@ nsFileChannel::GetFile(nsIFile **file)
 //-----------------------------------------------------------------------------
 // nsFileChannel::nsIUploadChannel
 
 NS_IMETHODIMP
 nsFileChannel::SetUploadStream(nsIInputStream *stream,
                                const nsACString &contentType,
                                int64_t contentLength)
 {
-  NS_ENSURE_TRUE(!IsPending(), NS_ERROR_IN_PROGRESS);
+  NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
 
   if ((mUploadStream = stream)) {
     mUploadLength = contentLength;
     if (mUploadLength < 0) {
       // Make sure we know how much data we are uploading.
       uint64_t avail;
       nsresult rv = mUploadStream->Available(&avail);
       if (NS_FAILED(rv))
--- a/netwerk/protocol/ftp/FTPChannelChild.cpp
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et tw=80 : */
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/ChannelDiverterChild.h"
 #include "mozilla/net/FTPChannelChild.h"
 #include "mozilla/dom/TabChild.h"
 #include "nsFtpProtocolHandler.h"
 #include "nsITabChild.h"
 #include "nsStringStream.h"
 #include "nsNetUtil.h"
 #include "base/compiler_specific.h"
 #include "mozilla/ipc/InputStreamUtils.h"
@@ -28,16 +29,19 @@ namespace net {
 FTPChannelChild::FTPChannelChild(nsIURI* uri)
 : mIPCOpen(false)
 , mCanceled(false)
 , mSuspendCount(0)
 , mIsPending(false)
 , mWasOpened(false)
 , mLastModifiedTime(0)
 , mStartPos(0)
+, mDivertingToParent(false)
+, mFlushedForDiversion(false)
+, mSuspendSent(false)
 {
   LOG(("Creating FTPChannelChild @%x\n", this));
   // grab a reference to the handler to ensure that it doesn't go away.
   NS_ADDREF(gFtpHandler);
   SetURI(uri);
   mEventQ = new ChannelEventQueue(static_cast<nsIFTPChannel*>(this));
 }
 
@@ -62,23 +66,24 @@ FTPChannelChild::ReleaseIPDLReference()
   mIPCOpen = false;
   Release();
 }
 
 //-----------------------------------------------------------------------------
 // FTPChannelChild::nsISupports
 //-----------------------------------------------------------------------------
 
-NS_IMPL_ISUPPORTS_INHERITED5(FTPChannelChild,
+NS_IMPL_ISUPPORTS_INHERITED6(FTPChannelChild,
                              nsBaseChannel,
                              nsIFTPChannel,
                              nsIUploadChannel,
                              nsIResumableChannel,
                              nsIProxiedChannel,
-                             nsIChildChannel)
+                             nsIChildChannel,
+                             nsIDivertableChannel)
 
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 FTPChannelChild::GetLastModifiedTime(PRTime* lastModifiedTime)
 {
   *lastModifiedTime = mLastModifiedTime;
   return NS_OK;
@@ -237,16 +242,23 @@ class FTPStartRequestEvent : public Chan
 
 bool
 FTPChannelChild::RecvOnStartRequest(const int64_t& aContentLength,
                                     const nsCString& aContentType,
                                     const PRTime& aLastModified,
                                     const nsCString& aEntityID,
                                     const URIParams& aURI)
 {
+  // 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 FTPStartRequestEvent(this, aContentLength, aContentType,
                                               aLastModified, aEntityID, aURI));
   } else {
     DoOnStartRequest(aContentLength, aContentType, aLastModified,
                      aEntityID, aURI);
   }
   return true;
@@ -256,30 +268,45 @@ void
 FTPChannelChild::DoOnStartRequest(const int64_t& aContentLength,
                                   const nsCString& aContentType,
                                   const PRTime& aLastModified,
                                   const nsCString& aEntityID,
                                   const URIParams& aURI)
 {
   LOG(("FTPChannelChild::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!");
+
   mContentLength = aContentLength;
   SetContentType(aContentType);
   mLastModifiedTime = aLastModified;
   mEntityID = aEntityID;
 
   nsCString spec;
   nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
   uri->GetSpec(spec);
   nsBaseChannel::URI()->SetSpec(spec);
 
   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
   nsresult rv = mListener->OnStartRequest(this, mListenerContext);
   if (NS_FAILED(rv))
     Cancel(rv);
+
+  if (mDivertingToParent) {
+    mListener = nullptr;
+    mListenerContext = nullptr;
+    if (mLoadGroup) {
+      mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+    }
+  }
 }
 
 class FTPDataAvailableEvent : public ChannelEvent
 {
  public:
   FTPDataAvailableEvent(FTPChannelChild* aChild, const nsCString& aData,
                         const uint64_t& aOffset, const uint32_t& aCount)
   : mChild(aChild), mData(aData), mOffset(aOffset), mCount(aCount) {}
@@ -291,31 +318,45 @@ class FTPDataAvailableEvent : public Cha
   uint32_t mCount;
 };
 
 bool
 FTPChannelChild::RecvOnDataAvailable(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 FTPDataAvailableEvent(this, data, offset, count));
   } else {
+    MOZ_RELEASE_ASSERT(!mDivertingToParent,
+                       "ShouldEnqueue when diverting to parent!");
+
     DoOnDataAvailable(data, offset, count);
   }
   return true;
 }
 
 void
 FTPChannelChild::DoOnDataAvailable(const nsCString& data,
                                    const uint64_t& offset,
                                    const uint32_t& count)
 {
   LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this));
 
+  if (mDivertingToParent) {
+    MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+      "Should not be processing any more callbacks from parent!");
+
+    SendDivertOnDataAvailable(data, offset, count);
+    return;
+  }
+
   if (mCanceled)
     return;
 
   // NOTE: the OnDataAvailable contract requires the client to read all the data
   // in the inputstream.  This code relies on that ('data' will go away after
   // this function).  Apparently the previous, non-e10s behavior was to actually
   // support only reading part of the data, allowing later calls to read the
   // rest.
@@ -346,30 +387,41 @@ class FTPStopRequestEvent : public Chann
  private:
   FTPChannelChild* mChild;
   nsresult mStatusCode;
 };
 
 bool
 FTPChannelChild::RecvOnStopRequest(const nsresult& statusCode)
 {
+  MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+    "Should not be receiving any more callbacks from parent!");
+
   if (mEventQ->ShouldEnqueue()) {
     mEventQ->Enqueue(new FTPStopRequestEvent(this, statusCode));
   } else {
     DoOnStopRequest(statusCode);
   }
   return true;
 }
 
 void
 FTPChannelChild::DoOnStopRequest(const nsresult& statusCode)
 {
   LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%u]\n",
            this, statusCode));
 
+  if (mDivertingToParent) {
+    MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
+      "Should not be processing any more callbacks from parent!");
+
+    SendDivertOnStopRequest(statusCode);
+    return;
+  }
+
   if (!mCanceled)
     mStatus = statusCode;
 
   { // Ensure that all queued ipdl events are dispatched before
     // we initiate protocol deletion below.
     mIsPending = false;
     AutoEventEnqueuer ensureSerialDispatch(mEventQ);
     (void)mListener->OnStopRequest(this, mListenerContext, statusCode);
@@ -425,16 +477,73 @@ FTPChannelChild::DoFailedAsyncOpen(const
 
   mListener = nullptr;
   mListenerContext = nullptr;
 
   if (mIPCOpen)
     Send__delete__(this);
 }
 
+class FTPFlushedForDiversionEvent : public ChannelEvent
+{
+ public:
+  FTPFlushedForDiversionEvent(FTPChannelChild* aChild)
+  : mChild(aChild)
+  {
+    MOZ_RELEASE_ASSERT(aChild);
+  }
+
+  void Run()
+  {
+    mChild->FlushedForDiversion();
+  }
+ private:
+  FTPChannelChild* mChild;
+};
+
+bool
+FTPChannelChild::RecvFlushedForDiversion()
+{
+  MOZ_ASSERT(mDivertingToParent);
+
+  if (mEventQ->ShouldEnqueue()) {
+    mEventQ->Enqueue(new FTPFlushedForDiversionEvent(this));
+  } else {
+    MOZ_CRASH();
+  }
+  return true;
+}
+
+void
+FTPChannelChild::FlushedForDiversion()
+{
+  MOZ_RELEASE_ASSERT(mDivertingToParent);
+
+  // Once this is set, it should not be unset before FTPChannelChild 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
+FTPChannelChild::RecvDivertMessages()
+{
+  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.
+  if (NS_WARN_IF(NS_FAILED(Resume()))) {
+    return false;
+  }
+  return true;
+}
+
 class FTPDeleteSelfEvent : public ChannelEvent
 {
  public:
   FTPDeleteSelfEvent(FTPChannelChild* aChild)
   : mChild(aChild) {}
   void Run() { mChild->DoDeleteSelf(); }
  private:
   FTPChannelChild* mChild;
@@ -470,30 +579,39 @@ FTPChannelChild::Cancel(nsresult status)
     SendCancel(status);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FTPChannelChild::Suspend()
 {
   NS_ENSURE_TRUE(mIPCOpen, 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
 FTPChannelChild::Resume()
 {
   NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
 
-  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();
   }
   mEventQ->Resume();
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
@@ -547,11 +665,44 @@ FTPChannelChild::CompleteRedirectSetup(n
     mLoadGroup->AddRequest(this, nullptr);
 
   // We already have an open IPDL connection to the parent. If on-modify-request
   // listeners or load group observers canceled us, let the parent handle it
   // and send it back to us naturally.
   return NS_OK;
 }
 
+//-----------------------------------------------------------------------------
+// FTPChannelChild::nsIDivertableChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+FTPChannelChild::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) && !mIPCOpen) {
+    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;
+}
+
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/ftp/FTPChannelChild.h
+++ b/netwerk/protocol/ftp/FTPChannelChild.h
@@ -11,16 +11,17 @@
 #include "mozilla/net/PFTPChannelChild.h"
 #include "mozilla/net/ChannelEventQueue.h"
 #include "nsBaseChannel.h"
 #include "nsIFTPChannel.h"
 #include "nsIUploadChannel.h"
 #include "nsIProxiedChannel.h"
 #include "nsIResumableChannel.h"
 #include "nsIChildChannel.h"
+#include "nsIDivertableChannel.h"
 
 #include "nsIStreamListener.h"
 #include "PrivateBrowsingChannel.h"
 
 namespace mozilla {
 namespace net {
 
 // This class inherits logic from nsBaseChannel that is not needed for an
@@ -30,26 +31,28 @@ namespace net {
 
 class FTPChannelChild : public PFTPChannelChild
                       , public nsBaseChannel
                       , public nsIFTPChannel
                       , public nsIUploadChannel
                       , public nsIResumableChannel
                       , public nsIProxiedChannel
                       , public nsIChildChannel
+                      , public nsIDivertableChannel
 {
 public:
   typedef ::nsIStreamListener nsIStreamListener;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIFTPCHANNEL
   NS_DECL_NSIUPLOADCHANNEL
   NS_DECL_NSIRESUMABLECHANNEL
   NS_DECL_NSIPROXIEDCHANNEL
   NS_DECL_NSICHILDCHANNEL
+  NS_DECL_NSIDIVERTABLECHANNEL
 
   NS_IMETHOD Cancel(nsresult status);
   NS_IMETHOD Suspend();
   NS_IMETHOD Resume();
 
   FTPChannelChild(nsIURI* uri);
   virtual ~FTPChannelChild();
 
@@ -63,27 +66,31 @@ public:
   NS_IMETHOD IsPending(bool* result);
 
   nsresult OpenContentStream(bool async,
                              nsIInputStream** stream,
                              nsIChannel** channel) MOZ_OVERRIDE;
 
   bool IsSuspended();
 
+  void FlushedForDiversion();
+
 protected:
   bool RecvOnStartRequest(const int64_t& aContentLength,
                           const nsCString& aContentType,
                           const PRTime& aLastModified,
                           const nsCString& aEntityID,
                           const URIParams& aURI) MOZ_OVERRIDE;
   bool RecvOnDataAvailable(const nsCString& data,
                            const uint64_t& offset,
                            const uint32_t& count) MOZ_OVERRIDE;
   bool RecvOnStopRequest(const nsresult& statusCode) MOZ_OVERRIDE;
   bool RecvFailedAsyncOpen(const nsresult& statusCode) MOZ_OVERRIDE;
+  bool RecvFlushedForDiversion() MOZ_OVERRIDE;
+  bool RecvDivertMessages() MOZ_OVERRIDE;
   bool RecvDeleteSelf() MOZ_OVERRIDE;
 
   void DoOnStartRequest(const int64_t& aContentLength,
                         const nsCString& aContentType,
                         const PRTime& aLastModified,
                         const nsCString& aEntityID,
                         const URIParams& aURI);
   void DoOnDataAvailable(const nsCString& data,
@@ -107,16 +114,25 @@ private:
   bool mCanceled;
   uint32_t mSuspendCount;
   bool mIsPending;
   bool mWasOpened;
   
   PRTime mLastModifiedTime;
   uint64_t mStartPos;
   nsCString mEntityID;
+
+  // 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;
 };
 
 inline bool
 FTPChannelChild::IsSuspended()
 {
   return mSuspendCount != 0;
 }
 
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -6,30 +6,35 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/net/FTPChannelParent.h"
 #include "nsFTPChannel.h"
 #include "nsNetUtil.h"
 #include "nsFtpProtocolHandler.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
+#include "mozilla/unused.h"
 #include "SerializedLoadContext.h"
 
 using namespace mozilla::ipc;
 
 #undef LOG
 #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
 
 namespace mozilla {
 namespace net {
 
 FTPChannelParent::FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatus aOverrideStatus)
   : mIPCClosed(false)
   , mLoadContext(aLoadContext)
   , mPBOverride(aOverrideStatus)
+  , mStatus(NS_OK)
+  , mDivertingFromChild(false)
+  , mDivertedOnStartRequest(false)
+  , mSuspendedForDiversion(false)
 {
   nsIProtocolHandler* handler;
   CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
   NS_ASSERTION(handler, "no ftp handler");
 }
 
 FTPChannelParent::~FTPChannelParent()
 {
@@ -174,25 +179,112 @@ FTPChannelParent::RecvSuspend()
 bool
 FTPChannelParent::RecvResume()
 {
   if (mChannel)
     mChannel->Resume();
   return true;
 }
 
+bool
+FTPChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
+                                            const uint64_t& offset,
+                                            const uint32_t& count)
+{
+  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 = OnDataAvailable(mChannel, nullptr, stringStream, offset, count);
+
+  stringStream->Close();
+  if (NS_FAILED(rv)) {
+    if (mChannel) {
+      mChannel->Cancel(rv);
+    }
+    mStatus = rv;
+  }
+  return true;
+}
+
+bool
+FTPChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
+{
+  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);
+  }
+
+  OnStopRequest(mChannel, nullptr, status);
+  return true;
+}
+
+bool
+FTPChannelParent::RecvDivertComplete()
+{
+  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;
+}
+
 //-----------------------------------------------------------------------------
 // FTPChannelParent::nsIRequestObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 FTPChannelParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this));
 
+  if (mDivertingFromChild) {
+    MOZ_RELEASE_ASSERT(mDivertToListener,
+                       "Cannot divert if listener is unset!");
+    return mDivertToListener->OnStartRequest(aRequest, aContext);
+  }
+
   nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest);
   MOZ_ASSERT(chan);
   NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
 
   int64_t contentLength;
   chan->GetContentLength(&contentLength);
   nsCString contentType;
   chan->GetContentType(contentType);
@@ -230,16 +322,22 @@ FTPChannelParent::OnStartRequest(nsIRequ
 NS_IMETHODIMP
 FTPChannelParent::OnStopRequest(nsIRequest* aRequest,
                                 nsISupports* aContext,
                                 nsresult aStatusCode)
 {
   LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%ul]\n",
        this, aStatusCode));
 
+  if (mDivertingFromChild) {
+    MOZ_RELEASE_ASSERT(mDivertToListener,
+                       "Cannot divert if listener is unset!");
+    return mDivertToListener->OnStopRequest(aRequest, aContext, aStatusCode);
+  }
+
   if (mIPCClosed || !SendOnStopRequest(aStatusCode)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
@@ -249,17 +347,24 @@ FTPChannelParent::OnStopRequest(nsIReque
 NS_IMETHODIMP
 FTPChannelParent::OnDataAvailable(nsIRequest* aRequest,
                                   nsISupports* aContext,
                                   nsIInputStream* aInputStream,
                                   uint64_t aOffset,
                                   uint32_t aCount)
 {
   LOG(("FTPChannelParent::OnDataAvailable [this=%p]\n", this));
-  
+
+  if (mDivertingFromChild) {
+    MOZ_RELEASE_ASSERT(mDivertToListener,
+                       "Cannot divert if listener is unset!");
+    return mDivertToListener->OnDataAvailable(aRequest, aContext, aInputStream,
+                                              aOffset, aCount);
+  }
+
   nsCString data;
   nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
   if (NS_FAILED(rv))
     return rv;
 
   if (mIPCClosed || !SendOnDataAvailable(data, aOffset, aCount))
     return NS_ERROR_UNEXPECTED;
 
@@ -291,12 +396,201 @@ FTPChannelParent::GetInterface(const nsI
     NS_ADDREF(mLoadContext);
     *result = static_cast<nsILoadContext*>(mLoadContext);
     return NS_OK;
   }
 
   return QueryInterface(uuid, result);
 }
 
+//-----------------------------------------------------------------------------
+// FTPChannelParent::ADivertableParentChannel
+//-----------------------------------------------------------------------------
+nsresult
+FTPChannelParent::SuspendForDiversion()
+{
+  MOZ_ASSERT(mChannel);
+  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.
+  nsresult rv = mChannel->Suspend();
+  MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
+  mSuspendedForDiversion = 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
+FTPChannelParent::ResumeForDiversion()
+{
+  MOZ_ASSERT(mChannel);
+  MOZ_ASSERT(mDivertToListener);
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot ResumeForDiversion if not diverting!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (mSuspendedForDiversion) {
+    nsresult rv = mChannel->Resume();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      FailDiversion(NS_ERROR_UNEXPECTED, true);
+      return rv;
+    }
+    mSuspendedForDiversion = false;
+  }
+
+  // Delete() will tear down IPDL, but ref from underlying nsFTPChannel will
+  // keep us alive if there's more data to be delivered to listener.
+  if (NS_WARN_IF(NS_FAILED(Delete()))) {
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return NS_ERROR_UNEXPECTED;   
+  }
+  return NS_OK;
+}
+
+void
+FTPChannelParent::DivertTo(nsIStreamListener *aListener)
+{
+  MOZ_ASSERT(aListener);
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot DivertTo new listener if diverting is not set!");
+    return;
+  }
+
+  if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  mDivertToListener = aListener;
+
+  // Call OnStartRequest and SendDivertMessages asynchronously to avoid
+  // reentering client context.
+  NS_DispatchToCurrentThread(
+    NS_NewRunnableMethod(this, &FTPChannelParent::StartDiversion));
+  return;
+}
+
+void
+FTPChannelParent::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 = OnStartRequest(mChannel, nullptr);
+  if (NS_FAILED(rv)) {
+    if (mChannel) {
+      mChannel->Cancel(rv);
+    }
+    mStatus = rv;
+    return;
+  }
+
+  // After OnStartRequest has been called, tell FTPChannelChild to divert the
+  // OnDataAvailables and OnStopRequest to this FTPChannelParent.
+  if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return;
+  }
+}
+
+class FTPFailDiversionEvent : public nsRunnable
+{
+public:
+  FTPFailDiversionEvent(FTPChannelParent *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<FTPChannelParent> mChannelParent;
+  nsresult mErrorCode;
+  bool mSkipResume;
+};
+
+void
+FTPChannelParent::FailDiversion(nsresult aErrorCode,
+                                            bool aSkipResume)
+{
+  MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+  MOZ_RELEASE_ASSERT(mDivertingFromChild);
+  MOZ_RELEASE_ASSERT(mDivertToListener);
+  MOZ_RELEASE_ASSERT(mChannel);
+
+  NS_DispatchToCurrentThread(
+    new FTPFailDiversionEvent(this, aErrorCode, aSkipResume));
+}
+
+void
+FTPChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
+                                        bool aSkipResume)
+{
+  MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
+  MOZ_RELEASE_ASSERT(mDivertingFromChild);
+  MOZ_RELEASE_ASSERT(mDivertToListener);
+  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 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);
+    mDivertToListener->OnStartRequest(mChannel, nullptr);
+    mChannel->ForcePending(false);
+  }
+  // If the channel is pending, it will call OnStopRequest itself; otherwise, do
+  // it here.
+  if (!isPending) {
+    mDivertToListener->OnStopRequest(mChannel, nullptr, aErrorCode);
+  }
+  mDivertToListener = nullptr;
+  mChannel = nullptr;
+
+  if (!mIPCClosed) {
+    unused << SendDeleteSelf();
+  }
+}
+
 //---------------------
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/ftp/FTPChannelParent.h
+++ b/netwerk/protocol/ftp/FTPChannelParent.h
@@ -3,63 +3,105 @@
 
 /* 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_FTPChannelParent_h
 #define mozilla_net_FTPChannelParent_h
 
+#include "ADivertableParentChannel.h"
 #include "mozilla/net/PFTPChannelParent.h"
 #include "mozilla/net/NeckoParent.h"
 #include "nsIParentChannel.h"
 #include "nsIInterfaceRequestor.h"
 
 class nsFtpChannel;
 class nsILoadContext;
 
 namespace mozilla {
 namespace net {
 
 class FTPChannelParent : public PFTPChannelParent
                        , public nsIParentChannel
                        , public nsIInterfaceRequestor
+                       , public ADivertableParentChannel
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIPARENTCHANNEL
   NS_DECL_NSIINTERFACEREQUESTOR
 
   FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatus aOverrideStatus);
   virtual ~FTPChannelParent();
 
   bool Init(const FTPChannelCreationArgs& 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:
+  // private, supporting function for ADivertableParentChannel.
+  nsresult ResumeForDiversion();
+
+  // Asynchronously calls NotifyDiversionFailed.
+  void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
+
   bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos,
                    const nsCString& aEntityID,
                    const OptionalInputStreamParams& aUploadStream);
 
   // used to connect redirected-to channel in parent with just created
   // ChildChannel.  Used during HTTP->FTP redirects.
   bool ConnectChannel(const uint32_t& channelId);
 
   virtual bool RecvCancel(const nsresult& status) MOZ_OVERRIDE;
   virtual bool RecvSuspend() MOZ_OVERRIDE;
   virtual bool RecvResume() 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;
 
   nsRefPtr<nsFtpChannel> mChannel;
 
   bool mIPCClosed;
 
   nsCOMPtr<nsILoadContext> mLoadContext;
 
   PBOverrideStatus mPBOverride;
+
+  // If OnStart/OnData/OnStop have been diverted from the child, forward them to
+  // this listener.
+  nsCOMPtr<nsIStreamListener> mDivertToListener;
+  // 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;
+
+  // Set if we successfully suspended the nsHttpChannel for diversion. Unset
+  // when we call ResumeForDiversion.
+  bool mSuspendedForDiversion;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_FTPChannelParent_h
--- a/netwerk/protocol/ftp/PFTPChannel.ipdl
+++ b/netwerk/protocol/ftp/PFTPChannel.ipdl
@@ -27,20 +27,39 @@ parent:
   // see PNecko.ipdl
 
   __delete__();
 
   Cancel(nsresult status);
   Suspend();
   Resume();
 
+  // Divert OnDataAvailable to the parent.
+  DivertOnDataAvailable(nsCString data,
+                        uint64_t  offset,
+                        uint32_t  count);
+
+  // Divert OnStopRequest to the parent.
+  DivertOnStopRequest(nsresult statusCode);
+
+  // Child has no more events/messages to divert to the parent.
+  DivertComplete();
+
 child:
   OnStartRequest(int64_t aContentLength, nsCString aContentType,
                  PRTime aLastModified, nsCString aEntityID, URIParams aURI);
   OnDataAvailable(nsCString data, uint64_t offset, uint32_t count);
   OnStopRequest(nsresult statusCode);
   FailedAsyncOpen(nsresult statusCode);
+
+  // Parent has been suspended for diversion; no more events to be enqueued.
+  FlushedForDiversion();
+
+  // Child should resume processing the ChannelEventQueue, i.e. diverting any
+  // OnDataAvailable and OnStopRequest messages in the queue back to the parent.
+  DivertMessages();
+
   DeleteSelf();
 };
 
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/ftp/nsFTPChannel.cpp
+++ b/netwerk/protocol/ftp/nsFTPChannel.cpp
@@ -33,17 +33,17 @@ NS_IMPL_ISUPPORTS_INHERITED4(nsFtpChanne
 
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsFtpChannel::SetUploadStream(nsIInputStream *stream,
                               const nsACString &contentType,
                               int64_t contentLength)
 {
-    NS_ENSURE_TRUE(!IsPending(), NS_ERROR_IN_PROGRESS);
+    NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
 
     mUploadStream = stream;
 
     // NOTE: contentLength is intentionally ignored here.
  
     return NS_OK;
 }
 
@@ -56,17 +56,17 @@ nsFtpChannel::GetUploadStream(nsIInputSt
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsFtpChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID)
 {
-    NS_ENSURE_TRUE(!IsPending(), NS_ERROR_IN_PROGRESS);
+    NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
     mEntityID = aEntityID;
     mStartPos = aStartPos;
     mResumeRequested = (mStartPos || !mEntityID.IsEmpty());
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFtpChannel::GetEntityID(nsACString& entityID)
@@ -191,8 +191,31 @@ nsFtpChannel::GetFTPEventSink(nsCOMPtr<n
         nsCOMPtr<nsIFTPEventSink> ftpSink;
         GetCallback(ftpSink);
         if (ftpSink) {
             mFTPEventSink = new FTPEventSinkProxy(ftpSink);
         }
     }
     aResult = mFTPEventSink;
 }
+
+void
+nsFtpChannel::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
+nsFtpChannel::IsPending(bool *result)
+{
+  *result = Pending();
+  return NS_OK;
+}
+
+bool
+nsFtpChannel::Pending() const
+{
+  return nsBaseChannel::Pending() || mForcePending;
+}
--- a/netwerk/protocol/ftp/nsFTPChannel.h
+++ b/netwerk/protocol/ftp/nsFTPChannel.h
@@ -31,29 +31,36 @@ public:
     NS_DECL_NSIRESUMABLECHANNEL
     NS_DECL_NSIPROXIEDCHANNEL
 
     nsFtpChannel(nsIURI *uri, nsIProxyInfo *pi)
         : mProxyInfo(pi)
         , mStartPos(0)
         , mResumeRequested(false)
         , mLastModifiedTime(0)
+        , mForcePending(false)
     {
         SetURI(uri);
     }
 
     nsIProxyInfo *ProxyInfo() {
         return mProxyInfo;
     }
 
     void SetProxyInfo(nsIProxyInfo *pi)
     {
         mProxyInfo = pi;
     }
 
+    NS_IMETHOD IsPending(bool *result) MOZ_OVERRIDE;
+
+    // This is a short-cut to calling nsIRequest::IsPending().
+    // Overrides Pending in nsBaseChannel.
+    bool Pending() const MOZ_OVERRIDE;
+
     // Were we asked to resume a download?
     bool ResumeRequested() { return mResumeRequested; }
 
     // Download from this byte offset
     uint64_t StartPos() { return mStartPos; }
 
     // ID of the entity to resume downloading
     const nsCString &EntityID() {
@@ -76,26 +83,30 @@ public:
     // Data stream to upload
     nsIInputStream *UploadStream() {
         return mUploadStream;
     }
 
     // Helper function for getting the nsIFTPEventSink.
     void GetFTPEventSink(nsCOMPtr<nsIFTPEventSink> &aResult);
 
+public: /* Internal Necko use only. */
+    void ForcePending(bool aForcePending);
+
 protected:
     virtual ~nsFtpChannel() {}
     virtual nsresult OpenContentStream(bool async, nsIInputStream **result,
                                        nsIChannel** channel);
     virtual bool GetStatusArg(nsresult status, nsString &statusArg);
     virtual void OnCallbacksChanged();
 
 private:
     nsCOMPtr<nsIProxyInfo>    mProxyInfo; 
     nsCOMPtr<nsIFTPEventSink> mFTPEventSink;
     nsCOMPtr<nsIInputStream>  mUploadStream;
     uint64_t                  mStartPos;
     nsCString                 mEntityID;
     bool                      mResumeRequested;
     PRTime                    mLastModifiedTime;
+    bool                      mForcePending;
 };
 
 #endif /* nsFTPChannel_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,16 +827,70 @@ 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()
+{
+  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()
+{
+  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.
   if (mRedirectChannelChild)
     rv = mRedirectChannelChild->CompleteRedirectSetup(mListener,
@@ -938,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();
 
@@ -1436,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,
@@ -113,16 +118,18 @@ protected:
   bool RecvFailedAsyncOpen(const nsresult& status) MOZ_OVERRIDE;
   bool RecvRedirect1Begin(const uint32_t& newChannel,
                           const URIParams& newURI,
                           const uint32_t& redirectFlags,
                           const nsHttpResponseHead& responseHead) MOZ_OVERRIDE;
   bool RecvRedirect3Complete() MOZ_OVERRIDE;
   bool RecvAssociateApplicationCache(const nsCString& groupID,
                                      const nsCString& clientID) MOZ_OVERRIDE;
+  bool RecvFlushedForDiversion() MOZ_OVERRIDE;
+  bool RecvDivertMessages() MOZ_OVERRIDE;
   bool RecvDeleteSelf() MOZ_OVERRIDE;
 
   bool GetAssociatedContentSecurity(nsIAssociatedContentSecurity** res = nullptr);
   virtual void DoNotifyListenerCleanup();
 
 private:
   RequestHeaderTuples mClientSetRequestHeaders;
   nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
@@ -135,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)
 {
@@ -430,25 +434,114 @@ HttpChannelParent::RecvMarkOfflineCacheE
   if (mOfflineForeignMarker) {
     mOfflineForeignMarker->MarkAsForeign();
     mOfflineForeignMarker = 0;
   }
 
   return true;
 }
 
+bool
+HttpChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
+                                             const uint64_t& offset,
+                                             const uint32_t& count)
+{
+  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)
+{
+  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()
+{
+  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;
@@ -487,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
 //-----------------------------------------------------------------------------
@@ -528,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
@@ -624,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;
   }
@@ -657,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.
+  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,
@@ -84,25 +99,34 @@ protected:
   virtual bool RecvCancel(const nsresult& status) MOZ_OVERRIDE;
   virtual bool RecvRedirect2Verify(const nsresult& result,
                                    const RequestHeaderTuples& changedHeaders,
                                    const OptionalURIParams& apiRedirectUri) MOZ_OVERRIDE;
   virtual bool RecvUpdateAssociatedContentSecurity(const int32_t& broken,
                                                    const int32_t& no) MOZ_OVERRIDE;
   virtual bool RecvDocumentChannelCleanup() MOZ_OVERRIDE;
   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;
@@ -116,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/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -61,16 +61,27 @@ parent:
   // load this document from the offline cache group it was just loaded from.
   // Marking the cache entry as foreign in its cache group will prevent
   // the document to load from the bad offline cache group. After it is marked,
   // we reload the document to take the effect. If we fail to mark the entry
   // as foreign, we will end up in the same situation and reload again and
   // again, indefinitely.
   MarkOfflineCacheEntryAsForeign();
 
+  // Divert OnDataAvailable to the parent.
+  DivertOnDataAvailable(nsCString data,
+                        uint64_t  offset,
+                        uint32_t  count);
+
+  // Divert OnStopRequest to the parent.
+  DivertOnStopRequest(nsresult statusCode);
+
+  // Child has no more events/messages to divert to the parent.
+  DivertComplete();
+
   __delete__();
 
 child:
   OnStartRequest(nsHttpResponseHead  responseHead,
                  bool                useResponseHead,
                  nsHttpHeaderArray   requestHeaders,
                  bool                isFromCache,
                  bool                cacheEntryAvailable,
@@ -108,16 +119,23 @@ child:
 
   // Called if redirect successful so that child can complete setup.
   Redirect3Complete();
 
   // Associte the child with an application ids
   AssociateApplicationCache(nsCString groupID,
                             nsCString clientID);
 
+  // Parent has been suspended for diversion; no more events to be enqueued.
+  FlushedForDiversion();
+
+  // Child should resume processing the ChannelEventQueue, i.e. diverting any
+  // OnDataAvailable and OnStopRequest messages in the queue back to the parent.
+  DivertMessages();
+
   // Tell child to delete channel (all IPDL deletes must be done from child to
   // avoid races: see bug 591708).
   DeleteSelf();
 };
 
 
 } // namespace net
 } // namespace mozilla
--- 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__