bug 528222 Basic SPDY web transport support. r=honzab r=bsmith sr=biesi
authorPatrick McManus <mcmanus@ducksong.com>
Fri, 02 Dec 2011 10:28:56 -0500
changeset 81177 0bd45ead1676ae227025ed4dd20181a94ffecff8
parent 81173 330cc2666567bf32f080a39d4feca0c31bd8c0d3
child 81178 0eb13ad19d08be53e991cc03e4d70ec4fd9fb995
push id21564
push usermak77@bonardo.net
push dateSat, 03 Dec 2011 11:10:17 +0000
treeherdermozilla-central@a68c96c1d8e0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab, bsmith, biesi
bugs528222
milestone11.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 528222 Basic SPDY web transport support. r=honzab r=bsmith sr=biesi patch 0
modules/libpref/src/init/all.js
netwerk/base/src/nsSocketTransport2.cpp
netwerk/protocol/http/Makefile.in
netwerk/protocol/http/SpdySession.cpp
netwerk/protocol/http/SpdySession.h
netwerk/protocol/http/SpdyStream.cpp
netwerk/protocol/http/SpdyStream.h
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpPipeline.cpp
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
netwerk/socket/nsISSLSocketControl.idl
security/manager/ssl/src/nsNSSCallbacks.cpp
security/manager/ssl/src/nsNSSIOLayer.cpp
security/manager/ssl/src/nsNSSIOLayer.h
toolkit/components/telemetry/TelemetryHistograms.h
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -782,16 +782,22 @@ pref("network.http.qos", 0);
 // to wait before trying a different connection. 0 means do not use a second
 // connection.
 pref("network.http.connection-retry-timeout", 250);
 
 // Disable IPv6 for backup connections to workaround problems about broken
 // IPv6 connectivity.
 pref("network.http.fast-fallback-to-IPv4", true);
 
+// Try and use SPDY when using SSL
+pref("network.http.spdy.enabled", false);
+pref("network.http.spdy.chunk-size", 4096);
+pref("network.http.spdy.timeout", 180);
+pref("network.http.spdy.coalesce-hostnames", true);
+
 // default values for FTP
 // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
 // Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22)
 // per Section 4.7 "Low-Latency Data Service Class".
 pref("network.ftp.data.qos", 0);
 pref("network.ftp.control.qos", 0);
 
 // </http>
