bug 378637 part 12 - proxy over TLS (i.e. https proxying) r=hurley
authorPatrick McManus <mcmanus@ducksong.com>
Wed, 16 Apr 2014 09:52:43 -0400
changeset 183607 1d1935f80c97
parent 183606 5f549d1d837b
child 183608 ab6d433e563b
push id26799
push userphilringnalda@gmail.com
push date2014-05-18 00:55 +0000
treeherdermozilla-central@00ef3a7d7aa7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley
bugs378637
milestone32.0a1
bug 378637 part 12 - proxy over TLS (i.e. https proxying) r=hurley
netwerk/base/src/nsSocketTransport2.cpp
netwerk/base/src/nsSocketTransport2.h
netwerk/protocol/http/ASpdySession.cpp
netwerk/protocol/http/ASpdySession.h
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/Http2Session.h
netwerk/protocol/http/NullHttpTransaction.cpp
netwerk/protocol/http/NullHttpTransaction.h
netwerk/protocol/http/SpdySession3.cpp
netwerk/protocol/http/SpdySession3.h
netwerk/protocol/http/SpdySession31.cpp
netwerk/protocol/http/SpdySession31.h
netwerk/protocol/http/SpdyStream3.cpp
netwerk/protocol/http/SpdyStream3.h
netwerk/protocol/http/SpdyStream31.cpp
netwerk/protocol/http/TunnelUtils.cpp
netwerk/protocol/http/TunnelUtils.h
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttpAtomList.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionInfo.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/netwerk/base/src/nsSocketTransport2.cpp
+++ b/netwerk/base/src/nsSocketTransport2.cpp
@@ -748,16 +748,17 @@ nsSocketOutputStream::AsyncWait(nsIOutpu
 
 nsSocketTransport::nsSocketTransport()
     : mTypes(nullptr)
     , mTypeCount(0)
     , mPort(0)
     , mProxyPort(0)
     , mProxyTransparent(false)
     , mProxyTransparentResolvesHost(false)
+    , mHttpsProxy(false)
     , mConnectionFlags(0)
     , mState(STATE_CLOSED)
     , mAttached(false)
     , mInputClosed(true)
     , mOutputClosed(true)
     , mResolving(false)
     , mNetAddrIsSet(false)
     , mLock("nsSocketTransport.mLock")
@@ -778,23 +779,31 @@ nsSocketTransport::nsSocketTransport()
     mTimeouts[TIMEOUT_CONNECT]    = UINT16_MAX; // no timeout
     mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout
 }
 
 nsSocketTransport::~nsSocketTransport()
 {
     SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
 
+    CleanupTypes();
+}
+
+void
+nsSocketTransport::CleanupTypes()
+{
     // cleanup socket type info
     if (mTypes) {
-        uint32_t i;
-        for (i=0; i<mTypeCount; ++i)
+        for (uint32_t i = 0; i < mTypeCount; ++i) {
             PL_strfree(mTypes[i]);
+        }
         free(mTypes);
+        mTypes = nullptr;
     }
+    mTypeCount = 0;
 }
 
 nsresult
 nsSocketTransport::Init(const char **types, uint32_t typeCount,
                         const nsACString &host, uint16_t port,
                         nsIProxyInfo *givenProxyInfo)
 {
     MOZ_EVENT_TRACER_NAME_OBJECT(this, host.BeginReading());
@@ -805,26 +814,32 @@ nsSocketTransport::Init(const char **typ
         NS_ENSURE_ARG(proxyInfo);
     }
 
     // init socket type info
 
     mPort = port;
     mHost = host;
 
+    if (proxyInfo) {
+        mHttpsProxy = proxyInfo->IsHTTPS();
+    }
+
     const char *proxyType = nullptr;
     if (proxyInfo) {
         mProxyPort = proxyInfo->Port();
         mProxyHost = proxyInfo->Host();
         // grab proxy type (looking for "socks" for example)
         proxyType = proxyInfo->Type();
-        if (proxyType && (strcmp(proxyType, "http") == 0 ||
-                          strcmp(proxyType, "direct") == 0 ||
-                          strcmp(proxyType, "unknown") == 0))
+        if (proxyType && (proxyInfo->IsHTTP() ||
+                          proxyInfo->IsHTTPS() ||
+                          proxyInfo->IsDirect() ||
+                          !strcmp(proxyType, "unknown"))) {
             proxyType = nullptr;
+        }
     }
 
     SOCKET_LOG(("nsSocketTransport::Init [this=%p host=%s:%hu proxy=%s:%hu]\n",
         this, mHost.get(), mPort, mProxyHost.get(), mProxyPort));
 
     // include proxy type as a socket type if proxy type is not "http"
     mTypeCount = typeCount + (proxyType != nullptr);
     if (!mTypeCount)
@@ -1100,18 +1115,24 @@ nsSocketTransport::BuildSocket(PRFileDes
 
             if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE)
                 proxyFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
 
             nsCOMPtr<nsISupports> secinfo;
             if (i == 0) {
                 // if this is the first type, we'll want the 
                 // service to allocate a new socket
+
+                // when https proxying we want to just connect to the proxy as if
+                // it were the end host (i.e. expect the proxy's cert)
+
                 rv = provider->NewSocket(mNetAddr.raw.family,
-                                         host, port, proxyHost, proxyPort,
+                                         mHttpsProxy ? proxyHost : host,
+                                         mHttpsProxy ? proxyPort : port,
+                                         proxyHost, proxyPort,
                                          proxyFlags, &fd,
                                          getter_AddRefs(secinfo));
 
                 if (NS_SUCCEEDED(rv) && !fd) {
                     NS_NOTREACHED("NewSocket succeeded but failed to create a PRFileDesc");
                     rv = NS_ERROR_UNEXPECTED;
                 }
             }
@@ -1158,16 +1179,17 @@ nsSocketTransport::BuildSocket(PRFileDes
 
         if (NS_FAILED(rv)) {
             SOCKET_LOG(("  error pushing io layer [%u:%s rv=%x]\n", i, mTypes[i], rv));
             if (fd)
                 PR_Close(fd);
         }
     }
 
+    CleanupTypes();
     return rv;
 }
 
 nsresult
 nsSocketTransport::InitiateSocket()
 {
     SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
 
--- a/netwerk/base/src/nsSocketTransport2.h
+++ b/netwerk/base/src/nsSocketTransport2.h
@@ -145,16 +145,17 @@ public:
     // called when a socket event is handled
     void OnSocketEvent(uint32_t type, nsresult status, nsISupports *param);
 
     uint64_t ByteCountReceived() { return mInput.ByteCount(); }
     uint64_t ByteCountSent() { return mOutput.ByteCount(); }
 protected:
 
     virtual ~nsSocketTransport();
+    void     CleanupTypes();
 
 private:
 
     // event types
     enum {
         MSG_ENSURE_CONNECT,
         MSG_DNS_LOOKUP_COMPLETE,
         MSG_RETRY_INIT_SOCKET,
@@ -264,16 +265,17 @@ private:
     char       **mTypes;
     uint32_t     mTypeCount;
     nsCString    mHost;
     nsCString    mProxyHost;
     uint16_t     mPort;
     uint16_t     mProxyPort;
     bool mProxyTransparent;
     bool mProxyTransparentResolvesHost;
+    bool mHttpsProxy;
     uint32_t     mConnectionFlags;
     
     uint16_t         SocketPort() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort; }
     const nsCString &SocketHost() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost; }
 
     //-------------------------------------------------------------------------
     // members accessible only on the socket transport thread:
     //  (the exception being initialization/shutdown time)
--- a/netwerk/protocol/http/ASpdySession.cpp
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -23,16 +23,24 @@
 #include "SpdySession31.h"
 #include "Http2Session.h"
 
 #include "mozilla/Telemetry.h"
 
 namespace mozilla {
 namespace net {
 
+ASpdySession::ASpdySession()
+{
+}
+
+ASpdySession::~ASpdySession()
+{
+}
+
 ASpdySession *
 ASpdySession::NewSpdySession(uint32_t version,
                              nsISocketTransport *aTransport)
 {
   // This is a necko only interface, so we can enforce version
   // requests as a precondition
   MOZ_ASSERT(version == SPDY_VERSION_3 ||
              version == SPDY_VERSION_31 ||
@@ -180,12 +188,11 @@ SpdyPushCache::RemovePushedStreamHttp2(n
 {
   Http2PushedStream *rv = mHashHttp2.Get(key);
   LOG3(("SpdyPushCache::RemovePushedStreamHttp2 %s 0x%X\n",
         key.get(), rv ? rv->StreamID() : 0));
   if (rv)
     mHashHttp2.Remove(key);
   return rv;
 }
-
 } // namespace mozilla::net
 } // namespace mozilla
 
--- a/netwerk/protocol/http/ASpdySession.h
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -10,20 +10,26 @@
 #include "nsAHttpTransaction.h"
 #include "prinrval.h"
 #include "nsString.h"
 
 class nsISocketTransport;
 
 namespace mozilla { namespace net {
 
+class nsHttpConnectionInfo;
+
 class ASpdySession : public nsAHttpTransaction
 {
 public:
-  virtual bool AddStream(nsAHttpTransaction *, int32_t) = 0;
+  ASpdySession();
+  virtual ~ASpdySession();
+
+  virtual bool AddStream(nsAHttpTransaction *, int32_t,
+                         bool, nsIInterfaceRequestor *) = 0;
   virtual bool CanReuse() = 0;
   virtual bool RoomForMoreStreams() = 0;
   virtual PRIntervalTime IdleTime() = 0;
   virtual uint32_t ReadTimeoutTick(PRIntervalTime now) = 0;
   virtual void DontReuse() = 0;
 
   static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *);
 
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -368,17 +368,19 @@ Http2Session::RegisterStreamID(Http2Stre
   }
 
   mStreamIDHash.Put(aNewID, stream);
   return aNewID;
 }
 
 bool
 Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
-                        int32_t aPriority)
+                        int32_t aPriority,
+                        bool aUseTunnel,
+                        nsIInterfaceRequestor *aCallbacks)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   // integrity check
   if (mStreamTransactionHash.Get(aHttpTransaction)) {
     LOG3(("   New transaction already present\n"));
     MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
     return false;
@@ -2886,16 +2888,17 @@ Http2Session::TransactionHasDataToWrite(
           this, caller));
     return;
   }
 
   LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n",
         this, stream->StreamID()));
 
   mReadyForWrite.Push(stream);
+  SetWriteCallbacks();
 }
 
 void
 Http2Session::TransactionHasDataToWrite(Http2Stream *stream)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
         this, stream, stream->StreamID()));
@@ -2935,37 +2938,37 @@ Http2Session::CancelPipeline(nsresult re
 nsAHttpTransaction::Classifier
 Http2Session::Classification()
 {
   if (!mConnection)
     return nsAHttpTransaction::CLASS_GENERAL;
   return mConnection->Classification();
 }
 
+void
+Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **aOut)
+{
+  *aOut = nullptr;
+}
+
 //-----------------------------------------------------------------------------
 // unused methods of nsAHttpTransaction
 // We can be sure of this because Http2Session is only constructed in
-// nsHttpConnection and is never passed out of that object
+// nsHttpConnection and is never passed out of that object or a TLSFilterTransaction
+// TLS tunnel
 //-----------------------------------------------------------------------------
 
 void
 Http2Session::SetConnection(nsAHttpConnection *)
 {
   // This is unexpected
   MOZ_ASSERT(false, "Http2Session::SetConnection()");
 }
 
 void
-Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **)
-{
-  // This is unexpected
-  MOZ_ASSERT(false, "Http2Session::GetSecurityCallbacks()");
-}
-
-void
 Http2Session::SetProxyConnectFailed()
 {
   MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
 }
 
 bool
 Http2Session::IsDone()
 {
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -36,17 +36,18 @@ public:
     NS_DECL_NSAHTTPTRANSACTION
     NS_DECL_NSAHTTPCONNECTION(mConnection)
     NS_DECL_NSAHTTPSEGMENTREADER
     NS_DECL_NSAHTTPSEGMENTWRITER
 
    Http2Session(nsISocketTransport *);
   ~Http2Session();
 
-  bool AddStream(nsAHttpTransaction *, int32_t);
+  bool AddStream(nsAHttpTransaction *, int32_t,
+                 bool, nsIInterfaceRequestor *);
   bool CanReuse() { return !mShouldGoAway && !mClosed; }
   bool RoomForMoreStreams();
 
   // When the connection is active this is called up to once every 1 second
   // return the interval (in seconds) that the connection next wants to
   // have this invoked. It might happen sooner depending on the needs of
   // other connections.
   uint32_t  ReadTimeoutTick(PRIntervalTime now);
--- a/netwerk/protocol/http/NullHttpTransaction.cpp
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -10,17 +10,17 @@
 #include "nsHttp.h"
 #include "NullHttpTransaction.h"
 #include "nsHttpHandler.h"
 #include "nsHttpRequestHead.h"
 
 namespace mozilla {
 namespace net {
 
-NS_IMPL_ISUPPORTS0(NullHttpTransaction)
+NS_IMPL_ISUPPORTS(NullHttpTransaction, nsISupportsWeakReference)
 
 NullHttpTransaction::NullHttpTransaction(nsHttpConnectionInfo *ci,
                                          nsIInterfaceRequestor *callbacks,
                                          uint32_t caps)
   : mStatus(NS_OK)
   , mCaps(caps | NS_HTTP_ALLOW_KEEPALIVE)
   , mCapsToClear(0)
   , mCallbacks(callbacks)
--- a/netwerk/protocol/http/NullHttpTransaction.h
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -16,26 +16,26 @@
 // anticipation of a real transaction needing to use it soon.
 
 namespace mozilla { namespace net {
 
 class nsAHttpConnection;
 class nsHttpConnectionInfo;
 class nsHttpRequestHead;
 
-class NullHttpTransaction MOZ_FINAL : public nsAHttpTransaction
+class NullHttpTransaction : public nsAHttpTransaction
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSAHTTPTRANSACTION
 
   NullHttpTransaction(nsHttpConnectionInfo *ci,
                       nsIInterfaceRequestor *callbacks,
                       uint32_t caps);
-  ~NullHttpTransaction();
+  virtual ~NullHttpTransaction();
 
   // Overload of nsAHttpTransaction methods
   bool IsNullTransaction() MOZ_OVERRIDE MOZ_FINAL { return true; }
   bool ResponseTimeoutEnabled() const MOZ_OVERRIDE MOZ_FINAL {return true; }
   PRIntervalTime ResponseTimeout() MOZ_OVERRIDE MOZ_FINAL
   {
     return PR_SecondsToInterval(15);
   }
--- a/netwerk/protocol/http/SpdySession3.cpp
+++ b/netwerk/protocol/http/SpdySession3.cpp
@@ -19,16 +19,17 @@
 #include "nsHttpHandler.h"
 #include "nsILoadGroup.h"
 #include "prprf.h"
 #include "SpdyPush3.h"
 #include "SpdySession3.h"
 #include "SpdyStream3.h"
 #include "PSpdyPush.h"
 #include "SpdyZlibReporter.h"
+#include "TunnelUtils.h"
 
 #include <algorithm>
 
 #ifdef DEBUG
 // defined by the socket transport service while active
 extern PRThread *gSocketThread;
 #endif
 
@@ -329,44 +330,46 @@ SpdySession3::RegisterStreamID(SpdyStrea
   }
 
   mStreamIDHash.Put(aNewID, stream);
   return aNewID;
 }
 
 bool
 SpdySession3::AddStream(nsAHttpTransaction *aHttpTransaction,
-                        int32_t aPriority)
+                        int32_t aPriority,
+                        bool aUseTunnel,
+                        nsIInterfaceRequestor *aCallbacks)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   // integrity check
   if (mStreamTransactionHash.Get(aHttpTransaction)) {
     LOG3(("   New transaction already present\n"));
     MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
     return false;
   }
 
-  // assert that
-  // a] in the case we have a connection, that the new transaction connection
-  //    is either undefined or on the same connection
-  // b] in the case we don't have a connection, that the new transaction
-  //    connection is defined so we can adopt it
-  MOZ_ASSERT((mConnection && (!aHttpTransaction->Connection() ||
-                              mConnection == aHttpTransaction->Connection())) ||
-             (!mConnection && aHttpTransaction->Connection()));
-
   if (!mConnection) {
     mConnection = aHttpTransaction->Connection();
   }
+
   aHttpTransaction->SetConnection(this);
+
+  if (aUseTunnel) {
+    LOG3(("SpdySession3::AddStream session=%p trans=%p OnTunnel",
+          this, aHttpTransaction));
+    DispatchOnTunnel(aHttpTransaction, aCallbacks);
+    return true;
+  }
+
   SpdyStream3 *stream = new SpdyStream3(aHttpTransaction, this, aPriority);
 
-  LOG3(("SpdySession3::AddStream session=%p stream=%p NextID=0x%X (tentative)",
-        this, stream, mNextStreamID));
+  LOG3(("SpdySession3::AddStream session=%p stream=%p serial=%u "
+        "NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID));
 
   mStreamTransactionHash.Put(aHttpTransaction, stream);
 
   if (RoomForMoreConcurrent()) {
     LOG3(("SpdySession3::AddStream %p stream %p activated immediately.",
           this, stream));
     ActivateStream(stream);
   }
@@ -902,16 +905,20 @@ SpdySession3::CloseStream(SpdyStream3 *a
   if (aStream == mInputFrameDataStream) {
     LOG3(("Stream had active partial read frame on close"));
     ChangeDownstreamState(DISCARDING_DATA_FRAME);
     mInputFrameDataStream = nullptr;
   }
 
   RemoveStreamFromQueues(aStream);
 
+  if (aStream->IsTunnel()) {
+    UnRegisterTunnel(aStream);
+  }
+
   // Send the stream the close() indication
   aStream->Close(aResult);
 }
 
 nsresult
 SpdySession3::HandleSynStream(SpdySession3 *self)
 {
   MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM);
@@ -1023,17 +1030,23 @@ SpdySession3::HandleSynStream(SpdySessio
   // ownership of the pushed stream is by the transaction hash, just as it
   // is for a client initiated stream. Errors that aren't fatal to the
   // whole session must call cleanupStream() after this point in order
   // to remove the stream from that hash.
   self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
   self->mPushedStreams.AppendElement(pushedStream);
 
   // The pushed stream is unidirectional so it is fully open immediately
-  pushedStream->SetFullyOpen();
+  rv = pushedStream->SetFullyOpen();
+  if (NS_FAILED(rv)) {
+    LOG(("SpdySession3::HandleSynStream pushedstream fully open failed\n"));
+    self->CleanupStream(pushedStream, rv, RST_CANCEL);
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
 
   // Uncompress the response headers into a stream specific buffer, leaving them
   // in spdy format for the time being.
   rv = pushedStream->Uncompress(&self->mDownstreamZlib,
                                 self->mInputFrameBuffer + 18,
                                 self->mInputFrameDataSize - 10);
   if (NS_FAILED(rv)) {
     LOG(("SpdySession3::HandleSynStream uncompress failed\n"));
@@ -1096,20 +1109,20 @@ SpdySession3::HandleSynReply(SpdySession
 
   if (self->mInputFrameDataSize < 4) {
     LOG3(("SpdySession3::HandleSynReply %p SYN REPLY too short data=%d",
           self, self->mInputFrameDataSize));
     // A framing error is a session wide error that cannot be recovered
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
-  LOG3(("SpdySession3::HandleSynReply %p lookup via streamID in syn_reply.\n",
-        self));
   uint32_t streamID =
     NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t));
+  LOG3(("SpdySession3::HandleSynReply %p lookup via streamID 0x%X in syn_reply.\n",
+        self, streamID));
   nsresult rv = self->SetInputFrameDataStream(streamID);
   if (NS_FAILED(rv))
     return rv;
 
   if (!self->mInputFrameDataStream) {
     // Cannot find stream. We can continue the SPDY session, but we need to
     // uncompress the header block to maintain the correct compression context
 
@@ -1162,17 +1175,29 @@ SpdySession3::HandleSynReply(SpdySession
           self->mInputFrameDataStream->RecvdFin()));
 
     self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ALREADY_OPENED,
                         self->mInputFrameDataStream->RecvdFin() ?
                         RST_STREAM_ALREADY_CLOSED : RST_STREAM_IN_USE);
     self->ResetDownstreamState();
     return NS_OK;
   }
-  self->mInputFrameDataStream->SetFullyOpen();
+
+  rv = self->mInputFrameDataStream->SetFullyOpen();
+  if (NS_FAILED(rv)) {
+    LOG(("SpdySession3::HandleSynReply SetFullyOpen failed\n"));
+    if (self->mInputFrameDataStream->IsTunnel()) {
+      gHttpHandler->ConnMgr()->CancelTransactions(
+        self->mInputFrameDataStream->Transaction()->ConnectionInfo(),
+        NS_ERROR_CONNECTION_REFUSED);
+    }
+    self->CleanupStream(self->mInputFrameDataStream, rv, RST_CANCEL);
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
 
   self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN;
   self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize);
   self->mLastDataReadEpoch = self->mLastReadEpoch;
 
   if (self->mInputFrameBuffer[4] & ~kFlag_Data_FIN) {
     LOG3(("SynReply %p had undefined flag set 0x%X\n", self, streamID));
     self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
@@ -1991,42 +2016,42 @@ SpdySession3::WriteSegments(nsAHttpSegme
   if (mDownstreamState == PROCESSING_DATA_FRAME ||
       mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
 
     // The cleanup stream should only be set while stream->WriteSegments is
     // on the stack and then cleaned up in this code block afterwards.
     MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
     mNeedsCleanup = nullptr;                     /* just in case */
 
+    SpdyStream3 *stream = mInputFrameDataStream;
     mSegmentWriter = writer;
     rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
     mSegmentWriter = nullptr;
 
     mLastDataReadEpoch = mLastReadEpoch;
 
     if (SoftStreamError(rv)) {
       // This will happen when the transaction figures out it is EOF, generally
       // due to a content-length match being made. Return OK from this function
       // otherwise the whole session would be torn down.
-      SpdyStream3 *stream = mInputFrameDataStream;
 
       // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
       // back to PROCESSING_DATA_FRAME where we came from
       mDownstreamState = PROCESSING_DATA_FRAME;
 
       if (mInputFrameDataRead == mInputFrameDataSize)
         ResetDownstreamState();
       LOG3(("SpdySession3::WriteSegments session=%p stream=%p 0x%X "
             "needscleanup=%p. cleanup stream based on "
             "stream->writeSegments returning code %X\n",
             this, stream, stream ? stream->StreamID() : 0,
             mNeedsCleanup, rv));
       CleanupStream(stream, NS_OK, RST_CANCEL);
-      MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame");
-      mNeedsCleanup = nullptr;                     /* just in case */
+      MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup == stream);
+      mNeedsCleanup = nullptr;
       return NS_OK;
     }
 
     if (mNeedsCleanup) {
       LOG3(("SpdySession3::WriteSegments session=%p stream=%p 0x%X "
             "cleanup stream based on mNeedsCleanup.\n",
             this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
       CleanupStream(mNeedsCleanup, NS_OK, RST_CANCEL);
@@ -2439,16 +2464,88 @@ SpdySession3::SetNeedsCleanup()
 
 void
 SpdySession3::ConnectPushedStream(SpdyStream3 *stream)
 {
   mReadyForRead.Push(stream);
   ForceRecv();
 }
 
+uint32_t
+SpdySession3::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  uint32_t rv = 0;
+  mTunnelHash.Get(aConnInfo->HashKey(), &rv);
+  return rv;
+}
+
+void
+SpdySession3::RegisterTunnel(SpdyStream3 *aTunnel)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
+  uint32_t newcount = FindTunnelCount(ci) + 1;
+  mTunnelHash.Remove(ci->HashKey());
+  mTunnelHash.Put(ci->HashKey(), newcount);
+  LOG3(("SpdySession3::RegisterTunnel %p stream=%p tunnels=%d [%s]",
+        this, aTunnel, newcount, ci->HashKey().get()));
+}
+
+void
+SpdySession3::UnRegisterTunnel(SpdyStream3 *aTunnel)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
+  MOZ_ASSERT(FindTunnelCount(ci));
+  uint32_t newcount = FindTunnelCount(ci) - 1;
+  mTunnelHash.Remove(ci->HashKey());
+  if (newcount) {
+    mTunnelHash.Put(ci->HashKey(), newcount);
+  }
+  LOG3(("SpdySession3::UnRegisterTunnel %p stream=%p tunnels=%d [%s]",
+        this, aTunnel, newcount, ci->HashKey().get()));
+}
+
+void
+SpdySession3::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
+                               nsIInterfaceRequestor *aCallbacks)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
+  nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
+  MOZ_ASSERT(trans);
+
+  LOG3(("SpdySession3::DispatchOnTunnel %p trans=%p", this, trans));
+
+  aHttpTransaction->SetConnection(nullptr);
+
+  // this transaction has done its work of setting up a tunnel, let
+  // the connection manager queue it if necessary
+  trans->SetDontRouteViaWildCard(true);
+
+  if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
+    LOG3(("SpdySession3::DispatchOnTunnel %p create on new tunnel %s",
+          this, ci->HashKey().get()));
+    nsRefPtr<SpdyConnectTransaction> connectTrans =
+      new SpdyConnectTransaction(ci, aCallbacks,
+                                 trans->Caps(), trans, this);
+    AddStream(connectTrans, trans->Priority(),
+              false, nullptr);
+    SpdyStream3 *tunnel = mStreamTransactionHash.Get(connectTrans);
+    MOZ_ASSERT(tunnel);
+    RegisterTunnel(tunnel);
+  }
+
+  // requeue it. The connection manager is responsible for actually putting
+  // this on the tunnel connection with the specific ci now that it
+  // has DontRouteViaWildCard set.
+  gHttpHandler->InitiateTransaction(trans, trans->Priority());
+}
+
 //-----------------------------------------------------------------------------
 // Modified methods of nsAHttpConnection
 //-----------------------------------------------------------------------------
 
 void
 SpdySession3::TransactionHasDataToWrite(nsAHttpTransaction *caller)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -2463,27 +2560,34 @@ SpdySession3::TransactionHasDataToWrite(
           this, caller));
     return;
   }
 
   LOG3(("SpdySession3::TransactionHasDataToWrite %p ID is 0x%X\n",
         this, stream->StreamID()));
 
   mReadyForWrite.Push(stream);
+  SetWriteCallbacks();
+
+  // NSPR poll will not poll the network if there are non system PR_FileDesc's
+  // that are ready - so we can get into a deadlock waiting for the system IO
+  // to come back here if we don't force the send loop manually.
+  ForceSend();
 }
 
 void
 SpdySession3::TransactionHasDataToWrite(SpdyStream3 *stream)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   LOG3(("SpdySession3::TransactionHasDataToWrite %p stream=%p ID=%x",
         this, stream, stream->StreamID()));
 
   mReadyForWrite.Push(stream);
   SetWriteCallbacks();
+  ForceSend();
 }
 
 bool
 SpdySession3::IsPersistent()
 {
   return true;
 }
 
@@ -2513,37 +2617,37 @@ SpdySession3::CancelPipeline(nsresult re
 nsAHttpTransaction::Classifier
 SpdySession3::Classification()
 {
   if (!mConnection)
     return nsAHttpTransaction::CLASS_GENERAL;
   return mConnection->Classification();
 }
 
+void
+SpdySession3::GetSecurityCallbacks(nsIInterfaceRequestor **aOut)
+{
+  *aOut = nullptr;
+}
+
 //-----------------------------------------------------------------------------
 // unused methods of nsAHttpTransaction
 // We can be sure of this because SpdySession3 is only constructed in
-// nsHttpConnection and is never passed out of that object
+// nsHttpConnection and is never passed out of that object or a TLSFilterTransaction
+// TLS tunnel
 //-----------------------------------------------------------------------------
 
 void
 SpdySession3::SetConnection(nsAHttpConnection *)
 {
   // This is unexpected
   MOZ_ASSERT(false, "SpdySession3::SetConnection()");
 }
 
 void
-SpdySession3::GetSecurityCallbacks(nsIInterfaceRequestor **)
-{
-  // This is unexpected
-  MOZ_ASSERT(false, "SpdySession3::GetSecurityCallbacks()");
-}
-
-void
 SpdySession3::SetProxyConnectFailed()
 {
   MOZ_ASSERT(false, "SpdySession3::SetProxyConnectFailed()");
 }
 
 bool
 SpdySession3::IsDone()
 {
--- a/netwerk/protocol/http/SpdySession3.h
+++ b/netwerk/protocol/http/SpdySession3.h
@@ -19,33 +19,35 @@
 #include "zlib.h"
 
 class nsISocketTransport;
 
 namespace mozilla { namespace net {
 
 class SpdyPushedStream3;
 class SpdyStream3;
+class nsHttpTransaction;
 
 class SpdySession3 MOZ_FINAL : public ASpdySession
                              , public nsAHttpConnection
                              , public nsAHttpSegmentReader
                              , public nsAHttpSegmentWriter
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSAHTTPTRANSACTION
   NS_DECL_NSAHTTPCONNECTION(mConnection)
   NS_DECL_NSAHTTPSEGMENTREADER
   NS_DECL_NSAHTTPSEGMENTWRITER
 
   SpdySession3(nsISocketTransport *);
   ~SpdySession3();
 
-  bool AddStream(nsAHttpTransaction *, int32_t);
+  bool AddStream(nsAHttpTransaction *, int32_t,
+                 bool, nsIInterfaceRequestor *);
   bool CanReuse() { return !mShouldGoAway && !mClosed; }
   bool RoomForMoreStreams();
 
   // When the connection is active this is called up to once every 1 second
   // return the interval (in seconds) that the connection next wants to
   // have this invoked. It might happen sooner depending on the needs of
   // other connections.
   uint32_t  ReadTimeoutTick(PRIntervalTime now);
@@ -377,13 +379,22 @@ private:
 
   // used as a temporary buffer while enumerating the stream hash during GoAway
   nsDeque  mGoAwayStreamsToRestart;
 
   // Each session gets a unique serial number because the push cache is correlated
   // by the load group and the serial number can be used as part of the cache key
   // to make sure streams aren't shared across sessions.
   uint64_t        mSerial;
+
+private:
+/// connect tunnels
+  void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *);
+  void RegisterTunnel(SpdyStream3 *);
+  void UnRegisterTunnel(SpdyStream3 *);
+  uint32_t FindTunnelCount(nsHttpConnectionInfo *);
+
+  nsDataHashtable<nsCStringHashKey, uint32_t> mTunnelHash;
 };
 
 }} // namespace mozilla::net
 
 #endif // mozilla_net_SpdySession3_h
--- a/netwerk/protocol/http/SpdySession31.cpp
+++ b/netwerk/protocol/http/SpdySession31.cpp
@@ -333,17 +333,19 @@ SpdySession31::RegisterStreamID(SpdyStre
   }
 
   mStreamIDHash.Put(aNewID, stream);
   return aNewID;
 }
 
 bool
 SpdySession31::AddStream(nsAHttpTransaction *aHttpTransaction,
-                           int32_t aPriority)
+                         int32_t aPriority,
+                         bool aUseTunnel,
+                         nsIInterfaceRequestor *aCallbacks)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   // integrity check
   if (mStreamTransactionHash.Get(aHttpTransaction)) {
     LOG3(("   New transaction already present\n"));
     MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
     return false;
@@ -2608,16 +2610,17 @@ SpdySession31::TransactionHasDataToWrite
           this, caller));
     return;
   }
 
   LOG3(("SpdySession31::TransactionHasDataToWrite %p ID is 0x%X\n",
         this, stream->StreamID()));
 
   mReadyForWrite.Push(stream);
