Bug 1197679 - If nsUnknownDecoder is involved in e10s DivertToParent can break. r=jduell
authorDragana Damjanovic dd.mozilla@gmail.com
Sun, 11 Oct 2015 18:13:09 +0200
changeset 267148 2e9ee2819de4a8b12372cd249419120bc4cfb668
parent 267147 651b3818a85116ed41d967af9fc0c5ec9b57f95a
child 267149 7c5db9a3a4b1ab8d809c8e5943f911870b8639b5
push id66402
push userarchaeopteryx@coole-files.de
push dateSun, 11 Oct 2015 16:15:00 +0000
treeherdermozilla-inbound@ee4cb52e6b15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs1197679
milestone44.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 1197679 - If nsUnknownDecoder is involved in e10s DivertToParent can break. r=jduell
netwerk/base/nsIDivertableChannel.idl
netwerk/ipc/ChannelEventQueue.h
netwerk/protocol/ftp/FTPChannelChild.cpp
netwerk/protocol/ftp/FTPChannelChild.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/streamconv/converters/nsUnknownDecoder.cpp
--- a/netwerk/base/nsIDivertableChannel.idl
+++ b/netwerk/base/nsIDivertableChannel.idl
@@ -17,17 +17,17 @@ 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)]
+[uuid(7a9bf52d-f828-4b31-b8df-b40fdd37d007)]
 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:
@@ -52,9 +52,27 @@ interface nsIDivertableChannel : nsISupp
    *
    * @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();
+
+  /**
+   * nsUnknownDecoder delays calling OnStartRequest until it gets enough data
+   * to decide about the content type (until OnDataAvaiable is called). In a
+   * OnStartRequest DivertToParent can be called but some OnDataAvailables are
+   * already called and therefore can not be diverted to parent.
+   *
+   * nsUnknownDecoder will call UnknownDecoderInvolvedKeepData in its
+   * OnStartRequest function and when it calls OnStartRequest of the next
+   * listener it will call UnknownDecoderInvolvedOnStartRequestCalled. In this
+   * function Child process will decide to discarge data if it is not diverting
+   * to parent or keep them if it is diverting to parent.
+   */
+  void unknownDecoderInvolvedKeepData();
+
+  void unknownDecoderInvolvedOnStartRequestCalled();
+
+  readonly attribute bool divertingToParent;
 };
--- a/netwerk/ipc/ChannelEventQueue.h
+++ b/netwerk/ipc/ChannelEventQueue.h
@@ -46,16 +46,17 @@ class ChannelEventQueue final
 
   // Checks to determine if an IPDL-generated channel event can be processed
   // immediately, or needs to be queued using Enqueue().
   inline bool ShouldEnqueue();
 
   // Puts IPDL-generated channel event into queue, to be run later
   // automatically when EndForcedQueueing and/or Resume is called.
   inline void Enqueue(ChannelEvent* callback);
+  inline nsresult PrependEvents(nsTArray<nsAutoPtr<ChannelEvent> >& aEvents);
 
   // After StartForcedQueueing is called, ShouldEnqueue() will return true and
   // no events will be run/flushed until EndForcedQueueing is called.
   // - Note: queueing may still be required after EndForcedQueueing() (if the
   //   queue is suspended, etc):  always call ShouldEnqueue() to determine
   //   whether queueing is needed.
   inline void StartForcedQueueing();
   inline void EndForcedQueueing();
@@ -122,16 +123,29 @@ ChannelEventQueue::StartForcedQueueing()
 
 inline void
 ChannelEventQueue::EndForcedQueueing()
 {
   mForced = false;
   MaybeFlushQueue();
 }
 
+inline nsresult
+ChannelEventQueue::PrependEvents(nsTArray<nsAutoPtr<ChannelEvent> >& aEvents)
+{
+  if (!mEventQueue.InsertElementsAt(0, aEvents.Length())) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  for (uint32_t i = 0; i < aEvents.Length(); i++) {
+    mEventQueue.ReplaceElementAt(i, aEvents[i].forget());
+  }
+  return NS_OK;
+}
+
 inline void
 ChannelEventQueue::Suspend()
 {
   mSuspended = true;
   mSuspendCount++;
 }
 
 inline void