--- a/netwerk/base/src/nsSocketTransport2.cpp
+++ b/netwerk/base/src/nsSocketTransport2.cpp
@@ -547,17 +547,18 @@ nsSocketOutputStream::Flush()
 
 NS_IMETHODIMP
 nsSocketOutputStream::Write(const char *buf, PRUint32 count, PRUint32 *countWritten)
 {
     SOCKET_LOG(("nsSocketOutputStream::Write [this=%x count=%u]\n", this, count));
 
     *countWritten = 0;
 
-    if (count == 0)
+    // A write of 0 bytes can be used to force the initial SSL handshake.
+    if (count == 0 && mByteCount)
         return NS_OK;
 
     PRFileDesc *fd;
     {
         MutexAutoLock lock(mTransport->mLock);
 
         if (NS_FAILED(mCondition))
             return mCondition;
--- a/netwerk/protocol/http/Makefile.in
+++ b/netwerk/protocol/http/Makefile.in
@@ -105,16 +105,18 @@ CPPSRCS = \
   HttpBaseChannel.cpp \
   nsHttpChannel.cpp \
   nsHttpPipeline.cpp \
   nsHttpActivityDistributor.cpp \
   nsHttpChannelAuthProvider.cpp \
   HttpChannelParent.cpp \
   HttpChannelChild.cpp \
   HttpChannelParentListener.cpp \
+  SpdySession.cpp \
+  SpdyStream.cpp \
   $(NULL)
 
 LOCAL_INCLUDES = \
   -I$(srcdir)/../../base/src \
   -I$(topsrcdir)/xpcom/ds \
   -I$(topsrcdir)/content/base/src \
   -I$(topsrcdir)/content/events/src \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SpdySession.cpp
@@ -0,0 +1,1773 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick McManus <mcmanus@ducksong.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsHttp.h"
+#include "SpdySession.h"
+#include "SpdyStream.h"
+#include "nsHttpConnection.h"
+#include "prnetdb.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Preferences.h"
+
+#ifdef DEBUG
+// defined by the socket transport service while active
+extern PRThread *gSocketThread;
+#endif
+
+namespace mozilla {
+namespace net {
+
+// SpdySession has multiple inheritance of things that implement
+// nsISupports, so this magic is taken from nsHttpPipeline that
+// implements some of the same abstract classes.
+NS_IMPL_THREADSAFE_ADDREF(SpdySession)
+NS_IMPL_THREADSAFE_RELEASE(SpdySession)
+NS_INTERFACE_MAP_BEGIN(SpdySession)
+    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+SpdySession::SpdySession(nsAHttpTransaction *aHttpTransaction,
+                         nsISocketTransport *aSocketTransport,
+                         PRInt32 firstPriority)
+  : mSocketTransport(aSocketTransport),
+    mSegmentReader(nsnull),
+    mSegmentWriter(nsnull),
+    mSendingChunkSize(kSendingChunkSize),
+    mNextStreamID(1),
+    mConcurrentHighWater(0),
+    mDownstreamState(BUFFERING_FRAME_HEADER),
+    mPartialFrame(nsnull),
+    mFrameBufferSize(kDefaultBufferSize),
+    mFrameBufferUsed(0),
+    mFrameDataLast(false),
+    mFrameDataStream(nsnull),
+    mNeedsCleanup(nsnull),
+    mDecompressBufferSize(kDefaultBufferSize),
+    mDecompressBufferUsed(0),
+    mShouldGoAway(false),
+    mClosed(false),
+    mCleanShutdown(false),
+    mGoAwayID(0),
+    mMaxConcurrent(kDefaultMaxConcurrent),
+    mConcurrent(0),
+    mServerPushedResources(0),
+    mOutputQueueSize(kDefaultQueueSize),
+    mOutputQueueUsed(0),
+    mOutputQueueSent(0)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+  LOG(("SpdySession::SpdySession %p transaction 1 = %p",
+       this, aHttpTransaction));
+  
+  mStreamIDHash.Init();
+  mStreamTransactionHash.Init();
+  mConnection = aHttpTransaction->Connection();
+  mFrameBuffer = new char[mFrameBufferSize];
+  mDecompressBuffer = new char[mDecompressBufferSize];
+  mOutputQueueBuffer = new char[mOutputQueueSize];
+  zlibInit();
+  
+  mSendingChunkSize =
+    Preferences::GetInt("network.http.spdy.chunk-size", kSendingChunkSize);
+  AddStream(aHttpTransaction, firstPriority);
+}
+
+PLDHashOperator
+SpdySession::Shutdown(nsAHttpTransaction *key,
+                      nsAutoPtr<SpdyStream> &stream,
+                      void *closure)
+{
+  SpdySession *self = static_cast<SpdySession *>(closure);
+  
+  if (self->mCleanShutdown &&
+      self->mGoAwayID < stream->StreamID())
+    stream->Close(NS_ERROR_NET_RESET); // can be restarted
+  else
+    stream->Close(NS_ERROR_ABORT);
+
+  return PL_DHASH_NEXT;
+}
+
+SpdySession::~SpdySession()
+{
+  LOG(("SpdySession::~SpdySession %p", this));
+
+  inflateEnd(&mDownstreamZlib);
+  deflateEnd(&mUpstreamZlib);
+  
+  mStreamTransactionHash.Enumerate(Shutdown, this);
+  Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
+  Telemetry::Accumulate(Telemetry::SPDY_TOTAL_STREAMS, (mNextStreamID - 1) / 2);
+  Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
+                        mServerPushedResources);
+}
+
+typedef nsresult  (*Control_FX) (SpdySession *self);
+static Control_FX sControlFunctions[] = 
+{
+  nsnull,
+  SpdySession::HandleSynStream,
+  SpdySession::HandleSynReply,
+  SpdySession::HandleRstStream,
+  SpdySession::HandleSettings,
+  SpdySession::HandleNoop,
+  SpdySession::HandlePing,
+  SpdySession::HandleGoAway,
+  SpdySession::HandleHeaders,
+  SpdySession::HandleWindowUpdate
+};
+
+bool
+SpdySession::RoomForMoreConcurrent()
+{
+  return (mConcurrent < mMaxConcurrent);
+}
+
+bool
+SpdySession::RoomForMoreStreams()
+{
+  if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
+    return false;
+
+  return !mShouldGoAway;
+}
+
+PRUint32
+SpdySession::RegisterStreamID(SpdyStream *stream)
+{
+  LOG(("SpdySession::RegisterStreamID session=%p stream=%p id=0x%X concurrent=%d",
+       this, stream, mNextStreamID, mConcurrent));
+
+  NS_ABORT_IF_FALSE(mNextStreamID < 0xfffffff0,
+                    "should have stopped admitting streams");
+  
+  PRUint32 result = mNextStreamID;
+  mNextStreamID += 2;
+
+  // We've used up plenty of ID's on this session. Start
+  // moving to a new one before there is a crunch involving
+  // server push streams or concurrent non-registered submits
+  if (mNextStreamID >= kMaxStreamID)
+    mShouldGoAway = true;
+
+  mStreamIDHash.Put(result, stream);
+  return result;
+}
+
+bool
+SpdySession::AddStream(nsAHttpTransaction *aHttpTransaction,
+                       PRInt32 aPriority)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  NS_ABORT_IF_FALSE(!mStreamTransactionHash.Get(aHttpTransaction),
+                    "AddStream duplicate transaction pointer");
+
+  aHttpTransaction->SetConnection(this);
+  SpdyStream *stream = new SpdyStream(aHttpTransaction,
+                                      this,
+                                      mSocketTransport,
+                                      mSendingChunkSize,
+                                      &mUpstreamZlib,
+                                      aPriority);
+
+  
+  LOG(("SpdySession::AddStream %p stream %p NextID=0x%X (tentative)",
+       this, stream, mNextStreamID));
+
+  mStreamTransactionHash.Put(aHttpTransaction, stream);
+
+  if (RoomForMoreConcurrent()) {
+    LOG(("SpdySession::AddStream %p stream %p activated immediately.",
+         this, stream));
+    ActivateStream(stream);
+  }
+  else {
+    LOG(("SpdySession::AddStream %p stream %p queued.",
+         this, stream));
+    mQueuedStreams.Push(stream);
+  }
+  
+  return true;
+}
+
+void
+SpdySession::ActivateStream(SpdyStream *stream)
+{
+  mConcurrent++;
+  if (mConcurrent > mConcurrentHighWater)
+    mConcurrentHighWater = mConcurrent;
+  LOG(("SpdySession::AddStream %p activating stream %p Currently %d"
+         "streams in session, high water mark is %d",
+       this, stream, mConcurrent, mConcurrentHighWater));
+
+  mReadyForWrite.Push(stream);
+  SetWriteCallbacks(stream->Transaction());
+
+  // Kick off the SYN transmit without waiting for the poll loop
+  PRUint32 countRead;
+  ReadSegments(nsnull, kDefaultBufferSize, &countRead);
+}
+
+void
+SpdySession::ProcessPending()
+{
+  while (RoomForMoreConcurrent()) {
+    SpdyStream *stream = static_cast<SpdyStream *>(mQueuedStreams.PopFront());
+    if (!stream)
+      return;
+    LOG(("SpdySession::ProcessPending %p stream %p activated from queue.",
+         this, stream));
+    ActivateStream(stream);
+  }
+}
+
+void
+SpdySession::SetWriteCallbacks(nsAHttpTransaction *aTrans)
+{
+  if (mConnection && (WriteQueueSize() || mOutputQueueUsed))
+      mConnection->ResumeSend(aTrans);
+}
+
+void
+SpdySession::FlushOutputQueue()
+{
+  if (!mSegmentReader || !mOutputQueueUsed)
+    return;
+  
+  nsresult rv;
+  PRUint32 countRead;
+  PRUint32 avail = mOutputQueueUsed - mOutputQueueSent;
+
+  rv = mSegmentReader->
+    OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
+                                     &countRead);
+  LOG(("SpdySession::FlushOutputQueue %p sz=%d rv=%x actual=%d",
+       this, avail, rv, countRead));
+  
+  // Dont worry about errors on write, we will pick this up as a read error too
+  if (NS_FAILED(rv))
+    return;
+  
+  if (countRead == avail) {
+    mOutputQueueUsed = 0;
+    mOutputQueueSent = 0;
+    return;
+  }
+
+  mOutputQueueSent += countRead;
+  if (mOutputQueueSize - mOutputQueueUsed < kQueueTailRoom) {
+    // The output queue is filling up and we just sent some data out, so
+    // this is a good time to rearrange the output queue.
+
+    mOutputQueueUsed -= mOutputQueueSent;
+    memmove(mOutputQueueBuffer.get(),
+            mOutputQueueBuffer.get() + mOutputQueueSent,
+            mOutputQueueUsed);
+    mOutputQueueSent = 0;
+  }
+}
+
+void
+SpdySession::DontReuse()
+{
+  mShouldGoAway = true;
+  if(!mStreamTransactionHash.Count())
+    Close(NS_OK);
+}
+
+PRUint32
+SpdySession::WriteQueueSize()
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+  PRUint32 count = mUrgentForWrite.GetSize() + mReadyForWrite.GetSize();
+
+  if (mPartialFrame)
+    ++count;
+  return count;
+}
+
+void
+SpdySession::ChangeDownstreamState(enum stateType newState)
+{
+  LOG(("SpdyStream::ChangeDownstreamState() %p from %X to %X",
+       this, mDownstreamState, newState));
+  mDownstreamState = newState;
+
+  if (mDownstreamState == BUFFERING_FRAME_HEADER) {
+    if (mFrameDataLast && mFrameDataStream) {
+      mFrameDataLast = 0;
+      if (!mFrameDataStream->RecvdFin()) {
+        mFrameDataStream->SetRecvdFin(true);
+        --mConcurrent;
+        ProcessPending();
+      }
+    }
+    mFrameBufferUsed = 0;
+    mFrameDataStream = nsnull;
+  }
+  
+  return;
+}
+
+void
+SpdySession::EnsureBuffer(nsAutoArrayPtr<char> &buf,
+                          PRUint32 newSize,
+                          PRUint32 preserve,
+                          PRUint32 &objSize)
+{
+  if (objSize >= newSize)
+    return;
+  
+  objSize = newSize;
+  nsAutoArrayPtr<char> tmp(new char[objSize]);
+  memcpy (tmp, buf, preserve);
+  buf = tmp;
+}
+
+void
+SpdySession::zlibInit()
+{
+  mDownstreamZlib.zalloc = SpdyStream::zlib_allocator;
+  mDownstreamZlib.zfree = SpdyStream::zlib_destructor;
+  mDownstreamZlib.opaque = Z_NULL;
+
+  inflateInit(&mDownstreamZlib);
+
+  mUpstreamZlib.zalloc = SpdyStream::zlib_allocator;
+  mUpstreamZlib.zfree = SpdyStream::zlib_destructor;
+  mUpstreamZlib.opaque = Z_NULL;
+
+  deflateInit(&mUpstreamZlib, Z_DEFAULT_COMPRESSION);
+  deflateSetDictionary(&mUpstreamZlib,
+                       reinterpret_cast<const unsigned char *>
+                       (SpdyStream::kDictionary),
+                       strlen(SpdyStream::kDictionary) + 1);
+
+}
+
+nsresult
+SpdySession::DownstreamUncompress(char *blockStart, PRUint32 blockLen)
+{
+  mDecompressBufferUsed = 0;
+
+  mDownstreamZlib.avail_in = blockLen;
+  mDownstreamZlib.next_in = reinterpret_cast<unsigned char *>(blockStart);
+
+  do {
+    mDownstreamZlib.next_out =
+      reinterpret_cast<unsigned char *>(mDecompressBuffer.get()) +
+      mDecompressBufferUsed;
+    mDownstreamZlib.avail_out = mDecompressBufferSize - mDecompressBufferUsed;
+    int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH);
+
+    if (zlib_rv == Z_NEED_DICT)
+      inflateSetDictionary(&mDownstreamZlib,
+                           reinterpret_cast<const unsigned char *>
+                           (SpdyStream::kDictionary),
+                           strlen(SpdyStream::kDictionary) + 1);
+    
+    if (zlib_rv == Z_DATA_ERROR || zlib_rv == Z_MEM_ERROR)
+      return NS_ERROR_FAILURE;
+
+    mDecompressBufferUsed += mDecompressBufferSize -mDecompressBufferUsed -
+      mDownstreamZlib.avail_out;
+
+    if (mDownstreamZlib.avail_out) {
+      LOG(("SpdySession::DownstreamUncompress %p Large Headers - so far %d",
+           this, mDecompressBufferSize));
+      EnsureBuffer(mDecompressBuffer,
+                   mDecompressBufferSize + 4096,
+                   mDecompressBufferUsed,
+                   mDecompressBufferSize);
+    }
+  }
+  while (mDownstreamZlib.avail_in);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::FindHeader(nsCString name,
+                        nsDependentCSubstring &value)
+{
+  const unsigned char *nvpair = reinterpret_cast<unsigned char *>
+    (mDecompressBuffer.get()) + 2;
+  const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
+    (mDecompressBuffer.get()) + mDecompressBufferUsed;
+  if (lastHeaderByte < nvpair)
+    return NS_ERROR_ILLEGAL_VALUE;
+  PRUint16 numPairs =
+    PR_ntohs(reinterpret_cast<PRUint16 *>(mDecompressBuffer.get())[0]);
+  for (PRUint16 index = 0; index < numPairs; ++index) {
+    if (lastHeaderByte < nvpair + 2)
+      return NS_ERROR_ILLEGAL_VALUE;
+    PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1];
+    if (lastHeaderByte < nvpair + 2 + nameLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+    nsDependentCSubstring nameString =
+      Substring (reinterpret_cast<const char *>(nvpair) + 2,
+                 reinterpret_cast<const char *>(nvpair) + 2 + nameLen);
+    if (lastHeaderByte < nvpair + 4 + nameLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+    PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen];
+    if (lastHeaderByte < nvpair + 4 + nameLen + valueLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+    if (nameString.Equals(name)) {
+      value.Assign(((char *)nvpair) + 4 + nameLen, valueLen);
+      return NS_OK;
+    }
+    nvpair += 4 + nameLen + valueLen;
+  }
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+SpdySession::ConvertHeaders(nsDependentCSubstring &status,
+                            nsDependentCSubstring &version)
+{
+
+  mFlatHTTPResponseHeaders.Truncate();
+  mFlatHTTPResponseHeadersOut = 0;
+  mFlatHTTPResponseHeaders.SetCapacity(mDecompressBufferUsed + 64);
+
+  // Connection, Keep-Alive and chunked transfer encodings are to be
+  // removed.
+
+  // Content-Length is 'advisory'.. we will not strip it because it can
+  // create UI feedback.
+  
+  mFlatHTTPResponseHeaders.Append(version);
+  mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(" "));
+  mFlatHTTPResponseHeaders.Append(status);
+  mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+
+  const unsigned char *nvpair = reinterpret_cast<unsigned char *>
+    (mDecompressBuffer.get()) + 2;
+  const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
+    (mDecompressBuffer.get()) + mDecompressBufferUsed;
+
+  if (lastHeaderByte < nvpair)
+    return NS_ERROR_ILLEGAL_VALUE;
+
+  PRUint16 numPairs =
+    PR_ntohs(reinterpret_cast<PRUint16 *>(mDecompressBuffer.get())[0]);
+
+  for (PRUint16 index = 0; index < numPairs; ++index) {
+    if (lastHeaderByte < nvpair + 2)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1];
+    if (lastHeaderByte < nvpair + 2 + nameLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    nsDependentCSubstring nameString =
+      Substring (reinterpret_cast<const char *>(nvpair) + 2,
+                 reinterpret_cast<const char *>(nvpair) + 2 + nameLen);
+
+    // a null in the name string is particularly wrong because it will
+    // break the fix-up-nulls-in-value-string algorithm.
+    if (nameString.FindChar(0) != -1)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    if (lastHeaderByte < nvpair + 4 + nameLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+    PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen];
+    if (lastHeaderByte < nvpair + 4 + nameLen + valueLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    if (!nameString.Equals(NS_LITERAL_CSTRING("version")) &&
+        !nameString.Equals(NS_LITERAL_CSTRING("status")) &&
+        !nameString.Equals(NS_LITERAL_CSTRING("connection")) &&
+        !nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding")) &&
+        !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) {
+      nsDependentCSubstring valueString =
+        Substring (reinterpret_cast<const char *>(nvpair) + 4 + nameLen,
+                   reinterpret_cast<const char *>(nvpair) + 4 + nameLen +
+                   valueLen);
+      
+      mFlatHTTPResponseHeaders.Append(nameString);
+      mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": "));
+
+      PRInt32 valueIndex;
+      // NULLs are really "\r\nhdr: "
+      while ((valueIndex = valueString.FindChar(0)) != -1) {
+        nsCString replacement = NS_LITERAL_CSTRING("\r\n");
+        replacement.Append(nameString);
+        replacement.Append(NS_LITERAL_CSTRING(": "));
+        valueString.Replace(valueIndex, 1, replacement);
+      }
+
+      mFlatHTTPResponseHeaders.Append(valueString);
+      mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+    }
+    nvpair += 4 + nameLen + valueLen;
+  }
+
+  mFlatHTTPResponseHeaders.Append(
+    NS_LITERAL_CSTRING("X-Firefox-Spdy: 1\r\n\r\n"));
+  LOG (("decoded response headers are:\n%s",
+        mFlatHTTPResponseHeaders.get()));
+  
+  return NS_OK;
+}
+
+void
+SpdySession::GeneratePing(PRUint32 aID)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  LOG(("SpdySession::GeneratePing %p 0x%X\n", this, aID));
+
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12,
+               mOutputQueueUsed, mOutputQueueSize);
+  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+  mOutputQueueUsed += 12;
+
+  packet[0] = kFlag_Control;
+  packet[1] = 2;                                  /* version 2 */
+  packet[2] = 0;
+  packet[3] = CONTROL_TYPE_PING;
+  packet[4] = 0;                                  /* flags */
+  packet[5] = 0;
+  packet[6] = 0;
+  packet[7] = 4;                                  /* length */
+  
+  aID = PR_htonl(aID);
+  memcpy (packet + 8, &aID, 4);
+
+  FlushOutputQueue();
+}
+
+void
+SpdySession::GenerateRstStream(PRUint32 aStatusCode, PRUint32 aID)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  LOG(("SpdySession::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
+
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 16,
+               mOutputQueueUsed, mOutputQueueSize);
+  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+  mOutputQueueUsed += 16;
+
+  packet[0] = kFlag_Control;
+  packet[1] = 2;                                  /* version 2 */
+  packet[2] = 0;
+  packet[3] = CONTROL_TYPE_RST_STREAM;
+  packet[4] = 0;                                  /* flags */
+  packet[5] = 0;
+  packet[6] = 0;
+  packet[7] = 8;                                  /* length */
+  
+  aID = PR_htonl(aID);
+  memcpy (packet + 8, &aID, 4);
+  aStatusCode = PR_htonl(aStatusCode);
+  memcpy (packet + 12, &aStatusCode, 4);
+
+  FlushOutputQueue();
+}
+
+void
+SpdySession::GenerateGoAway()
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  LOG(("SpdySession::GenerateGoAway %p\n", this));
+
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12,
+               mOutputQueueUsed, mOutputQueueSize);
+  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+  mOutputQueueUsed += 12;
+
+  memset (packet, 0, 12);
+  packet[0] = kFlag_Control;
+  packet[1] = 2;                                  /* version 2 */
+  packet[3] = CONTROL_TYPE_GOAWAY;
+  packet[7] = 4;                                  /* data length */
+  
+  // last-good-stream-id are bytes 8-11, when we accept server push this will
+  // need to be set non zero
+
+  FlushOutputQueue();
+}
+
+void
+SpdySession::CleanupStream(SpdyStream *aStream, nsresult aResult)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  LOG(("SpdySession::CleanupStream %p %p 0x%x %X\n",
+       this, aStream, aStream->StreamID(), aResult));
+
+  nsresult abortCode = NS_OK;
+
+  if (!aStream->RecvdFin() && aStream->StreamID()) {
+    LOG(("Stream had not processed recv FIN, sending RST"));
+    GenerateRstStream(RST_CANCEL, aStream->StreamID());
+    --mConcurrent;
+    ProcessPending();
+  }
+  
+  // Check if partial frame writer
+  if (mPartialFrame == aStream) {
+    LOG(("Stream had active partial write frame - need to abort session"));
+    abortCode = aResult;
+    if (NS_SUCCEEDED(abortCode))
+      abortCode = NS_ERROR_ABORT;
+    
+    mPartialFrame = nsnull;
+  }
+  
+  // Check if partial frame reader
+  if (aStream == mFrameDataStream) {
+    LOG(("Stream had active partial read frame on close"));
+    ChangeDownstreamState(DISCARD_DATA_FRAME);
+    mFrameDataStream = nsnull;
+  }
+
+  // check the streams blocked on write, this is linear but the list
+  // should be pretty short.
+  PRUint32 size = mReadyForWrite.GetSize();
+  for (PRUint32 count = 0; count < size; ++count) {
+    SpdyStream *stream = static_cast<SpdyStream *>(mReadyForWrite.PopFront());
+    if (stream != aStream)
+      mReadyForWrite.Push(stream);
+  }
+
+  // Check the streams blocked on urgent (i.e. window update) writing.
+  // This should also be short.
+  size = mUrgentForWrite.GetSize();
+  for (PRUint32 count = 0; count < size; ++count) {
+    SpdyStream *stream = static_cast<SpdyStream *>(mUrgentForWrite.PopFront());
+    if (stream != aStream)
+      mUrgentForWrite.Push(stream);
+  }
+
+  // Remove the stream from the ID hash table. (this one isn't short, which is
+  // why it is hashed.)
+  mStreamIDHash.Remove(aStream->StreamID());
+
+  // Send the stream the close() indication
+  aStream->Close(aResult);
+
+  // removing from the stream transaction hash will
+  // delete the SpdyStream and drop the reference to
+  // its transaction
+  mStreamTransactionHash.Remove(aStream->Transaction());
+
+  if (NS_FAILED(abortCode))
+    Close(abortCode);
+  else if (mShouldGoAway && !mStreamTransactionHash.Count())
+    Close(NS_OK);
+}
+
+nsresult
+SpdySession::HandleSynStream(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM,
+                    "wrong control type");
+  
+  if (self->mFrameDataSize < 12) {
+    LOG(("SpdySession::HandleSynStream %p SYN_STREAM too short data=%d",
+         self, self->mFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  PRUint32 streamID =
+    PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]);
+
+  LOG(("SpdySession::HandleSynStream %p recv SYN_STREAM (push) for ID 0x%X.",
+       self, streamID));
+    
+  if (streamID & 0x01) {                   // test for odd stream ID
+    LOG(("SpdySession::HandleSynStream %p recvd SYN_STREAM id must be even.",
+         self));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  ++(self->mServerPushedResources);
+
+  // Anytime we start using the high bit of stream ID (either client or server)
+  // begin to migrate to a new session.
+  if (streamID >= kMaxStreamID)
+    self->mShouldGoAway = true;
+
+  // todo populate cache. For now, just reject server push p3
+  self->GenerateRstStream(RST_REFUSED_STREAM, streamID);
+  self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::HandleSynReply(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY,
+                    "wrong control type");
+
+  if (self->mFrameDataSize < 8) {
+    LOG(("SpdySession::HandleSynReply %p SYN REPLY too short data=%d",
+         self, self->mFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+  
+  PRUint32 streamID =
+    PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]);
+  self->mFrameDataStream = self->mStreamIDHash.Get(streamID);
+  if (!self->mFrameDataStream) {
+    LOG(("SpdySession::HandleSynReply %p lookup streamID in syn_reply "
+         "0x%X failed. NextStreamID = 0x%x", self, streamID,
+          self->mNextStreamID));
+    if (streamID >= self->mNextStreamID)
+      self->GenerateRstStream(RST_INVALID_STREAM, streamID);
+    
+    // It is likely that this is a reply to a stream ID that has been canceled.
+    // For the most part we would like to ignore it, but the header needs to be
+    // be parsed to keep the compression context synchronized
+    self->DownstreamUncompress(self->mFrameBuffer + 14,
+                               self->mFrameDataSize - 6);
+    self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+    return NS_OK;
+  }
+  
+  if (!self->mFrameDataStream->SetFullyOpen()) {
+    // "If an endpoint receives multiple SYN_REPLY frames for the same active
+    // stream ID, it must drop the stream, and send a RST_STREAM for the
+    // stream with the error PROTOCOL_ERROR."
+    //
+    // In addition to that we abort the session - this is a serious protocol
+    // violation.
+
+    self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID);
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  self->mFrameDataLast = self->mFrameBuffer[4] & kFlag_Data_FIN;
+
+  if (self->mFrameBuffer[4] & kFlag_Data_UNI) {
+    LOG(("SynReply had unidirectional flag set on it - nonsensical"));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  LOG(("SpdySession::HandleSynReply %p SYN_REPLY for 0x%X fin=%d",
+       self, streamID, self->mFrameDataLast));
+  
+  // The spdystream needs to see flattened http headers
+  // The Frame Buffer currently holds the complete SYN_REPLY
+  // frame. The interesting data is at offset 14, where the
+  // compressed name/value header block lives.
+  // We unpack that into the mDecompressBuffer - we can't do
+  // it streamed because the version and status information
+  // is not guaranteed to be first. This is then finally
+  // converted to HTTP format in mFlatHTTPResponseHeaders
+
+  nsresult rv = self->DownstreamUncompress(self->mFrameBuffer + 14,
+                                           self->mFrameDataSize - 6);
+  if (NS_FAILED(rv))
+    return rv;
+  
+  Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE,
+                        self->mFrameDataSize - 6);
+  PRUint32 ratio =
+    (self->mFrameDataSize - 6) * 100 / self->mDecompressBufferUsed;
+  Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
+
+  // status and version are required.
+  nsDependentCSubstring status, version;
+  rv = self->FindHeader(NS_LITERAL_CSTRING("status"), status);
+  if (NS_FAILED(rv))
+    return rv;
+
+  rv = self->FindHeader(NS_LITERAL_CSTRING("version"), version);
+  if (NS_FAILED(rv))
+    return rv;
+
+  rv = self->ConvertHeaders(status, version);
+  if (NS_FAILED(rv))
+    return rv;
+
+  self->ChangeDownstreamState(PROCESSING_CONTROL_SYN_REPLY);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::HandleRstStream(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_RST_STREAM,
+                    "wrong control type");
+
+  if (self->mFrameDataSize != 8) {
+    LOG(("SpdySession::HandleRstStream %p RST_STREAM wrong length data=%d",
+         self, self->mFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  PRUint32 streamID =
+    PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]);
+
+  self->mDownstreamRstReason =
+    PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[3]);
+
+  LOG(("SpdySession::HandleRstStream %p RST_STREAM Reason Code %u ID %x",
+       self, self->mDownstreamRstReason, streamID));
+
+  if (self->mDownstreamRstReason == RST_INVALID_STREAM ||
+      self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) {
+    // basically just ignore this
+    self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+    return NS_OK;
+  }
+
+  self->mFrameDataStream = self->mStreamIDHash.Get(streamID);
+  if (!self->mFrameDataStream) {
+    LOG(("SpdySession::HandleRstStream %p lookup streamID for RST Frame "
+         "0x%X failed", self, streamID));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+    
+  self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::HandleSettings(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SETTINGS,
+                    "wrong control type");
+
+  if (self->mFrameDataSize < 4) {
+    LOG(("SpdySession::HandleSettings %p SETTINGS wrong length data=%d",
+         self, self->mFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  PRUint32 numEntries =
+    PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]);
+
+  LOG(("SpdySession::HandleSettings %p SETTINGS Control Frame with %d entries",
+       self, numEntries));
+
+  for (PRUint32 index = 0; index < numEntries; ++index) {
+    // To clarify the v2 spec:
+    // Each entry is a 24 bits of a little endian id
+    // followed by 8 bits of flags
+    // followed by a 32 bit big endian value
+    
+    unsigned char *setting = reinterpret_cast<unsigned char *>
+      (self->mFrameBuffer.get()) + 12 + index * 8;
+
+    PRUint32 id = (setting[2] << 16) + (setting[1] << 8) + setting[0];
+    PRUint32 flags = setting[3];
+    PRUint32 value =  PR_ntohl(reinterpret_cast<PRUint32 *>(setting)[1]);
+
+    LOG(("Settings ID %d, Flags %X, Value %d",id, flags, value));
+
+    switch (id)
+    {
+    case SETTINGS_TYPE_UPLOAD_BW:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_UL_BW, value);
+      break;
+      
+    case SETTINGS_TYPE_DOWNLOAD_BW:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_DL_BW, value);
+      break;
+      
+    case SETTINGS_TYPE_RTT:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RTT, value);
+      break;
+      
+    case SETTINGS_TYPE_MAX_CONCURRENT:
+      self->mMaxConcurrent = value;
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
+      break;
+      
+    case SETTINGS_TYPE_CWND:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_CWND, value);
+      break;
+      
+    case SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RETRANS, value);
+      break;
+      
+    case SETTINGS_TYPE_INITIAL_WINDOW:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
+      break;
+      
+    default:
+      break;
+    }
+    
+  }
+  
+  self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::HandleNoop(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_NOOP,
+                    "wrong control type");
+
+  if (self->mFrameDataSize != 0) {
+    LOG(("SpdySession::HandleNoop %p NOP had data %d",
+         self, self->mFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  LOG(("SpdySession::HandleNoop %p NOP.", self));
+
+  self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::HandlePing(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_PING,
+                    "wrong control type");
+
+  if (self->mFrameDataSize != 4) {
+    LOG(("SpdySession::HandlePing %p PING had wrong amount of data %d",
+         self, self->mFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  PRUint32 pingID =
+    PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]);
+
+  LOG(("SpdySession::HandlePing %p PING ID 0x%X.", self, pingID));
+
+  if (pingID & 0x01) {
+    // We never expect to see an odd PING beacuse we never generate PING.
+      // The spec mandates ignoring this
+    LOG(("SpdySession::HandlePing %p PING ID from server was odd.",
+         self));
+  }
+  else {
+    self->GeneratePing(pingID);
+  }
+    
+  self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::HandleGoAway(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_GOAWAY,
+                    "wrong control type");
+
+  if (self->mFrameDataSize != 4) {
+    LOG(("SpdySession::HandleGoAway %p GOAWAY had wrong amount of data %d",
+         self, self->mFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  self->mShouldGoAway = true;
+  self->mGoAwayID =
+    PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]);
+  self->mCleanShutdown = true;
+  
+  LOG(("SpdySession::HandleGoAway %p GOAWAY Last-Good-ID 0x%X.",
+       self, self->mGoAwayID));
+  self->ResumeRecv(self);
+  self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::HandleHeaders(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_HEADERS,
+                    "wrong control type");
+
+  if (self->mFrameDataSize < 10) {
+    LOG(("SpdySession::HandleHeaders %p HEADERS had wrong amount of data %d",
+         self, self->mFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  PRUint32 streamID =
+    PR_ntohl(reinterpret_cast<PRUint32 *>(self->mFrameBuffer.get())[2]);
+
+  // this is actually not legal in the HTTP mapping of SPDY. All
+  // headers are in the syn or syn reply. Log and ignore it.
+
+  LOG(("SpdySession::HandleHeaders %p HEADERS for Stream 0x%X. "
+       "They are ignored in the HTTP/SPDY mapping.",
+       self, streamID));
+  self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+  return NS_OK;
+}
+
+nsresult
+SpdySession::HandleWindowUpdate(SpdySession *self)
+{
+  NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE,
+                    "wrong control type");
+  LOG(("SpdySession::HandleWindowUpdate %p WINDOW UPDATE was "
+       "received. WINDOW UPDATE is no longer defined in v2. Ignoring.",
+       self));
+
+  self->ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+  return NS_OK;
+}
+
+// Used for the hashtable enumeration to propogate OnTransportStatus events
+struct transportStatus
+{
+  nsITransport *transport;
+  nsresult status;
+  PRUint64 progress;
+};
+
+static PLDHashOperator
+StreamTransportStatus(nsAHttpTransaction *key,
+                      nsAutoPtr<SpdyStream> &stream,
+                      void *closure)
+{
+  struct transportStatus *status =
+    static_cast<struct transportStatus *>(closure);
+
+  stream->Transaction()->OnTransportStatus(status->transport,
+                                           status->status,
+                                           status->progress);
+  return PL_DHASH_NEXT;
+}
+
+
+//-----------------------------------------------------------------------------
+// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
+// of these methods
+//-----------------------------------------------------------------------------
+
+void
+SpdySession::OnTransportStatus(nsITransport* aTransport,
+                               nsresult aStatus,
+                               PRUint64 aProgress)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+  // nsHttpChannel synthesizes progress events in OnDataAvailable
+  if (aStatus == nsISocketTransport::STATUS_RECEIVING_FROM)
+    return;
+
+  // STATUS_SENDING_TO is handled by SpdyStream
+  if (aStatus == nsISocketTransport::STATUS_SENDING_TO)
+    return;
+
+  struct transportStatus status;
+  
+  status.transport = aTransport;
+  status.status = aStatus;
+  status.progress = aProgress;
+
+  mStreamTransactionHash.Enumerate(StreamTransportStatus, &status);
+}
+
+// ReadSegments() is used to write data to the network. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to SPDY data. Sometimes control data like window-update are
+// generated instead.
+
+nsresult
+SpdySession::ReadSegments(nsAHttpSegmentReader *reader,
+                          PRUint32 count,
+                          PRUint32 *countRead)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  
+  nsresult rv;
+  *countRead = 0;
+
+  // First priority goes to frames that were writing to the network but were
+  // blocked part way through. Then to frames that have no streams (e.g ping
+  // reply) and then third to streams marked urgent (generally they have
+  // window updates), and finally to streams generally
+  // ready to send data frames (http requests).
+
+  LOG(("SpdySession::ReadSegments %p parial frame stream=%p",
+       this, mPartialFrame));
+
+  SpdyStream *stream = mPartialFrame;
+  mPartialFrame = nsnull;
+
+  if (!stream)
+    stream = static_cast<SpdyStream *>(mUrgentForWrite.PopFront());
+  if (!stream)
+    stream = static_cast<SpdyStream *>(mReadyForWrite.PopFront());
+  if (!stream) {
+    LOG(("SpdySession %p could not identify a stream to write; suspending.",
+         this));
+    FlushOutputQueue();
+    SetWriteCallbacks(nsnull);
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+  
+  LOG(("SpdySession %p will write from SpdyStream %p", this, stream));
+
+  NS_ABORT_IF_FALSE(!mSegmentReader || !reader || (mSegmentReader == reader),
+                    "Inconsistent Write Function Callback");
+
+  if (reader)
+    mSegmentReader = reader;
+  rv = stream->ReadSegments(this, count, countRead);
+
+  FlushOutputQueue();
+
+  if (stream->BlockedOnWrite()) {
+
+    // We are writing a frame out, but it is blocked on the output stream.
+    // Make sure to service that stream next write because we can only
+    // multiplex between complete frames.
+
+    LOG(("SpdySession::ReadSegments %p dealing with block on write", this));
+
+    NS_ABORT_IF_FALSE(!mPartialFrame, "partial frame should be empty");
+
+    mPartialFrame = stream;
+    SetWriteCallbacks(stream->Transaction());
+    return rv;
+  }
+
+  if (stream->RequestBlockedOnRead()) {
+    
+    // We are blocked waiting for input - either more http headers or
+    // any request body data. When more data from the request stream
+    // becomes available the httptransaction will call conn->ResumeSend().
+    
+    LOG(("SpdySession::ReadSegments %p dealing with block on read", this));
+
+    // call readsegments again if there are other streams ready
+    // to run in this session
+    if (WriteQueueSize())
+      rv = NS_OK;
+    else
+      rv = NS_BASE_STREAM_WOULD_BLOCK;
+    SetWriteCallbacks(stream->Transaction());
+    return rv;
+  }
+  
+  NS_ABORT_IF_FALSE(rv != NS_BASE_STREAM_WOULD_BLOCK,
+                    "Stream Would Block inconsistency");
+  
+  if (NS_FAILED(rv)) {
+    LOG(("SpdySession::ReadSegments %p returning FAIL code %X",
+         this, rv));
+    return rv;
+  }
+  
+  if (*countRead > 0) {
+    LOG(("SpdySession::ReadSegments %p stream=%p generated end of frame %d",
+         this, *countRead));
+    mReadyForWrite.Push(stream);
+    SetWriteCallbacks(stream->Transaction());
+    return rv;
+  }
+  
+  LOG(("SpdySession::ReadSegments %p stream=%p stream send complete", this));
+  
+  // in normal http this is done by nshttpconnection, but that class does not
+  // know which http transaction has made this state transition.
+  stream->Transaction()->
+    OnTransportStatus(mSocketTransport, nsISocketTransport::STATUS_WAITING_FOR,
+                      LL_ZERO);
+  /* we now want to recv data */
+  mConnection->ResumeRecv(stream->Transaction());
+
+  // call readsegments again if there are other streams ready
+  // to go in this session
+  SetWriteCallbacks(stream->Transaction());
+
+  return rv;
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just the SPDY frame header and from there the appropriate SPDYStream
+// is identified from the Stream-ID. The http transaction associated with
+// that read then pulls in the data directly, which it will feed to
+// OnWriteSegment(). That function will gateway it into http and feed
+// it to the appropriate transaction.
+
+// we call writer->OnWriteSegment to get a spdy header.. and decide if it is
+// data or control.. if it is control, just deal with it.
+// if it is data, identify the spdy stream
+// call stream->WriteSegemnts which can call this::OnWriteSegment to get the
+// data. It always gets full frames if they are part of the stream
+
+nsresult
+SpdySession::WriteSegments(nsAHttpSegmentWriter *writer,
+                           PRUint32 count,
+                           PRUint32 *countWritten)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  
+  nsresult rv;
+  *countWritten = 0;
+
+  if (mClosed)
+    return NS_ERROR_FAILURE;
+
+  SetWriteCallbacks(nsnull);
+  
+  // We buffer all control frames and act on them in this layer.
+  // We buffer the first 8 bytes of data frames (the header) but
+  // the actual data is passed through unprocessed.
+  
+  if (mDownstreamState == BUFFERING_FRAME_HEADER) {
+    // The first 8 bytes of every frame is header information that
+    // we are going to want to strip before passing to http. That is
+    // true of both control and data packets.
+    
+    NS_ABORT_IF_FALSE(mFrameBufferUsed < 8,
+                      "Frame Buffer Used Too Large for State");
+
+    rv = writer->OnWriteSegment(mFrameBuffer + mFrameBufferUsed,
+                                8 - mFrameBufferUsed,
+                                countWritten);
+    if (NS_FAILED(rv)) {
+      if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+        ResumeRecv(nsnull);
+      }
+      return rv;
+    }
+
+    mFrameBufferUsed += *countWritten;
+
+    if (mFrameBufferUsed < 8)
+    {
+      LOG(("SpdySession::WriteSegments %p "
+           "BUFFERING FRAME HEADER incomplete size=%d",
+           this, mFrameBufferUsed));
+      return rv;
+    }
+
+    // For both control and data frames the second 32 bit word of the header
+    // is 8-flags, 24-length. (network byte order)
+    mFrameDataSize =
+      PR_ntohl(reinterpret_cast<PRUint32 *>(mFrameBuffer.get())[1]);
+    mFrameDataSize &= 0x00ffffff;
+    mFrameDataRead = 0;
+    
+    if (mFrameBuffer[0] & kFlag_Control) {
+      EnsureBuffer(mFrameBuffer, mFrameDataSize + 8, 8, mFrameBufferSize);
+      ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
+      
+      // The first 32 bit word of the header is
+      // 1 ctrl - 15 version - 16 type
+      PRUint16 version =
+        PR_ntohs(reinterpret_cast<PRUint16 *>(mFrameBuffer.get())[0]);
+      version &= 0x7fff;
+      
+      mFrameControlType =
+        PR_ntohs(reinterpret_cast<PRUint16 *>(mFrameBuffer.get())[1]);
+      
+      LOG(("SpdySession::WriteSegments %p - Control Frame Identified "
+           "type %d version %d data len %d",
+           this, mFrameControlType, version, mFrameDataSize));
+
+      if (mFrameControlType >= CONTROL_TYPE_LAST ||
+          mFrameControlType <= CONTROL_TYPE_FIRST)
+        return NS_ERROR_ILLEGAL_VALUE;
+
+      // The protocol document says this value must be 1 even though this
+      // is known as version 2.. Testing interop indicates that is a typo
+      // in the protocol document
+      if (version != 2) {
+        return NS_ERROR_ILLEGAL_VALUE;
+      }
+    }
+    else {
+      ChangeDownstreamState(PROCESSING_DATA_FRAME);
+
+      PRUint32 streamID =
+        PR_ntohl(reinterpret_cast<PRUint32 *>(mFrameBuffer.get())[0]);
+      mFrameDataStream = mStreamIDHash.Get(streamID);
+      if (!mFrameDataStream) {
+        LOG(("SpdySession::WriteSegments %p lookup streamID 0x%X failed. "
+             "Next = 0x%x", this, streamID, mNextStreamID));
+        if (streamID >= mNextStreamID)
+          GenerateRstStream(RST_INVALID_STREAM, streamID);
+          ChangeDownstreamState(DISCARD_DATA_FRAME);
+      }
+      mFrameDataLast = (mFrameBuffer[4] & kFlag_Data_FIN);
+      Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, mFrameDataSize >> 10);
+      LOG(("Start Processing Data Frame. Session=%p Stream 0x%x Fin=%d Len=%d",
+           this, streamID, mFrameDataLast, mFrameDataSize));
+
+      if (mFrameBuffer[4] & kFlag_Data_ZLIB) {
+        LOG(("Data flag has ZLIB flag set which is not valid >=2 spdy"));
+        return NS_ERROR_ILLEGAL_VALUE;
+      }
+    }
+  }
+
+  if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
+    if (mDownstreamRstReason == RST_REFUSED_STREAM)
+      rv = NS_ERROR_NET_RESET;            //we can retry this 100% safely
+    else if (mDownstreamRstReason == RST_CANCEL ||
+             mDownstreamRstReason == RST_PROTOCOL_ERROR ||
+             mDownstreamRstReason == RST_INTERNAL_ERROR ||
+             mDownstreamRstReason == RST_UNSUPPORTED_VERSION)
+      rv = NS_ERROR_NET_INTERRUPT;
+    else
+      rv = NS_ERROR_ILLEGAL_VALUE;
+
+    if (mDownstreamRstReason != RST_REFUSED_STREAM &&
+        mDownstreamRstReason != RST_CANCEL)
+      mShouldGoAway = true;
+
+    // mFrameDataStream is reset by ChangeDownstreamState
+    SpdyStream *stream = mFrameDataStream;
+    ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+    CleanupStream(stream, rv);
+    return NS_OK;
+  }
+
+  if (mDownstreamState == PROCESSING_DATA_FRAME ||
+      mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) {
+
+    mSegmentWriter = writer;
+    rv = mFrameDataStream->WriteSegments(this, count, countWritten);
+    mSegmentWriter = nsnull;
+
+    if (rv == NS_BASE_STREAM_CLOSED) {
+      // This will happen when the transaction figures out it is EOF, generally
+      // due to a content-length match being made
+      SpdyStream *stream = mFrameDataStream;
+      if (mFrameDataRead == mFrameDataSize)
+        ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+      CleanupStream(stream, NS_OK);
+      NS_ABORT_IF_FALSE(!mNeedsCleanup, "double cleanup out of data frame");
+      return NS_OK;
+    }
+    
+    if (mNeedsCleanup) {
+      CleanupStream(mNeedsCleanup, NS_OK);
+      mNeedsCleanup = nsnull;
+    }
+
+    // In v3 this is where we would generate a window update
+
+    return rv;
+  }
+
+  if (mDownstreamState == DISCARD_DATA_FRAME) {
+    char trash[4096];
+    PRUint32 count = NS_MIN(4096U, mFrameDataSize - mFrameDataRead);
+
+    if (!count) {
+      ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+      *countWritten = 1;
+      return NS_OK;
+    }
+
+    rv = writer->OnWriteSegment(trash, count, countWritten);
+
+    if (NS_FAILED(rv)) {
+      // maybe just blocked reading from network
+      ResumeRecv(nsnull);
+      return rv;
+    }
+
+    mFrameDataRead += *countWritten;
+
+    if (mFrameDataRead == mFrameDataSize)
+      ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+    return rv;
+  }
+  
+  NS_ABORT_IF_FALSE(mDownstreamState == BUFFERING_CONTROL_FRAME,
+                    "Not in Bufering Control Frame State");
+  NS_ABORT_IF_FALSE(mFrameBufferUsed == 8,
+                    "Frame Buffer Header Not Present");
+
+  rv = writer->OnWriteSegment(mFrameBuffer + 8 + mFrameDataRead,
+                              mFrameDataSize - mFrameDataRead,
+                              countWritten);
+  if (NS_FAILED(rv)) {
+    // maybe just blocked reading from network
+    ResumeRecv(nsnull);
+    return rv;
+  }
+
+  mFrameDataRead += *countWritten;
+
+  if (mFrameDataRead != mFrameDataSize)
+    return NS_OK;
+
+  rv = sControlFunctions[mFrameControlType](this);
+
+  NS_ABORT_IF_FALSE(NS_FAILED(rv) ||
+                    mDownstreamState != BUFFERING_CONTROL_FRAME,
+                    "Control Handler returned OK but did not change state");
+
+  if (mShouldGoAway && !mStreamTransactionHash.Count())
+    Close(NS_OK);
+  return rv;
+}
+
+void
+SpdySession::Close(nsresult aReason)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+  if (mClosed)
+    return;
+
+  LOG(("SpdySession::Close %p %X", this, aReason));
+
+  mClosed = true;
+  mStreamTransactionHash.Enumerate(Shutdown, this);
+  GenerateGoAway();
+  mConnection = nsnull;
+}
+
+void
+SpdySession::CloseTransaction(nsAHttpTransaction *aTransaction,
+                              nsresult aResult)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  LOG(("spdysession::CloseTransaction %p %p %x", this, aTransaction, aResult));
+
+  // Generally this arrives as a cancel event from the connection manager.
+
+  // need to find the stream and call CleanupStream() on it.
+  SpdyStream *stream = mStreamTransactionHash.Get(aTransaction);
+  if (!stream) {
+    LOG(("spdysession::CloseTransaction %p %p %x - not found.",
+         this, aTransaction, aResult));
+    return;
+  }
+  LOG(("SpdySession::CloseTranscation probably a cancel. "
+       "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p",
+       this, aTransaction, aResult, stream->StreamID(), stream));
+  CleanupStream(stream, aResult);
+  ResumeRecv(this);
+}
+
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdySession::OnReadSegment(const char *buf,
+                           PRUint32 count,
+                           PRUint32 *countRead)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  
+  nsresult rv;
+  
+  if (!mOutputQueueUsed && mSegmentReader) {
+
+    // try and write directly without output queue
+    rv = mSegmentReader->OnReadSegment(buf, count, countRead);
+    if (NS_SUCCEEDED(rv) || (rv != NS_BASE_STREAM_WOULD_BLOCK))
+      return rv;
+  }
+  
+  if (mOutputQueueUsed + count > mOutputQueueSize)
+    FlushOutputQueue();
+
+  if (mOutputQueueUsed + count > mOutputQueueSize)
+    count = mOutputQueueSize - mOutputQueueUsed;
+
+  if (!count)
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  
+  memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
+  mOutputQueueUsed += count;
+  *countRead = count;
+
+  FlushOutputQueue();
+    
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdySession::OnWriteSegment(char *buf,
+                            PRUint32 count,
+                            PRUint32 *countWritten)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter");
+  nsresult rv;
+
+  if (mDownstreamState == PROCESSING_DATA_FRAME) {
+
+    if (mFrameDataLast &&
+        mFrameDataRead == mFrameDataSize) {
+      // This will result in Close() being called
+      mNeedsCleanup = mFrameDataStream;
+
+      LOG(("SpdySession::OnWriteSegment %p - recorded downstream fin of "
+           "stream %p 0x%X", this, mFrameDataStream,
+           mFrameDataStream->StreamID()));
+      *countWritten = 0;
+      ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+      return NS_BASE_STREAM_CLOSED;
+    }
+    
+    count = NS_MIN(count, mFrameDataSize - mFrameDataRead);
+    rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+    if (NS_FAILED(rv))
+      return rv;
+
+    mFrameDataRead += *countWritten;
+    
+    if ((mFrameDataRead == mFrameDataSize) && !mFrameDataLast)
+      ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+
+    return rv;
+  }
+  
+  if (mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) {
+    
+    if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
+        mFrameDataLast) {
+      *countWritten = 0;
+      ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+      return NS_BASE_STREAM_CLOSED;
+    }
+      
+    count = NS_MIN(count,
+                   mFlatHTTPResponseHeaders.Length() -
+                   mFlatHTTPResponseHeadersOut);
+    memcpy(buf,
+           mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
+           count);
+    mFlatHTTPResponseHeadersOut += count;
+    *countWritten = count;
+
+    if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
+        !mFrameDataLast)
+      ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+    return NS_OK;
+  }
+
+  return NS_ERROR_UNEXPECTED;
+}
+
+//-----------------------------------------------------------------------------
+// Modified methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdySession::ResumeSend(nsAHttpTransaction *caller)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  LOG(("SpdySession::ResumeSend %p caller=%p", this, caller));
+
+  // a trapped signal from the http transaction to the connection that
+  // it is no longer blocked on read.
+
+  if (!mConnection)
+    return NS_ERROR_FAILURE;
+
+  SpdyStream *stream = mStreamTransactionHash.Get(caller);
+  if (stream)
+    mReadyForWrite.Push(stream);
+  else
+    LOG(("SpdySession::ResumeSend %p caller %p not found", this, caller));
+  
+  return mConnection->ResumeSend(caller);
+}
+
+nsresult
+SpdySession::ResumeRecv(nsAHttpTransaction *caller)
+{
+  if (!mConnection)
+    return NS_ERROR_FAILURE;
+
+  return mConnection->ResumeRecv(caller);
+}
+
+bool
+SpdySession::IsPersistent()
+{
+  return PR_TRUE;
+}
+
+nsresult
+SpdySession::TakeTransport(nsISocketTransport **,
+                           nsIAsyncInputStream **,
+                           nsIAsyncOutputStream **)
+{
+  NS_ABORT_IF_FALSE(false, "TakeTransport of SpdySession");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsHttpConnection *
+SpdySession::TakeHttpConnection()
+{
+  NS_ABORT_IF_FALSE(false, "TakeHttpConnection of SpdySession");
+  return nsnull;
+}
+
+nsISocketTransport *
+SpdySession::Transport()
+{
+    if (!mConnection)
+        return nsnull;
+    return mConnection->Transport();
+}
+
+//-----------------------------------------------------------------------------
+// unused methods of nsAHttpTransaction
+// We can be sure of this because SpdySession is only constructed in
+// nsHttpConnection and is never passed out of that object
+//-----------------------------------------------------------------------------
+
+void
+SpdySession::SetConnection(nsAHttpConnection *)
+{
+  // This is unexpected
+  NS_ABORT_IF_FALSE(false, "SpdySession::SetConnection()");
+}
+
+void
+SpdySession::GetSecurityCallbacks(nsIInterfaceRequestor **,
+                                  nsIEventTarget **)
+{
+  // This is unexpected
+  NS_ABORT_IF_FALSE(false, "SpdySession::GetSecurityCallbacks()");
+}
+
+void
+SpdySession::SetSSLConnectFailed()
+{
+  NS_ABORT_IF_FALSE(false, "SpdySession::SetSSLConnectFailed()");
+}
+
+bool
+SpdySession::IsDone()
+{
+  NS_ABORT_IF_FALSE(false, "SpdySession::IsDone()");
+  return PR_FALSE;
+}
+
+nsresult
+SpdySession::Status()
+{
+  NS_ABORT_IF_FALSE(false, "SpdySession::Status()");
+  return NS_ERROR_UNEXPECTED;
+}
+
+PRUint32
+SpdySession::Available()
+{
+  NS_ABORT_IF_FALSE(false, "SpdySession::Available()");
+  return 0;
+}
+
+nsHttpRequestHead *
+SpdySession::RequestHead()
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  NS_ABORT_IF_FALSE(false,
+                    "SpdySession::RequestHead() "
+                    "should not be called after SPDY is setup");
+  return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Pass through methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsAHttpConnection *
+SpdySession::Connection()
+{
+    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    return mConnection;
+}
+
+nsresult
+SpdySession::OnHeadersAvailable(nsAHttpTransaction *transaction,
+                                nsHttpRequestHead *requestHead,
+                                nsHttpResponseHead *responseHead,
+                                bool *reset)
+{
+  return mConnection->OnHeadersAvailable(transaction,
+                                         requestHead,
+                                         responseHead,
+                                         reset);
+}
+
+void
+SpdySession::GetConnectionInfo(nsHttpConnectionInfo **connInfo)
+{
+  mConnection->GetConnectionInfo(connInfo);
+}
+
+void
+SpdySession::GetSecurityInfo(nsISupports **supports)
+{
+  mConnection->GetSecurityInfo(supports);
+}
+
+bool
+SpdySession::IsReused()
+{
+  return mConnection->IsReused();
+}
+
+nsresult
+SpdySession::PushBack(const char *buf, PRUint32 len)
+{
+  return mConnection->PushBack(buf, len);
+}
+
+bool
+SpdySession::LastTransactionExpectedNoContent()
+{
+  return mConnection->LastTransactionExpectedNoContent();
+}
+
+void
+SpdySession::SetLastTransactionExpectedNoContent(bool val)
+{
+  mConnection->SetLastTransactionExpectedNoContent(val);
+}
+
+} // namespace mozilla::net
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SpdySession.h
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick McManus <mcmanus@ducksong.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_net_SpdySession_h
+#define mozilla_net_SpdySession_h
+
+// SPDY as defined by
+// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2
+
+#include "nsAHttpTransaction.h"
+#include "nsAHttpConnection.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsDeque.h"
+#include "nsHashKeys.h"
+#include "zlib.h"
+
+class nsHttpConnection;
+class nsISocketTransport;
+
+namespace mozilla { namespace net {
+
+class SpdyStream;
+
+class SpdySession : public nsAHttpTransaction
+                  , public nsAHttpConnection
+                  , public nsAHttpSegmentReader
+                  , public nsAHttpSegmentWriter
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSAHTTPTRANSACTION
+  NS_DECL_NSAHTTPCONNECTION
+  NS_DECL_NSAHTTPSEGMENTREADER
+  NS_DECL_NSAHTTPSEGMENTWRITER
+
+  SpdySession(nsAHttpTransaction *, nsISocketTransport *, PRInt32);
+  ~SpdySession();
+
+  bool AddStream(nsAHttpTransaction *, PRInt32);
+  bool CanReuse() { return !mShouldGoAway && !mClosed; }
+  void DontReuse();
+  bool RoomForMoreStreams();
+  PRUint32 RegisterStreamID(SpdyStream *);
+
+  const static PRUint8 kFlag_Control   = 0x80;
+
+  const static PRUint8 kFlag_Data_FIN  = 0x01;
+  const static PRUint8 kFlag_Data_UNI  = 0x02;
+  const static PRUint8 kFlag_Data_ZLIB = 0x02;
+  
+  const static PRUint8 kPri00   = 0x00;
+  const static PRUint8 kPri01   = 0x40;
+  const static PRUint8 kPri02   = 0x80;
+  const static PRUint8 kPri03   = 0xC0;
+
+  enum
+  {
+    CONTROL_TYPE_FIRST = 0,
+    CONTROL_TYPE_SYN_STREAM = 1,
+    CONTROL_TYPE_SYN_REPLY = 2,
+    CONTROL_TYPE_RST_STREAM = 3,
+    CONTROL_TYPE_SETTINGS = 4,
+    CONTROL_TYPE_NOOP = 5,
+    CONTROL_TYPE_PING = 6,
+    CONTROL_TYPE_GOAWAY = 7,
+    CONTROL_TYPE_HEADERS = 8,
+    CONTROL_TYPE_WINDOW_UPDATE = 9,               /* no longer in v2 */
+    CONTROL_TYPE_LAST = 10
+  };
+
+  enum
+  {
+    RST_PROTOCOL_ERROR = 1,
+    RST_INVALID_STREAM = 2,
+    RST_REFUSED_STREAM = 3,
+    RST_UNSUPPORTED_VERSION = 4,
+    RST_CANCEL = 5,
+    RST_INTERNAL_ERROR = 6,
+    RST_FLOW_CONTROL_ERROR = 7,
+    RST_BAD_ASSOC_STREAM = 8
+  };
+
+  enum
+  {
+    SETTINGS_TYPE_UPLOAD_BW = 1, // kb/s
+    SETTINGS_TYPE_DOWNLOAD_BW = 2, // kb/s
+    SETTINGS_TYPE_RTT = 3, // ms
+    SETTINGS_TYPE_MAX_CONCURRENT = 4, // streams
+    SETTINGS_TYPE_CWND = 5, // packets
+    SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage
+    SETTINGS_TYPE_INITIAL_WINDOW = 7  // bytes. Not used in v2.
+  };
+
+  // This should be big enough to hold all of your control packets,
+  // but if it needs to grow for huge headers it can do so dynamically.
+  // About 1% of requests to SPDY google services seem to be > 1000
+  // with all less than 2000.
+  const static PRUint32 kDefaultBufferSize = 2000;
+
+  const static PRUint32 kDefaultQueueSize =  16000;
+  const static PRUint32 kQueueTailRoom    =  4000;
+  const static PRUint32 kSendingChunkSize = 4000;
+  const static PRUint32 kDefaultMaxConcurrent = 100;
+  const static PRUint32 kMaxStreamID = 0x7800000;
+  
+  static nsresult HandleSynStream(SpdySession *);
+  static nsresult HandleSynReply(SpdySession *);
+  static nsresult HandleRstStream(SpdySession *);
+  static nsresult HandleSettings(SpdySession *);
+  static nsresult HandleNoop(SpdySession *);
+  static nsresult HandlePing(SpdySession *);
+  static nsresult HandleGoAway(SpdySession *);
+  static nsresult HandleHeaders(SpdySession *);
+  static nsresult HandleWindowUpdate(SpdySession *);
+
+  static void EnsureBuffer(nsAutoArrayPtr<char> &,
+                           PRUint32, PRUint32, PRUint32 &);
+private:
+
+  enum stateType {
+    BUFFERING_FRAME_HEADER,
+    BUFFERING_CONTROL_FRAME,
+    PROCESSING_DATA_FRAME,
+    DISCARD_DATA_FRAME,
+    PROCESSING_CONTROL_SYN_REPLY,
+    PROCESSING_CONTROL_RST_STREAM
+  };
+
+  PRUint32    WriteQueueSize();
+  void        ChangeDownstreamState(enum stateType);
+  nsresult    DownstreamUncompress(char *, PRUint32);
+  void        zlibInit();
+  nsresult    FindHeader(nsCString, nsDependentCSubstring &);
+  nsresult    ConvertHeaders(nsDependentCSubstring &,
+                             nsDependentCSubstring &);
+  void        GeneratePing(PRUint32);
+  void        GenerateRstStream(PRUint32, PRUint32);
+  void        GenerateGoAway();
+  void        CleanupStream(SpdyStream *, nsresult);
+
+  void        SetWriteCallbacks(nsAHttpTransaction *);
+  void        FlushOutputQueue();
+
+  bool        RoomForMoreConcurrent();
+  void        ActivateStream(SpdyStream *);
+  void        ProcessPending();
+
+  static PLDHashOperator Shutdown(nsAHttpTransaction *,
+                                  nsAutoPtr<SpdyStream> &,
+                                  void *);
+
+  // This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken
+  // from the first transcation on this session. That object contains the
+  // pointer to the real network-level nsHttpConnection object.
+  nsRefPtr<nsAHttpConnection> mConnection;
+
+  // The underlying socket transport object is needed to propogate some events
+  nsISocketTransport         *mSocketTransport;
+
+  // These are temporary state variables to hold the argument to
+  // Read/WriteSegments so it can be accessed by On(read/write)segment
+  // further up the stack.
+  nsAHttpSegmentReader       *mSegmentReader;
+  nsAHttpSegmentWriter       *mSegmentWriter;
+
+  PRUint32          mSendingChunkSize;        /* the transmisison chunk size */
+  PRUint32          mNextStreamID;            /* 24 bits */
+  PRUint32          mConcurrentHighWater;     /* max parallelism on session */
+
+  stateType         mDownstreamState; /* in frame, between frames, etc..  */
+
+  // Maintain 5 indexes - one by stream ID, one by transaction ptr,
+  // one list of streams ready to write, one list of streams that are queued
+  // due to max parallelism settings, and one list of streams
+  // that must be given priority to write for window updates. The objects
+  // are not ref counted - they get destryoed
+  // by the nsClassHashtable implementation when they are removed from
+  // there.
+  nsDataHashtable<nsUint32HashKey, SpdyStream *>      mStreamIDHash;
+  nsClassHashtable<nsPtrHashKey<nsAHttpTransaction>,
+                   SpdyStream>                        mStreamTransactionHash;
+  nsDeque                                             mReadyForWrite;
+  nsDeque                                             mQueuedStreams;
+
+  // UrgentForWrite is meant to carry window updates. They were defined in
+  // the v2 spec but apparently never implemented so are now scheduled to
+  // be removed. But they will be reintroduced for v3, so we will leave
+  // this queue in place to ease that transition.
+  nsDeque           mUrgentForWrite;
+
+  // If we block while wrting out a frame then this points to the stream
+  // that was blocked. When writing again that stream must be the first
+  // one to write. It is null if there is not a partial frame.
+  SpdyStream        *mPartialFrame;
+
+  // Compression contexts for header transport using deflate.
+  // SPDY compresses only HTTP headers and does not reset zlib in between
+  // frames.
+  z_stream            mDownstreamZlib;
+  z_stream            mUpstreamZlib;
+
+  // mFrameBuffer is used to store received control packets and the 8 bytes
+  // of header on data packets
+  PRUint32             mFrameBufferSize;
+  PRUint32             mFrameBufferUsed;
+  nsAutoArrayPtr<char> mFrameBuffer;
+  
+  // mFrameDataSize/Read are used for tracking the amount of data consumed
+  // in a data frame. the data itself is not buffered in spdy
+  // The frame size is mFrameDataSize + the constant 8 byte header
+  PRUint32             mFrameDataSize;
+  PRUint32             mFrameDataRead;
+  bool                 mFrameDataLast; // This frame was marked FIN
+
+  // When a frame has been received that is addressed to a particular stream
+  // (e.g. a data frame after the stream-id has been decoded), this points
+  // to the stream.
+  SpdyStream          *mFrameDataStream;
+  
+  // A state variable to cleanup a closed stream after the stack has unwound.
+  SpdyStream          *mNeedsCleanup;
+
+  // The CONTROL_TYPE value for a control frame
+  PRUint32             mFrameControlType;
+
+  // This reason code in the last processed RESET frame
+  PRUint32             mDownstreamRstReason;
+
+  // These are used for decompressing downstream spdy response headers
+  // This is done at the session level because sometimes the stream
+  // has already been canceled but the decompression still must happen
+  // to keep the zlib state correct for the next state of headers.
+  PRUint32             mDecompressBufferSize;
+  PRUint32             mDecompressBufferUsed;
+  nsAutoArrayPtr<char> mDecompressBuffer;
+
+  // for the conversion of downstream http headers into spdy formatted headers
+  nsCString            mFlatHTTPResponseHeaders;
+  PRUint32             mFlatHTTPResponseHeadersOut;
+
+  // when set, the session will go away when it reaches 0 streams
+  bool                 mShouldGoAway;
+
+  // the session has received a nsAHttpTransaction::Close()  call
+  bool                 mClosed;
+
+  // the session received a GoAway frame with a valid GoAwayID
+  bool                 mCleanShutdown;
+
+  // If a GoAway message was received this is the ID of the last valid
+  // stream. 0 otherwise. (0 is never a valid stream id.)
+  PRUint32             mGoAwayID;
+
+  // The limit on number of concurrent streams for this session. Normally it
+  // is basically unlimited, but the SETTINGS control message from the
+  // server might bring it down.
+  PRUint32             mMaxConcurrent;
+
+  // The actual number of concurrent streams at this moment. Generally below
+  // mMaxConcurrent, but the max can be lowered in real time to a value
+  // below the current value
+  PRUint32             mConcurrent;
+
+  // The number of server initiated SYN-STREAMS, tracked for telemetry
+  PRUint32             mServerPushedResources;
+
+  // This is a output queue of bytes ready to be written to the SSL stream.
+  // When that streams returns WOULD_BLOCK on direct write the bytes get
+  // coalesced together here. This results in larger writes to the SSL layer.
+  // The buffer is not dynamically grown to accomodate stream writes, but
+  // does expand to accept infallible session wide frames like GoAway and RST.
+  PRUint32             mOutputQueueSize;
+  PRUint32             mOutputQueueUsed;
+  PRUint32             mOutputQueueSent;
+  nsAutoArrayPtr<char> mOutputQueueBuffer;
+};
+
+}} // namespace mozilla::net
+
+#endif // mozilla_net_SpdySession_h
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SpdyStream.cpp
@@ -0,0 +1,843 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick McManus <mcmanus@ducksong.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsHttp.h"
+#include "SpdySession.h"
+#include "SpdyStream.h"
+#include "nsAlgorithm.h"
+#include "prnetdb.h"
+#include "nsHttpRequestHead.h"
+#include "mozilla/Telemetry.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
+
+#ifdef DEBUG
+// defined by the socket transport service while active
+extern PRThread *gSocketThread;
+#endif
+
+namespace mozilla {
+namespace net {
+
+SpdyStream::SpdyStream(nsAHttpTransaction *httpTransaction,
+                       SpdySession *spdySession,
+                       nsISocketTransport *socketTransport,
+                       PRUint32 chunkSize,
+                       z_stream *compressionContext,
+                       PRInt32 priority)
+  : mUpstreamState(GENERATING_SYN_STREAM),
+    mTransaction(httpTransaction),
+    mSession(spdySession),
+    mSocketTransport(socketTransport),
+    mSegmentReader(nsnull),
+    mSegmentWriter(nsnull),
+    mStreamID(0),
+    mChunkSize(chunkSize),
+    mSynFrameComplete(0),
+    mBlockedOnWrite(0),
+    mRequestBlockedOnRead(0),
+    mSentFinOnData(0),
+    mRecvdFin(0),
+    mFullyOpen(0),
+    mTxInlineFrameAllocation(SpdySession::kDefaultBufferSize),
+    mTxInlineFrameSize(0),
+    mTxInlineFrameSent(0),
+    mTxStreamFrameSize(0),
+    mTxStreamFrameSent(0),
+    mZlib(compressionContext),
+    mRequestBodyLen(0),
+    mPriority(priority)
+{
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+  LOG(("SpdyStream::SpdyStream %p", this));
+
+  mTxInlineFrame = new char[mTxInlineFrameAllocation];
+}
+
+SpdyStream::~SpdyStream()
+{
+}
+
+// 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.
+
+nsresult
+SpdyStream::ReadSegments(nsAHttpSegmentReader *reader,
+                         PRUint32 count,
+                         PRUint32 *countRead)
+{
+  LOG(("SpdyStream::ReadSegments %p count=%d state=%x",
+       this, count, mUpstreamState));
+
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  LOG(("SpdyStream %p ReadSegments reader=%p count=%d",
+       this, reader, count));
+  
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  mBlockedOnWrite = 0;
+  mRequestBlockedOnRead = 0;
+
+  switch (mUpstreamState) {
+  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 = nsnull;
+
+    if (NS_SUCCEEDED(rv) &&
+        mUpstreamState == GENERATING_SYN_STREAM &&
+        !mSynFrameComplete)
+      mBlockedOnWrite = 1;
+    
+    // Mark that we are blocked on read if we the http transaction
+    // is going to get us going again.
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mBlockedOnWrite)
+      mRequestBlockedOnRead = 1;
+
+    if (!mBlockedOnWrite && NS_SUCCEEDED(rv) && (!*countRead)) {
+      LOG(("ReadSegments %p Send Request data complete from %x",
+           this, mUpstreamState));
+      if (mSentFinOnData) {
+        ChangeState(UPSTREAM_COMPLETE);
+      }
+      else {
+        GenerateDataFrameHeader(0, true);
+        ChangeState(SENDING_FIN_STREAM);
+        mBlockedOnWrite = 1;
+        rv = NS_BASE_STREAM_WOULD_BLOCK;
+      }
+    }
+
+    break;
+
+  case SENDING_SYN_STREAM:
+    // We were trying to send the SYN-STREAM but only got part of it out
+    // before being blocked. Try and send more.
+    mSegmentReader = reader;
+    rv = TransmitFrame(nsnull, nsnull);
+    mSegmentReader = nsnull;
+    *countRead = 0;
+    if (NS_SUCCEEDED(rv))
+      rv = NS_BASE_STREAM_WOULD_BLOCK;
+
+    if (!mTxInlineFrameSize) {
+      if (mSentFinOnData) {
+        ChangeState(UPSTREAM_COMPLETE);
+        rv = NS_OK;
+      }
+      else {
+        ChangeState(GENERATING_REQUEST_BODY);
+        mBlockedOnWrite = 1;
+      }
+    }
+    break;
+
+  case SENDING_FIN_STREAM:
+    // We were trying to send the SYN-STREAM but only got part of it out
+    // before being blocked. Try and send more.
+    if (!mSentFinOnData) {
+      mSegmentReader = reader;
+      rv = TransmitFrame(nsnull, nsnull);
+      mSegmentReader = nsnull;
+      if (!mTxInlineFrameSize)
+        ChangeState(UPSTREAM_COMPLETE);
+    }
+    else {
+      rv = NS_OK;
+      mTxInlineFrameSize = 0;         // cancel fin data packet
+      ChangeState(UPSTREAM_COMPLETE);
+    }
+    
+    *countRead = 0;
+
+    // don't change OK to WOULD BLOCK. we are really done sending if OK
+    break;
+
+  default:
+    NS_ABORT_IF_FALSE(false, "SpdyStream::ReadSegments unknown state");
+    break;
+  }
+
+  return rv;
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just the SPDY frame header and from there the appropriate SPDYStream
+// is identified from the Stream-ID. The http transaction associated with
+// that read then pulls in the data directly.
+
+nsresult
+SpdyStream::WriteSegments(nsAHttpSegmentWriter *writer,
+                          PRUint32 count,
+                          PRUint32 *countWritten)
+{
+  LOG(("SpdyStream::WriteSegments %p count=%d state=%x",
+       this, count, mUpstreamState));
+  
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  NS_ABORT_IF_FALSE(!mSegmentWriter, "segment writer in progress");
+
+  mSegmentWriter = writer;
+  nsresult rv = mTransaction->WriteSegments(writer, count, countWritten);
+  mSegmentWriter = nsnull;
+  return rv;
+}
+
+PLDHashOperator
+SpdyStream::hdrHashEnumerate(const nsACString &key,
+                             nsAutoPtr<nsCString> &value,
+                             void *closure)
+{
+  SpdyStream *self = static_cast<SpdyStream *>(closure);
+
+  self->CompressToFrame(key);
+  self->CompressToFrame(value.get());
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+SpdyStream::ParseHttpRequestHeaders(const char *buf,
+                                    PRUint32 avail,
+                                    PRUint32 *countUsed)
+{
+  // Returns NS_OK even if the headers are incomplete
+  // set mSynFrameComplete flag if they are complete
+
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  NS_ABORT_IF_FALSE(mUpstreamState == GENERATING_SYN_STREAM, "wrong state");
+
+  LOG(("SpdyStream::ParseHttpRequestHeaders %p avail=%d state=%x",
+       this, avail, mUpstreamState));
+
+  mFlatHttpRequestHeaders.Append(buf, avail);
+
+  // We can use the simple double crlf because firefox is the
+  // only client we are parsing
+  PRInt32 endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+  
+  if (endHeader == -1) {
+    // We don't have all the headers yet
+    LOG(("SpdyStream::ParseHttpRequestHeaders %p "
+         "Need more header bytes. Len = %d",
+         this, mFlatHttpRequestHeaders.Length()));
+    *countUsed = avail;
+    return NS_OK;
+  }
+           
+  // We have recvd all the headers, trim the local
+  // buffer of the final empty line, and set countUsed to reflect
+  // the whole header has been consumed.
+  PRUint32 oldLen = mFlatHttpRequestHeaders.Length();
+  mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+  *countUsed = avail - (oldLen - endHeader) + 4;
+  mSynFrameComplete = 1;
+
+  // It is now OK to assign a streamID that we are assured will
+  // be monotonically increasing amongst syn-streams on this
+  // session
+  mStreamID = mSession->RegisterStreamID(this);
+  NS_ABORT_IF_FALSE(mStreamID & 1,
+                    "Spdy Stream Channel ID must be odd");
+
+  if (mStreamID >= 0x80000000) {
+    // streamID must fit in 31 bits. This is theoretically possible
+    // because stream ID assignment is asynchronous to stream creation
+    // because of the protocol requirement that the ID in syn-stream
+    // be monotonically increasing. In reality this is really not possible
+    // because new streams stop being added to a session with 0x10000000 / 2
+    // IDs still available and no race condition is going to bridge that gap,
+    // so we can be comfortable on just erroring out for correctness in that
+    // case.
+    LOG(("Stream assigned out of range ID: 0x%X", mStreamID));
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Now we need to convert the flat http headers into a set
+  // of SPDY headers..  writing to mTxInlineFrame{sz}
+
+  mTxInlineFrame[0] = SpdySession::kFlag_Control;
+  mTxInlineFrame[1] = 2;                          /* version */
+  mTxInlineFrame[2] = 0;
+  mTxInlineFrame[3] = SpdySession::CONTROL_TYPE_SYN_STREAM;
+  // 4 to 7 are length and flags, we'll fill that in later
+  
+  PRUint32 networkOrderID = PR_htonl(mStreamID);
+  memcpy(mTxInlineFrame + 8, &networkOrderID, 4);
+  
+  // this is the associated-to field, which is not used sending
+  // from the client in the http binding
+  memset (mTxInlineFrame + 12, 0, 4);
+
+  // Priority flags are the C0 mask of byte 16.
+  // From low to high: 00 40 80 C0
+  // higher raw priority values are actually less important
+  //
+  // The other 6 bits of 16 are unused. Spdy/3 will expand
+  // priority to 4 bits.
+  //
+  // When Spdy/3 implements WINDOW_UPDATE the lowest priority
+  // streams over a threshold (32?) should be given tiny
+  // receive windows, separate from their spdy priority
+  //
+  if (mPriority >= nsISupportsPriority::PRIORITY_LOW)
+    mTxInlineFrame[16] = SpdySession::kPri00;
+  else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL)
+    mTxInlineFrame[16] = SpdySession::kPri01;
+  else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH)
+    mTxInlineFrame[16] = SpdySession::kPri02;
+  else
+    mTxInlineFrame[16] = SpdySession::kPri03;
+
+  mTxInlineFrame[17] = 0;                         /* unused */
+  
+//  nsCString methodHeader;
+//  mTransaction->RequestHead()->Method()->ToUTF8String(methodHeader);
+  const char *methodHeader = mTransaction->RequestHead()->Method().get();
+
+  nsCString hostHeader;
+  mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
+
+  nsCString versionHeader;
+  if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1)
+    versionHeader = NS_LITERAL_CSTRING("HTTP/1.1");
+  else
+    versionHeader = NS_LITERAL_CSTRING("HTTP/1.0");
+
+  nsClassHashtable<nsCStringHashKey, nsCString> hdrHash;
+  
+  // use mRequestHead() to get a sense of how big to make the hash,
+  // even though we are parsing the actual text stream because
+  // it is legit to append headers.
+  hdrHash.Init(1 + (mTransaction->RequestHead()->Headers().Count() * 2));
+  
+  const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
+
+  // need to hash all the headers together to remove duplicates, special
+  // headers, etc..
+
+  PRInt32 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n");
+  while (true) {
+    PRInt32 startIndex = crlfIndex + 2;
+
+    crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex);
+    if (crlfIndex == -1)
+      break;
+    
+    PRInt32 colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex,
+                                                      crlfIndex - startIndex);
+    if (colonIndex == -1)
+      break;
+    
+    nsDependentCSubstring name = Substring(beginBuffer + startIndex,
+                                           beginBuffer + colonIndex);
+    // all header names are lower case in spdy
+    ToLowerCase(name);
+
+    if (name.Equals("method") ||
+        name.Equals("version") ||
+        name.Equals("scheme") ||
+        name.Equals("keep-alive") ||
+        name.Equals("accept-encoding") ||
+        name.Equals("TE") ||
+        name.Equals("connection") ||
+        name.Equals("proxy-connection") ||
+        name.Equals("url"))
+      continue;
+    
+    nsCString *val = hdrHash.Get(name);
+    if (!val) {
+      val = new nsCString();
+      hdrHash.Put(name, val);
+    }
+
+    PRInt32 valueIndex = colonIndex + 1;
+    while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
+      ++valueIndex;
+    
+    nsDependentCSubstring v = Substring(beginBuffer + valueIndex,
+                                        beginBuffer + crlfIndex);
+    if (!val->IsEmpty())
+      val->Append(static_cast<char>(0));
+    val->Append(v);
+
+    if (name.Equals("content-length")) {
+      PRInt64 len;
+      if (nsHttp::ParseInt64(val->get(), nsnull, &len))
+        mRequestBodyLen = len;
+    }
+  }
+  
+  mTxInlineFrameSize = 18;
+
+  LOG(("http request headers to encode are: \n%s",
+       mFlatHttpRequestHeaders.get()));
+
+  // The header block length
+  PRUint16 count = hdrHash.Count() + 4; /* method, scheme, url, version */
+  CompressToFrame(count);
+
+  // method, scheme, url, and version headers for request line
+
+  CompressToFrame(NS_LITERAL_CSTRING("method"));
+  CompressToFrame(methodHeader, strlen(methodHeader));
+  CompressToFrame(NS_LITERAL_CSTRING("scheme"));
+  CompressToFrame(NS_LITERAL_CSTRING("https"));
+  CompressToFrame(NS_LITERAL_CSTRING("url"));
+  CompressToFrame(mTransaction->RequestHead()->RequestURI());
+  CompressToFrame(NS_LITERAL_CSTRING("version"));
+  CompressToFrame(versionHeader);
+  
+  hdrHash.Enumerate(hdrHashEnumerate, this);
+  CompressFlushFrame();
+  
+  // 4 to 7 are length and flags, which we can now fill in
+  (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[1] =
+    PR_htonl(mTxInlineFrameSize - 8);
+
+  NS_ABORT_IF_FALSE(!mTxInlineFrame[4],
+                    "Size greater than 24 bits");
+  
+  // For methods other than POST and PUT, we will set the fin bit
+  // right on the syn stream packet.
+
+  if (mTransaction->RequestHead()->Method() != nsHttp::Post &&
+      mTransaction->RequestHead()->Method() != nsHttp::Put) {
+    mSentFinOnData = 1;
+    mTxInlineFrame[4] = SpdySession::kFlag_Data_FIN;
+  }
+
+  Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameSize - 18);
+
+  // The size of the input headers is approximate
+  PRUint32 ratio =
+    (mTxInlineFrameSize - 18) * 100 /
+    (11 + mTransaction->RequestHead()->RequestURI().Length() +
+     mFlatHttpRequestHeaders.Length());
+  
+  Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+  return NS_OK;
+}
+
+nsresult
+SpdyStream::TransmitFrame(const char *buf,
+                          PRUint32 *countUsed)
+{
+  NS_ABORT_IF_FALSE(mTxInlineFrameSize, "empty stream frame in transmit");
+  NS_ABORT_IF_FALSE(mSegmentReader, "TransmitFrame with null mSegmentReader");
+  
+  PRUint32 transmittedCount;
+  nsresult rv;
+  
+  LOG(("SpdyStream::TransmitFrame %p inline=%d of %d stream=%d of %d",
+       this, mTxInlineFrameSent, mTxInlineFrameSize,
+       mTxStreamFrameSent, mTxStreamFrameSize));
+  if (countUsed)
+    *countUsed = 0;
+  mBlockedOnWrite = 0;
+
+  // In the (relatively common) event that we have a small amount of data
+  // split between the inlineframe and the streamframe, then move the stream
+  // data into the inlineframe via copy in order to coalesce into one write.
+  // Given the interaction with ssl this is worth the small copy cost.
+  if (mTxStreamFrameSize && mTxInlineFrameSize &&
+      !mTxInlineFrameSent && !mTxStreamFrameSent &&
+      mTxStreamFrameSize < SpdySession::kDefaultBufferSize &&
+      mTxInlineFrameSize + mTxStreamFrameSize < mTxInlineFrameAllocation) {
+    LOG(("Coalesce Transmit"));
+    memcpy (mTxInlineFrame + mTxInlineFrameSize,
+            buf, mTxStreamFrameSize);
+    mTxInlineFrameSize += mTxStreamFrameSize;
+    mTxStreamFrameSent = 0;
+    mTxStreamFrameSize = 0;
+  }
+
+  // This function calls mSegmentReader->OnReadSegment to report the actual SPDY
+  // bytes through to the SpdySession and then the HttpConnection which calls
+  // the socket write function.
+  
+  while (mTxInlineFrameSent < mTxInlineFrameSize) {
+    rv = mSegmentReader->OnReadSegment(mTxInlineFrame + mTxInlineFrameSent,
+                                       mTxInlineFrameSize - mTxInlineFrameSent,
+                                       &transmittedCount);
+    LOG(("SpdyStream::TransmitFrame for inline %p result %x len=%d",
+         this, rv, transmittedCount));
+    
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+      mBlockedOnWrite = 1;
+
+    if (NS_FAILED(rv))     // this will include WOULD_BLOCK
+      return rv;
+    
+    mTxInlineFrameSent += transmittedCount;
+  }
+
+  PRUint32 offset = 0;
+  NS_ABORT_IF_FALSE(mTxStreamFrameSize >= mTxStreamFrameSent,
+                    "negative unsent");
+  PRUint32 avail =  mTxStreamFrameSize - mTxStreamFrameSent;
+
+  while (avail) {
+    NS_ABORT_IF_FALSE(countUsed, "null countused pointer in a stream context");
+    rv = mSegmentReader->OnReadSegment(buf + offset, avail, &transmittedCount);
+
+    LOG(("SpdyStream::TransmitFrame for stream %p result %x len=%d",
+         this, rv, transmittedCount));
+
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+      mBlockedOnWrite = 1;
+
+    if (NS_FAILED(rv))     // this will include WOULD_BLOCK
+      return rv;
+    
+    if (mUpstreamState == SENDING_REQUEST_BODY) {
+      mTransaction->OnTransportStatus(mSocketTransport,
+                                      nsISocketTransport::STATUS_SENDING_TO,
+                                      transmittedCount);
+    }
+    
+    *countUsed += transmittedCount;
+    avail -= transmittedCount;
+    offset += transmittedCount;
+    mTxStreamFrameSent += transmittedCount;
+  }
+
+  if (!avail) {
+    mTxInlineFrameSent = 0;
+    mTxInlineFrameSize = 0;
+    mTxStreamFrameSent = 0;
+    mTxStreamFrameSize = 0;
+  }
+    
+  return NS_OK;
+}
+
+void
+SpdyStream::ChangeState(enum stateType newState)
+{
+  LOG(("SpdyStream::ChangeState() %p from %X to %X", mUpstreamState, newState));
+  mUpstreamState = newState;
+  return;
+}
+
+void
+SpdyStream::GenerateDataFrameHeader(PRUint32 dataLength, bool lastFrame)
+{
+  LOG(("SpdyStream::GenerateDataFrameHeader %p len=%d last=%d",
+       this, dataLength, lastFrame));
+
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  NS_ABORT_IF_FALSE(!mTxInlineFrameSize, "inline frame not empty");
+  NS_ABORT_IF_FALSE(!mTxInlineFrameSent, "inline partial send not 0");
+  NS_ABORT_IF_FALSE(!mTxStreamFrameSize, "stream frame not empty");
+  NS_ABORT_IF_FALSE(!mTxStreamFrameSent, "stream partial send not 0");
+  NS_ABORT_IF_FALSE(!(dataLength & 0xff000000), "datalength > 24 bits");
+  
+  (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID);
+  (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[1] =
+    PR_htonl(dataLength);
+  
+  NS_ABORT_IF_FALSE(!(mTxInlineFrame[0] & 0x80),
+                    "control bit set unexpectedly");
+  NS_ABORT_IF_FALSE(!mTxInlineFrame[4], "flag bits set unexpectedly");
+  
+  mTxInlineFrameSize = 8;
+  mTxStreamFrameSize = dataLength;
+
+  if (lastFrame) {
+    mTxInlineFrame[4] |= SpdySession::kFlag_Data_FIN;
+    if (dataLength)
+      mSentFinOnData = 1;
+  }
+}
+
+void
+SpdyStream::CompressToFrame(const nsACString &str)
+{
+  CompressToFrame(str.BeginReading(), str.Length());
+}
+
+void
+SpdyStream::CompressToFrame(const nsACString *str)
+{
+  CompressToFrame(str->BeginReading(), str->Length());
+}
+
+// Dictionary taken from
+// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 
+// Name/Value Header Block Format
+// spec indicates that the compression dictionary is not null terminated
+// but in reality it is. see:
+// https://groups.google.com/forum/#!topic/spdy-dev/2pWxxOZEIcs
+
+const char *SpdyStream::kDictionary =
+  "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
+  "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
+  "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
+  "-agent10010120020120220320420520630030130230330430530630740040140240340440"
+  "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
+  "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
+  "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
+  "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
+  "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
+  "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
+  "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
+  "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
+  ".1statusversionurl";
+
+// use for zlib data types
+void *
+SpdyStream::zlib_allocator(void *opaque, uInt items, uInt size)
+{
+  return moz_xmalloc(items * size);
+}
+
+// use for zlib data types
+void
+SpdyStream::zlib_destructor(void *opaque, void *addr)
+{
+  moz_free(addr);
+}
+
+void
+SpdyStream::ExecuteCompress(PRUint32 flushMode)
+{
+  // Expect mZlib->avail_in and mZlib->next_in to be set.
+  // Append the compressed version of next_in to mTxInlineFrame
+
+  do
+  {
+    PRUint32 avail = mTxInlineFrameAllocation - mTxInlineFrameSize;
+    if (avail < 1) {
+      SpdySession::EnsureBuffer(mTxInlineFrame,
+                                mTxInlineFrameAllocation + 2000,
+                                mTxInlineFrameSize,
+                                mTxInlineFrameAllocation);
+      avail = mTxInlineFrameAllocation - mTxInlineFrameSize;
+    }
+
+    mZlib->next_out = reinterpret_cast<unsigned char *> (mTxInlineFrame.get()) +
+      mTxInlineFrameSize;
+    mZlib->avail_out = avail;
+    deflate(mZlib, flushMode);
+    mTxInlineFrameSize += avail - mZlib->avail_out;
+  } while (mZlib->avail_in > 0 || !mZlib->avail_out);
+}
+
+void
+SpdyStream::CompressToFrame(PRUint16 data)
+{
+  // convert the data to network byte order and write that
+  // to the compressed stream
+  
+  data = PR_htons(data);
+
+  mZlib->next_in = reinterpret_cast<unsigned char *> (&data);
+  mZlib->avail_in = 2;
+  ExecuteCompress(Z_NO_FLUSH);
+}
+
+
+void
+SpdyStream::CompressToFrame(const char *data, PRUint32 len)
+{
+  // Format calls for a network ordered 16 bit length
+  // followed by the utf8 string
+
+  // for now, silently truncate headers greater than 64KB. Spdy/3 will
+  // fix this by making the len a 32 bit quantity
+  if (len > 0xffff)
+    len = 0xffff;
+
+  PRUint16 networkLen = len;
+  networkLen = PR_htons(len);
+  
+  // write out the length
+  mZlib->next_in = reinterpret_cast<unsigned char *> (&networkLen);
+  mZlib->avail_in = 2;
+  ExecuteCompress(Z_NO_FLUSH);
+  
+  // write out the data
+  mZlib->next_in = (unsigned char *)data;
+  mZlib->avail_in = len;
+  ExecuteCompress(Z_NO_FLUSH);
+}
+
+void
+SpdyStream::CompressFlushFrame()
+{
+  mZlib->next_in = (unsigned char *) "";
+  mZlib->avail_in = 0;
+  ExecuteCompress(Z_SYNC_FLUSH);
+}
+
+void
+SpdyStream::Close(nsresult reason)
+{
+  mTransaction->Close(reason);
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdyStream::OnReadSegment(const char *buf,
+                          PRUint32 count,
+                          PRUint32 *countRead)
+{
+  LOG(("SpdyStream::OnReadSegment %p count=%d state=%x",
+       this, count, mUpstreamState));
+
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  NS_ABORT_IF_FALSE(mSegmentReader, "OnReadSegment with null mSegmentReader");
+  
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  PRUint32 dataLength;
+
+  switch (mUpstreamState) {
+  case GENERATING_SYN_STREAM:
+    // The buffer is the HTTP request stream, including at least part of the
+    // HTTP request header. This state's job is to build a SYN_STREAM frame
+    // from the header information. count is the number of http bytes available
+    // (which may include more than the header), and in countRead we return
+    // the number of those bytes that we consume (i.e. the portion that are
+    // header bytes)
+
+    rv = ParseHttpRequestHeaders(buf, count, countRead);
+    if (NS_FAILED(rv))
+      return rv;
+    LOG(("ParseHttpRequestHeaders %p used %d of %d.",
+         this, *countRead, count));
+    if (mSynFrameComplete) {
+      NS_ABORT_IF_FALSE(mTxInlineFrameSize,
+                        "OnReadSegment SynFrameComplete 0b");
+      rv = TransmitFrame(nsnull, nsnull);
+      if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
+        rv = NS_OK;
+      if (mTxInlineFrameSize)
+        ChangeState(SENDING_SYN_STREAM);
+      else
+        ChangeState(GENERATING_REQUEST_BODY);
+      break;
+    }
+    NS_ABORT_IF_FALSE(*countRead == count,
+                      "Header parsing not complete but unused data");
+    break;
+
+  case GENERATING_REQUEST_BODY:
+    NS_ABORT_IF_FALSE(!mTxInlineFrameSent,
+                      "OnReadSegment in generating_request_body with "
+                      "frame in progress");
+    if (count < mChunkSize && count < mRequestBodyLen) {
+      LOG(("SpdyStream %p id %x has %d to write out of a bodylen %d"
+           " with a chunk size of %d. Waiting for more.",
+           this, mStreamID, count, mChunkSize, mRequestBodyLen));
+      rv = NS_BASE_STREAM_WOULD_BLOCK;
+      break;
+    }
+    
+    dataLength = NS_MIN(count, mChunkSize);
+    if (dataLength > mRequestBodyLen)
+      return NS_ERROR_UNEXPECTED;
+    mRequestBodyLen -= dataLength;
+    GenerateDataFrameHeader(dataLength, !mRequestBodyLen);
+    ChangeState(SENDING_REQUEST_BODY);
+    // NO BREAK
+
+  case SENDING_REQUEST_BODY:
+    NS_ABORT_IF_FALSE(mTxInlineFrameSize, "OnReadSegment Send Data Header 0b");
+    rv = TransmitFrame(buf, countRead);
+    LOG(("TransmitFrame() rv=%x returning %d data bytes. "
+         "Header is %d/%d Body is %d/%d.",
+         rv, *countRead,
+         mTxInlineFrameSent, mTxInlineFrameSize,
+         mTxStreamFrameSent, mTxStreamFrameSize));
+
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
+      rv = NS_OK;
+
+    // If that frame was all sent, look for another one
+    if (!mTxInlineFrameSize)
+        ChangeState(GENERATING_REQUEST_BODY);
+    break;
+
+  case SENDING_SYN_STREAM:
+    rv = NS_BASE_STREAM_WOULD_BLOCK;
+    break;
+
+  case SENDING_FIN_STREAM:
+    NS_ABORT_IF_FALSE(false,
+                      "resuming partial fin stream out of OnReadSegment");
+    break;
+    
+  default:
+    NS_ABORT_IF_FALSE(false, "SpdyStream::OnReadSegment non-write state");
+    break;
+  }
+  
+  return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdyStream::OnWriteSegment(char *buf,
+                           PRUint32 count,
+                           PRUint32 *countWritten)
+{
+  LOG(("SpdyStream::OnWriteSegment %p count=%d state=%x",
+       this, count, mUpstreamState));
+
+  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+  NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter");
+
+  return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+}
+
+} // namespace mozilla::net
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SpdyStream.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick McManus <mcmanus@ducksong.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef mozilla_net_SpdyStream_h
+#define mozilla_net_SpdyStream_h
+
+#include "nsAHttpTransaction.h"
+
+namespace mozilla { namespace net {
+
+class SpdyStream : public nsAHttpSegmentReader
+                 , public nsAHttpSegmentWriter
+{
+public:
+  NS_DECL_NSAHTTPSEGMENTREADER
+  NS_DECL_NSAHTTPSEGMENTWRITER
+
+  SpdyStream(nsAHttpTransaction *,
+             SpdySession *, nsISocketTransport *,
+             PRUint32, z_stream *, PRInt32);
+  ~SpdyStream();
+
+  PRUint32 StreamID() { return mStreamID; }
+
+  nsresult ReadSegments(nsAHttpSegmentReader *,  PRUint32, PRUint32 *);
+  nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *);
+
+  bool BlockedOnWrite()
+  {
+    return static_cast<bool>(mBlockedOnWrite);
+  }
+
+  bool RequestBlockedOnRead()
+  {
+    return static_cast<bool>(mRequestBlockedOnRead);
+  }
+
+  // returns false if called more than once
+  bool SetFullyOpen()
+  {
+    bool result = !mFullyOpen;
+    mFullyOpen = 1;
+    return result;
+  }
+
+  nsAHttpTransaction *Transaction()
+  {
+    return mTransaction;
+  }
+
+  void Close(nsresult reason);
+
+  void SetRecvdFin(bool aStatus) { mRecvdFin = aStatus ? 1 : 0; }
+  bool RecvdFin() { return mRecvdFin; }
+
+  // The zlib header compression dictionary defined by SPDY,
+  // and hooks to the mozilla allocator for zlib to use.
+  static const char *kDictionary;
+  static void *zlib_allocator(void *, uInt, uInt);
+  static void zlib_destructor(void *, void *);
+
+private:
+
+  enum stateType {
+    GENERATING_SYN_STREAM,
+    SENDING_SYN_STREAM,
+    GENERATING_REQUEST_BODY,
+    SENDING_REQUEST_BODY,
+    SENDING_FIN_STREAM,
+    UPSTREAM_COMPLETE
+  };
+
+  static PLDHashOperator hdrHashEnumerate(const nsACString &,
+                                          nsAutoPtr<nsCString> &,
+                                          void *);
+
+  void     ChangeState(enum stateType );
+  nsresult ParseHttpRequestHeaders(const char *, PRUint32, PRUint32 *);
+  nsresult TransmitFrame(const char *, PRUint32 *);
+  void     GenerateDataFrameHeader(PRUint32, bool);
+
+  void     CompressToFrame(const nsACString &);
+  void     CompressToFrame(const nsACString *);
+  void     CompressToFrame(const char *, PRUint32);
+  void     CompressToFrame(PRUint16);
+  void     CompressFlushFrame();
+  void     ExecuteCompress(PRUint32);
+  
+  // Each stream goes from syn_stream to upstream_complete, perhaps
+  // looping on multiple instances of generating_request_body and
+  // sending_request_body for each SPDY chunk in the upload.
+  enum stateType mUpstreamState;
+
+  // The underlying HTTP transaction
+  nsRefPtr<nsAHttpTransaction> mTransaction;
+
+  // The session that this stream is a subset of
+  SpdySession                *mSession;
+
+  // The underlying socket transport object is needed to propogate some events
+  nsISocketTransport         *mSocketTransport;
+
+  // These are temporary state variables to hold the argument to
+  // Read/WriteSegments so it can be accessed by On(read/write)segment
+  // further up the stack.
+  nsAHttpSegmentReader        *mSegmentReader;
+  nsAHttpSegmentWriter        *mSegmentWriter;
+
+  // The 24 bit SPDY stream ID
+  PRUint32                    mStreamID;
+
+  // The quanta upstream data frames are chopped into
+  PRUint32                    mChunkSize;
+
+  // Flag is set when all http request headers have been read
+  PRUint32                     mSynFrameComplete     : 1;
+
+  // Flag is set when there is more request data to send and the
+  // stream needs to be rescheduled for writing. Sometimes this
+  // is done as a matter of fairness, not really due to blocking
+  PRUint32                     mBlockedOnWrite       : 1;
+
+  // Flag is set when the HTTP processor has more data to send
+  // but has blocked in doing so.
+  PRUint32                     mRequestBlockedOnRead : 1;
+
+  // Flag is set when a FIN has been placed on a data or syn packet
+  // (i.e after the client has closed)
+  PRUint32                     mSentFinOnData        : 1;
+
+  // Flag is set after the response frame bearing the fin bit has
+  // been processed. (i.e. after the server has closed).
+  PRUint32                     mRecvdFin             : 1;
+
+  // Flag is set after syn reply received
+  PRUint32                     mFullyOpen            : 1;
+
+  // The InlineFrame and associated data is used for composing control
+  // frames and data frame headers.
+  nsAutoArrayPtr<char>         mTxInlineFrame;
+  PRUint32                     mTxInlineFrameAllocation;
+  PRUint32                     mTxInlineFrameSize;
+  PRUint32                     mTxInlineFrameSent;
+
+  // mTxStreamFrameSize and mTxStreamFrameSent track the progress of
+  // transmitting a request body data frame. The data frame itself
+  // is never copied into the spdy layer.
+  PRUint32                     mTxStreamFrameSize;
+  PRUint32                     mTxStreamFrameSent;
+
+  // Compression context and buffer for request header compression.
+  // This is a copy of SpdySession::mUpstreamZlib because it needs
+  //  to remain the same in all streams of a session.
+  z_stream                     *mZlib;
+  nsCString                    mFlatHttpRequestHeaders;
+
+  // 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.
+  PRInt64                      mRequestBodyLen;
+
+  // based on nsISupportsPriority definitions
+  PRInt32                      mPriority;
+
+};
+
+}} // namespace mozilla::net
+
+#endif // mozilla_net_SpdyStream_h
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -72,18 +72,18 @@ public:
                                         nsHttpResponseHead *,
                                         bool *reset) = 0;
 
     //
     // 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;