+  SetWriteCallbacks();
 }
 
 void
 SpdySession31::TransactionHasDataToWrite(SpdyStream31 *stream)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   LOG3(("SpdySession31::TransactionHasDataToWrite %p stream=%p ID=%x",
         this, stream, stream->StreamID()));
@@ -2658,37 +2661,37 @@ SpdySession31::CancelPipeline(nsresult r
 nsAHttpTransaction::Classifier
 SpdySession31::Classification()
 {
   if (!mConnection)
     return nsAHttpTransaction::CLASS_GENERAL;
   return mConnection->Classification();
 }
 
+void
+SpdySession31::GetSecurityCallbacks(nsIInterfaceRequestor **aOut)
+{
+  *aOut = nullptr;
+}
+
 //-----------------------------------------------------------------------------
 // unused methods of nsAHttpTransaction
 // We can be sure of this because SpdySession31 is only constructed in
-// nsHttpConnection and is never passed out of that object
+// nsHttpConnection and is never passed out of that object or a TLSFilterTransaction
+// TLS tunnel
 //-----------------------------------------------------------------------------
 
 void
 SpdySession31::SetConnection(nsAHttpConnection *)
 {
   // This is unexpected
   MOZ_ASSERT(false, "SpdySession31::SetConnection()");
 }
 
 void
-SpdySession31::GetSecurityCallbacks(nsIInterfaceRequestor **)
-{
-  // This is unexpected
-  MOZ_ASSERT(false, "SpdySession31::GetSecurityCallbacks()");
-}
-
-void
 SpdySession31::SetProxyConnectFailed()
 {
   MOZ_ASSERT(false, "SpdySession31::SetProxyConnectFailed()");
 }
 
 bool
 SpdySession31::IsDone()
 {
--- a/netwerk/protocol/http/SpdySession31.h
+++ b/netwerk/protocol/http/SpdySession31.h
@@ -34,17 +34,18 @@ public:
   NS_DECL_NSAHTTPTRANSACTION
   NS_DECL_NSAHTTPCONNECTION(mConnection)
   NS_DECL_NSAHTTPSEGMENTREADER
   NS_DECL_NSAHTTPSEGMENTWRITER
 
   SpdySession31(nsISocketTransport *);
   ~SpdySession31();
 
-  bool AddStream(nsAHttpTransaction *, int32_t);
+  bool AddStream(nsAHttpTransaction *, int32_t,
+                 bool, nsIInterfaceRequestor *);
   bool CanReuse() { return !mShouldGoAway && !mClosed; }
   bool RoomForMoreStreams();
 
   // When the connection is active this is called up to once every 1 second
   // return the interval (in seconds) that the connection next wants to
   // have this invoked. It might happen sooner depending on the needs of
   // other connections.
   uint32_t  ReadTimeoutTick(PRIntervalTime now);
--- a/netwerk/protocol/http/SpdyStream3.cpp
+++ b/netwerk/protocol/http/SpdyStream3.cpp
@@ -21,16 +21,17 @@
 #include "nsISocketTransport.h"
 #include "nsISupportsPriority.h"
 #include "prnetdb.h"
 #include "SpdyPush3.h"
 #include "SpdySession3.h"
 #include "SpdyStream3.h"
 #include "PSpdyPush.h"
 #include "SpdyZlibReporter.h"
+#include "TunnelUtils.h"
 
 #include <algorithm>
 
 #ifdef DEBUG
 // defined by the socket transport service while active
 extern PRThread *gSocketThread;
 #endif
 
@@ -65,30 +66,32 @@ SpdyStream3::SpdyStream3(nsAHttpTransact
   , mDecompressedBytes(0)
   , mRequestBodyLenRemaining(0)
   , mPriority(priority)
   , mLocalUnacked(0)
   , mBlockedOnRwin(false)
   , mTotalSent(0)
   , mTotalRead(0)
   , mPushSource(nullptr)
+  , mIsTunnel(false)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   LOG3(("SpdyStream3::SpdyStream3 %p", this));
 
   mRemoteWindow = spdySession->GetServerInitialWindow();
   mLocalWindow = spdySession->PushAllowance();
 
   mTxInlineFrame = new uint8_t[mTxInlineFrameSize];
   mDecompressBuffer = new char[mDecompressBufferSize];
 }
 
 SpdyStream3::~SpdyStream3()
 {
+  ClearTransactionsBlockedOnTunnel();
   mStreamID = SpdySession3::kDeadStreamID;
 }
 
 // ReadSegments() is used to write data down the socket. Generally, HTTP
 // request data is pulled from the approriate transaction and
 // converted to SPDY data. Sometimes control data like a window-update is
 // generated instead.
 
@@ -109,17 +112,18 @@ SpdyStream3::ReadSegments(nsAHttpSegment
   case GENERATING_SYN_STREAM:
   case GENERATING_REQUEST_BODY:
   case SENDING_REQUEST_BODY:
     // Call into the HTTP Transaction to generate the HTTP request
     // stream. That stream will show up in OnReadSegment().
     mSegmentReader = reader;
     rv = mTransaction->ReadSegments(this, count, countRead);
     mSegmentReader = nullptr;
-
+    LOG3(("SpdyStream3::ReadSegments %p trans readsegments rv %x read=%d\n",
+          this, rv, *countRead));
     // Check to see if the transaction's request could be written out now.
     // If not, mark the stream for callback when writing can proceed.
     if (NS_SUCCEEDED(rv) &&
         mUpstreamState == GENERATING_SYN_STREAM &&
         !mSynFrameComplete)
       mSession->TransactionHasDataToWrite(this);
 
     // mTxinlineFrameUsed represents any queued un-sent frame. It might
@@ -134,17 +138,18 @@ SpdyStream3::ReadSegments(nsAHttpSegment
     if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
       mRequestBlockedOnRead = 1;
 
     // If the sending flow control window is open (!mBlockedOnRwin) then
     // continue sending the request
     if (!mBlockedOnRwin &&
         !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
       LOG3(("SpdyStream3::ReadSegments %p 0x%X: Sending request data complete, "
-            "mUpstreamState=%x",this, mStreamID, mUpstreamState));
+            "mUpstreamState=%x finondata=%d",this, mStreamID,
+            mUpstreamState, mSentFinOnData));
       if (mSentFinOnData) {
         ChangeState(UPSTREAM_COMPLETE);
       }
       else {
         GenerateDataFrameHeader(0, true);
         ChangeState(SENDING_FIN_STREAM);
         mSession->TransactionHasDataToWrite(this);
         rv = NS_BASE_STREAM_WOULD_BLOCK;
@@ -278,17 +283,17 @@ SpdyStream3::ParseHttpRequestHeaders(con
   mFlatHttpRequestHeaders.SetLength(endHeader + 2);
   *countUsed = avail - (oldLen - endHeader) + 4;
   mSynFrameComplete = 1;
 
   nsAutoCString hostHeader;
   nsAutoCString hashkey;
   mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
 
-  CreatePushHashKey(NS_LITERAL_CSTRING("https"),
+  CreatePushHashKey(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"),
                     hostHeader, mSession->Serial(),
                     mTransaction->RequestHead()->RequestURI(),
                     mOrigin, hashkey);
 
   // check the push cache for GET
   if (mTransaction->RequestHead()->IsGet()) {
     // from :scheme, :host, :path
     nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
@@ -453,58 +458,88 @@ SpdyStream3::ParseHttpRequestHeaders(con
 
   mTxInlineFrameUsed = 18;
 
   // Do not naively log the request headers here beacuse they might
   // contain auth. The http transaction already logs the sanitized request
   // headers at this same level so it is not necessary to do so here.
 
   const char *methodHeader = mTransaction->RequestHead()->Method().get();
+  LOG3(("Stream method %p 0x%X %s\n", this, mStreamID, methodHeader));
 
   // The header block length
-  uint16_t count = hdrHash.Count() + 5; /* method, path, version, host, scheme */
+  uint16_t count = hdrHash.Count() + 4; /* :method, :path, :version, :host */
+  if (mTransaction->RequestHead()->IsConnect()) {
+    mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
+  } else {
+    ++count; // :scheme used if not connect
+  }
   CompressToFrame(count);
 
   // :method, :path, :version comprise a HTTP/1 request line, so send those first
   // to make life easy for any gateways
   CompressToFrame(NS_LITERAL_CSTRING(":method"));
   CompressToFrame(methodHeader, strlen(methodHeader));
+
   CompressToFrame(NS_LITERAL_CSTRING(":path"));
-  CompressToFrame(mTransaction->RequestHead()->RequestURI());
+  if (!mTransaction->RequestHead()->IsConnect()) {
+    CompressToFrame(mTransaction->RequestHead()->RequestURI());
+  } else {
+#ifdef DEBUG
+    nsRefPtr<SpdyConnectTransaction> qiTrans(do_QueryObject(mTransaction));
+    MOZ_ASSERT(qiTrans);
+#endif
+    mIsTunnel = true;
+    // Connect places host:port in :path. Don't use default port.
+    nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo();
+    if (!ci) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    nsAutoCString route;
+    route = ci->GetHost();
+    route.AppendLiteral(":");
+    route.AppendInt(ci->Port());
+    CompressToFrame(route);
+  }
+
   CompressToFrame(NS_LITERAL_CSTRING(":version"));
   CompressToFrame(versionHeader);
 
   CompressToFrame(NS_LITERAL_CSTRING(":host"));
   CompressToFrame(hostHeader);
-  CompressToFrame(NS_LITERAL_CSTRING(":scheme"));
-  CompressToFrame(NS_LITERAL_CSTRING("https"));
+
+  if (!mTransaction->RequestHead()->IsConnect()) {
+    // no :scheme with connect
+    CompressToFrame(NS_LITERAL_CSTRING(":scheme"));
+    CompressToFrame(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"));
+  }
 
   hdrHash.Enumerate(hdrHashEnumerate, this);
   CompressFlushFrame();
 
   // 4 to 7 are length and flags, which we can now fill in
   NetworkEndian::writeUint32(mTxInlineFrame + 1 * sizeof(uint32_t),
                              mTxInlineFrameUsed - 8);
 
   MOZ_ASSERT(!mTxInlineFrame[4], "Size greater than 24 bits");
 
   // Determine whether to put the fin bit on the syn stream frame or whether
   // to wait for a data packet to put it on.
 
   if (mTransaction->RequestHead()->IsGet() ||
-      mTransaction->RequestHead()->IsConnect() ||
       mTransaction->RequestHead()->IsHead()) {
-    // for GET, CONNECT, and HEAD place the fin bit right on the
+    // for GET and HEAD place the fin bit right on the
     // syn stream packet
 
     mSentFinOnData = 1;
     mTxInlineFrame[4] = SpdySession3::kFlag_Data_FIN;
   }
   else if (mTransaction->RequestHead()->IsPost() ||
            mTransaction->RequestHead()->IsPut() ||
+           mTransaction->RequestHead()->IsConnect() ||
            mTransaction->RequestHead()->IsOptions()) {
     // place fin in a data frame even for 0 length messages, I've seen
     // the google gateway be unhappy with fin-on-syn for 0 length POST
   }
   else if (!mRequestBodyLenRemaining) {
     // for other HTTP extension methods, rely on the content-length
     // to determine whether or not to put fin on syn
     mSentFinOnData = 1;
@@ -589,16 +624,19 @@ SpdyStream3::AdjustInitialWindow()
   LOG3(("AdjustInitialwindow %p 0x%X %u\n",
         this, stream->mStreamID, PR_ntohl(toack)));
 }
 
 void
 SpdyStream3::UpdateTransportReadEvents(uint32_t count)
 {
   mTotalRead += count;
+  if (!mSocketTransport) {
+    return;
+  }
 
   mTransaction->OnTransportStatus(mSocketTransport,
                                   NS_NET_STATUS_RECEIVING_FROM,
                                   mTotalRead);
 }
 
 void
 SpdyStream3::UpdateTransportSendEvents(uint32_t count)
@@ -1241,16 +1279,22 @@ SpdyStream3::ConvertHeaders(nsACString &
   LOG (("decoded response headers are:\n%s",
         aHeadersOut.BeginReading()));
 
   // The spdy formatted buffer isnt needed anymore - free it up
   mDecompressBuffer = nullptr;
   mDecompressBufferSize = 0;
   mDecompressBufferUsed = 0;
 
+  if (mIsTunnel) {
+    aHeadersOut.Truncate();
+    LOG(("SpdyStream3::ConvertHeaders %p 0x%X headers removed for tunnel\n",
+         this, mStreamID));
+  }
+
   return NS_OK;
 }
 
 void
 SpdyStream3::ExecuteCompress(uint32_t flushMode)
 {
   // Expect mZlib->avail_in and mZlib->next_in to be set.
   // Append the compressed version of next_in to mTxInlineFrame
@@ -1308,16 +1352,48 @@ SpdyStream3::CompressToFrame(const char 
 void
 SpdyStream3::CompressFlushFrame()
 {
   mZlib->next_in = (unsigned char *) "";
   mZlib->avail_in = 0;
   ExecuteCompress(Z_SYNC_FLUSH);
 }
 
+bool
+SpdyStream3::GetFullyOpen()
+{
+  return mFullyOpen;
+}
+
+nsresult
+SpdyStream3::SetFullyOpen()
+{
+  MOZ_ASSERT(!mFullyOpen);
+  mFullyOpen = 1;
+  if (mIsTunnel) {
+    nsDependentCSubstring statusSubstring;
+    nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"),
+                             statusSubstring);
+    if (NS_SUCCEEDED(rv)) {
+      nsCString status(statusSubstring);
+      nsresult errcode;
+
+      if (status.ToInteger(&errcode) != 200) {
+        LOG3(("SpdyStream3::SetFullyOpen %p Tunnel not 200", this));
+        return NS_ERROR_FAILURE;
+      }
+      LOG3(("SpdyStream3::SetFullyOpen %p Tunnel 200 OK", this));
+    }
+
+    MapStreamToHttpConnection();
+    ClearTransactionsBlockedOnTunnel();
+  }
+  return NS_OK;
+}
+
 void
 SpdyStream3::Close(nsresult reason)
 {
   mTransaction->Close(reason);
 }
 
 void
 SpdyStream3::UpdateRemoteWindow(int32_t delta)
@@ -1391,21 +1467,25 @@ SpdyStream3::OnReadSegment(const char *b
 
     if (dataLength > mRemoteWindow)
       dataLength = static_cast<uint32_t>(mRemoteWindow);
 
     LOG3(("SpdyStream3 this=%p id 0x%X remote window is %d. Chunk is %d\n",
           this, mStreamID, mRemoteWindow, dataLength));
     mRemoteWindow -= dataLength;
 
-    LOG3(("SpdyStream3 %p id %x request len remaining %d, "
-          "count avail %d, chunk used %d",
+    LOG3(("SpdyStream3 %p id %x request len remaining %u, "
+          "count avail %u, chunk used %u",
           this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
-    if (dataLength > mRequestBodyLenRemaining)
+    if (!dataLength && mRequestBodyLenRemaining) {
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+    if (dataLength > mRequestBodyLenRemaining) {
       return NS_ERROR_UNEXPECTED;
+    }
     mRequestBodyLenRemaining -= dataLength;
     GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
     ChangeState(SENDING_REQUEST_BODY);
     // NO BREAK
 
   case SENDING_REQUEST_BODY:
     MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
     rv = TransmitFrame(buf, countRead, false);
@@ -1461,11 +1541,32 @@ SpdyStream3::OnWriteSegment(char *buf,
   rv = mPushSource->GetBufferedData(buf, count, countWritten);
   if (NS_FAILED(rv))
     return rv;
 
   mSession->ConnectPushedStream(this);
   return NS_OK;
 }
 
+/// connect tunnels
+
+void
+SpdyStream3::ClearTransactionsBlockedOnTunnel()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  if (!mIsTunnel) {
+    return;
+  }
+  gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
+}
+
+void
+SpdyStream3::MapStreamToHttpConnection()
+{
+  nsRefPtr<SpdyConnectTransaction> qiTrans(do_QueryObject(mTransaction));
+  MOZ_ASSERT(qiTrans);
+  qiTrans->MapStreamToHttpConnection(mSocketTransport,
+                                     mTransaction->ConnectionInfo());
+}
+
 } // namespace mozilla::net
 } // namespace mozilla
-
--- a/netwerk/protocol/http/SpdyStream3.h
+++ b/netwerk/protocol/http/SpdyStream3.h
@@ -34,23 +34,20 @@ public:
 
   const nsAFlatCString &Origin()   const { return mOrigin; }
 
   bool RequestBlockedOnRead()
   {
     return static_cast<bool>(mRequestBlockedOnRead);
   }
 
-  // returns false if called more than once
-  bool GetFullyOpen() {return mFullyOpen;}
-  void SetFullyOpen()
-  {
-    MOZ_ASSERT(!mFullyOpen);
-    mFullyOpen = 1;
-  }
+  bool GetFullyOpen();
+  // returns failure if stream cannot be made ready and stream
+  // should be canceled
+  nsresult SetFullyOpen();
 
   bool HasRegisteredID() { return mStreamID != 0; }
 
   nsAHttpTransaction *Transaction() { return mTransaction; }
   virtual nsILoadGroupConnectionInfo *LoadGroupConnectionInfo()
   {
     return mTransaction ? mTransaction->LoadGroupConnectionInfo() : nullptr;
   }
@@ -215,17 +212,18 @@ private:
   uint32_t             mDecompressBufferUsed;
   uint32_t             mDecompressedBytes;
   nsAutoArrayPtr<char> mDecompressBuffer;
 
   // Track the content-length of a request body so that we can
   // place the fin flag on the last data packet instead of waiting
   // for a stream closed indication. Relying on stream close results
   // in an extra 0-length runt packet and seems to have some interop
-  // problems with the google servers.
+  // problems with the google servers. Connect does rely on stream
+  // close by setting this to the max value.
   int64_t                      mRequestBodyLenRemaining;
 
   // based on nsISupportsPriority definitions
   int32_t                      mPriority;
 
   // mLocalWindow, mRemoteWindow, and mLocalUnacked are for flow control.
   // *window are signed because they race conditions in asynchronous SETTINGS
   // messages can force them temporarily negative.
@@ -248,13 +246,22 @@ private:
   bool                         mBlockedOnRwin;
 
   // For Progress Events
   uint64_t                     mTotalSent;
   uint64_t                     mTotalRead;
 
   // For SpdyPush
   SpdyPushedStream3 *mPushSource;
+
+/// connect tunnels
+public:
+  bool IsTunnel() { return mIsTunnel; }
+private:
+  void ClearTransactionsBlockedOnTunnel();
+  void MapStreamToHttpConnection();
+
+  bool mIsTunnel;
 };
 
 }} // namespace mozilla::net
 
 #endif // mozilla_net_SpdyStream3_h
--- a/netwerk/protocol/http/SpdyStream31.cpp
+++ b/netwerk/protocol/http/SpdyStream31.cpp
@@ -283,17 +283,17 @@ SpdyStream31::ParseHttpRequestHeaders(co
   mFlatHttpRequestHeaders.SetLength(endHeader + 2);
   *countUsed = avail - (oldLen - endHeader) + 4;
   mSynFrameComplete = 1;
 
   nsAutoCString hostHeader;
   nsAutoCString hashkey;
   mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
 
-  CreatePushHashKey(NS_LITERAL_CSTRING("https"),
+  CreatePushHashKey(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"),
                     hostHeader, mSession->Serial(),
                     mTransaction->RequestHead()->RequestURI(),
                     mOrigin, hashkey);
 
   // check the push cache for GET
   if (mTransaction->RequestHead()->IsGet()) {
     // from :scheme, :host, :path
     nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
@@ -476,17 +476,17 @@ SpdyStream31::ParseHttpRequestHeaders(co
   CompressToFrame(NS_LITERAL_CSTRING(":path"));
   CompressToFrame(mTransaction->RequestHead()->RequestURI());
   CompressToFrame(NS_LITERAL_CSTRING(":version"));
   CompressToFrame(versionHeader);
 
   CompressToFrame(NS_LITERAL_CSTRING(":host"));
   CompressToFrame(hostHeader);
   CompressToFrame(NS_LITERAL_CSTRING(":scheme"));
-  CompressToFrame(NS_LITERAL_CSTRING("https"));
+  CompressToFrame(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"));
 
   hdrHash.Enumerate(hdrHashEnumerate, this);
   CompressFlushFrame();
 
   // 4 to 7 are length and flags, which we can now fill in
   (reinterpret_cast<uint32_t *>(mTxInlineFrame.get()))[1] =
     PR_htonl(mTxInlineFrameUsed - 8);
 
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -0,0 +1,1319 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "Http2Session.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsISocketProvider.h"
+#include "nsISocketProviderService.h"
+#include "nsISSLSocketControl.h"
+#include "nsISocketTransport.h"
+#include "nsNetAddr.h"
+#include "prerror.h"
+#include "prio.h"
+#include "TunnelUtils.h"
+
+#ifdef DEBUG
+// defined by the socket transport service while active
+extern PRThread *gSocketThread;
+#endif
+
+namespace mozilla {
+namespace net {
+
+static PRDescIdentity sLayerIdentity;
+static PRIOMethods sLayerMethods;
+static PRIOMethods *sLayerMethodsPtr = nullptr;
+
+TLSFilterTransaction::TLSFilterTransaction(nsAHttpTransaction *aWrapped,
+                                           const char *aTLSHost,
+                                           int32_t aTLSPort)
+  : mTransaction(aWrapped)
+  , mEncryptedTextUsed(0)
+  , mEncryptedTextSize(0)
+  , mSegmentReader(nullptr)
+  , mSegmentWriter(nullptr)
+  , mForce(false)
+  , mNudgeCounter(0)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  nsCOMPtr<nsISocketProvider> provider;
+  nsCOMPtr<nsISocketProviderService> spserv =
+    do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID);
+
+  if (spserv) {
+    spserv->GetSocketProvider("ssl", getter_AddRefs(provider));
+  }
+
+  // Install an NSPR layer to handle getpeername() with a failure. This is kind
+  // of silly, but the default one used by the pipe asserts when called and the
+  // nss code calls it to see if we are connected to a real socket or not.
+  if (!sLayerMethodsPtr) {
+    // one time initialization
+    sLayerIdentity = PR_GetUniqueIdentity("TLSFilterTransaction Layer");
+    sLayerMethods = *PR_GetDefaultIOMethods();
+    sLayerMethods.getpeername = GetPeerName;
+    sLayerMethods.getsocketoption = GetSocketOption;
+    sLayerMethods.setsocketoption = SetSocketOption;
+    sLayerMethods.read = FilterRead;
+    sLayerMethods.write = FilterWrite;
+    sLayerMethods.send = FilterSend;
+    sLayerMethods.recv = FilterRecv;
+    sLayerMethods.close = FilterClose;
+    sLayerMethodsPtr = &sLayerMethods;
+  }
+
+  mFD = PR_CreateIOLayerStub(sLayerIdentity, &sLayerMethods);
+
+  if (provider && mFD) {
+    mFD->secret = reinterpret_cast<PRFilePrivate *>(this);
+    provider->AddToSocket(PR_AF_INET, aTLSHost, aTLSPort, nullptr,
+                          0, 0, mFD, getter_AddRefs(mSecInfo));
+  }
+
+  if (mTransaction) {
+    nsCOMPtr<nsIInterfaceRequestor> callbacks;
+    mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+    nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
+    if (secCtrl) {
+      secCtrl->SetNotificationCallbacks(callbacks);
+    }
+  }
+}
+
+TLSFilterTransaction::~TLSFilterTransaction()
+{
+  Cleanup();
+}
+
+void
+TLSFilterTransaction::Cleanup()
+{
+  if (mTransaction) {
+    mTransaction->Close(NS_ERROR_ABORT);
+    mTransaction = nullptr;
+  }
+
+  if (mFD) {
+    PR_Close(mFD);
+    mFD = nullptr;
+  }
+  mSecInfo = nullptr;
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+}
+
+void
+TLSFilterTransaction::Close(nsresult aReason)
+{
+  if (!mTransaction) {
+    return;
+  }
+
+  mTransaction->Close(aReason);
+  mTransaction = nullptr;
+}
+
+nsresult
+TLSFilterTransaction::OnReadSegment(const char *aData,
+                                    uint32_t aCount,
+                                    uint32_t *outCountRead)
+{
+  LOG(("TLSFilterTransaction %p OnReadSegment %d (buffered %d)\n",
+       this, aCount, mEncryptedTextUsed));
+
+  mReadSegmentBlocked = false;
+  MOZ_ASSERT(mSegmentReader);
+  if (!mSecInfo) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+  *outCountRead = 0;
+
+    // get rid of buffer first
+  if (mEncryptedTextUsed) {
+    rv = mSegmentReader->CommitToSegmentSize(mEncryptedTextUsed, mForce);
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      return rv;
+    }
+
+    uint32_t amt;
+    rv = mSegmentReader->OnReadSegment(mEncryptedText, mEncryptedTextUsed, &amt);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    mEncryptedTextUsed -= amt;
+    if (mEncryptedTextUsed) {
+      memmove(mEncryptedText, mEncryptedText + amt, mEncryptedTextUsed);
+      return NS_OK;
+    }
+  }
+
+  // encrypt for network write
+  // write aData down the SSL layer into the FilterWrite() method where it will
+  // be queued into mEncryptedText. We need to copy it like this in order to
+  // guarantee atomic writes
+
+  EnsureBuffer(mEncryptedText, aCount + 4096,
+               0, mEncryptedTextSize);
+
+  while (aCount > 0) {
+    int32_t written = PR_Write(mFD, aData, aCount);
+    LOG(("TLSFilterTransaction %p OnReadSegment PRWrite(%d) = %d %d\n",
+         this, aCount, written,
+         PR_GetError() == PR_WOULD_BLOCK_ERROR));
+
+    if (written < 1) {
+      if (*outCountRead) {
+        return NS_OK;
+      }
+      // mTransaction ReadSegments actually obscures this code, so
+      // keep it in a member var for this::ReadSegments to insepct. Similar
+      // to nsHttpConnection::mSocketOutCondition
+      mReadSegmentBlocked = (PR_GetError() == PR_WOULD_BLOCK_ERROR);
+      return mReadSegmentBlocked ? NS_BASE_STREAM_WOULD_BLOCK : NS_ERROR_FAILURE;
+    }
+    aCount -= written;
+    aData += written;
+    *outCountRead += written;
+    mNudgeCounter = 0;
+  }
+
+  LOG(("TLSFilterTransaction %p OnReadSegment2 (buffered %d)\n",
+       this, mEncryptedTextUsed));
+
+  uint32_t amt = 0;
+  if (mEncryptedTextUsed) {
+    rv = mSegmentReader->CommitToSegmentSize(mEncryptedTextUsed, mForce);
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      // return OK because all the data was consumed and stored in this buffer
+      Connection()->TransactionHasDataToWrite(this);
+      return NS_OK;
+    }
+
+    rv = mSegmentReader->OnReadSegment(mEncryptedText, mEncryptedTextUsed, &amt);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
+  if (amt == mEncryptedTextUsed) {
+    mEncryptedText = nullptr;
+    mEncryptedTextUsed = 0;
+    mEncryptedTextSize = 0;
+  } else {
+    memmove(mEncryptedText, mEncryptedText + amt, mEncryptedTextUsed - amt);
+    mEncryptedTextUsed -= amt;
+    return NS_OK;
+  }
+  return NS_OK;
+}
+
+int32_t
+TLSFilterTransaction::FilterOutput(const char *aBuf, int32_t aAmount)
+{
+  EnsureBuffer(mEncryptedText, mEncryptedTextUsed + aAmount,
+               mEncryptedTextUsed, mEncryptedTextSize);
+  memcpy(mEncryptedText + mEncryptedTextUsed, aBuf, aAmount);
+  mEncryptedTextUsed += aAmount;
+  return aAmount;
+}
+
+nsresult
+TLSFilterTransaction::CommitToSegmentSize(uint32_t size, bool forceCommitment)
+{
+  if (!mSegmentReader) {
+      return NS_ERROR_FAILURE;
+  }
+
+  // pad the commit by a little bit to leave room for encryption overhead
+  // this isn't foolproof and we may still have to buffer, but its a good start
+  mForce = forceCommitment;
+  return mSegmentReader->CommitToSegmentSize(size + 1024, forceCommitment);
+}
+
+nsresult
+TLSFilterTransaction::OnWriteSegment(char *aData,
+                                     uint32_t aCount,
+                                     uint32_t *outCountRead)
+{
+
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(mSegmentWriter);
+  LOG(("TLSFilterTransaction::OnWriteSegment %p max=%d\n", this, aCount));
+  if (!mSecInfo) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // this will call through to FilterRead to get data from the higher
+  // level connection before removing the local TLS layer
+  int32_t bytesRead = PR_Read(mFD, aData, aCount);
+  if (bytesRead == -1) {
+    if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+    return NS_ERROR_FAILURE;
+  }
+  *outCountRead = bytesRead;
+  LOG(("TLSFilterTransaction::OnWriteSegment %p rv=%x didread=%d "
+        "2 layers of ssl stripped to plaintext\n", this, mFilterReadCode, bytesRead));
+  return mFilterReadCode;
+}
+
+int32_t
+TLSFilterTransaction::FilterInput(char *aBuf, int32_t aAmount)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(mSegmentWriter);
+  LOG(("TLSFilterTransaction::FilterInput max=%d\n", aAmount));
+
+  uint32_t outCountRead = 0;
+  mFilterReadCode = mSegmentWriter->OnWriteSegment(aBuf, aAmount, &outCountRead);
+  if (NS_SUCCEEDED(mFilterReadCode) && outCountRead) {
+    LOG(("TLSFilterTransaction::FilterRead rv=%x read=%d input from net "
+         "1 layer stripped, 1 still on\n", mFilterReadCode, outCountRead));
+    if (mReadSegmentBlocked) {
+      mNudgeCounter = 0;
+    }
+  }
+  if (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK) {
+    PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+    return -1;
+  }
+  return outCountRead;
+}
+
+nsresult
+TLSFilterTransaction::ReadSegments(nsAHttpSegmentReader *aReader,
+                                   uint32_t aCount, uint32_t *outCountRead)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG(("TLSFilterTransaction::ReadSegments %p max=%d\n", this, aCount));
+
+  if (!mTransaction) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  mReadSegmentBlocked = false;
+  mSegmentReader = aReader;
+  nsresult rv = mTransaction->ReadSegments(this, aCount, outCountRead);
+  LOG(("TLSFilterTransaction %p called trans->ReadSegments rv=%x %d\n",
+       this, rv, *outCountRead));
+  if (NS_SUCCEEDED(rv) && mReadSegmentBlocked) {
+    rv = NS_BASE_STREAM_WOULD_BLOCK;
+    LOG(("TLSFilterTransaction %p read segment blocked found rv=%x\n",
+         this, rv));
+    Connection()->ForceSend();
+  }
+
+  return rv;
+}
+
+nsresult
+TLSFilterTransaction::WriteSegments(nsAHttpSegmentWriter *aWriter,
+                                    uint32_t aCount, uint32_t *outCountWritten)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG(("TLSFilterTransaction::WriteSegments %p max=%d\n", this, aCount));
+
+  if (!mTransaction) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  mSegmentWriter = aWriter;
+  nsresult rv = mTransaction->WriteSegments(this, aCount, outCountWritten);
+  mSegmentWriter = nullptr;
+  LOG(("TLSFilterTransaction %p called trans->WriteSegments rv=%x %d\n",
+       this, rv, *outCountWritten));
+  return rv;
+}
+
+nsresult
+TLSFilterTransaction::GetTransactionSecurityInfo(nsISupports **outSecInfo)
+{
+  if (!mSecInfo) {
+    return NS_ERROR_FAILURE;
+  }
+
+  NS_ADDREF(*outSecInfo = mSecInfo);
+  return NS_OK;
+}
+
+nsresult
+TLSFilterTransaction::NudgeTunnel(NudgeTunnelCallback *aCallback,
+                                  nsAHttpSegmentReader *aReader,
+                                  nsAHttpSegmentWriter *aWriter)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG(("TLSFilterTransaction %p NudgeTunnel\n", this));
+  mNudgeCallback = nullptr;
+
+  if (!mSecInfo) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (aReader) {
+    mSegmentReader = aReader;
+  }
+  if (aWriter) {
+    mSegmentWriter = aWriter;
+  }
+
+  uint32_t notUsed;
+  PR_Write(mFD, "", 0);
+  OnReadSegment("", 0, &notUsed);
+
+  // The SSL Layer does some unusual things with PR_Poll that makes it a bad
+  // match for multiplexed SSL sessions. We work around this by manually polling for
+  // the moment during the brief handshake phase or otherwise blocked on write.
+  // Thankfully this is a pretty unusual state. NSPR doesn't help us here -
+  // asserting when polling without the NSPR IO layer on the bottom of
+  // the stack. As a follow-on we can do some NSPR and maybe libssl changes
+  // to make this more event driven, but this is acceptable for getting started.
+
+  uint32_t counter = mNudgeCounter++;
+  uint32_t delay;
+
+  if (!counter) {
+    delay = 0;
+  } else if (counter < 8) { // up to 48ms at 6
+    delay = 6;
+  } else if (counter < 34) { // up to 499 ms at 17ms
+    delay = 17;
+  } else { // after that at 51ms (3 old windows ticks)
+    delay = 51;
+  }
+
+  if(!mTimer) {
+    mTimer = do_CreateInstance("@mozilla.org/timer;1");
+  }
+
+  mNudgeCallback = aCallback;
+  if (!mTimer ||
+      NS_FAILED(mTimer->InitWithCallback(this, delay, nsITimer::TYPE_ONE_SHOT))) {
+    return StartTimerCallback();
+  }
+
+  LOG(("TLSFilterTransaction %p NudgeTunnel timer started\n", this));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSFilterTransaction::Notify(nsITimer *timer)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG(("TLSFilterTransaction %p NudgeTunnel notify\n", this));
+
+  if (timer != mTimer) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  StartTimerCallback();
+  return NS_OK;
+}
+
+nsresult
+TLSFilterTransaction::StartTimerCallback()
+{
+  LOG(("TLSFilterTransaction %p NudgeTunnel StartTimerCallback %p\n",
+       this, mNudgeCallback.get()));
+
+  if (mNudgeCallback) {
+    // This class can be called re-entrantly, so cleanup m* before ->on()
+    nsRefPtr<NudgeTunnelCallback> cb(mNudgeCallback);
+    mNudgeCallback = nullptr;
+    cb->OnTunnelNudged(this);
+  }
+  return NS_OK;
+}
+
+PRStatus
+TLSFilterTransaction::GetPeerName(PRFileDesc *aFD, PRNetAddr*addr)
+{
+  NetAddr peeraddr;
+  TLSFilterTransaction *self = reinterpret_cast<TLSFilterTransaction *>(aFD->secret);
+
+  if (!self->mTransaction ||
+      NS_FAILED(self->mTransaction->Connection()->Transport()->GetPeerAddr(&peeraddr))) {
+    return PR_FAILURE;
+  }
+  NetAddrToPRNetAddr(&peeraddr, addr);
+  return PR_SUCCESS;
+}
+
+PRStatus
+TLSFilterTransaction::GetSocketOption(PRFileDesc *aFD, PRSocketOptionData *aOpt)
+{
+  if (aOpt->option == PR_SockOpt_Nonblocking) {
+    aOpt->value.non_blocking = PR_TRUE;
+    return PR_SUCCESS;
+  }
+  return PR_FAILURE;
+}
+
+PRStatus
+TLSFilterTransaction::SetSocketOption(PRFileDesc *aFD, const PRSocketOptionData *aOpt)
+{
+  return PR_FAILURE;
+}
+
+PRStatus
+TLSFilterTransaction::FilterClose(PRFileDesc *aFD)
+{
+  return PR_SUCCESS;
+}
+
+int32_t
+TLSFilterTransaction::FilterWrite(PRFileDesc *aFD, const void *aBuf, int32_t aAmount)
+{
+  TLSFilterTransaction *self = reinterpret_cast<TLSFilterTransaction *>(aFD->secret);
+  return self->FilterOutput(static_cast<const char *>(aBuf), aAmount);
+}
+
+int32_t
+TLSFilterTransaction::FilterSend(PRFileDesc *aFD, const void *aBuf, int32_t aAmount,
+                                  int , PRIntervalTime)
+{
+  return FilterWrite(aFD, aBuf, aAmount);
+}
+
+int32_t
+TLSFilterTransaction::FilterRead(PRFileDesc *aFD, void *aBuf, int32_t aAmount)
+{
+  TLSFilterTransaction *self = reinterpret_cast<TLSFilterTransaction *>(aFD->secret);
+  return self->FilterInput(static_cast<char *>(aBuf), aAmount);
+}
+
+int32_t
+TLSFilterTransaction::FilterRecv(PRFileDesc *aFD, void *aBuf, int32_t aAmount,
+                                  int , PRIntervalTime)
+{
+  return FilterRead(aFD, aBuf, aAmount);
+}
+
+/////
+// The other methods of TLSFilterTransaction just call mTransaction->method
+/////
+
+void
+TLSFilterTransaction::SetConnection(nsAHttpConnection *aConnection)
+{
+  if (!mTransaction) {
+    return;
+  }
+
+  mTransaction->SetConnection(aConnection);
+}
+
+nsAHttpConnection *
+TLSFilterTransaction::Connection()
+{
+  if (!mTransaction) {
+    return nullptr;
+  }
+  return mTransaction->Connection();
+}
+
+void
+TLSFilterTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **outCB)
+{
+  if (!mTransaction) {
+    return;
+  }
+  mTransaction->GetSecurityCallbacks(outCB);
+}
+
+void
+TLSFilterTransaction::OnTransportStatus(nsITransport* aTransport,
+                                        nsresult aStatus, uint64_t aProgress)
+{
+  if (!mTransaction) {
+    return;
+  }
+  mTransaction->OnTransportStatus(aTransport, aStatus, aProgress);
+}
+
+nsHttpConnectionInfo *
+TLSFilterTransaction::ConnectionInfo()
+{
+  if (!mTransaction) {
+    return nullptr;
+  }
+  return mTransaction->ConnectionInfo();
+}
+
+bool
+TLSFilterTransaction::IsDone()
+{
+  if (!mTransaction) {
+    return true;
+  }
+  return mTransaction->IsDone();
+}
+
+nsresult
+TLSFilterTransaction::Status()
+{
+  if (!mTransaction) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  return mTransaction->Status();
+}
+
+uint32_t
+TLSFilterTransaction::Caps()
+{
+  if (!mTransaction) {
+    return 0;
+  }
+
+  return mTransaction->Caps();
+}
+
+void
+TLSFilterTransaction::SetDNSWasRefreshed()
+{
+  if (!mTransaction) {
+    return;
+  }
+
+  mTransaction->SetDNSWasRefreshed();
+}
+
+uint64_t
+TLSFilterTransaction::Available()
+{
+  if (!mTransaction) {
+    return 0;
+  }
+
+  return mTransaction->Available();
+}
+
+void
+TLSFilterTransaction::SetProxyConnectFailed()
+{
+  if (!mTransaction) {
+    return;
+  }
+
+  mTransaction->SetProxyConnectFailed();
+}
+
+nsHttpRequestHead *
+TLSFilterTransaction::RequestHead()
+{
+  if (!mTransaction) {
+    return nullptr;
+  }
+
+  return mTransaction->RequestHead();
+}
+
+uint32_t
+TLSFilterTransaction::Http1xTransactionCount()
+{
+  if (!mTransaction) {
+    return 0;
+  }
+
+  return mTransaction->Http1xTransactionCount();
+}
+
+nsresult
+TLSFilterTransaction::TakeSubTransactions(
+  nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions)
+{
+  LOG(("TLSFilterTransaction::TakeSubTransactions [this=%p] mTransaction %p\n",
+       this, mTransaction.get()));
+
+  if (!mTransaction) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  outTransactions.AppendElement(mTransaction);
+  mTransaction = nullptr;
+  return NS_OK;
+}
+
+nsresult
+TLSFilterTransaction::AddTransaction(nsAHttpTransaction *aTrans)
+{
+  LOG(("TLSFilterTransaction::AddTransaction [this=%p] aTrans=%p\n",
+       this, aTrans));
+
+  mTransaction = aTrans;
+  nsCOMPtr<nsIInterfaceRequestor> callbacks;
+  mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+  nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
+  if (secCtrl && callbacks) {
+    secCtrl->SetNotificationCallbacks(callbacks);
+  }
+
+  return NS_OK;
+}
+
+uint32_t
+TLSFilterTransaction::PipelineDepth()
+{
+  if (!mTransaction) {
+    return 0;
+  }
+
+  return mTransaction->PipelineDepth();
+}
+
+nsresult
+TLSFilterTransaction::SetPipelinePosition(int32_t aPosition)
+{
+  if (!mTransaction) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  return mTransaction->SetPipelinePosition(aPosition);
+}
+
+int32_t
+TLSFilterTransaction::PipelinePosition()
+{
+  if (!mTransaction) {
+    return 0;
+  }
+
+  return mTransaction->PipelinePosition();
+}
+
+
+class SocketTransportShim : public nsISocketTransport
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITRANSPORT
+  NS_DECL_NSISOCKETTRANSPORT
+
+  SocketTransportShim(nsISocketTransport *aWrapped)
+    : mWrapped(aWrapped)
+  {};
+
+  virtual ~SocketTransportShim() {};
+
+private:
+  nsCOMPtr<nsISocketTransport> mWrapped;
+};
+
+class OutputStreamShim : public nsIAsyncOutputStream
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOUTPUTSTREAM
+  NS_DECL_NSIASYNCOUTPUTSTREAM
+
+  friend class SpdyConnectTransaction;
+
+  OutputStreamShim(SpdyConnectTransaction *aTrans)
+    : mCallback(nullptr)
+    , mStatus(NS_OK)
+  {
+    mWeakTrans = do_GetWeakReference(aTrans);
+  }
+
+  virtual ~OutputStreamShim() {};
+
+private:
+  nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
+  nsIOutputStreamCallback *mCallback;
+  nsresult mStatus;
+};
+
+class InputStreamShim : public nsIAsyncInputStream
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIINPUTSTREAM
+  NS_DECL_NSIASYNCINPUTSTREAM
+
+  friend class SpdyConnectTransaction;
+
+  InputStreamShim(SpdyConnectTransaction *aTrans)
+    : mCallback(nullptr)
+    , mStatus(NS_OK)
+  {
+    mWeakTrans = do_GetWeakReference(aTrans);
+  }
+
+  virtual ~InputStreamShim() {};
+
+private:
+  nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
+  nsIInputStreamCallback *mCallback;
+  nsresult mStatus;
+};
+
+SpdyConnectTransaction::SpdyConnectTransaction(nsHttpConnectionInfo *ci,
+                                               nsIInterfaceRequestor *callbacks,
+                                               uint32_t caps,
+                                               nsAHttpTransaction *trans,
+                                               ASpdySession *session)
+  : NullHttpTransaction(ci, callbacks, caps | NS_HTTP_ALLOW_KEEPALIVE)
+  , mConnectStringOffset(0)
+  , mSession(session)
+  , mSegmentReader(nullptr)
+  , mInputDataSize(0)
+  , mInputDataUsed(0)
+  , mInputDataOffset(0)
+  , mOutputDataSize(0)
+  , mOutputDataUsed(0)
+  , mOutputDataOffset(0)
+{
+  LOG(("SpdyConnectTransaction ctor %p\n", this));
+
+  mTimestampSyn = TimeStamp::Now();
+  mRequestHead = new nsHttpRequestHead();
+  nsHttpConnection::MakeConnectString(trans, mRequestHead, mConnectString);
+}
+
+SpdyConnectTransaction::~SpdyConnectTransaction()
+{
+  LOG(("SpdyConnectTransaction dtor %p\n", this));
+  if (mRequestHead) {
+    delete mRequestHead;
+  }
+}
+
+void
+SpdyConnectTransaction::MapStreamToHttpConnection(nsISocketTransport *aTransport,
+                                                  nsHttpConnectionInfo *aConnInfo)
+{
+  mConnInfo = aConnInfo;
+
+  mTunnelTransport = new SocketTransportShim(aTransport);
+  mTunnelStreamIn = new InputStreamShim(this);
+  mTunnelStreamOut = new OutputStreamShim(this);
+  mTunneledConn = new nsHttpConnection();
+
+  // this new http connection has a specific hashkey (i.e. to a particular
+  // host via the tunnel) and is associated with the tunnel streams
+  LOG(("SpdyConnectTransaction new httpconnection %p %s\n",
+       mTunneledConn.get(), aConnInfo->HashKey().get()));
+
+  nsCOMPtr<nsIInterfaceRequestor> callbacks;
+  GetSecurityCallbacks(getter_AddRefs(callbacks));
+  mTunneledConn->SetTransactionCaps(Caps());
+  MOZ_ASSERT(aConnInfo->UsingHttpsProxy());
+  TimeDuration rtt = TimeStamp::Now() - mTimestampSyn;
+  mTunneledConn->Init(aConnInfo,
+                      gHttpHandler->ConnMgr()->MaxRequestDelay(),
+                      mTunnelTransport, mTunnelStreamIn, mTunnelStreamOut,
+                      true, callbacks,
+                      PR_MillisecondsToInterval(
+                        static_cast<uint32_t>(rtt.ToMilliseconds())));
+  mTunneledConn->SetupSecondaryTLS();
+  mTunneledConn->SetInSpdyTunnel(true);
+
+  gHttpHandler->ConnMgr()->ReclaimConnection(mTunneledConn);
+}
+
+nsresult
+SpdyConnectTransaction::Flush(uint32_t count, uint32_t *countRead)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG(("SpdyConnectTransaction::Flush %p count %d avail %d\n",
+       this, count, mOutputDataUsed - mOutputDataOffset));
+
+  if (!mSegmentReader) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  *countRead = 0;
+  count = std::min(count, (mOutputDataUsed - mOutputDataOffset));
+  if (count) {
+    nsresult rv;
+    rv = mSegmentReader->OnReadSegment(mOutputData + mOutputDataOffset,
+                                       count, countRead);
+    if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
+      LOG(("SpdyConnectTransaction::Flush %p Error %x\n", this, rv));
+      CreateShimError(rv);
+      return rv;
+    }
+  }
+
+  mOutputDataOffset += *countRead;
+  if (mOutputDataOffset == mOutputDataUsed) {
+    mOutputDataOffset = mOutputDataUsed = 0;
+  }
+  if (!(*countRead)) {
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+  return NS_OK;
+}
+
+nsresult
+SpdyConnectTransaction::ReadSegments(nsAHttpSegmentReader *reader,
+                                     uint32_t count,
+                                     uint32_t *countRead)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG(("SpdyConnectTransaction::ReadSegments %p count %d\n", this, count));
+
+  mSegmentReader = reader;
+
+  // spdy stream carrying tunnel is not setup yet.
+  if (!mTunneledConn) {
+    uint32_t toWrite = mConnectString.Length() - mConnectStringOffset;
+    toWrite = std::min(toWrite, count);
+    *countRead = toWrite;
+    if (toWrite) {
+      nsresult rv = mSegmentReader->
+        OnReadSegment(mConnectString.BeginReading() + mConnectStringOffset,
+                      toWrite, countRead);
+      mConnectStringOffset += toWrite;
+      if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
+        LOG(("SpdyConnectTransaction::ReadSegments %p OnReadSegmentError %x\n",
+             this, rv));
+        CreateShimError(rv);
+      }
+      return rv;
+    }
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+
+  *countRead = 0;
+  Flush(count, countRead);
+  if (!mTunnelStreamOut->mCallback) {
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+
+  nsresult rv =
+    mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  uint32_t subtotal;
+  count -= *countRead;
+  rv = Flush(count, &subtotal);
+  *countRead += subtotal;
+  return rv;
+}
+
+void
+SpdyConnectTransaction::CreateShimError(nsresult code)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(NS_FAILED(code));
+
+  if (mTunnelStreamOut && NS_SUCCEEDED(mTunnelStreamOut->mStatus)) {
+    mTunnelStreamOut->mStatus = code;
+  }
+
+  if (mTunnelStreamIn && NS_SUCCEEDED(mTunnelStreamIn->mStatus)) {
+    mTunnelStreamIn->mStatus = code;
+  }
+
+  if (mTunnelStreamIn && mTunnelStreamIn->mCallback) {
+    mTunnelStreamIn->mCallback->OnInputStreamReady(mTunnelStreamIn);
+  }
+
+  if (mTunnelStreamOut && mTunnelStreamOut->mCallback) {
+    mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
+  }
+}
+
+nsresult
+SpdyConnectTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
+                                      uint32_t count,
+                                      uint32_t *countWritten)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG(("SpdyConnectTransaction::WriteSegments %p max=%d cb=%p\n",
+       this, count, mTunnelStreamIn->mCallback));
+
+  // first call into the tunnel stream to get the demux'd data out of the
+  // spdy session.
+  EnsureBuffer(mInputData, mInputDataUsed + count, mInputDataUsed, mInputDataSize);
+  nsresult rv = writer->OnWriteSegment(mInputData + mInputDataUsed,
+                                       count, countWritten);
+  if (NS_FAILED(rv)) {
+    if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+      LOG(("SpdyConnectTransaction::Flush %p Error %x\n", this, rv));
+      CreateShimError(rv);
+    }
+    return rv;
+  }
+  mInputDataUsed += *countWritten;
+  LOG(("SpdyConnectTransaction %p %d new bytes [%d total] of ciphered data buffered\n",
+       this, *countWritten, mInputDataUsed - mInputDataOffset));
+
+  if (!mTunnelStreamIn->mCallback) {
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+
+  rv = mTunnelStreamIn->mCallback->OnInputStreamReady(mTunnelStreamIn);
+  LOG(("SpdyConnectTransaction::WriteSegments %p "
+       "after InputStreamReady callback %d total of ciphered data buffered rv=%x\n",
+       this, mInputDataUsed - mInputDataOffset, rv));
+  LOG(("SpdyConnectTransaction::WriteSegments %p "
+       "goodput %p out %llu\n", this, mTunneledConn.get(),
+       mTunneledConn->ContentBytesWritten()));
+  if (NS_SUCCEEDED(rv) && !mTunneledConn->ContentBytesWritten()) {
+    mTunnelStreamOut->AsyncWait(mTunnelStreamOut->mCallback, 0, 0, nullptr);
+  }
+  return rv;
+}
+
+nsHttpRequestHead *
+SpdyConnectTransaction::RequestHead()
+{
+  return mRequestHead;
+}
+
+void
+SpdyConnectTransaction::Close(nsresult code)
+{
+  LOG(("SpdyConnectTransaction close %p %x\n", this, code));
+
+  NullHttpTransaction::Close(code);
+  if (NS_FAILED(code) && (code != NS_BASE_STREAM_WOULD_BLOCK)) {
+    CreateShimError(code);
+  } else {
+    CreateShimError(NS_BASE_STREAM_CLOSED);
+  }
+}
+
+NS_IMETHODIMP
+OutputStreamShim::AsyncWait(nsIOutputStreamCallback *callback,
+                            unsigned int, unsigned int, nsIEventTarget *target)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  bool currentThread;
+
+  if (target &&
+      (NS_FAILED(target->IsOnCurrentThread(&currentThread)) || !currentThread)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  LOG(("OutputStreamShim::AsyncWait %p callback %p\n", this, callback));
+  mCallback = callback;
+  nsRefPtr<SpdyConnectTransaction> trans = do_QueryReferent(mWeakTrans);
+  if (!trans) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<nsAHttpConnection> spdySession(do_QueryObject(trans->mSession));
+  if (!spdySession) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  spdySession->TransactionHasDataToWrite(trans);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::CloseWithStatus(nsresult reason)
+{
+  nsRefPtr<SpdyConnectTransaction> trans = do_QueryReferent(mWeakTrans);
+  if (!trans) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<nsAHttpConnection> spdySession(do_QueryObject(trans->mSession));
+  if (!spdySession) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  spdySession->CloseTransaction(trans, reason);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Close()
+{
+  return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Flush()
+{
+  nsRefPtr<SpdyConnectTransaction> trans = do_QueryReferent(mWeakTrans);
+  if (!trans) {
+    return NS_ERROR_FAILURE;
+  }
+
+  uint32_t count = trans->mOutputDataUsed - trans->mOutputDataOffset;
+  if (!count) {
+    return NS_OK;
+  }
+
+  uint32_t countRead;
+  nsresult rv = trans->Flush(count, &countRead);
+  LOG(("OutputStreamShim::Flush %p before %d after %d\n",
+       this, count, trans->mOutputDataUsed - trans->mOutputDataOffset));
+  return rv;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Write(const char * aBuf, uint32_t aCount, uint32_t *_retval)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  if (NS_FAILED(mStatus)) {
+    return mStatus;
+  }
+
+  nsRefPtr<SpdyConnectTransaction> trans = do_QueryReferent(mWeakTrans);
+  if (!trans) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if ((trans->mOutputDataUsed + aCount) >= 512000) {
+    *_retval = 0;
+    // time for some flow control;
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+
+  EnsureBuffer(trans->mOutputData, trans->mOutputDataUsed + aCount,
+               trans->mOutputDataUsed, trans->mOutputDataSize);
+  memcpy(trans->mOutputData + trans->mOutputDataUsed,
+          aBuf, aCount);
+  trans->mOutputDataUsed += aCount;
+  *_retval = aCount;
+  LOG(("OutputStreamShim::Write %p new %d total %d\n", this, aCount, trans->mOutputDataUsed));
+
+  nsRefPtr<nsAHttpConnection> spdySession(do_QueryObject(trans->mSession));
+  if (!spdySession) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  spdySession->TransactionHasDataToWrite(trans);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, uint32_t *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::WriteSegments(nsReadSegmentFun aReader, void *aClosure, uint32_t aCount, uint32_t *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::IsNonBlocking(bool *_retval)
+{
+  *_retval = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::AsyncWait(nsIInputStreamCallback *callback,
+                           unsigned int, unsigned int, nsIEventTarget *target)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  bool currentThread;
+
+  if (target &&
+      (NS_FAILED(target->IsOnCurrentThread(&currentThread)) || !currentThread)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  LOG(("InputStreamShim::AsyncWait %p callback %p\n", this, callback));
+  mCallback = callback;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::CloseWithStatus(nsresult reason)
+{
+  nsRefPtr<SpdyConnectTransaction> trans = do_QueryReferent(mWeakTrans);
+  if (!trans) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<nsAHttpConnection> spdySession(do_QueryObject(trans->mSession));
+  if (!spdySession) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  spdySession->CloseTransaction(trans, reason);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::Close()
+{
+  return CloseWithStatus(NS_OK);
+}
+
+NS_IMETHODIMP
+InputStreamShim::Available(uint64_t *_retval)
+{
+  nsRefPtr<SpdyConnectTransaction> trans = do_QueryReferent(mWeakTrans);
+  if (!trans) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *_retval = trans->mInputDataUsed - trans->mInputDataOffset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::Read(char *aBuf, uint32_t aCount, uint32_t *_retval)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  if (NS_FAILED(mStatus)) {
+    return mStatus;
+  }
+
+  nsRefPtr<SpdyConnectTransaction> trans = do_QueryReferent(mWeakTrans);
+  if (!trans) {
+    return NS_ERROR_FAILURE;
+  }
+
+  uint32_t avail = trans->mInputDataUsed - trans->mInputDataOffset;
+  uint32_t tocopy = std::min(aCount, avail);
+  *_retval = tocopy;
+  memcpy(aBuf, trans->mInputData + trans->mInputDataOffset, tocopy);
+  trans->mInputDataOffset += tocopy;
+  if (trans->mInputDataOffset == trans->mInputDataUsed) {
+    trans->mInputDataOffset = trans->mInputDataUsed = 0;
+  }
+
+  return tocopy ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
+                              uint32_t aCount, uint32_t *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputStreamShim::IsNonBlocking(bool *_retval)
+{
+  *_retval = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveEnabled(bool aKeepaliveEnabled)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveVals(int32_t keepaliveIdleTime, int32_t keepaliveRetryInterval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetSecurityCallbacks(nsIInterfaceRequestor *aSecurityCallbacks)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+                                     uint32_t aSegmentCount, nsIInputStream * *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+                                      uint32_t aSegmentCount, nsIOutputStream * *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Close(nsresult aReason)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetEventSink(nsITransportEventSink *aSink, nsIEventTarget *aEventTarget)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#define FWD_TS_PTR(fx, ts) NS_IMETHODIMP \
+SocketTransportShim::fx(ts *arg) { return mWrapped->fx(arg); }
+
+#define FWD_TS_ADDREF(fx, ts) NS_IMETHODIMP \
+SocketTransportShim::fx(ts **arg) { return mWrapped->fx(arg); }
+
+#define FWD_TS(fx, ts) NS_IMETHODIMP \
+SocketTransportShim::fx(ts arg) { return mWrapped->fx(arg); }
+
+FWD_TS_PTR(GetKeepaliveEnabled, bool);
+FWD_TS_PTR(GetSendBufferSize, uint32_t);
+FWD_TS(SetSendBufferSize, uint32_t);
+FWD_TS_PTR(GetPort, int32_t);
+FWD_TS_PTR(GetPeerAddr, mozilla::net::NetAddr);
+FWD_TS_PTR(GetSelfAddr, mozilla::net::NetAddr);
+FWD_TS_ADDREF(GetScriptablePeerAddr, nsINetAddr);
+FWD_TS_ADDREF(GetScriptableSelfAddr, nsINetAddr);
+FWD_TS_ADDREF(GetSecurityInfo, nsISupports);
+FWD_TS_ADDREF(GetSecurityCallbacks, nsIInterfaceRequestor);
+FWD_TS_PTR(IsAlive, bool);
+FWD_TS_PTR(GetConnectionFlags, uint32_t);
+FWD_TS(SetConnectionFlags, uint32_t);
+FWD_TS_PTR(GetRecvBufferSize, uint32_t);
+FWD_TS(SetRecvBufferSize, uint32_t);
+
+NS_IMETHODIMP
+SocketTransportShim::GetHost(nsACString & aHost)
+{
+  return mWrapped->GetHost(aHost);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetTimeout(uint32_t aType, uint32_t *_retval)
+{
+  return mWrapped->GetTimeout(aType, _retval);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetTimeout(uint32_t aType, uint32_t aValue)
+{
+  return mWrapped->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetQoSBits(uint8_t *aQoSBits)
+{
+  return mWrapped->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetQoSBits(uint8_t aQoSBits)
+{
+  return mWrapped->SetQoSBits(aQoSBits);
+}
+
+NS_IMPL_ISUPPORTS(TLSFilterTransaction, nsITimerCallback)
+NS_IMPL_ISUPPORTS(SocketTransportShim, nsISocketTransport, nsITransport)
+NS_IMPL_ISUPPORTS(InputStreamShim, nsIInputStream, nsIAsyncInputStream)
+NS_IMPL_ISUPPORTS(OutputStreamShim, nsIOutputStream, nsIAsyncOutputStream)
+
+} // namespace mozilla::net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.h
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* 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_TLSFilterTransaction_h
+#define mozilla_net_TLSFilterTransaction_h
+
+#include "mozilla/Attributes.h"
+#include "nsAHttpTransaction.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsISocketTransport.h"
+#include "nsITimer.h"
+#include "NullHttpTransaction.h"
+
+// a TLSFilterTransaction wraps another nsAHttpTransaction but
+// applies a encode/decode filter of TLS onto the ReadSegments
+// and WriteSegments data. It is not used for basic https://
+// but it is used for supplemental TLS tunnels - such as those
+// needed by CONNECT tunnels in HTTP/2 or even CONNECT tunnels when
+// the underlying proxy connection is already running TLS
+//
+// HTTP/2 CONNECT tunnels cannot use pushed IO layers because of
+// the multiplexing involved on the base stream. i.e. the base stream
+// once it is decrypted may have parts that are encrypted with a
+// variety of keys, or none at all
+
+/* ************************************************************************
+The input path of http over a spdy CONNECT tunnel once it is established as a stream
+
+note the "real http transaction" can be either a http/1 transaction or another spdy session
+inside the tunnel.
+
+  nsHttpConnection::OnInputStreamReady (real socket)
+  nsHttpConnection::OnSocketReadable()
+  SpdySession::WriteSegment()
+  SpdyStream::WriteSegment (tunnel stream)
+  SpdyConnectTransaction::WriteSegment
+  SpdyStream::OnWriteSegment(tunnel stream)
+  SpdySession::OnWriteSegment()
+  SpdySession::NetworkRead()
+  nsHttpConnection::OnWriteSegment (real socket)
+  realSocketIn->Read() return data from network
+
+now pop the stack back up to SpdyConnectTransaction::WriteSegment, the data
+that has been read is stored mInputData
+
+  SpdyConnectTransaction.mTunneledConn::OnInputStreamReady(mTunnelStreamIn)
+  SpdyConnectTransaction.mTunneledConn::OnSocketReadable()
+  TLSFilterTransaction::WriteSegment()
+  nsHttpTransaction::WriteSegment(real http transaction)
+  TLSFilterTransaction::OnWriteSegment() removes tls on way back up stack
+  SpdyConnectTransaction.mTunneledConn::OnWriteSegment()
+  SpdyConnectTransaction.mTunneledConn.mTunnelStreamIn->Read() // gets data from mInputData
+
+The output path works similarly:
+  nsHttpConnection::OnOutputStreamReady (real socket)
+  nsHttpConnection::OnSocketWritable()
+  SpdySession::ReadSegments (locates tunnel)
+  SpdyStream::ReadSegments (tunnel stream)
+  SpdyConnectTransaction::ReadSegments()
+  SpdyConnectTransaction.mTunneledConn::OnOutputStreamReady (tunnel connection)
+  SpdyConnectTransaction.mTunneledConn::OnSocketWritable (tunnel connection)
+  TLSFilterTransaction::ReadSegment()
+  nsHttpTransaction::ReadSegment (real http transaction generates plaintext on way down)
+  TLSFilterTransaction::OnReadSegment (BUF and LEN gets encrypted here on way down)
+  SpdyConnectTransaction.mTunneledConn::OnReadSegment (BUF and LEN) (tunnel connection)
+  SpdyConnectTransaction.mTunneledConn.mTunnelStreamOut->Write(BUF, LEN) .. get stored in mOutputData
+
+Now pop the stack back up to SpdyConnectTransaction::ReadSegment(), where it has
+the encrypted text available in mOutputData
+
+  SpdyStream->OnReadSegment(BUF,LEN) from mOutputData. Tunnel stream
+  SpdySession->OnReadSegment() // encrypted data gets put in a data frame
+  nsHttpConnection->OnReadSegment()
+  realSocketOut->write() writes data to network
+
+**************************************************************************/
+
+struct PRSocketOptionData;
+
+namespace mozilla { namespace net {
+
+class nsHttpRequestHead;
+class TLSFilterTransaction;
+
+class NudgeTunnelCallback : public nsISupports
+{
+public:
+  virtual void OnTunnelNudged(TLSFilterTransaction *) = 0;
+};
+
+#define NS_DECL_NUDGETUNNELCALLBACK void OnTunnelNudged(TLSFilterTransaction *);
+
+class TLSFilterTransaction MOZ_FINAL
+  : public nsAHttpTransaction
+  , public nsAHttpSegmentReader
+  , public nsAHttpSegmentWriter
+  , public nsITimerCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSAHTTPTRANSACTION
+  NS_DECL_NSAHTTPSEGMENTREADER
+  NS_DECL_NSAHTTPSEGMENTWRITER
+  NS_DECL_NSITIMERCALLBACK
+
+  TLSFilterTransaction(nsAHttpTransaction *aWrappedTransaction,
+                       const char *tlsHost, int32_t tlsPort);
+  ~TLSFilterTransaction();
+
+  const nsAHttpTransaction *Transaction() const { return mTransaction.get(); }
+  nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment);
+  nsresult GetTransactionSecurityInfo(nsISupports **);
+  nsresult NudgeTunnel(NudgeTunnelCallback *callback,
+                       nsAHttpSegmentReader *reader,
+                       nsAHttpSegmentWriter *writer);
+
+private:
+  nsresult StartTimerCallback();
+  void Cleanup();
+  int32_t FilterOutput(const char *aBuf, int32_t aAmount);
+  int32_t FilterInput(char *aBuf, int32_t aAmount);
+
+  static PRStatus GetPeerName(PRFileDesc *fd, PRNetAddr*addr);
+  static PRStatus GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data);
+  static PRStatus SetSocketOption(PRFileDesc *fd, const PRSocketOptionData *data);
+  static int32_t FilterWrite(PRFileDesc *fd, const void *buf, int32_t amount);
+  static int32_t FilterRead(PRFileDesc *fd, void *buf, int32_t amount);
+  static int32_t FilterSend(PRFileDesc *fd, const void *buf, int32_t amount, int flags,
+                             PRIntervalTime timeout);
+  static int32_t FilterRecv(PRFileDesc *fd, void *buf, int32_t amount, int flags,
+                             PRIntervalTime timeout);
+  static PRStatus FilterClose(PRFileDesc *fd);
+
+private:
+  nsRefPtr<nsAHttpTransaction> mTransaction;
+  nsCOMPtr<nsISupports> mSecInfo;
+  nsCOMPtr<nsITimer> mTimer;
+  nsRefPtr<NudgeTunnelCallback> mNudgeCallback;
+
+  // buffered network output, after encryption
+  nsAutoArrayPtr<char> mEncryptedText;
+  uint32_t mEncryptedTextUsed;
+  uint32_t mEncryptedTextSize;
+
+  PRFileDesc *mFD;
+  nsAHttpSegmentReader *mSegmentReader;
+  nsAHttpSegmentWriter *mSegmentWriter;
+
+  nsresult mFilterReadCode;
+  bool mForce;
+  bool mReadSegmentBlocked;
+  uint32_t mNudgeCounter;
+};
+
+class SocketTransportShim;
+class InputStreamShim;
+class OutputStreamShim;
+class nsHttpConnection;
+class ASpdySession;
+
+class SpdyConnectTransaction MOZ_FINAL : public NullHttpTransaction
+{
+public:
+  SpdyConnectTransaction(nsHttpConnectionInfo *ci,
+                         nsIInterfaceRequestor *callbacks,
+                         uint32_t caps,
+                         nsAHttpTransaction *trans,
+                         ASpdySession *session);
+  ~SpdyConnectTransaction();
+
+  void MapStreamToHttpConnection(nsISocketTransport *aTransport,
+                                 nsHttpConnectionInfo *aConnInfo);
+
+  nsresult ReadSegments(nsAHttpSegmentReader *reader,
+                        uint32_t count, uint32_t *countRead) MOZ_OVERRIDE MOZ_FINAL;
+  nsresult WriteSegments(nsAHttpSegmentWriter *writer,
+                         uint32_t count, uint32_t *countWritten) MOZ_OVERRIDE MOZ_FINAL;
+  nsHttpRequestHead *RequestHead() MOZ_OVERRIDE MOZ_FINAL;
+  void Close(nsresult reason) MOZ_OVERRIDE MOZ_FINAL;
+
+private:
+  friend class InputStreamShim;
+  friend class OutputStreamShim;
+
+  nsresult Flush(uint32_t count, uint32_t *countRead);
+  void CreateShimError(nsresult code);
+
+  nsCString             mConnectString;
+  uint32_t              mConnectStringOffset;
+  nsHttpRequestHead     *mRequestHead;
+
+  ASpdySession         *mSession;
+  nsAHttpSegmentReader *mSegmentReader;
+
+  nsAutoArrayPtr<char> mInputData;
+  uint32_t             mInputDataSize;
+  uint32_t             mInputDataUsed;
+  uint32_t             mInputDataOffset;
+
+  nsAutoArrayPtr<char> mOutputData;
+  uint32_t             mOutputDataSize;
+  uint32_t             mOutputDataUsed;
+  uint32_t             mOutputDataOffset;
+
+  TimeStamp                      mTimestampSyn;
+  nsRefPtr<nsHttpConnection>     mTunneledConn;
+  nsRefPtr<nsHttpConnectionInfo> mConnInfo;
+  nsRefPtr<SocketTransportShim>  mTunnelTransport;
+  nsRefPtr<InputStreamShim>      mTunnelStreamIn;
+  nsRefPtr<OutputStreamShim>     mTunnelStreamOut;
+};
+
+}} // namespace mozilla::net
+
+#endif // mozilla_net_TLSFilterTransaction_h
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -73,16 +73,17 @@ SOURCES += [
     'NullHttpTransaction.cpp',
     'SpdyPush3.cpp',
     'SpdyPush31.cpp',
     'SpdySession3.cpp',
     'SpdySession31.cpp',
     'SpdyStream3.cpp',
     'SpdyStream31.cpp',
     'SpdyZlibReporter.cpp',
+    'TunnelUtils.cpp',
 ]
 
 # These files cannot be built in unified mode because of OS X headers.
 SOURCES += [
     'nsHttpHandler.cpp',
 ]
 
 IPDL_SOURCES += [
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -43,18 +43,19 @@ public:
     //
     // called by a transaction to resume either sending or receiving data
     // after a transaction returned NS_BASE_STREAM_WOULD_BLOCK from its
     // ReadSegments/WriteSegments methods.
     //
     virtual nsresult ResumeSend() = 0;
     virtual nsresult ResumeRecv() = 0;
 
-    // called by a transaction to force a "read from network" iteration
+    // called by a transaction to force a "send/recv from network" iteration
     // even if not scheduled by socket associated with connection
+    virtual nsresult ForceSend() = 0;
     virtual nsresult ForceRecv() = 0;
 
     // After a connection has had ResumeSend() called by a transaction,
     // and it is ready to write to the network it may need to know the
     // transaction that has data to write. This is only an issue for
     // multiplexed protocols like SPDY - plain HTTP or pipelined HTTP
     // implicitly have this information in a 1:1 relationship with the
     // transaction(s) they manage.
@@ -175,16 +176,22 @@ public:
         return (fwdObject)->ResumeSend();  \
     }                                      \
     nsresult ResumeRecv()                  \
     {                                      \
         if (!(fwdObject))                  \
             return NS_ERROR_FAILURE;       \
         return (fwdObject)->ResumeRecv();  \
     }                                      \
+    nsresult ForceSend()                   \
+    {                                      \
+        if (!(fwdObject))                  \
+            return NS_ERROR_FAILURE;       \
+        return (fwdObject)->ForceSend();   \
+    }                                      \
     nsresult ForceRecv()                   \
     {                                      \
         if (!(fwdObject))                  \
             return NS_ERROR_FAILURE;       \
         return (fwdObject)->ForceRecv();   \
     }                                      \
     nsISocketTransport *Transport()        \
     {                                      \
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -2,16 +2,17 @@
  * 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 nsAHttpTransaction_h__
 #define nsAHttpTransaction_h__
 
 #include "nsISupports.h"
 #include "nsTArray.h"
+#include "nsWeakReference.h"
 
 class nsIInterfaceRequestor;
 class nsIEventTarget;
 class nsITransport;
 class nsILoadGroupConnectionInfo;
 
 namespace mozilla { namespace net {
 
@@ -27,17 +28,17 @@ class nsHttpConnectionInfo;
 // Abstract base class for a HTTP transaction:
 //
 // A transaction is a "sink" for the response data.  The connection pushes
 // data to the transaction by writing to it.  The transaction supports
 // WriteSegments and may refuse to accept data if its buffers are full (its
 // write function returns NS_BASE_STREAM_WOULD_BLOCK in this case).
 //----------------------------------------------------------------------------
 
-class nsAHttpTransaction : public nsISupports
+class nsAHttpTransaction : public nsSupportsWeakReference
 {
 public:
     // called by the connection when it takes ownership of the transaction.
     virtual void SetConnection(nsAHttpConnection *) = 0;
 
     // used to obtain the connection associated with this transaction
     virtual nsAHttpConnection *Connection() = 0;
 
@@ -157,35 +158,46 @@ public:
         CLASS_SOLO,
 
         // Transactions that do not fit any of the other categories. HTML
         // is normally GENERAL.
         CLASS_GENERAL,
 
         CLASS_MAX
     };
+
+    // conceptually the security info is part of the connection, but sometimes
+    // in the case of TLS tunneled within TLS the transaction might present
+    // a more specific security info that cannot be represented as a layer in
+    // the connection due to multiplexing. This interface represents such an
+    // overload. If it returns NS_FAILURE the connection should be considered
+    // authoritative.
+    virtual nsresult GetTransactionSecurityInfo(nsISupports **)
+    {
+        return NS_ERROR_NOT_IMPLEMENTED;
+    }
 };
 
 #define NS_DECL_NSAHTTPTRANSACTION \
     void SetConnection(nsAHttpConnection *); \
     nsAHttpConnection *Connection(); \
     void GetSecurityCallbacks(nsIInterfaceRequestor **);       \
     void OnTransportStatus(nsITransport* transport, \
                            nsresult status, uint64_t progress); \
     bool     IsDone(); \
     nsresult Status(); \
     uint32_t Caps();   \
     void     SetDNSWasRefreshed(); \
     uint64_t Available(); \
-    nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *); \
-    nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *); \
+    virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *); \
+    virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *); \
     void     Close(nsresult reason);                                    \
     nsHttpConnectionInfo *ConnectionInfo();                             \
     void     SetProxyConnectFailed();                                   \
-    nsHttpRequestHead *RequestHead();                                   \
+    virtual nsHttpRequestHead *RequestHead();                                   \
     uint32_t Http1xTransactionCount();                                  \
     nsresult TakeSubTransactions(nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions); \
     nsresult AddTransaction(nsAHttpTransaction *);                      \
     uint32_t PipelineDepth();                                           \
     nsresult SetPipelinePosition(int32_t);                              \
     int32_t  PipelinePosition();
 
 //-----------------------------------------------------------------------------
--- a/netwerk/protocol/http/nsHttpAtomList.h
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -81,10 +81,11 @@ HTTP_ATOM(Transfer_Encoding,         "Tr
 HTTP_ATOM(URI,                       "URI")
 HTTP_ATOM(Upgrade,                   "Upgrade")
 HTTP_ATOM(User_Agent,                "User-Agent")
 HTTP_ATOM(Vary,                      "Vary")
 HTTP_ATOM(Version,                   "Version")
 HTTP_ATOM(WWW_Authenticate,          "WWW-Authenticate")
 HTTP_ATOM(Warning,                   "Warning")
 HTTP_ATOM(X_Firefox_Spdy,            "X-Firefox-Spdy")
+HTTP_ATOM(X_Firefox_Spdy_Proxy,      "X-Firefox-Spdy-Proxy")
 
 // methods are case sensitive and do not use atom table
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -8,35 +8,35 @@
 #include "HttpLog.h"
 
 // Log on level :5, instead of default :4.
 #undef LOG
 #define LOG(args) LOG5(args)
 #undef LOG_ENABLED
 #define LOG_ENABLED() LOG5_ENABLED()
 
+#include "ASpdySession.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/Telemetry.h"
 #include "nsHttpConnection.h"
+#include "nsHttpHandler.h"
+#include "nsHttpPipeline.h"
 #include "nsHttpRequestHead.h"
 #include "nsHttpResponseHead.h"
-#include "nsHttpHandler.h"
 #include "nsIOService.h"
 #include "nsISocketTransport.h"
 #include "nsSocketTransportService2.h"
 #include "nsISSLSocketControl.h"
-#include "sslt.h"
-#include "nsStringStream.h"
+#include "nsISupportsPriority.h"
+#include "nsPreloadedStream.h"
 #include "nsProxyRelease.h"
 #include "nsSocketTransport2.h"
-#include "nsPreloadedStream.h"
-#include "ASpdySession.h"
-#include "mozilla/Telemetry.h"
-#include "nsISupportsPriority.h"
-#include "nsHttpPipeline.h"
-#include <algorithm>
-#include "mozilla/ChaosMode.h"
+#include "nsStringStream.h"
+#include "sslt.h"
+#include "TunnelUtils.h"
 
 #ifdef DEBUG
 // defined by the socket transport service while active
 extern PRThread *gSocketThread;
 #endif
 
 namespace mozilla {
 namespace net {
@@ -50,27 +50,29 @@ nsHttpConnection::nsHttpConnection()
     , mHttpHandler(gHttpHandler)
     , mCallbacksLock("nsHttpConnection::mCallbacksLock")
     , mConsiderReusedAfterInterval(0)
     , mConsiderReusedAfterEpoch(0)
     , mCurrentBytesRead(0)
     , mMaxBytesRead(0)
     , mTotalBytesRead(0)
     , mTotalBytesWritten(0)
+    , mContentBytesWritten(0)
     , mConnectedTransport(false)
     , mKeepAlive(true) // assume to keep-alive by default
     , mKeepAliveMask(true)
     , mDontReuse(false)
     , mSupportsPipelining(false) // assume low-grade server
     , mIsReused(false)
     , mCompletedProxyConnect(false)
     , mLastTransactionExpectedNoContent(false)
     , mIdleMonitoring(false)
     , mProxyConnectInProgress(false)
     , mExperienced(false)
+    , mInSpdyTunnel(false)
     , mHttp1xTransactionCount(0)
     , mRemainingConnectionUses(0xffffffff)
     , mClassification(nsAHttpTransaction::CLASS_GENERAL)
     , mNPNComplete(false)
     , mSetupSSLCalled(false)
     , mUsingSpdyVersion(0)
     , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
     , mReportedSpdy(false)
@@ -116,58 +118,56 @@ nsHttpConnection::Init(nsHttpConnectionI
                        uint16_t maxHangTime,
                        nsISocketTransport *transport,
                        nsIAsyncInputStream *instream,
                        nsIAsyncOutputStream *outstream,
                        bool connectedTransport,
                        nsIInterfaceRequestor *callbacks,
                        PRIntervalTime rtt)
 {
-    MOZ_ASSERT(transport && instream && outstream,
-               "invalid socket information");
-    LOG(("nsHttpConnection::Init [this=%p "
-         "transport=%p instream=%p outstream=%p rtt=%d]\n",
-         this, transport, instream, outstream,
-         PR_IntervalToMilliseconds(rtt)));
-
+    LOG(("nsHttpConnection::Init this=%p", this));
     NS_ENSURE_ARG_POINTER(info);
     NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
 
     mConnectedTransport = connectedTransport;
     mConnInfo = info;
     mLastWriteTime = mLastReadTime = PR_IntervalNow();
     mSupportsPipelining =
         gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
     mRtt = rtt;
     mMaxHangTime = PR_SecondsToInterval(maxHangTime);
 
     mSocketTransport = transport;
     mSocketIn = instream;
     mSocketOut = outstream;
-    nsresult rv = mSocketTransport->SetEventSink(this, nullptr);
-    NS_ENSURE_SUCCESS(rv, rv);
 
     // See explanation for non-strictness of this operation in SetSecurityCallbacks.
     mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(callbacks, false);
-    rv = mSocketTransport->SetSecurityCallbacks(this);
-    NS_ENSURE_SUCCESS(rv, rv);
+
+    mSocketTransport->SetEventSink(this, nullptr);
+    mSocketTransport->SetSecurityCallbacks(this);
 
     return NS_OK;
 }
 
 void
 nsHttpConnection::StartSpdy(uint8_t spdyVersion)
 {
     LOG(("nsHttpConnection::StartSpdy [this=%p]\n", this));
 
     MOZ_ASSERT(!mSpdySession);
 
     mUsingSpdyVersion = spdyVersion;
     mEverUsedSpdy = true;
 
+    if (!mReportedSpdy) {
+        mReportedSpdy = true;
+        gHttpHandler->ConnMgr()->ReportSpdyConnection(this, true);
+    }
+
     // Setting the connection as reused allows some transactions that fail
     // with NS_ERROR_NET_RESET to be restarted and SPDY uses that code
     // to handle clean rejections (such as those that arrived after
     // a server goaway was generated).
     mIsReused = true;
 
     // If mTransaction is a pipeline object it might represent
     // several requests. If so, we need to unpack that and
@@ -192,143 +192,178 @@ nsHttpConnection::StartSpdy(uint8_t spdy
         LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
         MOZ_ASSERT(false,
                    "unexpected result from "
                    "nsAHttpTransaction::TakeSubTransactions()");
         mTransaction->Close(NS_ERROR_ABORT);
         return;
     }
 
+    if (NeedSpdyTunnel()) {
+        LOG3(("nsHttpConnection::StartSpdy %p Connecting To a HTTP/2 "
+              "Proxy and Need Connect", this));
+        MOZ_ASSERT(mProxyConnectStream);
+
+        mProxyConnectStream = nullptr;
+        mCompletedProxyConnect = true;
+        mProxyConnectInProgress = false;
+    }
+
     mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport);
+    bool spdyProxy = mConnInfo->UsingHttpsProxy() && !mTLSFilter;
+    if (spdyProxy) {
+        nsRefPtr<nsHttpConnectionInfo> wildCardProxyCi;
+        mConnInfo->CreateWildCard(getter_AddRefs(wildCardProxyCi));
+        gHttpHandler->ConnMgr()->MoveToWildCardConnEntry(mConnInfo,
+                                                         wildCardProxyCi, this);
+        mConnInfo = wildCardProxyCi;
+    }
+
     if (NS_FAILED(rv)) { // includes NS_ERROR_NOT_IMPLEMENTED
         MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty");
 
         // This is ok - treat mTransaction as a single real request.
         // Wrap the old http transaction into the new spdy session
         // as the first stream.
-        if (!mSpdySession->AddStream(mTransaction, mPriority)) {
-            MOZ_ASSERT(false); // this cannot happen!
-            mTransaction->Close(NS_ERROR_ABORT);
+        rv = AddTransaction(mTransaction, mPriority);
+        if (NS_FAILED(rv)) {
             return;
         }
         LOG(("nsHttpConnection::StartSpdy moves single transaction %p "
              "into SpdySession %p\n", mTransaction.get(), mSpdySession.get()));
-    }
-    else {
+    } else {
         int32_t count = list.Length();
 
         LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d "
              "into SpdySession %p\n", count, mSpdySession.get()));
 
         if (!count) {
             mTransaction->Close(NS_ERROR_ABORT);
             return;
         }
 
         for (int32_t index = 0; index < count; ++index) {
-            // AddStream() cannot fail
-            if (!mSpdySession->AddStream(list[index], mPriority)) {
-                MOZ_ASSERT(false, "SpdySession::AddStream failed");
-                LOG(("SpdySession::AddStream failed\n"));
-                mTransaction->Close(NS_ERROR_ABORT);
+            rv = AddTransaction(list[index], mPriority);
+            if (NS_FAILED(rv)) {
                 return;
             }
         }
     }
 
     // Disable TCP Keepalives - use SPDY ping instead.
     rv = DisableTCPKeepalives();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (NS_FAILED(rv)) {
         LOG(("nsHttpConnection::StartSpdy [%p] DisableTCPKeepalives failed "
              "rv[0x%x]", this, rv));
     }
 
-    mSupportsPipelining = false; // dont use http/1 pipelines with spdy
-    mTransaction = mSpdySession;
+    mSupportsPipelining = false; // don't use http/1 pipelines with spdy
     mIdleTimeout = gHttpHandler->SpdyTimeout();
+
+    if (!mTLSFilter) {
+        mTransaction = mSpdySession;
+    } else {
+        mTLSFilter->AddTransaction(mSpdySession);
+    }
 }
 
 bool
 nsHttpConnection::EnsureNPNComplete()
 {
     // If for some reason the components to check on NPN aren't available,
     // this function will just return true to continue on and disable SPDY
 
     MOZ_ASSERT(mSocketTransport);
     if (!mSocketTransport) {
         // this cannot happen
         mNPNComplete = true;
         return true;
     }
 
-    if (mNPNComplete)
+    if (mNPNComplete) {
         return true;
+    }
 
     nsresult rv;
-
     nsCOMPtr<nsISupports> securityInfo;
     nsCOMPtr<nsISSLSocketControl> ssl;
     nsAutoCString negotiatedNPN;
 
-    rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
-    if (NS_FAILED(rv))
+    GetSecurityInfo(getter_AddRefs(securityInfo));
+    if (!securityInfo) {
         goto npnComplete;
+    }
 
     ssl = do_QueryInterface(securityInfo, &rv);
     if (NS_FAILED(rv))
         goto npnComplete;
 
     rv = ssl->GetNegotiatedNPN(negotiatedNPN);
     if (rv == NS_ERROR_NOT_CONNECTED) {
-
         // By writing 0 bytes to the socket the SSL handshake machine is
         // pushed forward.
         uint32_t count = 0;
         rv = mSocketOut->Write("", 0, &count);
+        if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+            goto npnComplete;
+        }
 
-        if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK)
-            goto npnComplete;
         return false;
     }
 
-    if (NS_FAILED(rv))
+    if (NS_FAILED(rv)) {
         goto npnComplete;
-
-    LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'\n",
-         this, mConnInfo->Host(), negotiatedNPN.get()));
+    }
+    LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
+         this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
+         mTLSFilter ? " [Double Tunnel]" : ""));
 
     uint8_t spdyVersion;
     rv = gHttpHandler->SpdyInfo()->GetNPNVersionIndex(negotiatedNPN,
                                                       &spdyVersion);
-    if (NS_SUCCEEDED(rv))
+    if (NS_SUCCEEDED(rv)) {
         StartSpdy(spdyVersion);
+    }
 
     Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
 
 npnComplete:
     LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
     mNPNComplete = true;
     return true;
 }
 
+void
+nsHttpConnection::OnTunnelNudged(TLSFilterTransaction *trans)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    LOG(("nsHttpConnection::OnTunnelNudged %p\n", this));
+    if (trans != mTLSFilter) {
+        return;
+    }
+    LOG(("nsHttpConnection::OnTunnelNudged %p Calling OnSocketWritable\n", this));
+    OnSocketWritable();
+}
+
 // called on the socket thread
 nsresult
 nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnection::Activate [this=%p trans=%x caps=%x]\n",
          this, trans, caps));
 
     if (!trans->IsNullTransaction())
         mExperienced = true;
 
     mTransactionCaps = caps;
     mPriority = pri;
-    if (mTransaction && mUsingSpdyVersion)
+    if (mTransaction && mUsingSpdyVersion) {
         return AddTransaction(trans, pri);
+    }
 
     NS_ENSURE_ARG_POINTER(trans);
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
 
     // reset the read timers to wash away any idle time
     mLastWriteTime = mLastReadTime = PR_IntervalNow();
 
     // Connection failures are Activated() just like regular transacions.
@@ -350,18 +385,17 @@ nsHttpConnection::Activate(nsAHttpTransa
             return mSocketOutCondition;
         }
     }
 
     // Update security callbacks
     nsCOMPtr<nsIInterfaceRequestor> callbacks;
     trans->GetSecurityCallbacks(getter_AddRefs(callbacks));
     SetSecurityCallbacks(callbacks);
-
-    SetupSSL(caps);
+    SetupSSL();
 
     // take ownership of the transaction
     mTransaction = trans;
 
     MOZ_ASSERT(!mIdleMonitoring, "Activating a connection with an Idle Monitor");
     mIdleMonitoring = false;
 
     // set mKeepAlive according to what will be requested
@@ -382,69 +416,71 @@ nsHttpConnection::Activate(nsAHttpTransa
 
     // The overflow state is not needed between activations
     mInputOverflow = nullptr;
 
     mResponseTimeoutEnabled = mTransaction->ResponseTimeout() > 0 &&
                               mTransaction->ResponseTimeoutEnabled();
 
     rv = StartShortLivedTCPKeepalives();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (NS_FAILED(rv)) {
         LOG(("nsHttpConnection::Activate [%p] "
              "StartShortLivedTCPKeepalives failed rv[0x%x]",
              this, rv));
     }
 
+    if (mTLSFilter) {
+        mTLSFilter->AddTransaction(trans);
+        mTransaction = mTLSFilter;
+    }
+
     rv = OnOutputStreamReady(mSocketOut);
 
 failed_activation:
     if (NS_FAILED(rv)) {
         mTransaction = nullptr;
     }
 
     return rv;
 }
 
 void
-nsHttpConnection::SetupSSL(uint32_t caps)
+nsHttpConnection::SetupSSL()
 {
-    LOG(("nsHttpConnection::SetupSSL %p caps=0x%X\n", this, caps));
+    LOG(("nsHttpConnection::SetupSSL %p caps=0x%X %s\n",
+         this, mTransactionCaps,mConnInfo->HashKey().get()));
 
     if (mSetupSSLCalled) // do only once
         return;
     mSetupSSLCalled = true;
 
     if (mNPNComplete)
         return;
 
     // we flip this back to false if SetNPNList succeeds at the end
     // of this function
     mNPNComplete = true;
 
-    if (!mConnInfo->EndToEndSSL())
+    if (!mConnInfo->FirstHopSSL()) {
         return;
-
-    LOG(("nsHttpConnection::SetupSSL Setting up "
-         "Next Protocol Negotiation"));
-    nsCOMPtr<nsISupports> securityInfo;
-    nsresult rv =
-        mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
-    if (NS_FAILED(rv))
-        return;
-
-    nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
-    if (NS_FAILED(rv))
-        return;
-
-    if (caps & NS_HTTP_ALLOW_RSA_FALSESTART) {
-        LOG(("nsHttpConnection::SetupSSL %p "
-             ">= RSA Key Exchange Expected\n", this));
-        ssl->SetKEAExpected(ssl_kea_rsa);
     }
 
+    // if we are connected to the proxy with TLS, start the TLS
+    // flow immediately without waiting for a CONNECT sequence.
+    if (mInSpdyTunnel) {
+        InitSSLParams(false, true);
+    } else {
+        bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
+        InitSSLParams(usingHttpsProxy, usingHttpsProxy);
+    }
+}
+
+nsresult
+nsHttpConnection::SetupNPNList(nsISSLSocketControl *ssl, uint32_t caps)
+{
     nsTArray<nsCString> protocolArray;
 
     // The first protocol is used as the fallback if none of the
     // protocols supported overlap with the server's list.
     // In the case of overlap, matching priority is driven by
     // the order of the server's advertisement.
     protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
 
@@ -453,42 +489,51 @@ nsHttpConnection::SetupSSL(uint32_t caps
         LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
         for (uint32_t index = 0; index < SpdyInformation::kCount; ++index) {
             if (gHttpHandler->SpdyInfo()->ProtocolEnabled(index))
                 protocolArray.AppendElement(
                     gHttpHandler->SpdyInfo()->VersionString[index]);
         }
     }
 
-    if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) {
-        LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK"));
-        mNPNComplete = false;
-    }
+    nsresult rv = ssl->SetNPNList(protocolArray);
+    LOG(("nsHttpConnection::SetupNPNList %p %x\n",this, rv));
+    return rv;
 }
 
 nsresult
 nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction,
                                  int32_t priority)
 {
-    LOG(("nsHttpConnection::AddTransaction for SPDY"));
-
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     MOZ_ASSERT(mSpdySession && mUsingSpdyVersion,
                "AddTransaction to live http connection without spdy");
-    MOZ_ASSERT(mTransaction,
-               "AddTransaction to idle http connection");
 
-    if (!mSpdySession->AddStream(httpTransaction, priority)) {
-        MOZ_ASSERT(false, "AddStream should never fail due to"
-                   "RoomForMore() admission check");
+    // If this is a wild card nshttpconnection (i.e. a spdy proxy) then
+    // it is important to start the stream using the specific connection
+    // info of the transaction to ensure it is routed on the right tunnel
+
+    nsHttpConnectionInfo *transCI = httpTransaction->ConnectionInfo();
+
+    bool needTunnel = transCI->UsingHttpsProxy();
+    needTunnel = needTunnel && !mTLSFilter;
+    needTunnel = needTunnel && transCI->UsingConnect();
+    needTunnel = needTunnel && httpTransaction->QueryHttpTransaction();
+
+    LOG(("nsHttpConnection::AddTransaction for SPDY%s",
+         needTunnel ? " over tunnel" : ""));
+
+    if (!mSpdySession->AddStream(httpTransaction, priority,
+                                 needTunnel, mCallbacks)) {
+        MOZ_ASSERT(false); // this cannot happen!
+        httpTransaction->Close(NS_ERROR_ABORT);
         return NS_ERROR_FAILURE;
     }
 
     ResumeSend();
-
     return NS_OK;
 }
 
 void
 nsHttpConnection::Close(nsresult reason)
 {
     LOG(("nsHttpConnection::Close [this=%p reason=%x]\n", this, reason));
 
@@ -499,16 +544,18 @@ nsHttpConnection::Close(nsresult reason)
         mTCPKeepaliveTransitionTimer->Cancel();
         mTCPKeepaliveTransitionTimer = nullptr;
     }
 
     if (NS_FAILED(reason)) {
         if (mIdleMonitoring)
             EndIdleMonitoring();
 
+        mTLSFilter = nullptr;
+
         if (mSocketTransport) {
             mSocketTransport->SetEventSink(nullptr, nullptr);
 
             // If there are bytes sitting in the input queue then read them
             // into a junk buffer to avoid generating a tcp rst by closing a
             // socket with data pending. TLS is a classic case of this where
             // a Alert record might be superfulous to a clean HTTP/SPDY shutdown.
             // Never block to do this and limit it to a small amount of data.
@@ -531,29 +578,58 @@ nsHttpConnection::Close(nsresult reason)
                 mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
         }
         mKeepAlive = false;
     }
 }
 
 // called on the socket thread
 nsresult
-nsHttpConnection::ProxyStartSSL()
+nsHttpConnection::InitSSLParams(bool connectingToProxy, bool proxyStartSSL)
 {
-    LOG(("nsHttpConnection::ProxyStartSSL [this=%p]\n", this));
+    LOG(("nsHttpConnection::InitSSLParams [this=%p] connectingToProxy=%d\n",
+         this, connectingToProxy));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
+    nsresult rv;
     nsCOMPtr<nsISupports> securityInfo;
-    nsresult rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
-    if (NS_FAILED(rv)) return rv;
+    GetSecurityInfo(getter_AddRefs(securityInfo));
+    if (!securityInfo) {
+        return NS_ERROR_FAILURE;
+    }
 
     nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
-    if (NS_FAILED(rv)) return rv;
+    if (NS_FAILED(rv)){
+        return rv;
+    }
 
-    return ssl->ProxyStartSSL();
+    if (proxyStartSSL) {
+        rv = ssl->ProxyStartSSL();
+        if (NS_FAILED(rv)){
+            return rv;
+        }
+    }
+
+    if (NS_SUCCEEDED(SetupNPNList(ssl, mTransactionCaps))) {
+        LOG(("InitSSLParams Setting up SPDY Negotiation OK"));
+        mNPNComplete = false;
+    }
+
+    // transaction caps apply only to origin. we don't track
+    // proxy history.
+    if (!connectingToProxy &&
+        (mTransactionCaps & NS_HTTP_ALLOW_RSA_FALSESTART)) {
+        LOG(("nsHttpConnection::InitSSLParams %p "
+             ">= RSA Key Exchange Expected\n", this));
+        ssl->SetKEAExpected(ssl_kea_rsa);
+    } else {
+        ssl->SetKEAExpected(nsISSLSocketControl::KEY_EXCHANGE_UNKNOWN);
+    }
+
+    return NS_OK;
 }
 
 void
 nsHttpConnection::DontReuse()
 {
     mKeepAliveMask = false;
     mKeepAlive = false;
     mDontReuse = true;
@@ -649,17 +725,17 @@ nsHttpConnection::TimeToLive()
 bool
 nsHttpConnection::IsAlive()
 {
     if (!mSocketTransport || !mConnectedTransport)
         return false;
 
     // SocketTransport::IsAlive can run the SSL state machine, so make sure
     // the NPN options are set before that happens.
-    SetupSSL(mTransactionCaps);
+    SetupSSL();
 
     bool alive;
     nsresult rv = mSocketTransport->IsAlive(&alive);
     if (NS_FAILED(rv))
         alive = false;
 
 //#define TEST_RESTART_LOGIC
 #ifdef TEST_RESTART_LOGIC
@@ -741,16 +817,21 @@ nsHttpConnection::OnHeadersAvailable(nsA
 {
     LOG(("nsHttpConnection::OnHeadersAvailable [this=%p trans=%p response-head=%p]\n",
         this, trans, responseHead));
 
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     NS_ENSURE_ARG_POINTER(trans);
     MOZ_ASSERT(responseHead, "No response head?");
 
+    if (mInSpdyTunnel) {
+        responseHead->SetHeader(nsHttp::X_Firefox_Spdy_Proxy,
+                                NS_LITERAL_CSTRING("true"));
+    }
+
     // we won't change our keep-alive policy unless the server has explicitly
     // told us to do so.
 
     // inspect the connection headers for keep-alive info provided the
     // transaction completed successfully. In the case of a non-sensical close
     // and keep-alive favor the close out of conservatism.
 
     bool explicitKeepAlive = false;
@@ -887,26 +968,31 @@ nsHttpConnection::OnHeadersAvailable(nsA
 
     // If we're doing a proxy connect, we need to check whether or not
     // it was successful.  If so, we have to reset the transaction and step-up
     // the socket connection if using SSL. Finally, we have to wake up the
     // socket write request.
     if (mProxyConnectStream) {
         MOZ_ASSERT(!mUsingSpdyVersion,
                    "SPDY NPN Complete while using proxy connect stream");
-        mProxyConnectStream = 0;
+        mProxyConnectStream = nullptr;
         if (responseStatus == 200) {
             LOG(("proxy CONNECT succeeded! endtoendssl=%s\n",
                  mConnInfo->EndToEndSSL() ? "true" :"false"));
             *reset = true;
             nsresult rv;
             if (mConnInfo->EndToEndSSL()) {
-                rv = ProxyStartSSL();
-                if (NS_FAILED(rv)) // XXX need to handle this for real
-                    LOG(("ProxyStartSSL failed [rv=%x]\n", rv));
+                if (mConnInfo->UsingHttpsProxy()) {
+                    LOG(("%p new TLSFilterTransaction %s %d\n",
+                         this, mConnInfo->Host(), mConnInfo->Port()));
+                    SetupSecondaryTLS();
+                }
+
+                rv = InitSSLParams(false, true);
+                LOG(("InitSSLParams [rv=%x]\n", rv));
             }
             mCompletedProxyConnect = true;
             mProxyConnectInProgress = false;
             rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
             // XXX what if this fails -- need to handle this error
             MOZ_ASSERT(NS_SUCCEEDED(rv), "mSocketOut->AsyncWait failed");
         }
         else {
@@ -983,17 +1069,17 @@ nsHttpConnection::TakeTransport(nsISocke
     if (mTCPKeepaliveConfig == kTCPKeepaliveShortLivedConfig) {
         if (mTCPKeepaliveTransitionTimer) {
             mTCPKeepaliveTransitionTimer->Cancel();
             mTCPKeepaliveTransitionTimer = nullptr;
         }
         nsresult rv = StartLongLivedTCPKeepalives();
         LOG(("nsHttpConnection::TakeTransport [%p] calling "
              "StartLongLivedTCPKeepalives", this));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
+        if (NS_FAILED(rv)) {
             LOG(("nsHttpConnection::TakeTransport [%p] "
                  "StartLongLivedTCPKeepalives failed rv[0x%x]", this, rv));
         }
     }
 
     NS_IF_ADDREF(*aTransport = mSocketTransport);
     NS_IF_ADDREF(*aInputStream = mSocketIn);
     NS_IF_ADDREF(*aOutputStream = mSocketOut);
@@ -1118,32 +1204,46 @@ nsHttpConnection::UpdateTCPKeepalive(nsI
     }
 
     // Do not reduce keepalive probe frequency for idle connections.
     if (self->mIdleMonitoring) {
         return;
     }
 
     nsresult rv = self->StartLongLivedTCPKeepalives();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (NS_FAILED(rv)) {
         LOG(("nsHttpConnection::UpdateTCPKeepalive [%p] "
              "StartLongLivedTCPKeepalives failed rv[0x%x]",
              self, rv));
     }
 }
 
 void
 nsHttpConnection::GetSecurityInfo(nsISupports **secinfo)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    LOG(("nsHttpConnection::GetSecurityInfo trans=%p tlsfilter=%p socket=%p\n",
+         mTransaction.get(), mTLSFilter.get(), mSocketTransport.get()));
 
-    if (mSocketTransport) {
-        if (NS_FAILED(mSocketTransport->GetSecurityInfo(secinfo)))
-            *secinfo = nullptr;
+    if (mTransaction &&
+        NS_SUCCEEDED(mTransaction->GetTransactionSecurityInfo(secinfo))) {
+        return;
     }
+
+    if (mTLSFilter &&
+        NS_SUCCEEDED(mTLSFilter->GetTransactionSecurityInfo(secinfo))) {
+        return;
+    }
+
+    if (mSocketTransport &&
+        NS_SUCCEEDED(mSocketTransport->GetSecurityInfo(secinfo))) {
+        return;
+    }
+
+    *secinfo = nullptr;
 }
 
 void
 nsHttpConnection::SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks)
 {
     MutexAutoLock lock(mCallbacksLock);
     // This is called both on and off the main thread. For JS-implemented
     // callbacks, we requires that the call happen on the main thread, but
@@ -1198,42 +1298,64 @@ nsHttpConnection::ResumeRecv()
     if (mSocketIn)
         return mSocketIn->AsyncWait(this, 0, 0, nullptr);
 
     NS_NOTREACHED("no socket input stream");
     return NS_ERROR_UNEXPECTED;
 }
 
 
-class nsHttpConnectionForceRecv : public nsRunnable
+class nsHttpConnectionForceIO : public nsRunnable
 {
 public:
-    nsHttpConnectionForceRecv(nsHttpConnection *aConn)
-        : mConn(aConn) {}
+  nsHttpConnectionForceIO(nsHttpConnection *aConn, bool doRecv)
+     : mConn(aConn)
+     , mDoRecv(doRecv)
+    {}
 
     NS_IMETHOD Run()
     {
         MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
-        if (!mConn->mSocketIn)
+        if (mDoRecv) {
+            if (!mConn->mSocketIn)
+                return NS_OK;
+            return mConn->OnInputStreamReady(mConn->mSocketIn);
+        }
+        if (!mConn->mSocketOut)
             return NS_OK;
-        return mConn->OnInputStreamReady(mConn->mSocketIn);
+        return mConn->OnOutputStreamReady(mConn->mSocketOut);
     }
 private:
     nsRefPtr<nsHttpConnection> mConn;
+    bool mDoRecv;
 };
 
 // trigger an asynchronous read
 nsresult
 nsHttpConnection::ForceRecv()
 {
     LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
-    return NS_DispatchToCurrentThread(new nsHttpConnectionForceRecv(this));
+    return NS_DispatchToCurrentThread(new nsHttpConnectionForceIO(this, true));
+}
+
+// trigger an asynchronous write
+nsresult
+nsHttpConnection::ForceSend()
+{
+    LOG(("nsHttpConnection::ForceSend [this=%p]\n", this));
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    if (mTLSFilter) {
+        return mTLSFilter->NudgeTunnel(this, this, this);
+    }
+
+    return NS_DispatchToCurrentThread(new nsHttpConnectionForceIO(this, false));
 }
 
 void
 nsHttpConnection::BeginIdleMonitoring()
 {
     LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     MOZ_ASSERT(!mTransaction, "BeginIdleMonitoring() while active");
@@ -1265,17 +1387,18 @@ nsHttpConnection::EndIdleMonitoring()
 //-----------------------------------------------------------------------------
 
 void
 nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
 {
     LOG(("nsHttpConnection::CloseTransaction[this=%p trans=%p reason=%x]\n",
         this, trans, reason));
 
-    MOZ_ASSERT(trans == mTransaction, "wrong transaction");
+    MOZ_ASSERT((trans == mTransaction) ||
+               (mTLSFilter && mTLSFilter->Transaction() == trans));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     if (mCurrentBytesRead > mMaxBytesRead)
         mMaxBytesRead = mCurrentBytesRead;
 
     // mask this error code because its not a real error.
     if (reason == NS_BASE_STREAM_CLOSED)
         reason = NS_OK;
@@ -1350,83 +1473,84 @@ nsHttpConnection::OnReadSegment(const ch
 
 nsresult
 nsHttpConnection::OnSocketWritable()
 {
     LOG(("nsHttpConnection::OnSocketWritable [this=%p] host=%s\n",
          this, mConnInfo->Host()));
 
     nsresult rv;
-    uint32_t n;
+    uint32_t transactionBytes;
     bool again = true;
 
     do {
-        mSocketOutCondition = NS_OK;
+        rv = mSocketOutCondition = NS_OK;
+        transactionBytes = 0;
 
-        // If we're doing a proxy connect, then we need to bypass calling into
-        // the transaction.
-        //
-        // NOTE: this code path can't be shared since the transaction doesn't
-        // implement nsIInputStream.  doing so is not worth the added cost of
-        // extra indirections during normal reading.
-        //
-        if (mProxyConnectStream) {
+        // The SSL handshake must be completed before the transaction->readsegments()
+        // processing can proceed because we need to know how to format the
+        // request differently for http/1, http/2, spdy, etc.. and that is
+        // negotiated with NPN/ALPN in the SSL handshake.
+
+        if (mConnInfo->UsingHttpsProxy() && !EnsureNPNComplete()) {
+            mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+        } else if (mProxyConnectStream) {
+            // If we're need an HTTP/1 CONNECT tunnel through a proxy
+            // send it before doing the SSL handshake
             LOG(("  writing CONNECT request stream\n"));
             rv = mProxyConnectStream->ReadSegments(ReadFromStream, this,
-                                                      nsIOService::gDefaultSegmentSize,
-                                                      &n);
-        }
-        else if (!EnsureNPNComplete()) {
-            // When SPDY is disabled this branch is not executed because Activate()
-            // sets mNPNComplete to true in that case.
+                                                   nsIOService::gDefaultSegmentSize,
+                                                   &transactionBytes);
+        } else if (!EnsureNPNComplete()) {
+            mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+        } else {
 
-            // We are ready to proceed with SSL but the handshake is not done.
-            // When using NPN to negotiate between HTTPS and SPDY, we need to
-            // see the results of the handshake to know what bytes to send, so
-            // we cannot proceed with the request headers.
-
-            rv = NS_OK;
-            mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
-            n = 0;
-        }
-        else {
+            // for non spdy sessions let the connection manager know
             if (!mReportedSpdy) {
                 mReportedSpdy = true;
-                gHttpHandler->ConnMgr()->ReportSpdyConnection(this, mEverUsedSpdy);
+                MOZ_ASSERT(!mEverUsedSpdy);
+                gHttpHandler->ConnMgr()->ReportSpdyConnection(this, false);
             }
 
             LOG(("  writing transaction request stream\n"));
             mProxyConnectInProgress = false;
-            rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n);
+            rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize,
+                                            &transactionBytes);
+            mContentBytesWritten += transactionBytes;
         }
 
-        LOG(("  ReadSegments returned [rv=%x read=%u sock-cond=%x]\n",
-            rv, n, mSocketOutCondition));
+        LOG(("nsHttpConnection::OnSocketWritable %p "
+             "ReadSegments returned [rv=%x read=%u sock-cond=%x]\n",
+             this, rv, transactionBytes, mSocketOutCondition));
 
         // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
         if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
             rv = NS_OK;
-            n = 0;
+            transactionBytes = 0;
         }
 
         if (NS_FAILED(rv)) {
             // if the transaction didn't want to write any more data, then
             // wait for the transaction to call ResumeSend.
             if (rv == NS_BASE_STREAM_WOULD_BLOCK)
                 rv = NS_OK;
             again = false;
-        }
-        else if (NS_FAILED(mSocketOutCondition)) {
-            if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK)
-                rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // continue writing
-            else
+        } else if (NS_FAILED(mSocketOutCondition)) {
+            if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+                if (mTLSFilter) {
+                    LOG(("  blocked tunnel (handshake?)\n"));
+                    mTLSFilter->NudgeTunnel(this, this, this);
+                } else {
+                    rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // continue writing
+                }
+            } else {
                 rv = mSocketOutCondition;
+            }
             again = false;
-        }
-        else if (n == 0) {
+        } else if (!transactionBytes) {
             rv = NS_OK;
 
             if (mTransaction) { // in case the ReadSegments stack called CloseTransaction()
                 //
                 // at this point we've written out the entire transaction, and now we
                 // must wait for the server's response.  we manufacture a status message
                 // here to reflect the fact that we are waiting.  this message will be
                 // trumped (overwritten) if the server responds quickly.
@@ -1578,16 +1702,43 @@ nsHttpConnection::OnSocketReadable()
             }
         }
         // read more from the socket until error...
     } while (again);
 
     return rv;
 }
 
+void
+nsHttpConnection::SetupSecondaryTLS()
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(!mTLSFilter);
+    LOG(("nsHttpConnection %p SetupSecondaryTLS %s %d\n",
+         this, mConnInfo->Host(), mConnInfo->Port()));
+    mTLSFilter = new TLSFilterTransaction(mTransaction,
+                                          mConnInfo->Host(),
+                                          mConnInfo->Port());
+    if (mTransaction) {
+        mTransaction = mTLSFilter;
+    }
+}
+
+void
+nsHttpConnection::SetInSpdyTunnel(bool arg)
+{
+    MOZ_ASSERT(mTLSFilter);
+    mInSpdyTunnel = arg;
+
+    // don't setup another tunnel :)
+    mProxyConnectStream = nullptr;
+    mCompletedProxyConnect = true;
+    mProxyConnectInProgress = false;
+}
+
 nsresult
 nsHttpConnection::MakeConnectString(nsAHttpTransaction *trans,
                                     nsHttpRequestHead *request,
                                     nsACString &result)
 {
     result.Truncate();
     if (!trans->ConnectionInfo()) {
         return NS_ERROR_NOT_INITIALIZED;
@@ -1662,26 +1813,26 @@ nsHttpConnection::StartShortLivedTCPKeep
         // Set the idle time.
         idleTimeS = gHttpHandler->GetTCPKeepaliveShortLivedIdleTime();
         LOG(("nsHttpConnection::StartShortLivedTCPKeepalives[%p] "
              "idle time[%ds].", this, idleTimeS));
 
         retryIntervalS =
             std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
         rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
+        if (NS_FAILED(rv)) {
             return rv;
         }
         rv = mSocketTransport->SetKeepaliveEnabled(true);
         mTCPKeepaliveConfig = kTCPKeepaliveShortLivedConfig;
     } else {
         rv = mSocketTransport->SetKeepaliveEnabled(false);
         mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
     }
-    if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (NS_FAILED(rv)) {
         return rv;
     }
 
     // Start a timer to move to long-lived keepalive config.
     if(!mTCPKeepaliveTransitionTimer) {
         mTCPKeepaliveTransitionTimer =
             do_CreateInstance("@mozilla.org/timer;1");
     }
@@ -1736,50 +1887,51 @@ nsHttpConnection::StartLongLivedTCPKeepa
         // Increase the idle time.
         int32_t idleTimeS = gHttpHandler->GetTCPKeepaliveLongLivedIdleTime();
         LOG(("nsHttpConnection::StartLongLivedTCPKeepalives[%p] idle time[%ds]",
              this, idleTimeS));
 
         int32_t retryIntervalS =
             std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
         rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
+        if (NS_FAILED(rv)) {
             return rv;
         }
 
         // Ensure keepalive is enabled, if current status is disabled.
         if (mTCPKeepaliveConfig == kTCPKeepaliveDisabled) {
             rv = mSocketTransport->SetKeepaliveEnabled(true);
-            if (NS_WARN_IF(NS_FAILED(rv))) {
+            if (NS_FAILED(rv)) {
                 return rv;
             }
         }
         mTCPKeepaliveConfig = kTCPKeepaliveLongLivedConfig;
     } else {
         rv = mSocketTransport->SetKeepaliveEnabled(false);
         mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
     }
 
-    if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (NS_FAILED(rv)) {
         return rv;
     }
     return NS_OK;
 }
 
 nsresult
 nsHttpConnection::DisableTCPKeepalives()
 {
     MOZ_ASSERT(mSocketTransport);
     if (!mSocketTransport) {
         return NS_ERROR_NOT_INITIALIZED;
     }
+
     LOG(("nsHttpConnection::DisableTCPKeepalives [%p]", this));
     if (mTCPKeepaliveConfig != kTCPKeepaliveDisabled) {
         nsresult rv = mSocketTransport->SetKeepaliveEnabled(false);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
+        if (NS_FAILED(rv)) {
             return rv;
         }
         mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
     }
     if (mTCPKeepaliveTransitionTimer) {
         mTCPKeepaliveTransitionTimer->Cancel();
         mTCPKeepaliveTransitionTimer = nullptr;
     }
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -7,23 +7,25 @@
 #define nsHttpConnection_h__
 
 #include "nsHttpConnectionInfo.h"
 #include "nsAHttpTransaction.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsProxyRelease.h"
 #include "prinrval.h"
+#include "TunnelUtils.h"
 
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsITimer.h"
 
 class nsISocketTransport;
+class nsISSLSocketControl;
 
 namespace mozilla {
 namespace net {
 
 class nsHttpHandler;
 class ASpdySession;
 
 //-----------------------------------------------------------------------------
@@ -34,25 +36,27 @@ class ASpdySession;
 //-----------------------------------------------------------------------------
 
 class nsHttpConnection : public nsAHttpSegmentReader
                        , public nsAHttpSegmentWriter
                        , public nsIInputStreamCallback
                        , public nsIOutputStreamCallback
                        , public nsITransportEventSink
                        , public nsIInterfaceRequestor
+                       , public NudgeTunnelCallback
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSAHTTPSEGMENTREADER
     NS_DECL_NSAHTTPSEGMENTWRITER
     NS_DECL_NSIINPUTSTREAMCALLBACK
     NS_DECL_NSIOUTPUTSTREAMCALLBACK
     NS_DECL_NSITRANSPORTEVENTSINK
     NS_DECL_NSIINTERFACEREQUESTOR
+    NS_DECL_NUDGETUNNELCALLBACK
 
     nsHttpConnection();
     virtual ~nsHttpConnection();
 
     // Initialize the connection:
     //  info        - specifies the connection parameters.
     //  maxHangTime - limits the amount of time this connection can spend on a
     //                single transaction before it should no longer be kept
@@ -96,16 +100,21 @@ public:
         return mLastTransactionExpectedNoContent;
     }
 
     void SetLastTransactionExpectedNoContent(bool val)
     {
         mLastTransactionExpectedNoContent = val;
     }
 
+    bool NeedSpdyTunnel()
+    {
+        return mConnInfo->UsingHttpsProxy() && !mTLSFilter && mConnInfo->UsingConnect();
+    }
+
     nsISocketTransport   *Transport()      { return mSocketTransport; }
     nsAHttpTransaction   *Transaction()    { return mTransaction; }
     nsHttpConnectionInfo *ConnectionInfo() { return mConnInfo; }
 
     // nsAHttpConnection compatible methods (non-virtual):
     nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset);
     void     CloseTransaction(nsAHttpTransaction *, nsresult reason);
     void     GetConnectionInfo(nsHttpConnectionInfo **ci) { NS_IF_ADDREF(*ci = mConnInfo); }
@@ -117,17 +126,18 @@ public:
     bool     IsReused();
     void     SetIsReusedAfter(uint32_t afterMilliseconds);
     nsresult PushBack(const char *data, uint32_t length);
     nsresult ResumeSend();
     nsresult ResumeRecv();
     int64_t  MaxBytesRead() {return mMaxBytesRead;}
     uint8_t GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }
 
-    friend class nsHttpConnectionForceRecv;
+    friend class nsHttpConnectionForceIO;
+    nsresult ForceSend();
     nsresult ForceRecv();
 
     static NS_METHOD ReadFromStream(nsIInputStream *, void *, const char *,
                                     uint32_t, uint32_t, uint32_t *);
 
     // When a persistent connection is in the connection manager idle
     // connection pool, the nsHttpConnection still reads errors and hangups
     // on the socket so that it can be proactively released if the server
@@ -159,56 +169,60 @@ public:
     void Classify(nsAHttpTransaction::Classifier newclass)
     {
         mClassification = newclass;
     }
 
     // When the connection is active this is called every second
     void  ReadTimeoutTick();
 
-    int64_t BytesWritten() { return mTotalBytesWritten; }
+    int64_t BytesWritten() { return mTotalBytesWritten; } // includes TLS
+    int64_t ContentBytesWritten() { return mContentBytesWritten; }
 
     void    SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
     void    PrintDiagnostics(nsCString &log);
 
     void    SetTransactionCaps(uint32_t aCaps) { mTransactionCaps = aCaps; }
 
     // IsExperienced() returns true when the connection has started at least one
     // non null HTTP transaction of any version.
     bool    IsExperienced() { return mExperienced; }
 
     static nsresult MakeConnectString(nsAHttpTransaction *trans,
                                       nsHttpRequestHead *request,
                                       nsACString &result);
+    void    SetupSecondaryTLS();
+    void    SetInSpdyTunnel(bool arg);
 
 private:
     // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
     enum TCPKeepaliveConfig {
       kTCPKeepaliveDisabled = 0,
       kTCPKeepaliveShortLivedConfig,
       kTCPKeepaliveLongLivedConfig
     };
 
     // called to cause the underlying socket to start speaking SSL
-    nsresult ProxyStartSSL();
+    nsresult InitSSLParams(bool connectingToProxy, bool ProxyStartSSL);
+    nsresult SetupNPNList(nsISSLSocketControl *ssl, uint32_t caps);
 
     nsresult OnTransactionDone(nsresult reason);
     nsresult OnSocketWritable();
     nsresult OnSocketReadable();
 
     nsresult SetupProxyConnect();
 
     PRIntervalTime IdleTime();
     bool     IsAlive();
     bool     SupportsPipelining(nsHttpResponseHead *);
 
     // Makes certain the SSL handshake is complete and NPN negotiation
     // has had a chance to happen
     bool     EnsureNPNComplete();
-    void     SetupSSL(uint32_t caps);
+    void     SetupSSL();
 
     // Start the Spdy transaction handler when NPN indicates spdy/*
     void     StartSpdy(uint8_t versionLevel);
 
     // Directly Add a transaction to an active connection for SPDY
     nsresult AddTransaction(nsAHttpTransaction *, int32_t);
 
     // Used to set TCP keepalives for fast detection of dead connections during
@@ -226,16 +240,17 @@ private:
     nsresult                        mSocketOutCondition;
 
     nsCOMPtr<nsIInputStream>        mProxyConnectStream;
     nsCOMPtr<nsIInputStream>        mRequestStream;
 
     // mTransaction only points to the HTTP Transaction callbacks if the
     // transaction is open, otherwise it is null.
     nsRefPtr<nsAHttpTransaction>    mTransaction;
+    nsRefPtr<TLSFilterTransaction>  mTLSFilter;
 
     nsRefPtr<nsHttpHandler>         mHttpHandler; // keep gHttpHandler alive
 
     Mutex                           mCallbacksLock;
     nsMainThreadPtrHandle<nsIInterfaceRequestor> mCallbacks;
 
     nsRefPtr<nsHttpConnectionInfo> mConnInfo;
 
@@ -244,32 +259,34 @@ private:
     PRIntervalTime                  mMaxHangTime;    // max download time before dropping keep-alive status
     PRIntervalTime                  mIdleTimeout;    // value of keep-alive: timeout=
     PRIntervalTime                  mConsiderReusedAfterInterval;
     PRIntervalTime                  mConsiderReusedAfterEpoch;
     int64_t                         mCurrentBytesRead;   // data read per activation
     int64_t                         mMaxBytesRead;       // max read in 1 activation
     int64_t                         mTotalBytesRead;     // total data read
     int64_t                         mTotalBytesWritten;  // does not include CONNECT tunnel
+    int64_t                         mContentBytesWritten;  // does not include CONNECT tunnel or TLS
 
     nsRefPtr<nsIAsyncInputStream>   mInputOverflow;
 
     PRIntervalTime                  mRtt;
 
     bool                            mConnectedTransport;
     bool                            mKeepAlive;
     bool                            mKeepAliveMask;
     bool                            mDontReuse;
     bool                            mSupportsPipelining;
     bool                            mIsReused;
     bool                            mCompletedProxyConnect;
     bool                            mLastTransactionExpectedNoContent;
     bool                            mIdleMonitoring;
     bool                            mProxyConnectInProgress;
     bool                            mExperienced;
+    bool                            mInSpdyTunnel;
 
     // The number of <= HTTP/1.1 transactions performed on this connection. This
     // excludes spdy transactions.
     uint32_t                        mHttp1xTransactionCount;
 
     // Keep-Alive: max="mRemainingConnectionUses" provides the number of future
     // transactions (including the current one) that the server expects to allow
     // on this persistent connection.
--- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -63,18 +63,17 @@ nsHttpConnectionInfo::SetOriginServer(co
     //
 
     const char *keyHost;
     int32_t keyPort;
 
     if (mUsingHttpProxy && !mUsingConnect) {
         keyHost = ProxyHost();
         keyPort = ProxyPort();
-    }
-    else {
+    } else {
         keyHost = Host();
         keyPort = Port();
     }
 
     // The hashkey has 4 fields followed by host connection info
     // byte 0 is P/T/. {P,T} for Plaintext/TLS Proxy over HTTP
     // byte 1 is S/. S is for end to end ssl such as https:// uris
     // byte 2 is A/. A is for an anonymous channel (no cookies, etc..)
@@ -124,18 +123,39 @@ nsHttpConnectionInfo::SetOriginServer(co
 nsHttpConnectionInfo*
 nsHttpConnectionInfo::Clone() const
 {
     nsHttpConnectionInfo* clone = new nsHttpConnectionInfo(mHost, mPort, mUsername, mProxyInfo, mEndToEndSSL);
 
     // Make sure the anonymous and private flags are transferred!
     clone->SetAnonymous(GetAnonymous());
     clone->SetPrivate(GetPrivate());
+    MOZ_ASSERT(clone->Equals(this));
+    return clone;
+}
 
-    return clone;
+nsresult
+nsHttpConnectionInfo::CreateWildCard(nsHttpConnectionInfo **outParam)
+{
+    // T???mozilla.org:443 (https:proxy.ducksong.com:3128) [specifc form]
+    // TS??*:0 (https:proxy.ducksong.com:3128)   [wildcard form]
+
+    if (!mUsingHttpsProxy) {
+        MOZ_ASSERT(false);
+        return NS_ERROR_NOT_IMPLEMENTED;
+    }
+
+    nsRefPtr<nsHttpConnectionInfo> clone;
+    clone = new nsHttpConnectionInfo(NS_LITERAL_CSTRING("*"), 0,
+                                     mUsername, mProxyInfo, true);
+    // Make sure the anonymous and private flags are transferred!
+    clone->SetAnonymous(GetAnonymous());
+    clone->SetPrivate(GetPrivate());
+    clone.forget(outParam);
+    return NS_OK;
 }
 
 bool
 nsHttpConnectionInfo::UsingProxy()
 {
     if (!mProxyInfo)
         return false;
     return !mProxyInfo->IsDirect();
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -13,16 +13,25 @@
 #include "nsStringFwd.h"
 
 extern PRLogModuleInfo *gHttpLog;
 
 //-----------------------------------------------------------------------------
 // nsHttpConnectionInfo - holds the properties of a connection
 //-----------------------------------------------------------------------------
 
+// http:// uris through a proxy will all share the same CI, because they can
+// all use the same connection. (modulo pb and anonymous flags). They just use
+// the proxy as the origin host name.
+// however, https:// uris tunnel through the proxy so they will have different
+// CIs - the CI reflects both the proxy and the origin.
+// however, proxy conenctions made with http/2 (or spdy) can tunnel to the origin
+// and multiplex non tunneled transactions at the same time, so they have a
+// special wildcard CI that accepts all origins through that proxy.
+
 namespace mozilla { namespace net {
 
 class nsHttpConnectionInfo
 {
 public:
     nsHttpConnectionInfo(const nsACString &host, int32_t port,
                          const nsACString &username,
                          nsProxyInfo* proxyInfo,
@@ -37,18 +46,19 @@ public:
 
     void SetOriginServer(const nsACString &host, int32_t port);
 
     void SetOriginServer(const char *host, int32_t port)
     {
         SetOriginServer(nsDependentCString(host), port);
     }
 
-    // OK to treat this as an infalible allocation
+    // OK to treat these as an infalible allocation
     nsHttpConnectionInfo* Clone() const;
+    nsresult CreateWildCard(nsHttpConnectionInfo **outParam);
 
     const char *ProxyHost() const { return mProxyInfo ? mProxyInfo->Host().get() : nullptr; }
     int32_t     ProxyPort() const { return mProxyInfo ? mProxyInfo->Port() : -1; }
     const char *ProxyType() const { return mProxyInfo ? mProxyInfo->Type() : nullptr; }
 
     // Compare this connection info to another...
     // Two connections are 'equal' if they end up talking the same
     // protocol to the same server. This is needed to properly manage
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -1038,18 +1038,20 @@ nsHttpConnectionMgr::ShutdownPassCB(cons
 
 //-----------------------------------------------------------------------------
 
 bool
 nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
-    LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
-         ent->mConnInfo->HashKey().get()));
+    LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry "
+         "[ci=%s ent=%p active=%d idle=%d queued=%d]\n",
+         ent->mConnInfo->HashKey().get(), ent, ent->mActiveConns.Length(),
+         ent->mIdleConns.Length(), ent->mPendingQ.Length()));
 
     ProcessSpdyPendingQ(ent);
 
     nsHttpTransaction *trans;
     nsresult rv;
     bool dispatchedSuccessfully = false;
 
     // if !considerAll iterate the pending list until one is dispatched successfully.
@@ -1066,17 +1068,19 @@ nsHttpConnectionMgr::ProcessPendingQForE
         bool alreadyHalfOpen = false;
         for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) {
             if (ent->mHalfOpens[j]->Transaction() == trans) {
                 alreadyHalfOpen = true;
                 break;
             }
         }
 
-        rv = TryDispatchTransaction(ent, alreadyHalfOpen, trans);
+        rv = TryDispatchTransaction(ent,
+                                    alreadyHalfOpen || trans->DontRouteViaWildCard(),
+                                    trans);
         if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
             if (NS_SUCCEEDED(rv))
                 LOG(("  dispatching pending transaction...\n"));
             else
                 LOG(("  removing pending transaction based on "
                      "TryDispatchTransaction returning hard error %x\n", rv));
 
             if (ent->mPendingQ.RemoveElement(trans)) {
@@ -1561,18 +1565,20 @@ nsHttpConnectionMgr::IsUnderPressure(nsC
 //   not remain in the pending queue
 nsresult
 nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
                                             bool onlyReusedConnection,
                                             nsHttpTransaction *trans)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
-         "[ci=%s caps=%x]\n",
-         ent->mConnInfo->HashKey().get(), uint32_t(trans->Caps())));
+         "[trans=%p ci=%s caps=%x wildcardok=%d onlyreused=%d]\n",
+         trans, ent->mConnInfo->HashKey().get(),
+         uint32_t(trans->Caps()), !trans->DontRouteViaWildCard(),
+         onlyReusedConnection));
 
     nsHttpTransaction::Classifier classification = trans->Classification();
     uint32_t caps = trans->Caps();
 
     // no keep-alive means no pipelines either
     if (!(caps & NS_HTTP_ALLOW_KEEPALIVE))
         caps = caps & ~NS_HTTP_ALLOW_PIPELINING;
 
@@ -1751,26 +1757,26 @@ nsHttpConnectionMgr::DispatchTransaction
                                          nsHttpTransaction *trans,
                                          nsHttpConnection *conn)
 {
     uint32_t caps = trans->Caps();
     int32_t priority = trans->Priority();
     nsresult rv;
 
     LOG(("nsHttpConnectionMgr::DispatchTransaction "
-         "[ci=%s trans=%p caps=%x conn=%p priority=%d]\n",
+         "[ent-ci=%s trans=%p caps=%x conn=%p priority=%d]\n",
          ent->mConnInfo->HashKey().get(), trans, caps, conn, priority));
 
     // It is possible for a rate-paced transaction to be dispatched independent
     // of the token bucket when the amount of parallelization has changed or
     // when a muxed connection (e.g. spdy or pipelines) becomes available.
     trans->CancelPacing(NS_OK);
 
     if (conn->UsingSpdy()) {
-        LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s,"
+        LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
              "Connection host = %s\n",
              trans->ConnectionInfo()->Host(),
              conn->ConnectionInfo()->Host()));
         rv = conn->Activate(trans, caps, priority);
         MOZ_ASSERT(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
         if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
             AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
                 trans->GetPendingTime(), TimeStamp::Now());
@@ -1919,17 +1925,18 @@ nsHttpConnectionMgr::ProcessNewTransacti
     }
 
     trans->SetPendingTime();
 
     nsresult rv = NS_OK;
     nsHttpConnectionInfo *ci = trans->ConnectionInfo();
     MOZ_ASSERT(ci);
 
-    nsConnectionEntry *ent = GetOrCreateConnectionEntry(ci);
+    nsConnectionEntry *ent =
+        GetOrCreateConnectionEntry(ci, trans->DontRouteViaWildCard());
 
     // SPDY coalescing of hostnames means we might redirect from this
     // connection entry onto the preferred one.
     nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
     if (preferredEntry && (preferredEntry != ent)) {
         LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
              "redirected via coalescing from %s to %s\n", trans,
              ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host()));
@@ -1951,19 +1958,19 @@ nsHttpConnectionMgr::ProcessNewTransacti
     if (conn) {
         MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
         MOZ_ASSERT(((int32_t)ent->mActiveConns.IndexOf(conn)) != -1,
                    "Sticky Connection Not In Active List");
         LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
              "sticky connection=%p\n", trans, conn.get()));
         trans->SetConnection(nullptr);
         rv = DispatchTransaction(ent, trans, conn);
+    } else {
+        rv = TryDispatchTransaction(ent, trans->DontRouteViaWildCard(), trans);
     }
-    else
-        rv = TryDispatchTransaction(ent, false, trans);
 
     if (NS_SUCCEEDED(rv)) {
         LOG(("  ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
         return rv;
     }
 
     if (rv == NS_ERROR_NOT_AVAILABLE) {
         LOG(("  adding transaction to pending queue "
@@ -2282,16 +2289,54 @@ nsHttpConnectionMgr::OnMsgProcessPending
         // if we reach here, it means that we couldn't dispatch a transaction
         // for the specified connection info.  walk the connection table...
         mCT.Enumerate(ProcessOneTransactionCB, this);
     }
 
     NS_RELEASE(ci);
 }
 
+nsresult
+nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo *aCI, nsresult code)
+{
+    nsRefPtr<nsHttpConnectionInfo> ci(aCI);
+    LOG(("nsHttpConnectionMgr::CancelTransactions %s\n",ci->HashKey().get()));
+
+    int32_t intReason = static_cast<int32_t>(code);
+    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason, ci);
+    if (NS_SUCCEEDED(rv)) {
+        unused << ci.forget();
+    }
+    return rv;
+}
+
+void
+nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code, void *param)
+{
+    nsresult reason = static_cast<nsresult>(code);
+    nsRefPtr<nsHttpConnectionInfo> ci =
+        dont_AddRef(static_cast<nsHttpConnectionInfo *>(param));
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+    LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
+         ci->HashKey().get(), ent));
+    if (!ent) {
+        return;
+    }
+
+    nsRefPtr<nsHttpTransaction> trans;
+    for (int32_t i = ent->mPendingQ.Length() - 1; i >= 0; --i) {
+        trans = dont_AddRef(ent->mPendingQ[i]);
+        LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
+             ci->HashKey().get(), ent, trans.get()));
+        ent->mPendingQ.RemoveElementAt(i);
+        trans->Close(reason);
+        trans = nullptr;
+    }
+}
+
 void
 nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, void *)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
 
     // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
     mTimeOfNextWakeUp = UINT64_MAX;