--- a/netwerk/protocol/ftp/FTPChannelChild.cpp
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -24,16 +24,17 @@ using namespace mozilla::ipc;
 #undef LOG
 #define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
 
 namespace mozilla {
 namespace net {
 
 FTPChannelChild::FTPChannelChild(nsIURI* uri)
 : mIPCOpen(false)
+, mUnknownDecoderInvolved(false)
 , mCanceled(false)
 , mSuspendCount(0)
 , mIsPending(false)
 , mLastModifiedTime(0)
 , mStartPos(0)
 , mDivertingToParent(false)
 , mFlushedForDiversion(false)
 , mSuspendSent(false)
@@ -385,16 +386,50 @@ FTPChannelChild::RecvOnDataAvailable(con
     MOZ_RELEASE_ASSERT(!mDivertingToParent,
                        "ShouldEnqueue when diverting to parent!");
 
     DoOnDataAvailable(channelStatus, data, offset, count);
   }
   return true;
 }
 
+class MaybeDivertOnDataFTPEvent : public ChannelEvent
+{
+ public:
+  MaybeDivertOnDataFTPEvent(FTPChannelChild* child,
+                            const nsCString& data,
+                            const uint64_t& offset,
+                            const uint32_t& count)
+  : mChild(child)
+  , mData(data)
+  , mOffset(offset)
+  , mCount(count) {}
+
+  void Run()
+  {
+    mChild->MaybeDivertOnData(mData, mOffset, mCount);
+  }
+
+ private:
+  FTPChannelChild* mChild;
+  nsCString mData;
+  uint64_t mOffset;
+  uint32_t mCount;
+};
+
+void
+FTPChannelChild::MaybeDivertOnData(const nsCString& data,
+                                   const uint64_t& offset,
+                                   const uint32_t& count)
+{
+  if (mDivertingToParent) {
+    SendDivertOnDataAvailable(data, offset, count);
+  }
+}
+
 void
 FTPChannelChild::DoOnDataAvailable(const nsresult& channelStatus,
                                    const nsCString& data,
                                    const uint64_t& offset,
                                    const uint32_t& count)
 {
   LOG(("FTPChannelChild::DoOnDataAvailable [this=%p]\n", this));
 
@@ -408,16 +443,21 @@ FTPChannelChild::DoOnDataAvailable(const
 
     SendDivertOnDataAvailable(data, offset, count);
     return;
   }
 
   if (mCanceled)
     return;
 
+  if (mUnknownDecoderInvolved) {
+    mUnknownDecoderEventQ.AppendElement(
+      new MaybeDivertOnDataFTPEvent(this, data, offset, count));
+  }
+
   // 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.
   nsCOMPtr<nsIInputStream> stringStream;
   nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream),
                                       data.get(),
@@ -467,16 +507,42 @@ FTPChannelChild::RecvOnStopRequest(const
   if (mEventQ->ShouldEnqueue()) {
     mEventQ->Enqueue(new FTPStopRequestEvent(this, aChannelStatus));
   } else {
     DoOnStopRequest(aChannelStatus);
   }
   return true;
 }
 
+class MaybeDivertOnStopFTPEvent : public ChannelEvent
+{
+ public:
+  MaybeDivertOnStopFTPEvent(FTPChannelChild* child,
+                            const nsresult& aChannelStatus)
+  : mChild(child)
+  , mChannelStatus(aChannelStatus) {}
+
+  void Run()
+  {
+    mChild->MaybeDivertOnStop(mChannelStatus);
+  }
+
+ private:
+  FTPChannelChild* mChild;
+  nsresult mChannelStatus;
+};
+
+void
+FTPChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
+{
+  if (mDivertingToParent) {
+    SendDivertOnStopRequest(aChannelStatus);
+  }
+}
+
 void
 FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus)
 {
   LOG(("FTPChannelChild::DoOnStopRequest [this=%p status=%x]\n",
        this, aChannelStatus));
 
   if (mDivertingToParent) {
     MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
@@ -484,16 +550,21 @@ FTPChannelChild::DoOnStopRequest(const n
 
     SendDivertOnStopRequest(aChannelStatus);
     return;
   }
 
   if (!mCanceled)
     mStatus = aChannelStatus;
 
+  if (mUnknownDecoderInvolved) {
+    mUnknownDecoderEventQ.AppendElement(
+      new MaybeDivertOnStopFTPEvent(this, aChannelStatus));
+  }
+
   { // Ensure that all queued ipdl events are dispatched before
     // we initiate protocol deletion below.
     mIsPending = false;
     AutoEventEnqueuer ensureSerialDispatch(mEventQ);
     (void)mListener->OnStopRequest(this, mListenerContext, aChannelStatus);
     mListener = nullptr;
     mListenerContext = nullptr;
 
@@ -783,11 +854,41 @@ FTPChannelChild::DivertToParent(ChannelD
     gNeckoChild->SendPChannelDiverterConstructor(this);
   MOZ_RELEASE_ASSERT(diverter);
 
   *aChild = static_cast<ChannelDiverterChild*>(diverter);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+FTPChannelChild::UnknownDecoderInvolvedKeepData()
+{
+  mUnknownDecoderInvolved = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::UnknownDecoderInvolvedOnStartRequestCalled()
+{
+  mUnknownDecoderInvolved = false;
+
+  nsresult rv = NS_OK;
+
+  if (mDivertingToParent) {
+    rv = mEventQ->PrependEvents(mUnknownDecoderEventQ);
+  }
+  mUnknownDecoderEventQ.Clear();
+
+  return rv;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetDivertingToParent(bool* aDiverting)
+{
+  NS_ENSURE_ARG_POINTER(aDiverting);
+  *aDiverting = mDivertingToParent;
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/ftp/FTPChannelChild.h
+++ b/netwerk/protocol/ftp/FTPChannelChild.h
@@ -96,31 +96,44 @@ protected:
                         const nsCString& aContentType,
                         const PRTime& aLastModified,
                         const nsCString& aEntityID,
                         const URIParams& aURI);
   void DoOnDataAvailable(const nsresult& channelStatus,
                          const nsCString& data,
                          const uint64_t& offset,
                          const uint32_t& count);
+  void MaybeDivertOnData(const nsCString& data,
+                         const uint64_t& offset,
+                         const uint32_t& count);
+  void MaybeDivertOnStop(const nsresult& statusCode);
   void DoOnStopRequest(const nsresult& statusCode);
   void DoFailedAsyncOpen(const nsresult& statusCode);
   void DoDeleteSelf();
 
   friend class FTPStartRequestEvent;
   friend class FTPDataAvailableEvent;
+  friend class MaybeDivertOnDataFTPEvent;
   friend class FTPStopRequestEvent;
+  friend class MaybeDivertOnStopFTPEvent;
   friend class FTPFailedAsyncOpenEvent;
   friend class FTPDeleteSelfEvent;
 
 private:
   nsCOMPtr<nsIInputStream> mUploadStream;
 
   bool mIPCOpen;
   nsRefPtr<ChannelEventQueue> mEventQ;
+
+  // If nsUnknownDecoder is involved we queue onDataAvailable (and possibly
+  // OnStopRequest) so that we can divert them if needed when the listener's
+  // OnStartRequest is finally called
+  nsTArray<nsAutoPtr<ChannelEvent>> mUnknownDecoderEventQ;
+  bool mUnknownDecoderInvolved;
+
   bool mCanceled;
   uint32_t mSuspendCount;
   bool mIsPending;
 
   PRTime mLastModifiedTime;
   uint64_t mStartPos;
   nsCString mEntityID;
 
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -174,16 +174,17 @@ HttpChannelChild::HttpChannelChild()
   : HttpAsyncAborter<HttpChannelChild>(this)
   , mSynthesizedStreamLength(0)
   , mIsFromCache(false)
   , mCacheEntryAvailable(false)
   , mCacheExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
   , mSendResumeAt(false)
   , mIPCOpen(false)
   , mKeptAlive(false)
+  , mUnknownDecoderInvolved(false)
   , mDivertingToParent(false)
   , mFlushedForDiversion(false)
   , mSuspendSent(false)
   , mSynthesizedResponse(false)
   , mShouldParentIntercept(false)
 {
   LOG(("Creating HttpChannelChild @%x\n", this));
 
@@ -575,16 +576,52 @@ HttpChannelChild::RecvOnTransportAndData
                        "ShouldEnqueue when diverting to parent!");
 
     OnTransportAndData(channelStatus, transportStatus, progress, progressMax,
                        data, offset, count);
   }
   return true;
 }
 
+class MaybeDivertOnDataHttpEvent : public ChannelEvent
+{
+ public:
+  MaybeDivertOnDataHttpEvent(HttpChannelChild* child,
+                             const nsCString& data,
+                             const uint64_t& offset,
+                             const uint32_t& count)
+  : mChild(child)
+  , mData(data)
+  , mOffset(offset)
+  , mCount(count) {}
+
+  void Run()
+  {
+    mChild->MaybeDivertOnData(mData, mOffset, mCount);
+  }
+
+ private:
+  HttpChannelChild* mChild;
+  nsCString mData;
+  uint64_t mOffset;
+  uint32_t mCount;
+};
+
+void
+HttpChannelChild::MaybeDivertOnData(const nsCString& data,
+                                    const uint64_t& offset,
+                                    const uint32_t& count)
+{
+  LOG(("HttpChannelChild::MaybeDivertOnData [this=%p]", this));
+
+  if (mDivertingToParent) {
+    SendDivertOnDataAvailable(data, offset, count);
+  }
+}
+
 void
 HttpChannelChild::OnTransportAndData(const nsresult& channelStatus,
                                      const nsresult& transportStatus,
                                      const uint64_t progress,
                                      const uint64_t& progressMax,
                                      const nsCString& data,
                                      const uint64_t& offset,
                                      const uint32_t& count)
@@ -602,16 +639,23 @@ HttpChannelChild::OnTransportAndData(con
 
     SendDivertOnDataAvailable(data, offset, count);
     return;
   }
 
   if (mCanceled)
     return;
 
+  if (mUnknownDecoderInvolved) {
+    LOG(("UnknownDecoder is involved queue OnDataAvailable call. [this=%p]",
+         this));
+    mUnknownDecoderEventQ.AppendElement(
+      new MaybeDivertOnDataHttpEvent(this, data, offset, count));
+  }
+
   // Hold queue lock throughout all three calls, else we might process a later
   // necko msg in between them.
   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   DoOnStatus(this, transportStatus);
   DoOnProgress(this, progress, progressMax);
 
   // OnDataAvailable
@@ -737,31 +781,68 @@ HttpChannelChild::RecvOnStopRequest(cons
   } else {
     MOZ_ASSERT(!mDivertingToParent, "ShouldEnqueue when diverting to parent!");
 
     OnStopRequest(channelStatus, timing);
   }
   return true;
 }
 
+class MaybeDivertOnStopHttpEvent : public ChannelEvent
+{
+ public:
+  MaybeDivertOnStopHttpEvent(HttpChannelChild* child,
+                             const nsresult& channelStatus)
+  : mChild(child)
+  , mChannelStatus(channelStatus)
+  {}
+
+  void Run()
+  {
+    mChild->MaybeDivertOnStop(mChannelStatus);
+  }
+
+ private:
+  HttpChannelChild* mChild;
+  nsresult mChannelStatus;
+};
+
+void
+HttpChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
+{
+  LOG(("HttpChannelChild::MaybeDivertOnStop [this=%p, "
+       "mDivertingToParent=%d status=%x]", this, mDivertingToParent,
+       aChannelStatus));
+  if (mDivertingToParent) {
+    SendDivertOnStopRequest(aChannelStatus);
+  }
+}
+
 void
 HttpChannelChild::OnStopRequest(const nsresult& channelStatus,
                                 const ResourceTimingStruct& timing)
 {
   LOG(("HttpChannelChild::OnStopRequest [this=%p status=%x]\n",
-           this, channelStatus));
+       this, channelStatus));
 
   if (mDivertingToParent) {
     MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
       "Should not be processing any more callbacks from parent!");
 
     SendDivertOnStopRequest(channelStatus);
     return;
   }
 
+  if (mUnknownDecoderInvolved) {
+   LOG(("UnknownDecoder is involved queue OnStopRequest call. [this=%p]",
+        this));
+    mUnknownDecoderEventQ.AppendElement(
+      new MaybeDivertOnStopHttpEvent(this, channelStatus));
+  }
+
   mTransactionTimings.domainLookupStart = timing.domainLookupStart;
   mTransactionTimings.domainLookupEnd = timing.domainLookupEnd;
   mTransactionTimings.connectStart = timing.connectStart;
   mTransactionTimings.connectEnd = timing.connectEnd;
   mTransactionTimings.requestStart = timing.requestStart;
   mTransactionTimings.responseStart = timing.responseStart;
   mTransactionTimings.responseEnd = timing.responseEnd;
   mAsyncOpenTime = timing.fetchStart;
@@ -2234,16 +2315,51 @@ HttpChannelChild::DivertToParent(Channel
     gNeckoChild->SendPChannelDiverterConstructor(args);
   MOZ_RELEASE_ASSERT(diverter);
 
   *aChild = static_cast<ChannelDiverterChild*>(diverter);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpChannelChild::UnknownDecoderInvolvedKeepData()
+{
+  LOG(("HttpChannelChild::UnknownDecoderInvolvedKeepData [this=%p]",
+       this));
+  mUnknownDecoderInvolved = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::UnknownDecoderInvolvedOnStartRequestCalled()
+{
+  LOG(("HttpChannelChild::UnknownDecoderInvolvedOnStartRequestCalled "
+       "[this=%p, mDivertingToParent=%d]", this, mDivertingToParent));
+  mUnknownDecoderInvolved = false;
+
+  nsresult rv = NS_OK;
+
+  if (mDivertingToParent) {
+    rv = mEventQ->PrependEvents(mUnknownDecoderEventQ);
+  }
+  mUnknownDecoderEventQ.Clear();
+
+  return rv;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetDivertingToParent(bool* aDiverting)
+{
+  NS_ENSURE_ARG_POINTER(aDiverting);
+  *aDiverting = mDivertingToParent;
+  return NS_OK;
+}
+
+
 void
 HttpChannelChild::ResetInterception()
 {
   NS_ENSURE_TRUE_VOID(gNeckoChild != nullptr);
 
   if (mInterceptListener) {
     mInterceptListener->Cleanup();
   }
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -190,16 +190,22 @@ 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;
 
+  // If nsUnknownDecoder is involved OnStartRequest call will be delayed and
+  // this queue keeps OnDataAvailable data until OnStartRequest is finally
+  // called.
+  nsTArray<nsAutoPtr<ChannelEvent>> mUnknownDecoderEventQ;
+  bool mUnknownDecoderInvolved;
+
   // 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;
@@ -224,40 +230,46 @@ private:
                       const bool& isFromCache,
                       const bool& cacheEntryAvailable,
                       const uint32_t& cacheExpirationTime,
                       const nsCString& cachedCharset,
                       const nsCString& securityInfoSerialization,
                       const NetAddr& selfAddr,
                       const NetAddr& peerAddr,
                       const uint32_t& cacheKey);
+  void MaybeDivertOnData(const nsCString& data,
+                         const uint64_t& offset,
+                         const uint32_t& count);
   void OnTransportAndData(const nsresult& channelStatus,
                           const nsresult& status,
                           const uint64_t progress,
                           const uint64_t& progressMax,
                           const nsCString& data,
                           const uint64_t& offset,
                           const uint32_t& count);
   void OnStopRequest(const nsresult& channelStatus, const ResourceTimingStruct& timing);
+  void MaybeDivertOnStop(const nsresult& aChannelStatus);
   void OnProgress(const int64_t& progress, const int64_t& progressMax);
   void OnStatus(const nsresult& status);
   void FailedAsyncOpen(const nsresult& status);
   void HandleAsyncAbort();
   void Redirect1Begin(const uint32_t& newChannelId,
                       const URIParams& newUri,
                       const uint32_t& redirectFlags,
                       const nsHttpResponseHead& responseHead,
                       const nsACString& securityInfoSerialization);
   void Redirect3Complete();
   void DeleteSelf();
 
   friend class AssociateApplicationCacheEvent;
   friend class StartRequestEvent;
   friend class StopRequestEvent;
   friend class TransportAndDataEvent;
+  friend class MaybeDivertOnDataHttpEvent;
+  friend class MaybeDivertOnStopHttpEvent;
   friend class ProgressEvent;
   friend class StatusEvent;
   friend class FailedAsyncOpenEvent;
   friend class Redirect1Event;
   friend class Redirect3Event;
   friend class DeleteSelfEvent;
   friend class HttpAsyncAborter<HttpChannelChild>;
   friend class InterceptStreamListener;
--- a/netwerk/streamconv/converters/nsUnknownDecoder.cpp
+++ b/netwerk/streamconv/converters/nsUnknownDecoder.cpp
@@ -10,16 +10,17 @@
 #include "nsMimeTypes.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 
 #include "nsCRT.h"
 
 #include "nsIMIMEService.h"
 
+#include "nsIDivertableChannel.h"
 #include "nsIViewSourceChannel.h"
 #include "nsIHttpChannel.h"
 #include "nsIForcePendingChannel.h"
 #include "nsIEncodedChannel.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 
 #include <algorithm>
@@ -208,16 +209,25 @@ nsUnknownDecoder::OnDataAvailable(nsIReq
     }
   }
 
   // Must not fire ODA again if it failed once
   if (aCount && NS_SUCCEEDED(rv)) {
     NS_ASSERTION(!mContentType.IsEmpty(), 
                  "Content type should be known by now.");
 
+    nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+    if (divertable) {
+      bool diverting;
+      divertable->GetDivertingToParent(&diverting);
+      if (diverting) {
+        // The channel is diverted to the parent do not send any more data here.
+        return rv;
+      }
+    }
     rv = mNextListener->OnDataAvailable(request, aCtxt, aStream, 
                                         aSourceOffset, aCount);
   }
 
   return rv;
 }
 
 // ----
@@ -237,16 +247,21 @@ nsUnknownDecoder::OnStartRequest(nsIRequ
   if (NS_SUCCEEDED(rv) && !mBuffer) {
     mBuffer = new char[MAX_BUFFER_SIZE];
 
     if (!mBuffer) {
       rv = NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
+  nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+  if (divertable) {
+    divertable->UnknownDecoderInvolvedKeepData();
+  }
+
   // Do not pass the OnStartRequest on to the next listener (yet)...
   return rv;
 }
 
 NS_IMETHODIMP
 nsUnknownDecoder::OnStopRequest(nsIRequest* request, nsISupports *aCtxt,
                                 nsresult aStatus)
 {
@@ -642,23 +657,40 @@ nsresult nsUnknownDecoder::FireListenerN
 
     NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to set content type on channel!");
 
     if (NS_FAILED(rv)) {
       // Cancel the request to make sure it has the correct status if
       // mNextListener looks at it.
       request->Cancel(rv);
       mNextListener->OnStartRequest(request, aCtxt);
+
+      nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+      if (divertable) {
+        rv = divertable->UnknownDecoderInvolvedOnStartRequestCalled();
+      }
+
       return rv;
     }
   }
 
   // Fire the OnStartRequest(...)
   rv = mNextListener->OnStartRequest(request, aCtxt);
 
+   nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
+   if (divertable) {
+     rv = divertable->UnknownDecoderInvolvedOnStartRequestCalled();
+     bool diverting;
+     divertable->GetDivertingToParent(&diverting);
+     if (diverting) {
+       // The channel is diverted to the parent do not send any more data here.
+       return rv;
+     }
+   }
+
   if (NS_SUCCEEDED(rv)) {
     // install stream converter if required
     nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(request);
     if (encodedChannel) {
       nsCOMPtr<nsIStreamListener> listener;
       rv = encodedChannel->DoApplyContentConversions(mNextListener, getter_AddRefs(listener), aCtxt);
       if (NS_SUCCEEDED(rv) && listener) {
         mNextListener = listener;