+    virtual nsresult ResumeSend(nsAHttpTransaction *caller) = 0;
+    virtual nsresult ResumeRecv(nsAHttpTransaction *caller) = 0;
 
     //
     // called by the connection manager to close a transaction being processed
     // by this connection.
     //
     // @param transaction
     //        the transaction being closed.
     // @param reason
@@ -127,18 +127,18 @@ public:
 
     // Get the nsISocketTransport used by the connection without changing
     //  references or ownership.
     virtual nsISocketTransport *Transport() = 0;
 };
 
 #define NS_DECL_NSAHTTPCONNECTION \
     nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset); \
-    nsresult ResumeSend(); \
-    nsresult ResumeRecv(); \
+    nsresult ResumeSend(nsAHttpTransaction *); \
+    nsresult ResumeRecv(nsAHttpTransaction *); \
     void CloseTransaction(nsAHttpTransaction *, nsresult); \
     void GetConnectionInfo(nsHttpConnectionInfo **); \
     nsresult TakeTransport(nsISocketTransport **,    \
                            nsIAsyncInputStream **,   \
                            nsIAsyncOutputStream **); \
     void GetSecurityInfo(nsISupports **); \
     bool IsPersistent(); \
     bool IsReused(); \
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -57,16 +57,17 @@ class nsHttpRequestHead;
 // write function returns NS_BASE_STREAM_WOULD_BLOCK in this case).
 //----------------------------------------------------------------------------
 
 class nsAHttpTransaction : public nsISupports
 {
 public:
     // called by the connection when it takes ownership of the transaction.
     virtual void SetConnection(nsAHttpConnection *) = 0;
+    virtual nsAHttpConnection *Connection() = 0;
 
     // called by the connection to get security callbacks to set on the
     // socket transport.
     virtual void GetSecurityCallbacks(nsIInterfaceRequestor **,
                                       nsIEventTarget **) = 0;
 
     // called to report socket status (see nsITransportEventSink)
     virtual void OnTransportStatus(nsITransport* transport,
@@ -94,16 +95,17 @@ public:
     virtual void SetSSLConnectFailed() = 0;
     
     // called to retrieve the request headers of the transaction
     virtual nsHttpRequestHead *RequestHead() = 0;
 };
 
 #define NS_DECL_NSAHTTPTRANSACTION \
     void SetConnection(nsAHttpConnection *); \
+    nsAHttpConnection *Connection(); \
     void GetSecurityCallbacks(nsIInterfaceRequestor **, \
                               nsIEventTarget **);       \
     void OnTransportStatus(nsITransport* transport, \
                            nsresult status, PRUint64 progress); \
     bool     IsDone(); \
     nsresult Status(); \
     PRUint32 Available(); \
     nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); \
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -126,16 +126,21 @@ typedef PRUint8 nsHttpVersion;
 // a transaction with this caps flag keeps timing information
 #define NS_HTTP_TIMING_ENABLED       (1<<5)
 
 // a transaction with this caps flag will not only not use an existing
 // persistent connection but it will close outstanding ones to the same
 // host. Used by a forced reload to reset the connection states.
 #define NS_HTTP_CLEAR_KEEPALIVES     (1<<6)
 