@@ -2327,90 +2372,92 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
     //
     // 1) remove the connection from the active list
     // 2) if keep-alive, add connection to idle list
     // 3) post event to process the pending transaction queue
     //
 
     nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
                                                    conn, nullptr);
-    nsHttpConnectionInfo *ci = nullptr;
-
     if (!ent) {
-        // this should never happen
-        LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection ent == null\n"));
-        MOZ_ASSERT(false, "no connection entry");
-        NS_ADDREF(ci = conn->ConnectionInfo());
+        // this can happen if the connection is made outside of the
+        // connection manager and is being "reclaimed" for use with
+        // future transactions. HTTP/2 tunnels work like this.
+        ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true);
+        LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
+             "forced new hash entry %s\n",
+             conn, conn->ConnectionInfo()->HashKey().get()));
     }
-    else {
-        NS_ADDREF(ci = ent->mConnInfo);
-
-        // If the connection is in the active list, remove that entry
-        // and the reference held by the mActiveConns list.
-        // This is never the final reference on conn as the event context
-        // is also holding one that is released at the end of this function.
-
-        if (ent->mUsingSpdy) {
-            // Spdy connections aren't reused in the traditional HTTP way in
-            // the idleconns list, they are actively multplexed as active
-            // conns. Even when they have 0 transactions on them they are
-            // considered active connections. So when one is reclaimed it
-            // is really complete and is meant to be shut down and not
-            // reused.
-            conn->DontReuse();
-        }
-
-        if (ent->mActiveConns.RemoveElement(conn)) {
-            if (conn == ent->mYellowConnection)
-                ent->OnYellowComplete();
-            nsHttpConnection *temp = conn;
-            NS_RELEASE(temp);
-            DecrementActiveConnCount(conn);
-            ConditionallyStopTimeoutTick();
+
+    MOZ_ASSERT(ent);
+    nsHttpConnectionInfo *ci = nullptr;
+    NS_ADDREF(ci = ent->mConnInfo);
+
+    // If the connection is in the active list, remove that entry
+    // and the reference held by the mActiveConns list.
+    // This is never the final reference on conn as the event context
+    // is also holding one that is released at the end of this function.
+
+    if (conn->EverUsedSpdy()) {
+        // Spdy connections aren't reused in the traditional HTTP way in
+        // the idleconns list, they are actively multplexed as active
+        // conns. Even when they have 0 transactions on them they are
+        // considered active connections. So when one is reclaimed it
+        // is really complete and is meant to be shut down and not
+        // reused.
+        conn->DontReuse();
+    }
+
+    // a connection that still holds a reference to a transaction was
+    // not closed naturally (i.e. it was reset or aborted) and is
+    // therefore not something that should be reused.
+    if (conn->Transaction()) {
+        conn->DontReuse();
+    }
+
+    if (ent->mActiveConns.RemoveElement(conn)) {
+        if (conn == ent->mYellowConnection) {
+            ent->OnYellowComplete();
         }
-
-        // a connection that still holds a reference to a transaction was
-        // not closed naturally (i.e. it was reset or aborted) and is
-        // therefore not something that should be reused.
-        if (conn->Transaction()) {
-            conn->DontReuse();
+        nsHttpConnection *temp = conn;
+        NS_RELEASE(temp);
+        DecrementActiveConnCount(conn);
+        ConditionallyStopTimeoutTick();
+    }
+
+    if (conn->CanReuse()) {
+        LOG(("  adding connection to idle list\n"));
+        // Keep The idle connection list sorted with the connections that
+        // have moved the largest data pipelines at the front because these
+        // connections have the largest cwnds on the server.
+
+        // The linear search is ok here because the number of idleconns
+        // in a single entry is generally limited to a small number (i.e. 6)
+
+        uint32_t idx;
+        for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
+            nsHttpConnection *idleConn = ent->mIdleConns[idx];
+            if (idleConn->MaxBytesRead() < conn->MaxBytesRead())
+                break;
         }
 
-        if (conn->CanReuse()) {
-            LOG(("  adding connection to idle list\n"));
-            // Keep The idle connection list sorted with the connections that
-            // have moved the largest data pipelines at the front because these
-            // connections have the largest cwnds on the server.
-
-            // The linear search is ok here because the number of idleconns
-            // in a single entry is generally limited to a small number (i.e. 6)
-
-            uint32_t idx;
-            for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
-                nsHttpConnection *idleConn = ent->mIdleConns[idx];
-                if (idleConn->MaxBytesRead() < conn->MaxBytesRead())
-                    break;
-            }
-
-            NS_ADDREF(conn);
-            ent->mIdleConns.InsertElementAt(idx, conn);
-            mNumIdleConns++;
-            conn->BeginIdleMonitoring();
-
-            // If the added connection was first idle connection or has shortest
-            // time to live among the watched connections, pruning dead
-            // connections needs to be done when it can't be reused anymore.
-            uint32_t timeToLive = conn->TimeToLive();
-            if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
-                PruneDeadConnectionsAfter(timeToLive);
-        }
-        else {
-            LOG(("  connection cannot be reused; closing connection\n"));
-            conn->Close(NS_ERROR_ABORT);
-        }
+        NS_ADDREF(conn);
+        ent->mIdleConns.InsertElementAt(idx, conn);
+        mNumIdleConns++;
+        conn->BeginIdleMonitoring();
+
+        // If the added connection was first idle connection or has shortest
+        // time to live among the watched connections, pruning dead
+        // connections needs to be done when it can't be reused anymore.
+        uint32_t timeToLive = conn->TimeToLive();
+        if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
+            PruneDeadConnectionsAfter(timeToLive);
+    } else {
+        LOG(("  connection cannot be reused; closing connection\n"));
+        conn->Close(NS_ERROR_ABORT);
     }
 
     OnMsgProcessPendingQ(0, ci); // releases |ci|
     NS_RELEASE(conn);
 }
 
 void
 nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, void *param)
