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
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 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())
-        return;
-
-    LOG(("nsHttpConnection::SetupSSL Setting up "
-         "Next Protocol Negotiation"));
-    nsCOMPtr<nsISupports> securityInfo;
-    nsresult rv =
-        mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
-    if (NS_FAILED(rv))
+    if (!mConnInfo->FirstHopSSL()) {
         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 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();
 
-    if (!mSpdySession->AddStream(httpTransaction, priority)) {
-        MOZ_ASSERT(false, "AddStream should never fail due to"
-                   "RoomForMore() admission check");
+    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;
+    }
+
+    if (proxyStartSSL) {
+        rv = ssl->ProxyStartSSL();
+        if (NS_FAILED(rv)){
+            return rv;
+        }
+    }
 
-    return ssl->ProxyStartSSL();
+    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;