+// Disallow the use of the SPDY protocol. This is meant for the contexts
+// such as HTTP upgrade which are nonsensical for SPDY, it is not the
+// SPDY configuration variable.
+#define NS_HTTP_DISALLOW_SPDY        (1<<7)
+
 //-----------------------------------------------------------------------------
 // some default values
 //-----------------------------------------------------------------------------
 
 // hard upper limit on the number of requests that can be pipelined
 #define NS_HTTP_MAX_PIPELINED_REQUESTS 8 
 
 #define NS_HTTP_DEFAULT_PORT  80
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -629,16 +629,17 @@ nsHttpChannel::SetupTransaction()
     if (mUpgradeProtocolCallback) {
         mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
         mRequestHead.SetHeader(nsHttp::Connection,
                                nsDependentCString(nsHttp::Upgrade.get()),
                                true);
         mCaps |=  NS_HTTP_STICKY_CONNECTION;
         mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
         mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+        mCaps |=  NS_HTTP_DISALLOW_SPDY;
     }
 
     nsCOMPtr<nsIAsyncInputStream> responseStream;
     rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
                             mUploadStream, mUploadStreamHasHeaders,
                             NS_GetCurrentThread(), callbacks, this,
                             getter_AddRefs(responseStream));
     if (NS_FAILED(rv)) {
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -48,16 +48,19 @@
 #include "nsIServiceManager.h"
 #include "nsISSLSocketControl.h"
 #include "nsStringStream.h"
 #include "netCore.h"
 #include "nsNetCID.h"
 #include "nsProxyRelease.h"
 #include "prmem.h"
 #include "nsPreloadedStream.h"
+#include "SpdySession.h"
+#include "mozilla/Telemetry.h"
+#include "nsISupportsPriority.h"
 
 #ifdef DEBUG
 // defined by the socket transport service while active
 extern PRThread *gSocketThread;
 #endif
 
 static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
 
@@ -77,16 +80,20 @@ nsHttpConnection::nsHttpConnection()
     , mMaxBytesRead(0)
     , mKeepAlive(true) // assume to keep-alive by default
     , mKeepAliveMask(true)
     , mSupportsPipelining(false) // assume low-grade server
     , mIsReused(false)
     , mCompletedProxyConnect(false)
     , mLastTransactionExpectedNoContent(false)
     , mIdleMonitoring(false)
+    , mNPNComplete(false)
+    , mUsingSpdy(false)
+    , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
+    , mReportedSpdy(false)
 {
     LOG(("Creating nsHttpConnection @%x\n", this));
 
     // grab a reference to the handler to ensure that it doesn't go away.
     nsHttpHandler *handler = gHttpHandler;
     NS_ADDREF(handler);
 }
 
@@ -136,41 +143,143 @@ nsHttpConnection::Init(nsHttpConnectionI
     mCallbacks = callbacks;
     mCallbackTarget = callbackTarget;
     rv = mSocketTransport->SetSecurityCallbacks(this);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
+bool
+nsHttpConnection::EnsureNPNComplete()
+{
+    // NPN is only used by SPDY right now.
+    //
+    // 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
+
+    NS_ABORT_IF_FALSE(mSocketTransport, "EnsureNPNComplete "
+                      "socket transport precondition");
+
+    if (mNPNComplete)
+        return true;
+    
+    nsresult rv;
+
+    nsCOMPtr<nsISupports> securityInfo;
+    nsCOMPtr<nsISSLSocketControl> ssl;
+    nsCAutoString negotiatedNPN;
+    
+    rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
+    if (NS_FAILED(rv))
+        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.
+        PRUint32 count = 0;
+        rv = mSocketOut->Write("", 0, &count);
+
+        if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK)
+            goto npnComplete;
+        return false;
+    }
+    
+    if (NS_FAILED(rv))
+        goto npnComplete;
+
+    LOG(("nsHttpConnection::EnsureNPNComplete %p negotiated to '%s'",
+         this, negotiatedNPN.get()));
+    
+    if (negotiatedNPN.Equals(NS_LITERAL_CSTRING("spdy/2"))) {
+
+        mUsingSpdy = true;
+        mIsReused = true;    /* all spdy streams are reused */
+
+        // Wrap the old http transaction into the new spdy session
+        // as the first stream
+        mSpdySession = new SpdySession(mTransaction,
+                                       mSocketTransport,
+                                       mPriority);
+        mTransaction = mSpdySession;
+        mIdleTimeout = gHttpHandler->SpdyTimeout();
+    }
+
+    mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_CONNECT,
+                                   mUsingSpdy);
+
+npnComplete:
+    LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
+    mNPNComplete = true;
+    return true;
+}
+
 // called on the socket thread
 nsresult
-nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps)
+nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps, PRInt32 pri)
 {
     nsresult rv;
 
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     LOG(("nsHttpConnection::Activate [this=%x trans=%x caps=%x]\n",
          this, trans, caps));
 
+    mPriority = pri;
+    if (mTransaction && mUsingSpdy)
+        return AddTransaction(trans, pri);
+
     NS_ENSURE_ARG_POINTER(trans);
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
 
     // Update security callbacks
     nsCOMPtr<nsIInterfaceRequestor> callbacks;
     nsCOMPtr<nsIEventTarget>        callbackTarget;
     trans->GetSecurityCallbacks(getter_AddRefs(callbacks),
                                 getter_AddRefs(callbackTarget));
     if (callbacks != mCallbacks) {
         mCallbacks.swap(callbacks);
         if (callbacks)
             NS_ProxyRelease(mCallbackTarget, callbacks);
         mCallbackTarget = callbackTarget;
     }
 
+    // Setup NPN Negotiation if necessary (only for SPDY)
+    if (!mNPNComplete) {
+
+        mNPNComplete = true;
+
+        if (mConnInfo->UsingSSL() &&
+            !(caps & NS_HTTP_DISALLOW_SPDY) &&
+            gHttpHandler->IsSpdyEnabled()) {
+            LOG(("nsHttpConnection::Init Setting up SPDY Negotiation"));
+            nsCOMPtr<nsISupports> securityInfo;
+            nsresult rv =
+                mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
+            NS_ENSURE_SUCCESS(rv, rv);
+            
+            nsCOMPtr<nsISSLSocketControl> ssl =
+                do_QueryInterface(securityInfo, &rv);
+            NS_ENSURE_SUCCESS(rv, rv);
+
+            nsTArray<nsCString> protocolArray;
+            protocolArray.AppendElement(NS_LITERAL_CSTRING("spdy/2"));
+            protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
+            if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) {
+                LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK"));
+                mNPNComplete = false;
+            }
+        }
+    }
+
     // take ownership of the transaction
     mTransaction = trans;
 
     NS_ABORT_IF_FALSE(!mIdleMonitoring,
                       "Activating a connection with an Idle Monitor");
     mIdleMonitoring = false;
 
     // set mKeepAlive according to what will be requested
@@ -193,16 +302,39 @@ nsHttpConnection::Activate(nsAHttpTransa
 failed_activation:
     if (NS_FAILED(rv)) {
         mTransaction = nsnull;
     }
 
     return rv;
 }
 
+nsresult
+nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction,
+                                 PRInt32 priority)
+{
+    LOG(("nsHttpConnection::AddTransaction for SPDY"));
+
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    NS_ABORT_IF_FALSE(mSpdySession && mUsingSpdy,
+                      "AddTransaction to live http connection without spdy");
+    NS_ABORT_IF_FALSE(mTransaction,
+                      "AddTransaction to idle http connection");
+    
+    if (!mSpdySession->AddStream(httpTransaction, priority)) {
+        NS_ABORT_IF_FALSE(0, "AddStream should never fail due to"
+                          "RoomForMore() admission check");
+        return NS_ERROR_FAILURE;
+    }
+
+    ResumeSend(httpTransaction);
+
+    return NS_OK;
+}
+
 void
 nsHttpConnection::Close(nsresult reason)
 {
     LOG(("nsHttpConnection::Close [this=%x reason=%x]\n", this, reason));
 
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     if (NS_FAILED(reason)) {
@@ -232,40 +364,67 @@ nsHttpConnection::ProxyStartSSL()
     if (NS_FAILED(rv)) return rv;
 
     nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
     if (NS_FAILED(rv)) return rv;
 
     return ssl->ProxyStartSSL();
 }
 
+void
+nsHttpConnection::DontReuse()
+{
+    mKeepAliveMask = false;
+    mKeepAlive = false;
+    mIdleTimeout = 0;
+    if (mUsingSpdy)
+        mSpdySession->DontReuse();
+}
+
 bool
 nsHttpConnection::CanReuse()
 {
-    bool canReuse = IsKeepAlive() &&
+    bool canReuse;
+    
+    if (mUsingSpdy)
+        canReuse = mSpdySession->CanReuse();
+    else
+        canReuse = IsKeepAlive();
+    
+    canReuse = canReuse &&
         (NowInSeconds() - mLastReadTime < mIdleTimeout) &&
         IsAlive();
-    
+
     // An idle persistent connection should not have data waiting to be read
     // before a request is sent. Data here is likely a 408 timeout response
     // which we would deal with later on through the restart logic, but that
     // path is more expensive than just closing the socket now. SSL check can
     // be removed with fixing of 631801
 
     PRUint32 dataSize;
-    if (canReuse && mSocketIn && !mConnInfo->UsingSSL() &&
+    if (canReuse && mSocketIn && !mConnInfo->UsingSSL() && !mUsingSpdy &&
         NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
         LOG(("nsHttpConnection::CanReuse %p %s"
              "Socket not reusable because read data pending (%d) on it.\n",
              this, mConnInfo->Host(), dataSize));
         canReuse = false;
     }
     return canReuse;
 }
 
+bool
+nsHttpConnection::CanDirectlyActivate()
+{
+    // return true if a new transaction can be addded to ths connection at any
+    // time through Activate(). In practice this means this is a healthy SPDY
+    // connection with room for more concurrent streams.
+    
+    return UsingSpdy() && CanReuse() && mSpdySession->RoomForMoreStreams();
+}
+
 PRUint32 nsHttpConnection::TimeToLive()
 {
     PRInt32 tmp = mIdleTimeout - (NowInSeconds() - mLastReadTime);
     if (0 > tmp)
         tmp = 0;
 
     return tmp;
 }
@@ -290,16 +449,20 @@ nsHttpConnection::IsAlive()
 #endif
 
     return alive;
 }
 
 bool
 nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead)
 {
+    // SPDY supports infinite parallelism, so no need to pipeline.
+    if (mUsingSpdy)
+        return false;
+
     // XXX there should be a strict mode available that disables this
     // blacklisting.
 
     // assuming connection is HTTP/1.1 with keep-alive enabled
     if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingSSL()) {
         // XXX check for bad proxy servers...
         return true;
     }