@@ -2603,27 +2650,53 @@ nsHttpConnectionMgr::nsConnectionHandle:
     if (mConn) {
         gHttpHandler->ReclaimConnection(mConn);
         NS_RELEASE(mConn);
     }
 }
 
 NS_IMPL_ISUPPORTS0(nsHttpConnectionMgr::nsConnectionHandle)
 
+// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
+// dispatching a transaction according to these rules
+// 1] use an ent that matches the ci that can be dispatched immediately
+// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
+// 3] otherwise create an ent that matches ci and make new conn on it
+
 nsHttpConnectionMgr::nsConnectionEntry *
-nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *ci)
+nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *specificCI,
+                                                bool prohibitWildCard)
 {
-    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
-    if (ent)
-        return ent;
-
-    nsHttpConnectionInfo *clone = ci->Clone();
-    ent = new nsConnectionEntry(clone);
-    mCT.Put(ci->HashKey(), ent);
-    return ent;
+    // step 1
+    nsConnectionEntry *specificEnt = mCT.Get(specificCI->HashKey());
+    if (specificEnt && specificEnt->AvailableForDispatchNow()) {
+        return specificEnt;
+    }
+
+    if (!specificCI->UsingHttpsProxy()) {
+        prohibitWildCard = true;
+    }
+
+    // step 2
+    if (!prohibitWildCard) {
+        nsRefPtr<nsHttpConnectionInfo> wildCardProxyCI;
+        specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
+        nsConnectionEntry *wildCardEnt = mCT.Get(wildCardProxyCI->HashKey());
+        if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
+            return wildCardEnt;
+        }
+    }
+
+    // step 3
+    if (!specificEnt) {
+        nsRefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
+        specificEnt = new nsConnectionEntry(clone);
+        mCT.Put(clone->HashKey(), specificEnt);
+    }
+    return specificEnt;
 }
 
 nsresult
 nsHttpConnectionMgr::nsConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
                                                             nsHttpRequestHead *req,
                                                             nsHttpResponseHead *resp,
                                                             bool *reset)
 {
@@ -2652,17 +2725,17 @@ nsHttpConnectionMgr::OnMsgSpeculativeCon
 
     nsRefPtr<SpeculativeConnectArgs> args =
         dont_AddRef(static_cast<SpeculativeConnectArgs *>(param));
 
     LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
          args->mTrans->ConnectionInfo()->HashKey().get()));
 
     nsConnectionEntry *ent =
-        GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo());
+        GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);
 
     // If spdy has previously made a preferred entry for this host via
     // the ip pooling rules. If so, connect to the preferred host instead of
     // the one directly passed in here.
     nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
     if (preferredEntry)
         ent = preferredEntry;
 
@@ -2754,29 +2827,34 @@ nsHttpConnectionMgr::nsHalfOpenSocket::~
 nsresult
 nsHttpConnectionMgr::
 nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
                                nsIAsyncInputStream **instream,
                                nsIAsyncOutputStream **outstream,
                                bool isBackup)
 {
     nsresult rv;
-
-    const char* types[1];
-    types[0] = (mEnt->mConnInfo->EndToEndSSL()) ?
-        "ssl" : gHttpHandler->DefaultSocketType();
-    uint32_t typeCount = (types[0] != nullptr);
+    const char *socketTypes[1];
+    uint32_t typeCount = 0;
+    if (mEnt->mConnInfo->FirstHopSSL()) {
+        socketTypes[typeCount++] = "ssl";
+    } else {
+        socketTypes[typeCount] = gHttpHandler->DefaultSocketType();
+        if (socketTypes[typeCount]) {
+            typeCount++;
+        }
+    }
 
     nsCOMPtr<nsISocketTransport> socketTransport;
     nsCOMPtr<nsISocketTransportService> sts;
 
     sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = sts->CreateTransport(types, typeCount,
+    rv = sts->CreateTransport(socketTypes, typeCount,
                               nsDependentCString(mEnt->mConnInfo->Host()),
                               mEnt->mConnInfo->Port(),
                               mEnt->mConnInfo->ProxyInfo(),
                               getter_AddRefs(socketTransport));
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint32_t tmpFlags = 0;
     if (mCaps & NS_HTTP_REFRESH_DNS)
@@ -3273,16 +3351,27 @@ nsConnectionEntry::nsConnectionEntry(nsH
         mGreenDepth = kPipelineUnlimited;
         mPipelineState = PS_GREEN;
     }
     mInitialGreenDepth = mGreenDepth;
     memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX);
 }
 
 bool
+nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
+{
+    if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
+        return true;
+    }
+
+    return gHttpHandler->ConnMgr()->
+        GetSpdyPreferredConn(this) ? true : false;
+}
+
+bool
 nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining()
 {
     return mPipelineState != nsHttpConnectionMgr::PS_RED;
 }
 
 nsHttpConnectionMgr::PipeliningState
 nsHttpConnectionMgr::nsConnectionEntry::PipelineState()
 {
@@ -3609,10 +3698,67 @@ nsConnectionEntry::RecordIPFamilyPrefere
 void
 nsHttpConnectionMgr::
 nsConnectionEntry::ResetIPFamilyPreference()
 {
   mPreferIPv4 = false;
   mPreferIPv6 = false;
 }
 
+void
+nsHttpConnectionMgr::MoveToWildCardConnEntry(nsHttpConnectionInfo *specificCI,
+                                             nsHttpConnectionInfo *wildCardCI,
+                                             nsHttpConnection *proxyConn)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(specificCI->UsingHttpsProxy());
+
+    LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
+         "change CI from %s to %s\n", proxyConn, specificCI->HashKey().get(),
+         wildCardCI->HashKey().get()));
+
+    nsConnectionEntry *ent = LookupConnectionEntry(specificCI, proxyConn, nullptr);
+    LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy %d)\n",
+         proxyConn, ent, ent ? ent->mUsingSpdy : 0));
+
+    if (!ent || !ent->mUsingSpdy) {
+        return;
+    }
+
+    nsConnectionEntry *wcEnt = GetOrCreateConnectionEntry(wildCardCI, true);
+    if (wcEnt == ent) {
+        // nothing to do!
+        return;
+    }
+    wcEnt->mUsingSpdy = true;
+    wcEnt->mTestedSpdy = true;
+
+    LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
+         "idle=%d active=%d half=%d pending=%d\n", ent,
+         ent->mIdleConns.Length(), ent->mActiveConns.Length(),
+         ent->mHalfOpens.Length(), ent->mPendingQ.Length()));
+
+    LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
+         "idle=%d active=%d half=%d pending=%d\n", wcEnt,
+         wcEnt->mIdleConns.Length(), wcEnt->mActiveConns.Length(),
+         wcEnt->mHalfOpens.Length(), wcEnt->mPendingQ.Length()));
+
+    int32_t count = ent->mActiveConns.Length();
+    for (int32_t i = 0; i < count; ++i) {
+        if (ent->mActiveConns[i] == proxyConn) {
+            ent->mActiveConns.RemoveElementAt(i);
+            wcEnt->mActiveConns.InsertElementAt(0, proxyConn);
+            return;
+        }
+    }
+
+    count = ent->mIdleConns.Length();
+    for (int32_t i = 0; i < count; ++i) {
+        if (ent->mIdleConns[i] == proxyConn) {
+            ent->mIdleConns.RemoveElementAt(i);
+            wcEnt->mIdleConns.InsertElementAt(0, proxyConn);
+            return;
+        }
+    }
+}
+
 } // namespace mozilla::net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -79,16 +79,17 @@ public:
     nsresult AddTransaction(nsHttpTransaction *, int32_t priority);
 
     // called to reschedule the given transaction.  it must already have been
     // added to the connection manager via AddTransaction.
     nsresult RescheduleTransaction(nsHttpTransaction *, int32_t priority);
 
     // cancels a transaction w/ the given reason.
     nsresult CancelTransaction(nsHttpTransaction *, nsresult reason);