@@ -415,30 +578,37 @@ nsHttpConnection::OnHeadersAvailable(nsA
     // reused as well as the maximum amount of time the connection can be idle
     // before the server will close it.  we ignore the max reuse count, because
     // a "keep-alive" connection is by definition capable of being reused, and
     // we only care about being able to reuse it once.  if a timeout is not 
     // specified then we use our advertized timeout value.
     if (mKeepAlive) {
         val = responseHead->PeekHeader(nsHttp::Keep_Alive);
 
-        const char *cp = PL_strcasestr(val, "timeout=");
-        if (cp)
-            mIdleTimeout = (PRUint32) atoi(cp + 8);
-        else
-            mIdleTimeout = gHttpHandler->IdleTimeout();
+        if (!mUsingSpdy) {
+            const char *cp = PL_strcasestr(val, "timeout=");
+            if (cp)
+                mIdleTimeout = (PRUint32) atoi(cp + 8);
+            else
+                mIdleTimeout = gHttpHandler->IdleTimeout();
+        }
+        else {
+            mIdleTimeout = gHttpHandler->SpdyTimeout();
+        }
         
         LOG(("Connection can be reused [this=%x idle-timeout=%u]\n", this, mIdleTimeout));
     }
 
     // if we're doing an SSL proxy connect, then we need to check whether or not
     // the connect was successful.  if so, then we have to reset the transaction
     // and step-up the socket connection to SSL. finally, we have to wake up the
     // socket write request.
     if (mProxyConnectStream) {
+        NS_ABORT_IF_FALSE(!mUsingSpdy,
+                          "SPDY NPN Complete while using proxy connect stream");
         mProxyConnectStream = 0;
         if (responseHead->Status() == 200) {
             LOG(("proxy CONNECT succeeded! ssl=%s\n",
                  mConnInfo->UsingSSL() ? "true" :"false"));
             *reset = true;
             nsresult rv;
             if (mConnInfo->UsingSSL()) {
                 rv = ProxyStartSSL();
@@ -501,16 +671,18 @@ nsHttpConnection::SetIsReusedAfter(PRUin
     mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds);
 }
 
 nsresult
 nsHttpConnection::TakeTransport(nsISocketTransport  **aTransport,
                                 nsIAsyncInputStream **aInputStream,
                                 nsIAsyncOutputStream **aOutputStream)
 {
+    if (mUsingSpdy)
+        return NS_ERROR_FAILURE;
     if (mTransaction && !mTransaction->IsDone())
         return NS_ERROR_IN_PROGRESS;
     if (!(mSocketTransport && mSocketIn && mSocketOut))
         return NS_ERROR_NOT_INITIALIZED;
 
     if (mInputOverflow)
         mSocketIn = mInputOverflow.forget();
 
@@ -548,31 +720,31 @@ nsHttpConnection::PushBack(const char *d
         return NS_ERROR_UNEXPECTED;
     }
     
     mInputOverflow = new nsPreloadedStream(mSocketIn, data, length);
     return NS_OK;
 }
 
 nsresult
-nsHttpConnection::ResumeSend()
+nsHttpConnection::ResumeSend(nsAHttpTransaction *)
 {
     LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this));
 
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     if (mSocketOut)
         return mSocketOut->AsyncWait(this, 0, 0, nsnull);
 
     NS_NOTREACHED("no socket output stream");
     return NS_ERROR_UNEXPECTED;
 }
 
 nsresult
-nsHttpConnection::ResumeRecv()
+nsHttpConnection::ResumeRecv(nsAHttpTransaction *)
 {
     LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
 
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     if (mSocketIn)
         return mSocketIn->AsyncWait(this, 0, 0, nsnull);
 
@@ -581,17 +753,18 @@ nsHttpConnection::ResumeRecv()
 }
 
 void
 nsHttpConnection::BeginIdleMonitoring()
 {
     LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ABORT_IF_FALSE(!mTransaction, "BeginIdleMonitoring() while active");
-    
+    NS_ABORT_IF_FALSE(!mUsingSpdy, "Idle monitoring of spdy not allowed");
+
     LOG(("Entering Idle Monitoring Mode [this=%p]", this));
     mIdleMonitoring = true;
     if (mSocketIn)
         mSocketIn->AsyncWait(this, 0, 0, nsnull);
 }
 
 void
 nsHttpConnection::EndIdleMonitoring()
@@ -623,16 +796,22 @@ nsHttpConnection::CloseTransaction(nsAHt
 
     if (mCurrentBytesRead > mMaxBytesRead)
         mMaxBytesRead = mCurrentBytesRead;
 
     // mask this error code because its not a real error.
     if (reason == NS_BASE_STREAM_CLOSED)
         reason = NS_OK;
 
+    if (mUsingSpdy) {
+        DontReuse();
+        mUsingSpdy = false;
+        mSpdySession = nsnull;
+    }
+
     mTransaction->Close(reason);
     mTransaction = nsnull;
 
     if (mCallbacks) {
         nsIInterfaceRequestor *cbs = nsnull;
         mCallbacks.swap(cbs);
         NS_ProxyRelease(mCallbackTarget, cbs);
     }
@@ -687,30 +866,51 @@ nsHttpConnection::OnSocketWritable()
 {
     LOG(("nsHttpConnection::OnSocketWritable [this=%x]\n", this));
 
     nsresult rv;
     PRUint32 n;
     bool again = true;
 
     do {
+        mSocketOutCondition = NS_OK;
+
         // if we're doing an SSL 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) {
             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.
+
+            // 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 {
+            if (gHttpHandler->IsSpdyEnabled() && !mReportedSpdy) {
+                mReportedSpdy = true;
+                gHttpHandler->ConnMgr()->
+                    ReportSpdyConnection(this, mUsingSpdy);
+            }
+
             LOG(("  writing transaction request stream\n"));
             rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n);
         }
 
         LOG(("  ReadSegments returned [rv=%x read=%u sock-cond=%x]\n",
             rv, n, mSocketOutCondition));
 
         // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
@@ -826,16 +1026,18 @@ nsHttpConnection::OnSocketReadable()
 nsresult
 nsHttpConnection::SetupProxyConnect()
 {
     const char *val;
 
     LOG(("nsHttpConnection::SetupProxyConnect [this=%x]\n", this));
 
     NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED);
+    NS_ABORT_IF_FALSE(!mUsingSpdy,
+                      "SPDY NPN Complete while using proxy connect stream");
 
     nsCAutoString buf;
     nsresult rv = nsHttpHandler::GenerateHostPort(
             nsDependentCString(mConnInfo->Host()), mConnInfo->Port(), buf);
     if (NS_FAILED(rv))
         return rv;
 
     // CONNECT host:port HTTP/1.1
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -42,16 +42,17 @@
 #include "nsHttp.h"
 #include "nsHttpConnectionInfo.h"
 #include "nsAHttpConnection.h"
 #include "nsAHttpTransaction.h"
 #include "nsXPIDLString.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "prinrval.h"
+#include "SpdySession.h"
 
 #include "nsIStreamListener.h"
 #include "nsISocketTransport.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIEventTarget.h"
 
@@ -87,35 +88,36 @@ public:
     //                single transaction before it should no longer be kept 
     //                alive.  a value of 0xffff indicates no limit.
     nsresult Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime,
                   nsISocketTransport *, nsIAsyncInputStream *,
                   nsIAsyncOutputStream *, nsIInterfaceRequestor *,
                   nsIEventTarget *);
 
     // Activate causes the given transaction to be processed on this
-    // connection.  It fails if there is already an existing transaction.
-    nsresult Activate(nsAHttpTransaction *, PRUint8 caps);
+    // connection.  It fails if there is already an existing transaction unless
+    // a multiplexing protocol such as SPDY is being used
+    nsresult Activate(nsAHttpTransaction *, PRUint8 caps, PRInt32 pri);
 
     // Close the underlying socket transport.
     void Close(nsresult reason);
 
     //-------------------------------------------------------------------------
     // XXX document when these are ok to call
 
     bool     SupportsPipelining() { return mSupportsPipelining; }
-    bool     IsKeepAlive() { return mKeepAliveMask && mKeepAlive; }
+    bool     IsKeepAlive() { return mUsingSpdy ||
+                                    (mKeepAliveMask && mKeepAlive); }
     bool     CanReuse();   // can this connection be reused?
+    bool     CanDirectlyActivate();
 
     // Returns time in seconds for how long connection can be reused.
     PRUint32 TimeToLive();
 
-    void     DontReuse()   { mKeepAliveMask = false;
-                             mKeepAlive = false;
-                             mIdleTimeout = 0; }
+    void     DontReuse();
     void     DropTransport() { DontReuse(); mSocketTransport = 0; }
 
     bool     LastTransactionExpectedNoContent()
     {
         return mLastTransactionExpectedNoContent;
     }
 
     void     SetLastTransactionExpectedNoContent(bool val)
@@ -135,43 +137,52 @@ public:
                            nsIAsyncInputStream **,
                            nsIAsyncOutputStream **);
     void     GetSecurityInfo(nsISupports **);
     bool     IsPersistent() { return IsKeepAlive(); }
     bool     IsReused();
     void     SetIsReusedAfter(PRUint32 afterMilliseconds);
     void     SetIdleTimeout(PRUint16 val) {mIdleTimeout = val;}
     nsresult PushBack(const char *data, PRUint32 length);
-    nsresult ResumeSend();
-    nsresult ResumeRecv();
+    nsresult ResumeSend(nsAHttpTransaction *caller);
+    nsresult ResumeRecv(nsAHttpTransaction *caller);
     PRInt64  MaxBytesRead() {return mMaxBytesRead;}
 
     static NS_METHOD ReadFromStream(nsIInputStream *, void *, const char *,
                                     PRUint32, PRUint32, PRUint32 *);
 
     // 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
     // initiates a termination. Only call on socket thread.
     void BeginIdleMonitoring();
     void EndIdleMonitoring();
 
+    bool UsingSpdy() { return mUsingSpdy; }
+
 private:
     // called to cause the underlying socket to start speaking SSL
     nsresult ProxyStartSSL();
 
     nsresult OnTransactionDone(nsresult reason);
     nsresult OnSocketWritable();
     nsresult OnSocketReadable();
 
     nsresult SetupProxyConnect();
 
     bool     IsAlive();
     bool     SupportsPipelining(nsHttpResponseHead *);
     
+    // Makes certain the SSL handshake is complete and NPN negotiation
+    // has had a chance to happen
+    bool     EnsureNPNComplete();
+
+    // Directly Add a transaction to an active connection for SPDY
+    nsresult AddTransaction(nsAHttpTransaction *, PRInt32);
+
 private:
     nsCOMPtr<nsISocketTransport>    mSocketTransport;
     nsCOMPtr<nsIAsyncInputStream>   mSocketIn;
     nsCOMPtr<nsIAsyncOutputStream>  mSocketOut;
 
     nsresult                        mSocketInCondition;
     nsresult                        mSocketOutCondition;
 
@@ -199,11 +210,18 @@ private:
 
     bool                            mKeepAlive;
     bool                            mKeepAliveMask;
     bool                            mSupportsPipelining;
     bool                            mIsReused;
     bool                            mCompletedProxyConnect;
     bool                            mLastTransactionExpectedNoContent;
     bool                            mIdleMonitoring;
+
+    // SPDY related
+    bool                            mNPNComplete;
+    bool                            mUsingSpdy;
+    nsRefPtr<mozilla::net::SpdySession> mSpdySession;
+    PRInt32                         mPriority;
+    bool                            mReportedSpdy;
 };
 
 #endif // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -120,16 +120,17 @@ public:
     PRInt32       Port() const           { return mPort; }
     nsProxyInfo  *ProxyInfo()            { return mProxyInfo; }
     bool          UsingHttpProxy() const { return mUsingHttpProxy; }
     bool          UsingSSL() const       { return mUsingSSL; }
     PRInt32       DefaultPort() const    { return mUsingSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT; }
     void          SetAnonymous(bool anon)         
                                          { mHashKey.SetCharAt(anon ? 'A' : '.', 2); }
     bool          ShouldForceConnectMethod();
+    const nsCString &GetHost() { return mHost; }
 
 private:
     nsrefcnt               mRef;
     nsCString              mHashKey;
     nsCString              mHost;
     PRInt32                mPort;
     nsCOMPtr<nsProxyInfo>  mProxyInfo;
     bool                   mUsingHttpProxy;
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -42,16 +42,19 @@
 #include "nsHttpHandler.h"
 #include "nsNetCID.h"
 #include "nsCOMPtr.h"
 #include "nsNetUtil.h"
 
 #include "nsIServiceManager.h"
 
 #include "nsIObserverService.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
 
 using namespace mozilla;
 
 // defined by the socket transport service while active
 extern PRThread *gSocketThread;
 
 static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
 
@@ -135,16 +138,17 @@ nsHttpConnectionMgr::Init(PRUint16 maxCo
                           PRUint16 maxPersistConnsPerProxy,
                           PRUint16 maxRequestDelay,
                           PRUint16 maxPipelinedRequests)
 {
     LOG(("nsHttpConnectionMgr::Init\n"));
 
     {
         ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+        mSpdyPreferredHash.Init();
 
         mMaxConns = maxConns;
         mMaxConnsPerHost = maxConnsPerHost;
         mMaxConnsPerProxy = maxConnsPerProxy;
         mMaxPersistConnsPerHost = maxPersistConnsPerHost;
         mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
         mMaxRequestDelay = maxRequestDelay;
         mMaxPipelinedRequests = maxPipelinedRequests;
@@ -224,18 +228,23 @@ nsHttpConnectionMgr::PruneDeadConnection
         mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
         mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT);
     } else {
         NS_WARNING("failed to create: timer for pruning the dead connections!");
     }
 }
 
 void
-nsHttpConnectionMgr::StopPruneDeadConnectionsTimer()
+nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer()
 {
+    // Leave the timer in place if there are connections that potentially
+    // need management
+    if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
+        return;
+
     LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
 
     // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
     mTimeOfNextWakeUp = LL_MAXUINT;
     if (mTimer) {
         mTimer->Cancel();
         mTimer = NULL;
     }
@@ -403,21 +412,101 @@ nsHttpConnectionMgr::CloseIdleConnection
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
 
     if (!ent || !ent->mIdleConns.RemoveElement(conn))
         return NS_ERROR_UNEXPECTED;
 
     conn->Close(NS_ERROR_ABORT);
     NS_RELEASE(conn);
     mNumIdleConns--;
-    if (0 == mNumIdleConns)
-        StopPruneDeadConnectionsTimer();
+    ConditionallyStopPruneDeadConnectionsTimer();
     return NS_OK;
 }
 
+void
+nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
+                                          bool usingSpdy)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    
+    nsConnectionEntry *ent = mCT.Get(conn->ConnectionInfo()->HashKey());
+    NS_ABORT_IF_FALSE(ent, "no connection entry");
+    if (!ent)
+        return;
+
+    ent->mTestedSpdy = true;
+
+    if (!usingSpdy) {
+        if (ent->mUsingSpdy)
+            conn->DontReuse();
+        return;
+    }
+    
+    ent->mUsingSpdy = true;
+
+    PRUint32 ttl = conn->TimeToLive();
+    PRUint64 timeOfExpire = NowInSeconds() + ttl;
+    if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
+        PruneDeadConnectionsAfter(ttl);
+        
+    nsConnectionEntry *preferred = GetSpdyPreferred(ent->mDottedDecimalAddress);
+    LOG(("ReportSpdyConnection %s %s ent=%p ispreferred=%d\n",
+         ent->mConnInfo->Host(), ent->mDottedDecimalAddress.get(),
+         ent, preferred));
+    
+    if (!preferred) {
+        ent->mSpdyPreferred = true;
+        SetSpdyPreferred(ent->mDottedDecimalAddress, ent);
+        ent->mSpdyRedir = false;
+    }
+    else if (preferred != ent) {
+        // A different hostname is the preferred spdy host for this
+        // IP address.
+        ent->mSpdyRedir = true;
+        conn->DontReuse();
+    }
+    ProcessSpdyPendingQ();
+}
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::GetSpdyPreferred(nsACString &aDottedDecimal)
+{
+    if (!gHttpHandler->IsSpdyEnabled() ||
+        !gHttpHandler->CoalesceSpdy() ||
+        aDottedDecimal.IsEmpty())
+        return nsnull;
+
+    return mSpdyPreferredHash.Get(aDottedDecimal);
+}
+
+void
+nsHttpConnectionMgr::SetSpdyPreferred(nsACString &aDottedDecimal,
+                                      nsConnectionEntry *ent)
+{
+    if (!gHttpHandler->CoalesceSpdy())
+        return;
+
+    if (aDottedDecimal.IsEmpty())
+        return;
+    
+    mSpdyPreferredHash.Put(aDottedDecimal, ent);
+}
+
+void
+nsHttpConnectionMgr::RemoveSpdyPreferred(nsACString &aDottedDecimal)
+{
+    if (!gHttpHandler->CoalesceSpdy())
+        return;
+
+    if (aDottedDecimal.IsEmpty())
+        return;
+    
+    mSpdyPreferredHash.Remove(aDottedDecimal);
+}
+
 //-----------------------------------------------------------------------------
 // enumeration callbacks
 
 PLDHashOperator
 nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key,
                                              nsAutoPtr<nsConnectionEntry> &ent,
                                              void *closure)
 {
@@ -444,79 +533,101 @@ nsHttpConnectionMgr::PurgeExcessIdleConn
             // There are no idle conns left in this connection entry
             return PL_DHASH_NEXT;
         }
         nsHttpConnection *conn = ent->mIdleConns[0];
         ent->mIdleConns.RemoveElementAt(0);
         conn->Close(NS_ERROR_ABORT);
         NS_RELEASE(conn);
         self->mNumIdleConns--;
-        if (0 == self->mNumIdleConns)
-            self->StopPruneDeadConnectionsTimer();
+        self->ConditionallyStopPruneDeadConnectionsTimer();
     }
     return PL_DHASH_STOP;
 }
 
 PLDHashOperator
 nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key,
                                             nsAutoPtr<nsConnectionEntry> &ent,
                                             void *closure)
 {
     nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
 
     LOG(("  pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
 
     // Find out how long it will take for next idle connection to not be reusable
     // anymore.
+    bool liveConnections = false;
     PRUint32 timeToNextExpire = PR_UINT32_MAX;
     PRInt32 count = ent->mIdleConns.Length();
     if (count > 0) {
         for (PRInt32 i=count-1; i>=0; --i) {
             nsHttpConnection *conn = ent->mIdleConns[i];
             if (!conn->CanReuse()) {
                 ent->mIdleConns.RemoveElementAt(i);
                 conn->Close(NS_ERROR_ABORT);
                 NS_RELEASE(conn);
                 self->mNumIdleConns--;
             } else {
                 timeToNextExpire = NS_MIN(timeToNextExpire, conn->TimeToLive());
+                liveConnections = true;
             }
         }
     }
 
+    if (ent->mUsingSpdy) {
+        for (PRUint32 index = 0; index < ent->mActiveConns.Length(); ++index) {
+            nsHttpConnection *conn = ent->mActiveConns[index];
+            if (conn->UsingSpdy()) {
+                if (!conn->CanReuse()) {
+                    // marking it dont reuse will create an active tear down if
+                    // the spdy session is idle.
+                    conn->DontReuse();
+                }
+                else {
+                    timeToNextExpire = NS_MIN(timeToNextExpire,
+                                              conn->TimeToLive());
+                    liveConnections = true;
+                }
+            }
+        }
+    }
+    
     // If time to next expire found is shorter than time to next wake-up, we need to
     // change the time for next wake-up.
-    PRUint32 now = NowInSeconds();
-    if (0 < ent->mIdleConns.Length()) {
+    if (liveConnections) {
+        PRUint32 now = NowInSeconds();
         PRUint64 timeOfNextExpire = now + timeToNextExpire;
         // If pruning of dead connections is not already scheduled to happen
         // or time found for next connection to expire is is before
         // mTimeOfNextWakeUp, we need to schedule the pruning to happen
         // after timeToNextExpire.
         if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) {
             self->PruneDeadConnectionsAfter(timeToNextExpire);
         }
-    } else if (0 == self->mNumIdleConns) {
-        self->StopPruneDeadConnectionsTimer();
+    } else {
+        self->ConditionallyStopPruneDeadConnectionsTimer();
     }
 #ifdef DEBUG
     count = ent->mActiveConns.Length();
     if (count > 0) {
         for (PRInt32 i=count-1; i>=0; --i) {
             nsHttpConnection *conn = ent->mActiveConns[i];
             LOG(("    active conn [%x] with trans [%x]\n", conn, conn->Transaction()));
         }
     }
 #endif
 
     // if this entry is empty, then we can remove it.
     if (ent->mIdleConns.Length()   == 0 &&
         ent->mActiveConns.Length() == 0 &&
         ent->mHalfOpens.Length()   == 0 &&
-        ent->mPendingQ.Length()    == 0) {
+        ent->mPendingQ.Length()    == 0 &&
+        ((!ent->mTestedSpdy && !ent->mUsingSpdy) ||
+         !gHttpHandler->IsSpdyEnabled() ||
+         self->mCT.Count() > 300)) {
         LOG(("    removing empty connection entry\n"));
         return PL_DHASH_REMOVE;
     }
 
     // else, use this opportunity to compact our arrays...
     ent->mIdleConns.Compact();
     ent->mActiveConns.Compact();
     ent->mPendingQ.Compact();
@@ -552,18 +663,17 @@ nsHttpConnectionMgr::ShutdownPassCB(cons
         ent->mIdleConns.RemoveElementAt(0);
         self->mNumIdleConns--;
 
         conn->Close(NS_ERROR_ABORT);
         NS_RELEASE(conn);
     }
     // If all idle connections are removed,
     // we can stop pruning dead connections.
-    if (0 == self->mNumIdleConns)
-        self->StopPruneDeadConnectionsTimer();
+    self->ConditionallyStopPruneDeadConnectionsTimer();
 
     // close all pending transactions
     while (ent->mPendingQ.Length()) {
         trans = ent->mPendingQ[0];
 
         ent->mPendingQ.RemoveElementAt(0);
 
         trans->Close(NS_ERROR_ABORT);
@@ -577,25 +687,29 @@ nsHttpConnectionMgr::ShutdownPassCB(cons
     return PL_DHASH_REMOVE;
 }
 
 //-----------------------------------------------------------------------------
 
 bool
 nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent)
 {
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
         ent->mConnInfo->HashKey().get()));
 
-    PRInt32 i, count = ent->mPendingQ.Length();
+    if (gHttpHandler->IsSpdyEnabled())
+        ProcessSpdyPendingQ(ent);
+
+    PRUint32 i, count = ent->mPendingQ.Length();
     if (count > 0) {
         LOG(("  pending-count=%u\n", count));
         nsHttpTransaction *trans = nsnull;
         nsHttpConnection *conn = nsnull;
-        for (i=0; i<count; ++i) {
+        for (i = 0; i < count; ++i) {
             trans = ent->mPendingQ[i];
 
             // When this transaction has already established a half-open
             // connection, we want to prevent any duplicate half-open
             // connections from being established and bound to this
             // transaction. Allow only use of an idle persistent connection
             // (if found) for transactions referred by a half-open connection.
             bool alreadyHalfOpen = false;
@@ -604,16 +718,23 @@ nsHttpConnectionMgr::ProcessPendingQForE
                     alreadyHalfOpen = true;
                     break;
                 }
             }
 
             GetConnection(ent, trans, alreadyHalfOpen, &conn);
             if (conn)
                 break;
+
+            // Check to see if a pending transaction was dispatched with the
+            // coalesce logic
+            if (count != ent->mPendingQ.Length()) {
+                count = ent->mPendingQ.Length();
+                i = 0;
+            }
         }
         if (conn) {
             LOG(("  dispatching pending transaction...\n"));
 
             // remove pending transaction
             ent->mPendingQ.RemoveElementAt(i);
 
             nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn);
@@ -731,26 +852,33 @@ void
 nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent,
                                    nsHttpTransaction *trans,
                                    bool onlyReusedConnection,
                                    nsHttpConnection **result)
 {
     LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n",
         ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps())));
 