+    nsresult CancelTransactions(nsHttpConnectionInfo *, nsresult reason);
 
     // called to force the connection manager to prune its list of idle
     // connections.
     nsresult PruneDeadConnections();
 
     // Close all idle persistent connections and prevent any active connections
     // from being reused. Optional connection info resets CI specific
     // information such as Happy Eyeballs history.
@@ -197,16 +198,22 @@ public:
     // Causes a large amount of connection diagnostic information to be
     // printed to the javascript console
     void PrintDiagnostics();
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called only on the socket thread.
     //-------------------------------------------------------------------------
 
+    // called to change the connection entry associated with conn from specific into
+    // a wildcard (i.e. http2 proxy friendy) mapping
+    void MoveToWildCardConnEntry(nsHttpConnectionInfo *specificCI,
+                                 nsHttpConnectionInfo *wildcardCI,
+                                 nsHttpConnection *conn);
+
     // called to force the transaction queue to be processed once more, giving
     // preference to the specified connection.
     nsresult ProcessPendingQ(nsHttpConnectionInfo *);
     bool     ProcessPendingQForEntry(nsHttpConnectionInfo *);
 
     // Try and process all pending transactions
     nsresult ProcessPendingQ();
 
@@ -226,16 +233,18 @@ public:
     uint32_t GetSpdyCWNDSetting(nsHttpConnectionInfo *host);
 
     bool     SupportsPipelining(nsHttpConnectionInfo *);
 
     bool GetConnectionData(nsTArray<HttpRetParams> *);
 
     void ResetIPFamilyPreference(nsHttpConnectionInfo *);
 
+    uint16_t MaxRequestDelay() { return mMaxRequestDelay; }
+
 private:
     virtual ~nsHttpConnectionMgr();
 
     enum PipeliningState {
         // Host has proven itself pipeline capable through past experience and
         // large pipeline depths are allowed on multiple connections.
         PS_GREEN,
 
@@ -266,16 +275,18 @@ private:
         ~nsConnectionEntry();
 
         nsRefPtr<nsHttpConnectionInfo> mConnInfo;
         nsTArray<nsHttpTransaction*> mPendingQ;    // pending transaction queue
         nsTArray<nsHttpConnection*>  mActiveConns; // active connections
         nsTArray<nsHttpConnection*>  mIdleConns;   // idle persistent connections
         nsTArray<nsHalfOpenSocket*>  mHalfOpens;   // half open connections
 
+        bool AvailableForDispatchNow();
+
         // calculate the number of half open sockets that have not had at least 1
         // connection complete
         uint32_t UnconnectedHalfOpens();
 
         // Remove a particular half open socket from the mHalfOpens array
         void RemoveHalfOpen(nsHalfOpenSocket *);
 
         // Pipeline depths for various states
@@ -520,17 +531,18 @@ private:
     void     ReportProxyTelemetry(nsConnectionEntry *ent);
     nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *,
                              uint32_t, bool);
     void     AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
     void     DecrementActiveConnCount(nsHttpConnection *);
     void     StartedConnect();
     void     RecvdConnect();
 
-    nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *);
+    nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *,
+                                                  bool allowWildCard);
 
     nsresult MakeNewConnection(nsConnectionEntry *ent,
                                nsHttpTransaction *trans);
     bool     AddToShortestPipeline(nsConnectionEntry *ent,
                                    nsHttpTransaction *trans,
                                    nsHttpTransaction::Classifier classification,
                                    uint16_t depthLimit);
 
@@ -596,16 +608,17 @@ private:
                        void               *vparam = nullptr);
 
     // message handlers
     void OnMsgShutdown             (int32_t, void *);
     void OnMsgShutdownConfirm      (int32_t, void *);
     void OnMsgNewTransaction       (int32_t, void *);
     void OnMsgReschedTransaction   (int32_t, void *);
     void OnMsgCancelTransaction    (int32_t, void *);
+    void OnMsgCancelTransactions   (int32_t, void *);
     void OnMsgProcessPendingQ      (int32_t, void *);
     void OnMsgPruneDeadConnections (int32_t, void *);
     void OnMsgSpeculativeConnect   (int32_t, void *);
     void OnMsgReclaimConnection    (int32_t, void *);
     void OnMsgCompleteUpgrade      (int32_t, void *);
     void OnMsgUpdateParam          (int32_t, void *);
     void OnMsgDoShiftReloadConnectionCleanup (int32_t, void *);
     void OnMsgProcessFeedback      (int32_t, void *);
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -105,16 +105,17 @@ public:
     PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
     PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
     bool           AllowPush()   { return mAllowPush; }
     uint32_t       ConnectTimeout()  { return mConnectTimeout; }
     uint32_t       ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
     bool           CriticalRequestPrioritization() { return mCriticalRequestPrioritization; }
     double         BypassCacheLockThreshold() { return mBypassCacheLockThreshold; }
 
+    uint32_t       MaxConnectionsPerOrigin() { return mMaxPersistentConnectionsPerServer; }
     bool           UseRequestTokenBucket() { return mRequestTokenBucketEnabled; }
     uint16_t       RequestTokenBucketMinParallelism() { return mRequestTokenBucketMinParallelism; }
     uint32_t       RequestTokenBucketHz() { return mRequestTokenBucketHz; }
     uint32_t       RequestTokenBucketBurst() {return mRequestTokenBucketBurst; }
 
     bool           PromptTempRedirect()      { return mPromptTempRedirect; }
 
     // TCP Keepalive configuration values.
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -118,16 +118,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mReceivedData(false)
     , mStatusEventPending(false)
     , mHasRequestBody(false)
     , mProxyConnectFailed(false)
     , mHttpResponseMatched(false)
     , mPreserveStream(false)
     , mDispatchedAsBlocking(false)
     , mResponseTimeoutEnabled(true)
+    , mDontRouteViaWildCard(false)
     , mReportedStart(false)
     , mReportedResponseHeader(false)
     , mForTakeResponseHead(nullptr)
     , mResponseHeadTaken(false)
     , mSubmittedRatePacing(false)
     , mPassedRatePacing(false)
     , mSynchronousRatePaceRequest(false)
     , mCountRecv(0)
@@ -1059,16 +1060,17 @@ nsHttpTransaction::Restart()
 
     // limit the number of restart attempts - bug 92224
     if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
         LOG(("reached max request attempts, failing transaction @%p\n", this));
         return NS_ERROR_NET_RESET;
     }
 
     LOG(("restarting transaction @%p\n", this));
+    SetDontRouteViaWildCard(false);
 
     // rewind streams in case we already wrote out the request
     nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
     if (seekable)
         seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
 
     // clear old connection state...
     mSecurityInfo = 0;
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -97,16 +97,24 @@ public:
     // will drop any reference to the response headers after this call.
     nsHttpResponseHead *TakeResponseHead();
 
     // Called to find out if the transaction generated a complete response.
     bool ResponseIsComplete() { return mResponseIsComplete; }
 
     bool      ProxyConnectFailed() { return mProxyConnectFailed; }
 
+    // setting mDontRouteViaWildCard to true means the transaction should only
+    // be dispatched on a specific ConnectionInfo Hash Key (as opposed to a
+    // generic wild card one). That means in the specific case of carrying this
+    // transaction on an HTTP/2 tunnel it will only be dispatched onto an
+    // existing tunnel instead of triggering creation of a new one.
+    void SetDontRouteViaWildCard(bool var) { mDontRouteViaWildCard = var; }
+    bool DontRouteViaWildCard() { return mDontRouteViaWildCard; }
+
     // SetPriority() may only be used by the connection manager.
     void    SetPriority(int32_t priority) { mPriority = priority; }
     int32_t    Priority()                 { return mPriority; }
 
     const TimingStruct& Timings() const { return mTimings; }
     enum Classifier Classification() { return mClassification; }
 
     void PrintDiagnostics(nsCString &log);
@@ -242,16 +250,17 @@ private:
     bool                            mReceivedData;
     bool                            mStatusEventPending;
     bool                            mHasRequestBody;
     bool                            mProxyConnectFailed;
     bool                            mHttpResponseMatched;
     bool                            mPreserveStream;
     bool                            mDispatchedAsBlocking;
     bool                            mResponseTimeoutEnabled;
+    bool                            mDontRouteViaWildCard;
 
     // mClosed           := transaction has been explicitly closed
     // mTransactionDone  := transaction ran to completion or was interrupted
     // mResponseComplete := transaction ran to completion
 
     // For Restart-In-Progress Functionality
     bool                            mReportedStart;
     bool                            mReportedResponseHeader;