-    // First, see if an idle persistent connection may be reused instead of
+    // First, see if an existing connection can be used - either an idle
+    // persistent connection or an active spdy session may be reused instead of
     // establishing a new socket. We do not need to check the connection limits
     // yet as they govern the maximum number of open connections and reusing
     // an old connection never increases that.
 
     *result = nsnull;
 
     nsHttpConnection *conn = nsnull;
+    bool addConnToActiveList = true;
 
     if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
+
+        conn = GetSpdyPreferredConn(ent);
+        if (conn)
+            addConnToActiveList = false;
+
         // search the idle connection list. Each element in the list
         // has a reference, so if we remove it from the list into a local
         // ptr, that ptr now owns the reference
         while (!conn && (ent->mIdleConns.Length() > 0)) {
             conn = ent->mIdleConns[0];
             // we check if the connection can be reused before even checking if
             // it is a "matching" connection.
             if (!conn->CanReuse()) {
@@ -763,28 +891,46 @@ nsHttpConnectionMgr::GetConnection(nsCon
                 conn->EndIdleMonitoring();
             }
 
             ent->mIdleConns.RemoveElementAt(0);
             mNumIdleConns--;
 
             // If there are no idle connections left at all, we need to make
             // sure that we are not pruning dead connections anymore.
-            if (0 == mNumIdleConns)
-                StopPruneDeadConnectionsTimer();
+            ConditionallyStopPruneDeadConnectionsTimer();
         }
     }
 
     if (!conn) {
 
         // If the onlyReusedConnection parameter is TRUE, then GetConnection()
         // does not create new transports under any circumstances.
         if (onlyReusedConnection)
             return;
         
+        // If this is a possible Spdy connection we need to limit the number of
+        // connections outstanding to 1 while we wait for the spdy/https
+        // ReportSpdyConnection()
+    
+        if (gHttpHandler->IsSpdyEnabled() &&
+            ent->mConnInfo->UsingSSL() &&
+            !ent->mConnInfo->UsingHttpProxy())
+        {
+            nsConnectionEntry *preferred =
+                GetSpdyPreferred(ent->mDottedDecimalAddress);
+            if (preferred)
+                ent = preferred;
+
+            if ((!ent->mTestedSpdy || ent->mUsingSpdy) &&
+                (ent->mSpdyRedir || ent->mHalfOpens.Length() ||
+                 ent->mActiveConns.Length()))
+                return;
+        }
+        
         // Check if we need to purge an idle connection. Note that we may have
         // removed one above; if so, this will be a no-op. We do this before
         // checking the active connection limit to catch the case where we do
         // have an idle connection, but the purge timer hasn't fired yet.
         // XXX this just purges a random idle connection.  we should instead
         // enumerate the entire hash table to find the eldest idle connection.
         if (mNumIdleConns && mNumIdleConns + mNumActiveConns + 1 >= mMaxConns)
             mCT.Enumerate(PurgeExcessIdleConnectionsCB, this);
@@ -794,27 +940,34 @@ nsHttpConnectionMgr::GetConnection(nsCon
         // host or proxy. If we have, we're done.
         if (AtActiveConnectionLimit(ent, trans->Caps())) {
             LOG(("nsHttpConnectionMgr::GetConnection [ci = %s]"
                  "at active connection limit - will queue\n",
                  ent->mConnInfo->HashKey().get()));
             return;
         }
 
+        LOG(("nsHttpConnectionMgr::GetConnection Open Connection "
+             "%s %s ent=%p spdy=%d",
+             ent->mConnInfo->Host(), ent->mDottedDecimalAddress.get(),
+             ent, ent->mUsingSpdy));
+        
         nsresult rv = CreateTransport(ent, trans);
         if (NS_FAILED(rv))
             trans->Close(rv);
         return;
     }
 
-    // hold an owning ref to this connection
-    ent->mActiveConns.AppendElement(conn);
-    mNumActiveConns++;
+    if (addConnToActiveList) {
+        // hold an owning ref to this connection
+        ent->mActiveConns.AppendElement(conn);
+        mNumActiveConns++;
+    }
+    
     NS_ADDREF(conn);
-
     *result = conn;
 }
 
 void
 nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
                                    nsConnectionEntry *ent)
 {
     NS_ADDREF(conn);
@@ -845,39 +998,50 @@ nsHttpConnectionMgr::CreateTransport(nsC
     NS_ENSURE_SUCCESS(rv, rv);
 
     ent->mHalfOpens.AppendElement(sock);
     return NS_OK;
 }
 
 nsresult
 nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
-                                         nsAHttpTransaction *trans,
+                                         nsHttpTransaction *aTrans,
                                          PRUint8 caps,
                                          nsHttpConnection *conn)
 {
     LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n",
-        ent->mConnInfo->HashKey().get(), trans, caps, conn));
+        ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
+    nsresult rv;
+    
+    PRInt32 priority = aTrans->Priority();
+
+    if (conn->UsingSpdy()) {
+        rv = conn->Activate(aTrans, caps, priority);
+        NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
+        return rv;
+    }
 
     nsConnectionHandle *handle = new nsConnectionHandle(conn);
     if (!handle)
         return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(handle);
 
     nsHttpPipeline *pipeline = nsnull;
+    nsAHttpTransaction *trans = aTrans;
+
     if (conn->SupportsPipelining() && (caps & NS_HTTP_ALLOW_PIPELINING)) {
         LOG(("  looking to build pipeline...\n"));
         if (BuildPipeline(ent, trans, &pipeline))
             trans = pipeline;
     }
 
     // give the transaction the indirect reference to the connection.
     trans->SetConnection(handle);
 
-    nsresult rv = conn->Activate(trans, caps);
+    rv = conn->Activate(trans, caps, priority);
 
     if (NS_FAILED(rv)) {
         LOG(("  conn->Activate failed [rv=%x]\n", rv));
         ent->mActiveConns.RemoveElement(conn);
         mNumActiveConns--;
         // sever back references to connection, and do so without triggering
         // a call to ReclaimConnection ;-)
         trans->SetConnection(nsnull);
@@ -962,16 +1126,27 @@ nsHttpConnectionMgr::ProcessNewTransacti
         if (!clone)
             return NS_ERROR_OUT_OF_MEMORY;
         ent = new nsConnectionEntry(clone);
         if (!ent)
             return NS_ERROR_OUT_OF_MEMORY;
         mCT.Put(ci->HashKey(), ent);
     }
 
+    // SPDY coalescing of hostnames means we might redirect from this
+    // connection entry onto the preferred one.
+    nsConnectionEntry *preferredEntry =
+        GetSpdyPreferred(ent->mDottedDecimalAddress);
+    if (preferredEntry) {
+        LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+             "redirected via coalescing from %s to %s\n", trans,
+             ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host()));
+        ent = preferredEntry;
+    }
+
     // If we are doing a force reload then close out any existing conns
     // to this host so that changes in DNS, LBs, etc.. are reflected
     if (caps & NS_HTTP_CLEAR_KEEPALIVES)
         ClosePersistentConnections(ent);
 
     // Check if the transaction already has a sticky reference to a connection.
     // If so, then we can just use it directly by transferring its reference
     // to the new connection var instead of calling GetConnection() to search
@@ -1001,16 +1176,94 @@ nsHttpConnectionMgr::ProcessNewTransacti
     else {
         rv = DispatchTransaction(ent, trans, caps, conn);
         NS_RELEASE(conn);
     }
 
     return rv;
 }
 
+void
+nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
+{
+    nsHttpConnection *conn = GetSpdyPreferredConn(ent);
+    if (!conn)
+        return;
+
+    for (PRInt32 index = ent->mPendingQ.Length() - 1;
+         index >= 0 && conn->CanDirectlyActivate();
+         --index) {
+        nsHttpTransaction *trans = ent->mPendingQ[index];
+
+        if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
+            trans->Caps() & NS_HTTP_DISALLOW_SPDY)
+            continue;
+ 
+        ent->mPendingQ.RemoveElementAt(index);
+
+        nsresult rv2 = DispatchTransaction(ent, trans, trans->Caps(), conn);
+        NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv2), "Dispatch SPDY Transaction");
+        NS_RELEASE(trans);
+    }
+}
+
+PLDHashOperator
+nsHttpConnectionMgr::ProcessSpdyPendingQCB(const nsACString &key,
+                                           nsAutoPtr<nsConnectionEntry> &ent,
+                                           void *closure)
+{
+    nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
+    self->ProcessSpdyPendingQ(ent);
+    return PL_DHASH_NEXT;
+}
+
+void
+nsHttpConnectionMgr::ProcessSpdyPendingQ()
+{
+    mCT.Enumerate(ProcessSpdyPendingQCB, this);
+}
+
+nsHttpConnection *
+nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    NS_ABORT_IF_FALSE(ent, "no connection entry");
+
+    nsConnectionEntry *preferred = GetSpdyPreferred(ent->mDottedDecimalAddress);
+
+    // this entry is spdy-enabled if it is a redirect to another spdy host
+    if (preferred && preferred != ent) {
+        ent->mUsingSpdy = true;
+        ent->mSpdyRedir = true;
+    }
+    else {
+        ent->mSpdyRedir = false;
+        // don't clear usingSpdy, that will be reset in ReportSpdyConnection
+        // if it no longer applies
+    }
+
+    if (!preferred)
+        preferred = ent;
+    
+    nsHttpConnection *conn = nsnull;
+    
+    if (preferred->mUsingSpdy) {
+        for (PRUint32 index = 0;
+             index < preferred->mActiveConns.Length();
+             ++index) {
+            if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
+                conn = preferred->mActiveConns[index];
+                break;
+            }
+        }
+    }
+    
+    return conn;
+}
+
 //-----------------------------------------------------------------------------
 
 void
 nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *)
 {
     LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
 
     mCT.Enumerate(ShutdownPassCB, this);
@@ -1104,17 +1357,20 @@ nsHttpConnectionMgr::OnMsgProcessPending
 
 void
 nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *)
 {
     LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
 
     // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
     mTimeOfNextWakeUp = LL_MAXUINT;
-    if (mNumIdleConns > 0) 
+
+    // check canreuse() for all idle connections plus any active connections on
+    // connection entries that are using spdy.
+    if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
         mCT.Enumerate(PruneDeadConnectionsCB, this);
 }
 
 void
 nsHttpConnectionMgr::OnMsgClosePersistentConnections(PRInt32, void *)
 {
     LOG(("nsHttpConnectionMgr::OnMsgClosePersistentConnections\n"));
 
@@ -1140,16 +1396,20 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
 
     NS_ASSERTION(ent, "no connection entry");
     if (ent) {
         // 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)
+            conn->DontReuse();
+
         if (ent->mActiveConns.RemoveElement(conn)) {
             nsHttpConnection *temp = conn;
             NS_RELEASE(temp);
             mNumActiveConns--;
         }
 
         if (conn->CanReuse()) {
             LOG(("  adding connection to idle list\n"));
@@ -1168,17 +1428,17 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
             }
 
             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 idle connections, pruning dead
+            // time to live among the watched connections, pruning dead
             // connections needs to be done when it can't be reused anymore.
             PRUint32 timeToLive = conn->TimeToLive();
             if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
                 PruneDeadConnectionsAfter(timeToLive);
         }
         else {
             LOG(("  connection cannot be reused; closing connection\n"));
             // make sure the connection is closed and release our reference.
@@ -1218,16 +1478,25 @@ nsHttpConnectionMgr::OnMsgUpdateParam(PR
     case MAX_PIPELINED_REQUESTS:
         mMaxPipelinedRequests = value;
         break;
     default:
         NS_NOTREACHED("unexpected parameter name");
     }
 }
 
+// nsHttpConnectionMgr::nsConnectionEntry
+nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
+{
+    if (mSpdyPreferred)
+        gHttpHandler->ConnMgr()->RemoveSpdyPreferred(mDottedDecimalAddress);
+
+    NS_RELEASE(mConnInfo);
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpConnectionMgr::nsConnectionHandle
 
 nsHttpConnectionMgr::nsConnectionHandle::~nsConnectionHandle()
 {
     if (mConn) {
         gHttpHandler->ReclaimConnection(mConn);
         NS_RELEASE(mConn);
@@ -1241,25 +1510,25 @@ nsHttpConnectionMgr::nsConnectionHandle:
                                                             nsHttpRequestHead *req,
                                                             nsHttpResponseHead *resp,
                                                             bool *reset)
 {
     return mConn->OnHeadersAvailable(trans, req, resp, reset);
 }
 
 nsresult
-nsHttpConnectionMgr::nsConnectionHandle::ResumeSend()
+nsHttpConnectionMgr::nsConnectionHandle::ResumeSend(nsAHttpTransaction *caller)
 {
-    return mConn->ResumeSend();
+    return mConn->ResumeSend(caller);
 }
 
 nsresult
-nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv()
+nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv(nsAHttpTransaction *caller)
 {
-    return mConn->ResumeRecv();
+    return mConn->ResumeRecv(caller);
 }
 
 void
 nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
 {
     mConn->CloseTransaction(trans, reason);
 }
 
@@ -1411,20 +1680,45 @@ nsHalfOpenSocket::SetupStreams(nsISocket
         gHttpHandler->ConnMgr()->StartedConnect();
 
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
 {
-    nsresult rv = SetupStreams(getter_AddRefs(mSocketTransport),
-                               getter_AddRefs(mStreamIn),
-                               getter_AddRefs(mStreamOut),
-                               false);
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+    nsresult rv;
+    if (gHttpHandler->IsSpdyEnabled() &&
+        gHttpHandler->CoalesceSpdy() &&
+        mEnt->mConnInfo->UsingSSL() &&
+        !mEnt->mConnInfo->UsingHttpProxy() &&
+        !mEnt->mDidDNS) {
+
+        // Grab the DNS resolution for this host for un-sharding purposes
+        // This lookup will no doubt be coalesced by the DNS sub system as it
+        // overlaps with the SocketTransport
+        
+        nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID,
+                                                    &rv);
+        mEnt->mDidDNS = true;
+
+        if (NS_SUCCEEDED(rv)) {
+            nsCOMPtr<nsICancelable> cancelable;
+            dns->AsyncResolve(mEnt->mConnInfo->GetHost(), 0, this,
+                              NS_GetCurrentThread(),
+                              getter_AddRefs(cancelable));
+        }
+    }
+
+    rv = SetupStreams(getter_AddRefs(mSocketTransport),
+                      getter_AddRefs(mStreamIn),
+                      getter_AddRefs(mStreamOut),
+                      false);
     LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
          this, mEnt->mConnInfo->Host(), rv));
     if (NS_FAILED(rv)) {
         if (mStreamOut)
             mStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
         mStreamOut = nsnull;
         mStreamIn = nsnull;
         mSocketTransport = nsnull;
@@ -1446,21 +1740,38 @@ nsHttpConnectionMgr::nsHalfOpenSocket::S
             mBackupStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
         mBackupStreamOut = nsnull;
         mBackupStreamIn = nsnull;
         mBackupTransport = nsnull;
     }
     return rv;
 }
 
+
+NS_IMETHODIMP // nsIDNSListener
+nsHttpConnectionMgr::
+nsHalfOpenSocket::OnLookupComplete(nsICancelable *aRequest,
+                                   nsIDNSRecord *aRecord,
+                                   nsresult aStatus)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+    if (NS_SUCCEEDED(aStatus) && mEnt) {
+        aRecord->GetNextAddrAsString(mEnt->mDottedDecimalAddress);
+        gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
+    }
+    return NS_OK;
+}
+
 void
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
 {
     PRUint16 timeout = gHttpHandler->GetIdleSynTimeout();
     NS_ABORT_IF_FALSE(!mSynTimer, "timer already initd");
+    
     if (timeout) {
         // Setup the timer that will establish a backup socket
         // if we do not get a writable event on the main one.
         // We do this because a lost SYN takes a very long time
         // to repair at the TCP level.
         //
         // Failure to setup the timer is something we can live with,
         // so don't return an error in that case.
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -40,19 +40,21 @@
 #define nsHttpConnectionMgr_h__
 
 #include "nsHttpConnectionInfo.h"
 #include "nsHttpConnection.h"
 #include "nsHttpTransaction.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "nsISocketTransportService.h"
+#include "nsIDNSListener.h"
 
 #include "nsIObserver.h"
 #include "nsITimer.h"
 
 class nsHttpPipeline;
 
 //-----------------------------------------------------------------------------
 
@@ -91,18 +93,19 @@ public:
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called on any thread.
     //-------------------------------------------------------------------------
 
     // Schedules next pruning of dead connection to happen after
     // given time.
     void PruneDeadConnectionsAfter(PRUint32 time);
 
-    // Stops timer scheduled for next pruning of dead connections.
-    void StopPruneDeadConnectionsTimer();
+    // Stops timer scheduled for next pruning of dead connections if
+    // there are no more idle connections or active spdy ones
+    void ConditionallyStopPruneDeadConnectionsTimer();
 
     // adds a transaction to the list of managed transactions.
     nsresult AddTransaction(nsHttpTransaction *, PRInt32 priority);
 
     // called to reschedule the given transaction.  it must already have been
     // added to the connection manager via AddTransaction.
     nsresult RescheduleTransaction(nsHttpTransaction *, PRInt32 priority);
 
@@ -142,40 +145,65 @@ public:
     // preference to the specified connection.
     nsresult ProcessPendingQ(nsHttpConnectionInfo *);
 
     // This is used to force an idle connection to be closed and removed from
     // the idle connection list. It is called when the idle connection detects
     // that the network peer has closed the transport.
     nsresult CloseIdleConnection(nsHttpConnection *);
 
+    // The connection manager needs to know when a normal HTTP connection has been
+    // upgraded to SPDY because the dispatch and idle semantics are a little
+    // bit different.
+    void ReportSpdyConnection(nsHttpConnection *, bool usingSpdy);
+
 private:
     virtual ~nsHttpConnectionMgr();
     class nsHalfOpenSocket;
     
     // nsConnectionEntry
     //
     // mCT maps connection info hash key to nsConnectionEntry object, which
     // contains list of active and idle connections as well as the list of
     // pending transactions.
     //
     struct nsConnectionEntry
     {
         nsConnectionEntry(nsHttpConnectionInfo *ci)
-            : mConnInfo(ci)
+          : mConnInfo(ci),
+            mUsingSpdy(false),
+            mTestedSpdy(false),
+            mSpdyRedir(false),
+            mDidDNS(false),
+            mSpdyPreferred(false)
         {
             NS_ADDREF(mConnInfo);
         }
-       ~nsConnectionEntry() { NS_RELEASE(mConnInfo); }
+        ~nsConnectionEntry();
 
         nsHttpConnectionInfo        *mConnInfo;
         nsTArray<nsHttpTransaction*> mPendingQ;    // pending transaction queue
         nsTArray<nsHttpConnection*>  mActiveConns; // active connections
         nsTArray<nsHttpConnection*>  mIdleConns;   // idle persistent connections
         nsTArray<nsHalfOpenSocket*>  mHalfOpens;
+
+        // Spdy sometimes resolves the address in the socket manager in order
+        // to re-coalesce sharded HTTP hosts.
+        //
+        // When a set of hosts are coalesced together one of them is marked
+        // mSpdyPreferred, and the others are marked mSpdyRedir. The mapping is
+        // maintained in the conection manager mSpdyPreferred hash.
+        //
+        nsCString mDottedDecimalAddress;
+
+        bool mUsingSpdy;
+        bool mTestedSpdy;
+        bool mSpdyRedir;
+        bool mDidDNS;
+        bool mSpdyPreferred;
     };
 
     // nsConnectionHandle
     //
     // thin wrapper around a real connection, used to keep track of references
     // to the connection to determine when the connection may be reused.  the
     // transaction (or pipeline) owns a reference to this handle.  this extra
     // layer of indirection greatly simplifies consumer code, avoiding the
@@ -195,24 +223,26 @@ private:
     };
 
     // nsHalfOpenSocket is used to hold the state of an opening TCP socket
     // while we wait for it to establish and bind it to a connection
 
     class nsHalfOpenSocket : public nsIOutputStreamCallback,
                              public nsITransportEventSink,
                              public nsIInterfaceRequestor,
-                             public nsITimerCallback
+                             public nsITimerCallback,
+                             public nsIDNSListener
     {
     public:
         NS_DECL_ISUPPORTS
         NS_DECL_NSIOUTPUTSTREAMCALLBACK
         NS_DECL_NSITRANSPORTEVENTSINK
         NS_DECL_NSIINTERFACEREQUESTOR
         NS_DECL_NSITIMERCALLBACK
+        NS_DECL_NSIDNSLISTENER
 
         nsHalfOpenSocket(nsConnectionEntry *ent,
                          nsHttpTransaction *trans);
         ~nsHalfOpenSocket();
         
         nsresult SetupStreams(nsISocketTransport **,
                               nsIAsyncInputStream **,
                               nsIAsyncOutputStream **,
@@ -268,27 +298,41 @@ private:
     static PLDHashOperator PruneDeadConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
     static PLDHashOperator ShutdownPassCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
     static PLDHashOperator PurgeExcessIdleConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
     static PLDHashOperator ClosePersistentConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
     bool     ProcessPendingQForEntry(nsConnectionEntry *);
     bool     AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps);
     void     GetConnection(nsConnectionEntry *, nsHttpTransaction *,
                            bool, nsHttpConnection **);
-    nsresult DispatchTransaction(nsConnectionEntry *, nsAHttpTransaction *,
+    nsresult DispatchTransaction(nsConnectionEntry *, nsHttpTransaction *,
                                  PRUint8 caps, nsHttpConnection *);
     bool     BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **);
     nsresult ProcessNewTransaction(nsHttpTransaction *);
     nsresult EnsureSocketThreadTargetIfOnline();
     void     ClosePersistentConnections(nsConnectionEntry *ent);
     nsresult CreateTransport(nsConnectionEntry *, nsHttpTransaction *);
     void     AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
     void     StartedConnect();
     void     RecvdConnect();
-    
+
+    // Manage the preferred spdy connection entry for this address
+    nsConnectionEntry *GetSpdyPreferred(nsACString &aDottedDecimal);
+    void               SetSpdyPreferred(nsACString &aDottedDecimal,
+                                        nsConnectionEntry *ent);
+    void               RemoveSpdyPreferred(nsACString &aDottedDecimal);
+    nsHttpConnection  *GetSpdyPreferredConn(nsConnectionEntry *ent);
+    nsDataHashtable<nsCStringHashKey, nsConnectionEntry *>   mSpdyPreferredHash;
+
+    void               ProcessSpdyPendingQ(nsConnectionEntry *ent);
+    void               ProcessSpdyPendingQ();
+    static PLDHashOperator ProcessSpdyPendingQCB(
+        const nsACString &key, nsAutoPtr<nsConnectionEntry> &ent,
+        void *closure);
+
     // message handlers have this signature
     typedef void (nsHttpConnectionMgr:: *nsConnEventHandler)(PRInt32, void *);
 
     // nsConnEvent
     //
     // subclass of nsRunnable used to marshall events to the socket transport
     // thread.  this class is used to implement PostEvent.
     //
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -169,16 +169,17 @@ nsHttpHandler::nsHttpHandler()
     : mConnMgr(nsnull)
     , mHttpVersion(NS_HTTP_VERSION_1_1)
     , mProxyHttpVersion(NS_HTTP_VERSION_1_1)
     , mCapabilities(NS_HTTP_ALLOW_KEEPALIVE)
     , mProxyCapabilities(NS_HTTP_ALLOW_KEEPALIVE)
     , mReferrerLevel(0xff) // by default we always send a referrer
     , mFastFallbackToIPv4(false)
     , mIdleTimeout(10)
+    , mSpdyTimeout(180)
     , mMaxRequestAttempts(10)
     , mMaxRequestDelay(10)
     , mIdleSynTimeout(250)
     , mMaxConnections(24)
     , mMaxConnectionsPerServer(8)
     , mMaxPersistentConnectionsPerServer(2)
     , mMaxPersistentConnectionsPerProxy(4)
     , mMaxPipelinedRequests(2)
@@ -193,16 +194,18 @@ nsHttpHandler::nsHttpHandler()
     , mLegacyAppVersion("5.0")
     , mProduct("Gecko")
     , mUserAgentIsDirty(true)
     , mUseCache(true)
     , mPromptTempRedirect(true)
     , mSendSecureXSiteReferrer(true)
     , mEnablePersistentHttpsCaching(false)
     , mDoNotTrackEnabled(false)
+    , mEnableSpdy(false)
+    , mCoalesceSpdy(true)
 {
 #if defined(PR_LOGGING)
     gHttpLog = PR_NewLogModule("nsHttp");
 #endif
 
     LOG(("Creating nsHttpHandler [this=%x].\n", this));
 
     NS_ASSERTION(!gHttpHandler, "HTTP handler already created!");
@@ -1079,16 +1082,34 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     }
 
     if (PREF_CHANGED(HTTP_PREF("phishy-userpass-length"))) {
         rv = prefs->GetIntPref(HTTP_PREF("phishy-userpass-length"), &val);
         if (NS_SUCCEEDED(rv))
             mPhishyUserPassLength = (PRUint8) clamped(val, 0, 0xff);
     }
 
+    if (PREF_CHANGED(HTTP_PREF("spdy.enabled"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled"), &cVar);
+        if (NS_SUCCEEDED(rv))
+            mEnableSpdy = cVar;
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("spdy.coalesce-hostnames"), &cVar);
+        if (NS_SUCCEEDED(rv))
+            mCoalesceSpdy = cVar;
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("spdy.timeout"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("spdy.timeout"), &val);
+        if (NS_SUCCEEDED(rv))
+            mSpdyTimeout = (PRUint16) clamped(val, 1, 0xffff);
+    }
+
     //
     // INTL options
     //
 
     if (PREF_CHANGED(INTL_ACCEPT_LANGUAGES)) {
         nsCOMPtr<nsIPrefLocalizedString> pls;
         prefs->GetComplexValue(INTL_ACCEPT_LANGUAGES,
                                 NS_GET_IID(nsIPrefLocalizedString),
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -96,27 +96,31 @@ public:
     const nsAFlatCString &UserAgent();
 
     nsHttpVersion  HttpVersion()             { return mHttpVersion; }
     nsHttpVersion  ProxyHttpVersion()        { return mProxyHttpVersion; }
     PRUint8        ReferrerLevel()           { return mReferrerLevel; }
     bool           SendSecureXSiteReferrer() { return mSendSecureXSiteReferrer; }
     PRUint8        RedirectionLimit()        { return mRedirectionLimit; }
     PRUint16       IdleTimeout()             { return mIdleTimeout; }
+    PRUint16       SpdyTimeout()             { return mSpdyTimeout; }
     PRUint16       MaxRequestAttempts()      { return mMaxRequestAttempts; }
     const char    *DefaultSocketType()       { return mDefaultSocketType.get(); /* ok to return null */ }
     nsIIDNService *IDNConverter()            { return mIDNConverter; }
     PRUint32       PhishyUserPassLength()    { return mPhishyUserPassLength; }
     PRUint8        GetQoSBits()              { return mQoSBits; }
     PRUint16       GetIdleSynTimeout()       { return mIdleSynTimeout; }
     bool           FastFallbackToIPv4()      { return mFastFallbackToIPv4; }
     PRUint32       MaxSocketCount();
 
     bool           IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; }
 
+    bool           IsSpdyEnabled() { return mEnableSpdy; }
+    bool           CoalesceSpdy() { return mCoalesceSpdy; }
+
     bool           PromptTempRedirect()      { return mPromptTempRedirect; }
 
     nsHttpAuthCache     *AuthCache() { return &mAuthCache; }
     nsHttpConnectionMgr *ConnMgr()   { return mConnMgr; }
 
     // cache support
     nsresult GetCacheSession(nsCacheStoragePolicy, nsICacheSession **);
     PRUint32 GenerateUniqueID() { return ++mLastUniqueID; }
@@ -259,16 +263,17 @@ private:
     PRUint8  mProxyHttpVersion;
     PRUint8  mCapabilities;
     PRUint8  mProxyCapabilities;
     PRUint8  mReferrerLevel;
 
     bool mFastFallbackToIPv4;
 
     PRUint16 mIdleTimeout;
+    PRUint16 mSpdyTimeout;
     PRUint16 mMaxRequestAttempts;
     PRUint16 mMaxRequestDelay;
     PRUint16 mIdleSynTimeout;
 
     PRUint16 mMaxConnections;
     PRUint8  mMaxConnectionsPerServer;
     PRUint8  mMaxPersistentConnectionsPerServer;
     PRUint8  mMaxPersistentConnectionsPerProxy;
@@ -326,16 +331,20 @@ private:
     // if true allow referrer headers between secure non-matching hosts
     bool           mSendSecureXSiteReferrer;
 
     // Persistent HTTPS caching flag
     bool           mEnablePersistentHttpsCaching;
 
     // For broadcasting the preference to not be tracked
     bool           mDoNotTrackEnabled;
+    
+    // Try to use SPDY instead of HTTP/1.1 over SSL
+    bool           mEnableSpdy;
+    bool           mCoalesceSpdy;
 };
 
 //-----------------------------------------------------------------------------
 
 extern nsHttpHandler *gHttpHandler;
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
--- a/netwerk/protocol/http/nsHttpPipeline.cpp
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -123,17 +123,17 @@ nsHttpPipeline::AddTransaction(nsAHttpTr
 
     NS_ADDREF(trans);
     mRequestQ.AppendElement(trans);
 
     if (mConnection) {
         trans->SetConnection(this);
 
         if (mRequestQ.Length() == 1)
-            mConnection->ResumeSend();
+            mConnection->ResumeSend(trans);
     }
 
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpPipeline::nsISupports
 //-----------------------------------------------------------------------------
@@ -162,29 +162,29 @@ nsHttpPipeline::OnHeadersAvailable(nsAHt
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ASSERTION(mConnection, "no connection");
 
     // trans has now received its response headers; forward to the real connection
     return mConnection->OnHeadersAvailable(trans, requestHead, responseHead, reset);
 }
 
 nsresult
-nsHttpPipeline::ResumeSend()
+nsHttpPipeline::ResumeSend(nsAHttpTransaction *trans)
 {
     if (mConnection)
-        return mConnection->ResumeSend();
+        return mConnection->ResumeSend(trans);
     return NS_ERROR_UNEXPECTED;
 }
 
 nsresult
-nsHttpPipeline::ResumeRecv()
+nsHttpPipeline::ResumeRecv(nsAHttpTransaction *trans)
 {
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ASSERTION(mConnection, "no connection");
-    return mConnection->ResumeRecv();
+    return mConnection->ResumeRecv(trans);
 }
 
 void
 nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
 {
     LOG(("nsHttpPipeline::CloseTransaction [this=%x trans=%x reason=%x]\n",
         this, trans, reason));
 
@@ -369,16 +369,25 @@ nsHttpPipeline::SetConnection(nsAHttpCon
 
     NS_IF_ADDREF(mConnection = conn);
 
     PRInt32 i, count = mRequestQ.Length();
     for (i=0; i<count; ++i)
         Request(i)->SetConnection(this);
 }
 
+nsAHttpConnection *
+nsHttpPipeline::Connection()
+{
+    LOG(("nsHttpPipeline::Connection [this=%x conn=%x]\n", this, mConnection));
+
+    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    return mConnection;
+}
+
 void
 nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result,
                                      nsIEventTarget        **target)
 {
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     // return security callbacks from first request
     nsAHttpTransaction *trans = Request(0);
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -300,16 +300,22 @@ nsHttpTransaction::Init(PRUint8 caps,
                      nsIOService::gDefaultSegmentCount,
                      nsIOService::gBufferCache);
     if (NS_FAILED(rv)) return rv;
 
     NS_ADDREF(*responseBody = mPipeIn);
     return NS_OK;
 }
 
+nsAHttpConnection *
+nsHttpTransaction::Connection()
+{
+    return mConnection;
+}
+
 nsHttpResponseHead *
 nsHttpTransaction::TakeResponseHead()
 {
     if (!mHaveAllHeaders) {
         NS_WARNING("response headers not available or incomplete");
         return nsnull;
     }
 
@@ -1282,30 +1288,30 @@ NS_IMPL_THREADSAFE_QUERY_INTERFACE2(nsHt
 // nsHttpTransaction::nsIInputStreamCallback
 //-----------------------------------------------------------------------------
 
 // called on the socket thread
 NS_IMETHODIMP
 nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out)
 {
     if (mConnection) {
-        nsresult rv = mConnection->ResumeSend();
+        nsresult rv = mConnection->ResumeSend(this);
         if (NS_FAILED(rv))
             NS_ERROR("ResumeSend failed");
     }
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpTransaction::nsIOutputStreamCallback
 //-----------------------------------------------------------------------------
 
 // called on the socket thread
 NS_IMETHODIMP
 nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out)
 {
     if (mConnection) {
-        nsresult rv = mConnection->ResumeRecv();
+        nsresult rv = mConnection->ResumeRecv(this);
         if (NS_FAILED(rv))
             NS_ERROR("ResumeRecv failed");
     }
     return NS_OK;
 }
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -115,28 +115,27 @@ public:
     // attributes
     PRUint8                Caps()           { return mCaps; }
     nsHttpConnectionInfo  *ConnectionInfo() { return mConnInfo; }
     nsHttpResponseHead    *ResponseHead()   { return mHaveAllHeaders ? mResponseHead : nsnull; }
     nsISupports           *SecurityInfo()   { return mSecurityInfo; }
 
     nsIInterfaceRequestor *Callbacks()      { return mCallbacks; } 
     nsIEventTarget        *ConsumerTarget() { return mConsumerTarget; }
-    nsAHttpConnection     *Connection()     { return mConnection; }
 
     // Called to take ownership of the response headers; the transaction
     // 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      SSLConnectFailed() { return mSSLConnectFailed; }
 
-    // These methods may only be used by the connection manager.
+    // SetPriority() may only be used by the connection manager.
     void    SetPriority(PRInt32 priority) { mPriority = priority; }
     PRInt32    Priority()                 { return mPriority; }
 
     const TimingStruct& Timings() const { return mTimings; }
 
 private:
     nsresult Restart();
     char    *LocateHttpStart(char *buf, PRUint32 len,
--- a/netwerk/socket/nsISSLSocketControl.idl
+++ b/netwerk/socket/nsISSLSocketControl.idl
@@ -37,16 +37,42 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIInterfaceRequestor;
 
-[scriptable, uuid(a092097c-8386-4f1b-97b1-90eb70008c2d)]
+%{C++
+#include "nsTArray.h"
+class nsCString;
+%}
+[ref] native nsCStringTArrayRef(nsTArray<nsCString>);
+
+[scriptable, uuid(87fe5d5a-7f72-48d7-8a13-ef992e0cad43)]
 interface nsISSLSocketControl : nsISupports {
     attribute nsIInterfaceRequestor     notificationCallbacks;
 
     void proxyStartSSL();
     void StartTLS();
+
+    /* NPN (Next Protocol Negotiation) is a mechanism for
+       negotiating the protocol to be spoken inside the SSL
+       tunnel during the SSL handshake. The NPNList is the list
+       of offered client side protocols. setNPNList() needs to
+       be called before any data is read or written (including the
+       handshake to be setup correctly. */
+
+    [noscript] void setNPNList(in nsCStringTArrayRef aNPNList);
+
+    /* negotiatedNPN is '' if no NPN list was provided by the client,
+     * or if the server did not select any protocol choice from that
+     * list. That also includes the case where the server does not
+     * implement NPN.
+     *
+     * If negotiatedNPN is read before NPN has progressed to the point
+     * where this information is available NS_ERROR_NOT_CONNECTED is
+     * raised.
+     */
+    readonly attribute ACString negotiatedNPN;
 };
 
--- a/security/manager/ssl/src/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/src/nsNSSCallbacks.cpp
@@ -937,16 +937,29 @@ void PR_CALLBACK HandshakeCallback(PRFil
         }
       }
     }
 
     status->mHaveKeyLengthAndCipher = true;
     status->mKeyLength = keyLength;
     status->mSecretKeyLength = encryptBits;
     status->mCipherName.Assign(cipherName);
+
+    // Get the NPN value. Do this on the stack and copy it into
+    // a string rather than preallocating the string because right
+    // now we expect NPN to fail more often than it succeeds.
+    SSLNextProtoState state;
+    unsigned char npnbuf[256];
+    unsigned int npnlen;
+    
+    if (SSL_GetNextProto(fd, &state, npnbuf, &npnlen, 256) == SECSuccess &&
+        state == SSL_NEXT_PROTO_NEGOTIATED)
+      infoObject->SetNegotiatedNPN(reinterpret_cast<char *>(npnbuf), npnlen);
+    else
+      infoObject->SetNegotiatedNPN(nsnull, 0);
   }
 
   PORT_Free(cipherName);
   PR_FREEIF(certOrgName);
   PR_Free(signer);
 }
 
 struct OCSPDefaultResponders {
--- a/security/manager/ssl/src/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/src/nsNSSIOLayer.cpp
@@ -164,17 +164,18 @@ nsNSSSocketInfo::nsNSSSocketInfo()
     mForSTARTTLS(false),
     mHandshakePending(true),
     mHasCleartextPhase(false),
     mHandshakeInProgress(false),
     mAllowTLSIntoleranceTimeout(true),
     mRememberClientAuthCertificate(false),
     mHandshakeStartTime(0),
     mPort(0),
-    mIsCertIssuerBlacklisted(false)
+    mIsCertIssuerBlacklisted(false),
+    mNPNCompleted(false)
 {
 }
 
 nsNSSSocketInfo::~nsNSSSocketInfo()
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return;
@@ -430,16 +431,36 @@ nsNSSSocketInfo::GetErrorMessage(PRUnich
 
   nsresult rv = formatErrorMessage(lock);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aText = ToNewUnicode(mErrorMessageCached);
   return *aText != nsnull ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
 
+void
+nsNSSSocketInfo::SetNegotiatedNPN(const char *value, PRUint32 length)
+{
+  if (!value)
+    mNegotiatedNPN.Truncate();
+  else
+    mNegotiatedNPN.Assign(value, length);
+  mNPNCompleted = true;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetNegotiatedNPN(nsACString &aNegotiatedNPN)
+{
+  if (!mNPNCompleted)
+    return NS_ERROR_NOT_CONNECTED;
+
+  aNegotiatedNPN = mNegotiatedNPN;
+  return NS_OK;
+}
+
 static nsresult
 formatPlainErrorMessage(nsXPIDLCString const & host, PRInt32 port,
                         PRErrorCode err, nsString &returnedMessage);
 
 static nsresult
 formatOverridableCertErrorMessage(nsISSLStatus & sslStatus,
                                   PRErrorCode errorCodeToReport, 
                                   const nsXPIDLCString & host, PRInt32 port,
@@ -521,16 +542,46 @@ nsNSSSocketInfo::ProxyStartSSL()
 }
 
 NS_IMETHODIMP
 nsNSSSocketInfo::StartTLS()
 {
   return ActivateSSL();
 }
 
+NS_IMETHODIMP
+nsNSSSocketInfo::SetNPNList(nsTArray<nsCString> &protocolArray)
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown())
+    return NS_ERROR_NOT_AVAILABLE;
+  if (!mFd)
+    return NS_ERROR_FAILURE;
+
+  // the npn list is a concatenated list of 8 bit byte strings.
+  nsCString npnList;
+
+  for (PRUint32 index = 0; index < protocolArray.Length(); ++index) {
+    if (protocolArray[index].IsEmpty() ||
+        protocolArray[index].Length() > 255)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    npnList.Append(protocolArray[index].Length());
+    npnList.Append(protocolArray[index]);
+  }
+  
+  if (SSL_SetNextProtoNego(
+        mFd,
+        reinterpret_cast<const unsigned char *>(npnList.get()),
+        npnList.Length()) != SECSuccess)
+    return NS_ERROR_FAILURE;
+
+  return NS_OK;
+}
+
 static NS_DEFINE_CID(kNSSCertificateCID, NS_X509CERT_CID);
 #define NSSSOCKETINFOMAGIC { 0xa9863a23, 0x26b8, 0x4a9c, \
   { 0x83, 0xf1, 0xe9, 0xda, 0xdb, 0x36, 0xb8, 0x30 } }
 static NS_DEFINE_CID(kNSSSocketInfoMagic, NSSSOCKETINFOMAGIC);
 
 NS_IMETHODIMP
 nsNSSSocketInfo::Write(nsIObjectOutputStream* stream) {
   stream->WriteID(kNSSSocketInfoMagic);
--- a/security/manager/ssl/src/nsNSSIOLayer.h
+++ b/security/manager/ssl/src/nsNSSIOLayer.h
@@ -148,16 +148,18 @@ public:
   
   bool IsCertIssuerBlacklisted() const {
     return mIsCertIssuerBlacklisted;
   }
   void SetCertIssuerBlacklisted() {
     mIsCertIssuerBlacklisted = true;
   }
 
+  void SetNegotiatedNPN(const char *value, PRUint32 length);
+
   // XXX: These are only used on for diagnostic purposes
   enum CertVerificationState {
     before_cert_verification,
     waiting_for_cert_verification,
     after_cert_verification
   };
   void SetCertVerificationWaiting();
   // Use errorCode == 0 to indicate success; in that case, errorMessageType is
@@ -203,16 +205,19 @@ protected:
   nsXPIDLCString mHostName;
   PRErrorCode mIsCertIssuerBlacklisted;
 
   /* SSL Status */
   nsRefPtr<nsSSLStatus> mSSLStatus;
 
   nsresult ActivateSSL();
 
+  nsCString mNegotiatedNPN;
+  bool      mNPNCompleted;
+
 private:
   virtual void virtualDestroyNSSReference();
   void destructorSafeDestroyNSSReference();
 };
 
 class nsCStringHashSet;
 
 class nsSSLStatus;
--- a/toolkit/components/telemetry/TelemetryHistograms.h
+++ b/toolkit/components/telemetry/TelemetryHistograms.h
@@ -157,16 +157,34 @@ HISTOGRAM(HTTP_REQUEST_PER_PAGE_FROM_CAC
   _HTTP_HIST(HTTP_##prefix##_REVALIDATION, labelprefix "Positive cache validation time (ms)") \
   _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD, labelprefix "Overall load time - all (ms)") \
   _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD_CACHED, labelprefix "Overall load time - cache hits (ms)") \
   _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD_NET, labelprefix "Overall load time - network (ms)") \
 
 HTTP_HISTOGRAMS(PAGE, "page: ")
 HTTP_HISTOGRAMS(SUB, "subitem: ")
 
+HISTOGRAM(SPDY_PARALLEL_STREAMS, 1, 1000, 50, EXPONENTIAL, "SPDY: Streams concurrent active per connection")
+HISTOGRAM(SPDY_TOTAL_STREAMS, 1, 100000, 50, EXPONENTIAL,  "SPDY: Streams created per connection")
+HISTOGRAM(SPDY_SERVER_INITIATED_STREAMS, 1, 100000, 250, EXPONENTIAL,  "SPDY: Streams recevied per connection")
+HISTOGRAM(SPDY_CHUNK_RECVD, 1, 1000, 100, EXPONENTIAL,  "SPDY: Recvd Chunk Size (rounded to KB)")
+HISTOGRAM(SPDY_SYN_SIZE, 20, 20000, 50, EXPONENTIAL,  "SPDY: SYN Frame Header Size")
+HISTOGRAM(SPDY_SYN_RATIO, 1, 99, 20, LINEAR,  "SPDY: SYN Frame Header Ratio (lower better)")
+HISTOGRAM(SPDY_SYN_REPLY_SIZE, 16, 20000, 50, EXPONENTIAL,  "SPDY: SYN Reply Header Size")
+HISTOGRAM(SPDY_SYN_REPLY_RATIO, 1, 99, 20, LINEAR,  "SPDY: SYN Reply Header Ratio (lower better)")
+HISTOGRAM(SPDY_NPN_CONNECT, 0, 1, 2, BOOLEAN,  "SPDY: NPN Negotiated")
+
+HISTOGRAM(SPDY_SETTINGS_UL_BW, 1, 10000, 100, EXPONENTIAL,  "SPDY: Settings Upload Bandwidth")
+HISTOGRAM(SPDY_SETTINGS_DL_BW, 1, 10000, 100, EXPONENTIAL,  "SPDY: Settings Download Bandwidth")
+HISTOGRAM(SPDY_SETTINGS_RTT, 1, 1000, 100, EXPONENTIAL,  "SPDY: Settings RTT")
+HISTOGRAM(SPDY_SETTINGS_MAX_STREAMS, 1, 5000, 100, EXPONENTIAL,  "SPDY: Settings Max Streams parameter")
+HISTOGRAM(SPDY_SETTINGS_CWND, 1, 500, 50, EXPONENTIAL,  "SPDY: Settings CWND (packets)")
+HISTOGRAM(SPDY_SETTINGS_RETRANS, 1, 100, 50, EXPONENTIAL,  "SPDY: Retransmission Rate")
+HISTOGRAM(SPDY_SETTINGS_IW, 1, 1000, 50, EXPONENTIAL,  "SPDY: Settings IW (rounded to KB)")
+
 #undef _HTTP_HIST
 #undef HTTP_HISTOGRAMS
 
 HISTOGRAM(HTTP_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM(HTTP_DISK_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Disk Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM(HTTP_MEMORY_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Memory Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM(HTTP_OFFLINE_CACHE_DISPOSITION, 1, 5, 5, LINEAR, "HTTP Offline Cache Hit, Reval, Failed-Reval, Miss")
 HISTOGRAM(CACHE_DEVICE_SEARCH, 1, 100, 100, LINEAR, "Time to search cache (ms)")