Backed out changeset 379093669b39 (bug 1340655) for warning as err.or at nsHttpConnection.cpp(875). r=backout on a CLOSED TREE
authorSebastian Hengst <archaeopteryx@coole-files.de>
Wed, 01 Mar 2017 20:20:57 +0100
changeset 491185 665c5023212598462d33d7addc1e14d7c7c10ee3
parent 491184 a10bbbacbbdfc836ee3665b94abc8a80e7c92678
child 491186 52a406607871931b102f6dc76ce5eaff75cd8914
push id47336
push userbmo:eoger@fastmail.com
push dateWed, 01 Mar 2017 21:01:30 +0000
reviewersbackout
bugs1340655
milestone54.0a1
backs out379093669b392384fffab147a1e7b42e835bceee
Backed out changeset 379093669b39 (bug 1340655) for warning as err.or at nsHttpConnection.cpp(875). r=backout on a CLOSED TREE
modules/libpref/init/all.js
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/protocol/http/ConnectionDiagnostics.cpp
netwerk/protocol/http/Http2Push.cpp
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/NullHttpTransaction.cpp
netwerk/protocol/http/TunnelUtils.cpp
netwerk/protocol/http/TunnelUtils.h
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.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/nsHttpPipeline.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
netwerk/protocol/http/nsIHttpChannel.idl
netwerk/test/unit/test_assoc.js
netwerk/test/unit/xpcshell.ini
toolkit/components/telemetry/Histograms.json
toolkit/components/telemetry/histogram-whitelists.json
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1514,16 +1514,37 @@ pref("network.http.enablePerElementRefer
 pref("network.http.redirection-limit", 20);
 
 // Enable http compression: comment this out in case of problems with 1.1
 // NOTE: support for "compress" has been disabled per bug 196406.
 // NOTE: separate values with comma+space (", "): see bug 576033
 pref("network.http.accept-encoding", "gzip, deflate");
 pref("network.http.accept-encoding.secure", "gzip, deflate, br");
 
+pref("network.http.pipelining"      , false);
+pref("network.http.pipelining.ssl"  , false); // disable pipelining over SSL
+pref("network.http.pipelining.abtest", false);
+pref("network.http.proxy.pipelining", false);
+
+// Max number of requests in the pipeline
+pref("network.http.pipelining.maxrequests" , 32);
+
+// An optimistic request is one pipelined when policy might allow a new
+// connection instead
+pref("network.http.pipelining.max-optimistic-requests" , 4);
+
+pref("network.http.pipelining.aggressive", false);
+pref("network.http.pipelining.maxsize" , 300000);
+pref("network.http.pipelining.reschedule-on-timeout", true);
+pref("network.http.pipelining.reschedule-timeout", 1500);
+
+// The read-timeout is a ms timer that causes the transaction to be completely
+// restarted without pipelining.
+pref("network.http.pipelining.read-timeout", 30000);
+
 // Prompt for redirects resulting in unsafe HTTP requests
 pref("network.http.prompt-temp-redirect", false);
 
 // If true generate CORRUPTED_CONTENT errors for entities that
 // contain an invalid Assoc-Req response header
 pref("network.http.assoc-req.enforce", false);
 
 // On networks deploying QoS, it is recommended that these be lockpref()'d,
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -103,16 +103,17 @@ struct HttpChannelOpenArgs
   uint32_t                    loadFlags;
   RequestHeaderTuples         requestHeaders;
   nsCString                   requestMethod;
   OptionalIPCStream           uploadStream;
   bool                        uploadStreamHasHeaders;
   int16_t                     priority;
   uint32_t                    classOfService;
   uint8_t                     redirectionLimit;
+  bool                        allowPipelining;
   bool                        allowSTS;
   uint32_t                    thirdPartyFlags;
   bool                        resumeAt;
   uint64_t                    startPos;
   nsCString                   entityID;
   bool                        chooseApplicationCache;
   nsCString                   appCacheClientID;
   bool                        allowSpdy;
--- a/netwerk/protocol/http/ConnectionDiagnostics.cpp
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -60,18 +60,24 @@ nsHttpConnectionMgr::OnMsgPrintDiagnosti
     mLogData.AppendPrintf("   Idle Conns Length = %" PRIuSIZE "\n",
                           ent->mIdleConns.Length());
     mLogData.AppendPrintf("   Half Opens Length = %" PRIuSIZE "\n",
                           ent->mHalfOpens.Length());
     mLogData.AppendPrintf("   Coalescing Keys Length = %" PRIuSIZE "\n",
                           ent->mCoalescingKeys.Length());
     mLogData.AppendPrintf("   Spdy using = %d, preferred = %d\n",
                           ent->mUsingSpdy, ent->mInPreferredHash);
+    mLogData.AppendPrintf("   pipelinestate = %d penalty = %d\n",
+                          ent->mPipelineState, ent->mPipeliningPenalty);
 
     uint32_t i;
+    for (i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
+      mLogData.AppendPrintf("   pipeline per class penalty 0x%x %d\n",
+                            i, ent->mPipeliningClassPenalty[i]);
+    }
     for (i = 0; i < ent->mActiveConns.Length(); ++i) {
       mLogData.AppendPrintf("   :: Active Connection #%u\n", i);
       ent->mActiveConns[i]->PrintDiagnostics(mLogData);
     }
     for (i = 0; i < ent->mIdleConns.Length(); ++i) {
       mLogData.AppendPrintf("   :: Idle Connection #%u\n", i);
       ent->mIdleConns[i]->PrintDiagnostics(mLogData);
     }
@@ -141,16 +147,19 @@ nsHttpConnection::PrintDiagnostics(nsCSt
   log.AppendPrintf("    max-read/read/written %" PRId64 "/%" PRId64 "/%" PRId64 "\n",
                    mMaxBytesRead, mTotalBytesRead, mTotalBytesWritten);
 
   log.AppendPrintf("    rtt = %ums\n", PR_IntervalToMilliseconds(mRtt));
 
   log.AppendPrintf("    idlemonitoring = %d transactionCount=%d\n",
                    mIdleMonitoring, mHttp1xTransactionCount);
 
+  log.AppendPrintf("    supports pipeline = %d classification = 0x%x\n",
+                   mSupportsPipelining, mClassification);
+
   if (mSpdySession)
     mSpdySession->PrintDiagnostics(log);
 }
 
 void
 Http2Session::PrintDiagnostics(nsCString &log)
 {
   log.AppendPrintf("     ::: HTTP2\n");
@@ -193,12 +202,13 @@ nsHttpTransaction::PrintDiagnostics(nsCS
     return;
 
   nsAutoCString requestURI;
   mRequestHead->RequestURI(requestURI);
   log.AppendPrintf("     ::: uri = %s\n", requestURI.get());
   log.AppendPrintf("     caps = 0x%x\n", mCaps);
   log.AppendPrintf("     priority = %d\n", mPriority);
   log.AppendPrintf("     restart count = %u\n", mRestartCount);
+  log.AppendPrintf("     classification = 0x%x\n", mClassification);
 }
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/Http2Push.cpp
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -463,16 +463,40 @@ Http2PushTransactionBuffer::SetProxyConn
 void
 Http2PushTransactionBuffer::Close(nsresult reason)
 {
   mStatus = reason;
   mIsDone = true;
 }
 
 nsresult
+Http2PushTransactionBuffer::AddTransaction(nsAHttpTransaction *trans)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+Http2PushTransactionBuffer::PipelineDepth()
+{
+  return 0;
+}
+
+nsresult
+Http2PushTransactionBuffer::SetPipelinePosition(int32_t position)
+{
+  return NS_OK;
+}
+
+int32_t
+Http2PushTransactionBuffer::PipelinePosition()
+{
+  return 1;
+}
+
+nsresult
 Http2PushTransactionBuffer::GetBufferedData(char *buf,
                                             uint32_t count,
                                             uint32_t *countWritten)
 {
   *countWritten = std::min(count, static_cast<uint32_t>(Available()));
   if (*countWritten) {
     memcpy(buf, &mBufferedHTTP1[mBufferedHTTP1Consumed], *countWritten);
     mBufferedHTTP1Consumed += *countWritten;
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -36,17 +36,19 @@
 #include "sslt.h"
 #include "mozilla/Sprintf.h"
 #include "nsSocketTransportService2.h"
 #include "nsNetUtil.h"
 
 namespace mozilla {
 namespace net {
 
-// Http2Session has multiple inheritance of things that implement nsISupports
+// Http2Session 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_ADDREF(Http2Session)
 NS_IMPL_RELEASE(Http2Session)
 NS_INTERFACE_MAP_BEGIN(Http2Session)
 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
 NS_INTERFACE_MAP_END
 
 // "magic" refers to the string that preceeds HTTP/2 on the wire
 // to help find any intermediaries speaking an older version of HTTP
@@ -3804,16 +3806,31 @@ Http2Session::TakeTransport(nsISocketTra
 
 already_AddRefed<nsHttpConnection>
 Http2Session::TakeHttpConnection()
 {
   MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
   return nullptr;
 }
 
+uint32_t
+Http2Session::CancelPipeline(nsresult reason)
+{
+  // we don't pipeline inside http/2, so this isn't an issue
+  return 0;
+}
+
+nsAHttpTransaction::Classifier
+Http2Session::Classification()
+{
+  if (!mConnection)
+    return nsAHttpTransaction::CLASS_GENERAL;
+  return mConnection->Classification();
+}
+
 void
 Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **aOut)
 {
   *aOut = nullptr;
 }
 
 //-----------------------------------------------------------------------------
 // unused methods of nsAHttpTransaction
@@ -3903,16 +3920,52 @@ Http2Session::TakeSubTransactions(
 
     // Removing the stream from the hash will delete the stream and drop the
     // transaction reference the hash held.
     iter.Remove();
   }
   return NS_OK;
 }
 
+nsresult
+Http2Session::AddTransaction(nsAHttpTransaction *)
+{
+  // This API is meant for pipelining, Http2Session's should be
+  // extended with AddStream()
+
+  MOZ_ASSERT(false,
+             "Http2Session::AddTransaction() should not be called");
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+Http2Session::PipelineDepth()
+{
+  return IsDone() ? 0 : 1;
+}
+
+nsresult
+Http2Session::SetPipelinePosition(int32_t position)
+{
+  // This API is meant for pipelining, Http2Session's should be
+  // extended with AddStream()
+
+  MOZ_ASSERT(false,
+             "Http2Session::SetPipelinePosition() should not be called");
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+int32_t
+Http2Session::PipelinePosition()
+{
+  return 0;
+}
+
 //-----------------------------------------------------------------------------
 // Pass through methods of nsAHttpConnection
 //-----------------------------------------------------------------------------
 
 nsAHttpConnection *
 Http2Session::Connection()
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -157,16 +157,17 @@ HttpBaseChannel::HttpBaseChannel()
   , mPriority(PRIORITY_NORMAL)
   , mRedirectionLimit(gHttpHandler->RedirectionLimit())
   , mApplyConversion(true)
   , mCanceled(false)
   , mIsPending(false)
   , mWasOpened(false)
   , mRequestObserversCalled(false)
   , mResponseHeadersModified(false)
+  , mAllowPipelining(true)
   , mAllowSTS(true)
   , mThirdPartyFlags(0)
   , mUploadStreamHasHeaders(false)
   , mInheritApplicationCache(true)
   , mChooseApplicationCache(false)
   , mLoadedFromApplicationCache(false)
   , mChannelIsForDownload(false)
   , mTracingEnabled(true)
@@ -1948,25 +1949,26 @@ HttpBaseChannel::VisitOriginalResponseHe
   return mResponseHead->VisitHeaders(aVisitor,
       nsHttpHeaderArray::eFilterResponseOriginal);
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetAllowPipelining(bool *value)
 {
   NS_ENSURE_ARG_POINTER(value);
-  *value = false;
+  *value = mAllowPipelining;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::SetAllowPipelining(bool value)
 {
   ENSURE_CALLED_BEFORE_CONNECT();
-  // nop
+
+  mAllowPipelining = value;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetAllowSTS(bool *value)
 {
   NS_ENSURE_ARG_POINTER(value);
   *value = mAllowSTS;
@@ -3225,17 +3227,18 @@ HttpBaseChannel::SetupReplacementChannel
 
     nsAutoCString method;
     mRequestHead.Method(method);
     httpChannel->SetRequestMethod(method);
   }
   // convey the referrer if one was used for this channel to the next one
   if (mReferrer)
     httpChannel->SetReferrerWithPolicy(mReferrer, mReferrerPolicy);
-  // convey the mAllowSTS flags
+  // convey the mAllowPipelining and mAllowSTS flags
+  httpChannel->SetAllowPipelining(mAllowPipelining);
   httpChannel->SetAllowSTS(mAllowSTS);
   // convey the new redirection limit
   // make sure we don't underflow
   uint32_t redirectionLimit = mRedirectionLimit
     ? mRedirectionLimit - 1
     : 0;
   httpChannel->SetRedirectionLimit(redirectionLimit);
 
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -174,18 +174,18 @@ public:
   NS_IMETHOD VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *visitor) override;
   NS_IMETHOD GetResponseHeader(const nsACString &header, nsACString &value) override;
   NS_IMETHOD SetResponseHeader(const nsACString& header,
                                const nsACString& value, bool merge) override;
   NS_IMETHOD VisitResponseHeaders(nsIHttpHeaderVisitor *visitor) override;
   NS_IMETHOD GetOriginalResponseHeader(const nsACString &aHeader,
                                        nsIHttpHeaderVisitor *aVisitor) override;
   NS_IMETHOD VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor) override;
-  NS_IMETHOD GetAllowPipelining(bool *value) override; // deprecated
-  NS_IMETHOD SetAllowPipelining(bool value) override;  // deprecated
+  NS_IMETHOD GetAllowPipelining(bool *value) override;
+  NS_IMETHOD SetAllowPipelining(bool value) override;
   NS_IMETHOD GetAllowSTS(bool *value) override;
   NS_IMETHOD SetAllowSTS(bool value) override;
   NS_IMETHOD GetRedirectionLimit(uint32_t *value) override;
   NS_IMETHOD SetRedirectionLimit(uint32_t value) override;
   NS_IMETHOD IsNoStoreResponse(bool *value) override;
   NS_IMETHOD IsNoCacheResponse(bool *value) override;
   NS_IMETHOD IsPrivateResponse(bool *value) override;
   NS_IMETHOD GetResponseStatus(uint32_t *aValue) override;
@@ -482,16 +482,17 @@ protected:
 
   uint32_t                          mApplyConversion            : 1;
   uint32_t                          mCanceled                   : 1;
   uint32_t                          mIsPending                  : 1;
   uint32_t                          mWasOpened                  : 1;
   // if 1 all "http-on-{opening|modify|etc}-request" observers have been called
   uint32_t                          mRequestObserversCalled     : 1;
   uint32_t                          mResponseHeadersModified    : 1;
+  uint32_t                          mAllowPipelining            : 1;
   uint32_t                          mAllowSTS                   : 1;
   uint32_t                          mThirdPartyFlags            : 3;
   uint32_t                          mUploadStreamHasHeaders     : 1;
   uint32_t                          mInheritApplicationCache    : 1;
   uint32_t                          mChooseApplicationCache     : 1;
   uint32_t                          mLoadedFromApplicationCache : 1;
   uint32_t                          mChannelIsForDownload       : 1;
   uint32_t                          mTracingEnabled             : 1;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -2185,16 +2185,17 @@ HttpChannelChild::ContinueAsyncOpen()
   SerializeURI(mTopWindowURI, openArgs.topWindowURI());
 
   openArgs.preflightArgs() = optionalCorsPreflightArgs;
 
   openArgs.uploadStreamHasHeaders() = mUploadStreamHasHeaders;
   openArgs.priority() = mPriority;
   openArgs.classOfService() = mClassOfService;
   openArgs.redirectionLimit() = mRedirectionLimit;
+  openArgs.allowPipelining() = mAllowPipelining;
   openArgs.allowSTS() = mAllowSTS;
   openArgs.thirdPartyFlags() = mThirdPartyFlags;
   openArgs.resumeAt() = mSendResumeAt;
   openArgs.startPos() = mStartPos;
   openArgs.entityID() = mEntityID;
   openArgs.chooseApplicationCache() = mChooseApplicationCache;
   openArgs.appCacheClientID() = appCacheClientId;
   openArgs.allowSpdy() = mAllowSpdy;
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -115,17 +115,17 @@ HttpChannelParent::Init(const HttpChanne
   case HttpChannelCreationArgs::THttpChannelOpenArgs:
   {
     const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
     return DoAsyncOpen(a.uri(), a.original(), a.doc(), a.referrer(),
                        a.referrerPolicy(), a.apiRedirectTo(), a.topWindowURI(),
                        a.loadFlags(), a.requestHeaders(),
                        a.requestMethod(), a.uploadStream(),
                        a.uploadStreamHasHeaders(), a.priority(), a.classOfService(),
-                       a.redirectionLimit(), a.allowSTS(),
+                       a.redirectionLimit(), a.allowPipelining(), a.allowSTS(),
                        a.thirdPartyFlags(), a.resumeAt(), a.startPos(),
                        a.entityID(), a.chooseApplicationCache(),
                        a.appCacheClientID(), a.allowSpdy(), a.allowAltSvc(), a.beConservative(),
                        a.loadInfo(), a.synthesizedResponseHead(),
                        a.synthesizedSecurityInfoSerialization(),
                        a.cacheKey(), a.requestContextID(), a.preflightArgs(),
                        a.initialRwin(), a.blockAuthPrompt(),
                        a.suspendAfterSynthesizeResponse(),
@@ -300,16 +300,17 @@ HttpChannelParent::DoAsyncOpen(  const U
                                  const uint32_t&            aLoadFlags,
                                  const RequestHeaderTuples& requestHeaders,
                                  const nsCString&           requestMethod,
                                  const OptionalIPCStream&   uploadStream,
                                  const bool&                uploadStreamHasHeaders,
                                  const int16_t&             priority,
                                  const uint32_t&            classOfService,
                                  const uint8_t&             redirectionLimit,
+                                 const bool&                allowPipelining,
                                  const bool&                allowSTS,
                                  const uint32_t&            thirdPartyFlags,
                                  const bool&                doResumeAt,
                                  const uint64_t&            startPos,
                                  const nsCString&           entityID,
                                  const bool&                chooseApplicationCache,
                                  const nsCString&           appCacheClientID,
                                  const bool&                allowSpdy,
@@ -520,16 +521,17 @@ HttpChannelParent::DoAsyncOpen(  const U
 
   if (priority != nsISupportsPriority::PRIORITY_NORMAL) {
     mChannel->SetPriority(priority);
   }
   if (classOfService) {
     mChannel->SetClassFlags(classOfService);
   }
   mChannel->SetRedirectionLimit(redirectionLimit);
+  mChannel->SetAllowPipelining(allowPipelining);
   mChannel->SetAllowSTS(allowSTS);
   mChannel->SetThirdPartyFlags(thirdPartyFlags);
   mChannel->SetAllowSpdy(allowSpdy);
   mChannel->SetAllowAltSvc(allowAltSvc);
   mChannel->SetBeConservative(beConservative);
   mChannel->SetInitialRwin(aInitialRwin);
   mChannel->SetBlockAuthPrompt(aBlockAuthPrompt);
 
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -117,16 +117,17 @@ protected:
                    const uint32_t&            loadFlags,
                    const RequestHeaderTuples& requestHeaders,
                    const nsCString&           requestMethod,
                    const OptionalIPCStream&   uploadStream,
                    const bool&                uploadStreamHasHeaders,
                    const int16_t&             priority,
                    const uint32_t&            classOfService,
                    const uint8_t&             redirectionLimit,
+                   const bool&                allowPipelining,
                    const bool&                allowSTS,
                    const uint32_t&            thirdPartyFlags,
                    const bool&                doResumeAt,
                    const uint64_t&            startPos,
                    const nsCString&           entityID,
                    const bool&                chooseApplicationCache,
                    const nsCString&           appCacheClientID,
                    const bool&                allowSpdy,
--- a/netwerk/protocol/http/NullHttpTransaction.cpp
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -299,11 +299,35 @@ NullHttpTransaction::Close(nsresult reas
 }
 
 nsHttpConnectionInfo *
 NullHttpTransaction::ConnectionInfo()
 {
   return mConnectionInfo;
 }
 
+nsresult
+NullHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+NullHttpTransaction::PipelineDepth()
+{
+  return 0;
+}
+
+nsresult
+NullHttpTransaction::SetPipelinePosition(int32_t position)
+{
+    return NS_OK;
+}
+
+int32_t
+NullHttpTransaction::PipelinePosition()
+{
+  return 1;
+}
+
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/http/TunnelUtils.cpp
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -684,16 +684,69 @@ TLSFilterTransaction::SetProxiedTransact
   nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
   if (secCtrl && callbacks) {
     secCtrl->SetNotificationCallbacks(callbacks);
   }
 
   return NS_OK;
 }
 
+// AddTransaction is for adding pipelined subtransactions
+nsresult
+TLSFilterTransaction::AddTransaction(nsAHttpTransaction *aTrans)
+{
+  LOG(("TLSFilterTransaction::AddTransaction passing on subtransaction "
+       "[this=%p] aTrans=%p ,mTransaction=%p\n", this, aTrans, mTransaction.get()));
+
+  if (!mTransaction) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return mTransaction->AddTransaction(aTrans);
+}
+
+uint32_t
+TLSFilterTransaction::PipelineDepth()
+{
+  if (!mTransaction) {
+    return 0;
+  }
+
+  return mTransaction->PipelineDepth();
+}
+
+nsresult
+TLSFilterTransaction::SetPipelinePosition(int32_t aPosition)
+{
+  if (!mTransaction) {
+    return NS_OK;
+  }
+
+  return mTransaction->SetPipelinePosition(aPosition);
+}
+
+int32_t
+TLSFilterTransaction::PipelinePosition()
+{
+  if (!mTransaction) {
+    return 1;
+  }
+
+  return mTransaction->PipelinePosition();
+}
+
+nsHttpPipeline *
+TLSFilterTransaction::QueryPipeline()
+{
+  if (!mTransaction) {
+    return nullptr;
+  }
+  return mTransaction->QueryPipeline();
+}
+
 bool
 TLSFilterTransaction::IsNullTransaction()
 {
   if (!mTransaction) {
     return false;
   }
   return mTransaction->IsNullTransaction();
 }
--- a/netwerk/protocol/http/TunnelUtils.h
+++ b/netwerk/protocol/http/TunnelUtils.h
@@ -123,16 +123,17 @@ public:
   nsresult NudgeTunnel(NudgeTunnelCallback *callback);
   nsresult SetProxiedTransaction(nsAHttpTransaction *aTrans);
   void     newIODriver(nsIAsyncInputStream *aSocketIn,
                        nsIAsyncOutputStream *aSocketOut,
                        nsIAsyncInputStream **outSocketIn,
                        nsIAsyncOutputStream **outSocketOut);
 
   // nsAHttpTransaction overloads
+  nsHttpPipeline *QueryPipeline() override;
   bool IsNullTransaction() override;
   NullHttpTransaction *QueryNullTransaction() override;
   nsHttpTransaction *QueryHttpTransaction() override;
   SpdyConnectTransaction *QuerySpdyConnectTransaction() override;
 
 private:
   nsresult StartTimerCallback();
   void Cleanup();
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -83,16 +83,17 @@ UNIFIED_SOURCES += [
     'nsHttpChannel.cpp',
     'nsHttpChunkedDecoder.cpp',
     'nsHttpConnection.cpp',
     'nsHttpConnectionInfo.cpp',
     'nsHttpConnectionMgr.cpp',
     'nsHttpDigestAuth.cpp',
     'nsHttpHeaderArray.cpp',
     'nsHttpNTLMAuth.cpp',
+    'nsHttpPipeline.cpp',
     'nsHttpRequestHead.cpp',
     'nsHttpResponseHead.cpp',
     'nsHttpTransaction.cpp',
     'NullHttpChannel.cpp',
     'NullHttpTransaction.cpp',
     'TunnelUtils.cpp',
 ]
 
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -57,18 +57,18 @@ public:
     // called by a transaction to force a "send/recv from network" iteration
     // even if not scheduled by socket associated with connection
     virtual nsresult ForceSend() = 0;
     virtual nsresult ForceRecv() = 0;
 
     // After a connection has had ResumeSend() called by a transaction,
     // and it is ready to write to the network it may need to know the
     // transaction that has data to write. This is only an issue for
-    // multiplexed protocols like SPDY - h1
-    // implicitly has this information in a 1:1 relationship with the
+    // multiplexed protocols like SPDY - plain HTTP or pipelined HTTP
+    // implicitly have this information in a 1:1 relationship with the
     // transaction(s) they manage.
     virtual void TransactionHasDataToWrite(nsAHttpTransaction *)
     {
         // by default do nothing - only multiplexed protocols need to overload
         return;
     }
 
     // This is the companion to *HasDataToWrite() for the case
@@ -107,17 +107,17 @@ public:
     // persistent... important in determining the end of a response.
     virtual bool IsPersistent() = 0;
 
     // called to determine or set if a connection has been reused.
     virtual bool IsReused() = 0;
     virtual void DontReuse() = 0;
 
     // called by a transaction when the transaction reads more from the socket
-    // than it should have (eg. containing part of the next response).
+    // than it should have (eg. containing part of the next pipelined response).
     virtual nsresult PushBack(const char *data, uint32_t length) = 0;
 
     // Used to determine if the connection wants read events even though
     // it has not written out a transaction. Used when a connection has issued
     // a preamble such as a proxy ssl CONNECT sequence.
     virtual bool IsProxyConnectInProgress() = 0;
 
     // Used by a transaction to manage the state of previous response bodies on
@@ -128,16 +128,24 @@ public:
     // Transfer the base http connection object along with a
     // reference to it to the caller.
     virtual already_AddRefed<nsHttpConnection> TakeHttpConnection() = 0;
 
     // Get the nsISocketTransport used by the connection without changing
     //  references or ownership.
     virtual nsISocketTransport *Transport() = 0;
 
+    // Cancel and reschedule transactions deeper than the current response.
+    // Returns the number of canceled transactions.
+    virtual uint32_t CancelPipeline(nsresult originalReason) = 0;
+
+    // Read and write class of transaction that is carried on this connection
+    virtual nsAHttpTransaction::Classifier Classification() = 0;
+    virtual void Classify(nsAHttpTransaction::Classifier newclass) = 0;
+
     // The number of transaction bytes written out on this HTTP Connection, does
     // not count CONNECT tunnel setup
     virtual int64_t BytesWritten() = 0;
 
     // Update the callbacks used to provide security info. May be called on
     // any thread.
     virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
 
@@ -153,16 +161,18 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpCon
     nsresult TakeTransport(nsISocketTransport **,    \
                            nsIAsyncInputStream **,   \
                            nsIAsyncOutputStream **) override; \
     bool IsPersistent() override;                         \
     bool IsReused() override;                             \
     void DontReuse() override;                            \
     nsresult PushBack(const char *, uint32_t) override;   \
     already_AddRefed<nsHttpConnection> TakeHttpConnection() override; \
+    uint32_t CancelPipeline(nsresult originalReason) override; \
+    nsAHttpTransaction::Classifier Classification() override; \
     /*                                                    \
        Thes methods below have automatic definitions that just forward the \
        function to a lower level connection object        \
     */                                                    \
     void GetConnectionInfo(nsHttpConnectionInfo **result) \
       override                                            \
     {                                                     \
       if (!(fwdObject)) {                                 \
@@ -227,16 +237,22 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpCon
         (fwdObject)->LastTransactionExpectedNoContent();    \
     }                                                       \
     void SetLastTransactionExpectedNoContent(bool val)      \
       override                                              \
     {                                                       \
       if (fwdObject)                                        \
         (fwdObject)->SetLastTransactionExpectedNoContent(val); \
     }                                                       \
+    void Classify(nsAHttpTransaction::Classifier newclass)  \
+      override                                              \
+    {                                                       \
+    if (fwdObject)                                          \
+        (fwdObject)->Classify(newclass);                    \
+    }                                                       \
     int64_t BytesWritten() override                         \
     {     return fwdObject ? (fwdObject)->BytesWritten() : 0; } \
     void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) \
       override                                              \
     {                                                       \
         if (fwdObject)                                      \
             (fwdObject)->SetSecurityCallbacks(aCallbacks);  \
     }
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -14,16 +14,17 @@ class nsITransport;
 class nsIRequestContext;
 
 namespace mozilla { namespace net {
 
 class nsAHttpConnection;
 class nsAHttpSegmentReader;
 class nsAHttpSegmentWriter;
 class nsHttpTransaction;
+class nsHttpPipeline;
 class nsHttpRequestHead;
 class nsHttpConnectionInfo;
 class NullHttpTransaction;
 class SpdyConnectTransaction;
 
 //----------------------------------------------------------------------------
 // Abstract base class for a HTTP transaction:
 //
@@ -94,38 +95,58 @@ public:
 
     // called to indicate a failure with proxy CONNECT
     virtual void SetProxyConnectFailed() = 0;
 
     // called to retrieve the request headers of the transaction
     virtual nsHttpRequestHead *RequestHead() = 0;
 
     // determine the number of real http/1.x transactions on this
-    // abstract object. Pipelines had multiple, SPDY has 0,
+    // abstract object. Pipelines may have multiple, SPDY has 0,
     // normal http transactions have 1.
     virtual uint32_t Http1xTransactionCount() = 0;
 
     // called to remove the unused sub transactions from an object that can
-    // handle multiple transactions simultaneously (i.e. h2).
+    // handle multiple transactions simultaneously (i.e. pipelines or spdy).
     //
     // Returns NS_ERROR_NOT_IMPLEMENTED if the object does not implement
     // sub-transactions.
     //
     // Returns NS_ERROR_ALREADY_OPENED if the subtransactions have been
     // at least partially written and cannot be moved.
     //
     virtual nsresult TakeSubTransactions(
         nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) = 0;
 
+    // called to add a sub-transaction in the case of pipelined transactions
+    // classes that do not implement sub transactions
+    // return NS_ERROR_NOT_IMPLEMENTED
+    virtual nsresult AddTransaction(nsAHttpTransaction *transaction) = 0;
+
+    // The total length of the outstanding pipeline comprised of transacations
+    // and sub-transactions.
+    virtual uint32_t PipelineDepth() = 0;
+
+    // Used to inform the connection that it is being used in a pipelined
+    // context. That may influence the handling of some errors.
+    // The value is the pipeline position (> 1).
+    virtual nsresult SetPipelinePosition(int32_t) = 0;
+    virtual int32_t  PipelinePosition() = 0;
+
     // Occasionally the abstract interface has to give way to base implementations
-    // to respect differences between spdy, h2, etc..
+    // to respect differences between spdy, pipelines, etc..
     // These Query* (and IsNullTransaction()) functions provide a way to do
     // that without using xpcom or rtti. Any calling code that can't deal with
     // a null response from one of them probably shouldn't be using nsAHttpTransaction
 
+    // If we used rtti this would be the result of doing
+    // dynamic_cast<nsHttpPipeline *>(this).. i.e. it can be nullptr for
+    // non pipeline implementations of nsAHttpTransaction
+    virtual nsHttpPipeline *QueryPipeline() { return nullptr; }
+
     // equivalent to !!dynamic_cast<NullHttpTransaction *>(this)
     // A null transaction is expected to return BASE_STREAM_CLOSED on all of
     // its IO functions all the time.
     virtual bool IsNullTransaction() { return false; }
     virtual NullHttpTransaction *QueryNullTransaction() { return nullptr; }
 
     // If we used rtti this would be the result of doing
     // dynamic_cast<nsHttpTransaction *>(this).. i.e. it can be nullptr for
@@ -142,16 +163,39 @@ public:
 
     // return the connection information associated with the transaction
     virtual nsHttpConnectionInfo *ConnectionInfo() = 0;
 
     // The base definition of these is done in nsHttpTransaction.cpp
     virtual bool ResponseTimeoutEnabled() const;
     virtual PRIntervalTime ResponseTimeout();
 
+    // Every transaction is classified into one of the types below. When using
+    // HTTP pipelines, only transactions with the same type appear on the same
+    // pipeline.
+    enum Classifier  {
+        // Transactions that expect a short 304 (no-content) response
+        CLASS_REVALIDATION,
+
+        // Transactions for content expected to be CSS or JS
+        CLASS_SCRIPT,
+
+        // Transactions for content expected to be an image
+        CLASS_IMAGE,
+
+        // Transactions that cannot involve a pipeline
+        CLASS_SOLO,
+
+        // Transactions that do not fit any of the other categories. HTML
+        // is normally GENERAL.
+        CLASS_GENERAL,
+
+        CLASS_MAX
+    };
+
     // conceptually the security info is part of the connection, but sometimes
     // in the case of TLS tunneled within TLS the transaction might present
     // a more specific security info that cannot be represented as a layer in
     // the connection due to multiplexing. This interface represents such an
     // overload. If it returns NS_FAILURE the connection should be considered
     // authoritative.
     virtual nsresult GetTransactionSecurityInfo(nsISupports **)
     {
@@ -196,17 +240,21 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpTra
     uint64_t Available() override; \
     virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *) override; \
     virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *) override; \
     virtual void Close(nsresult reason) override;                                \
     nsHttpConnectionInfo *ConnectionInfo() override;                             \
     void     SetProxyConnectFailed() override;                                   \
     virtual nsHttpRequestHead *RequestHead() override;                                   \
     uint32_t Http1xTransactionCount() override;                                  \
-    nsresult TakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) override;
+    nsresult TakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) override; \
+    nsresult AddTransaction(nsAHttpTransaction *) override;                      \
+    uint32_t PipelineDepth() override;                                           \
+    nsresult SetPipelinePosition(int32_t) override;                              \
+    int32_t  PipelinePosition() override;
 
 //-----------------------------------------------------------------------------
 // nsAHttpSegmentReader
 //-----------------------------------------------------------------------------
 
 class nsAHttpSegmentReader
 {
 public:
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -46,17 +46,17 @@ namespace net {
 
 typedef uint8_t nsHttpVersion;
 
 //-----------------------------------------------------------------------------
 // http connection capabilities
 //-----------------------------------------------------------------------------
 
 #define NS_HTTP_ALLOW_KEEPALIVE      (1<<0)
-// NS_HTTP_ALLOW_PIPELINING          (1<<1) removed
+#define NS_HTTP_ALLOW_PIPELINING     (1<<1)
 
 // a transaction with this caps flag will continue to own the connection,
 // preventing it from being reclaimed, even after the transaction completes.
 #define NS_HTTP_STICKY_CONNECTION    (1<<2)
 
 // a transaction with this caps flag will, upon opening a new connection,
 // bypass the local DNS cache
 #define NS_HTTP_REFRESH_DNS          (1<<3)
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -793,26 +793,62 @@ nsHttpChannel::SetupTransactionRequestCo
 
     if (NS_FAILED(rv)) {
         return;
     }
 
     mTransaction->SetRequestContext(rc);
 }
 
+static bool
+SafeForPipelining(nsHttpRequestHead::ParsedMethodType method,
+                  const nsCString &methodString)
+{
+    if (method == nsHttpRequestHead::kMethod_Get ||
+        method == nsHttpRequestHead::kMethod_Head ||
+        method == nsHttpRequestHead::kMethod_Options) {
+        return true;
+    }
+
+    if (method != nsHttpRequestHead::kMethod_Custom) {
+        return false;
+    }
+
+    return (!strcmp(methodString.get(), "PROPFIND") ||
+            !strcmp(methodString.get(), "PROPPATCH"));
+}
+
 nsresult
 nsHttpChannel::SetupTransaction()
 {
     LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
 
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
 
     nsresult rv;
 
     mUsedNetwork = 1;
+    if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
+        //
+        // disable pipelining if:
+        //   (1) pipelining has been disabled by config
+        //   (2) pipelining has been disabled by connection mgr info
+        //   (3) request corresponds to a top-level document load (link click)
+        //   (4) request method is non-idempotent
+        //   (5) request is marked slow (e.g XHR)
+        //
+        nsAutoCString method;
+        mRequestHead.Method(method);
+        if (!mAllowPipelining ||
+           (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) ||
+            !SafeForPipelining(mRequestHead.ParsedMethod(), method)) {
+            LOG(("  pipelining disallowed\n"));
+            mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+        }
+    }
 
     if (!mAllowSpdy) {
         mCaps |= NS_HTTP_DISALLOW_SPDY;
     }
     if (mBeConservative) {
         mCaps |= NS_HTTP_BE_CONSERVATIVE;
     }
 
@@ -950,16 +986,17 @@ nsHttpChannel::SetupTransaction()
         mCaps |= NS_HTTP_TIMING_ENABLED;
 
     if (mUpgradeProtocolCallback) {
         mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
         mRequestHead.SetHeaderOnce(nsHttp::Connection,
                                    nsHttp::Upgrade.get(),
                                    true);
         mCaps |=  NS_HTTP_STICKY_CONNECTION;
+        mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
         mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
     }
 
     if (mPushedStream) {
         mTransaction->SetPushedStream(mPushedStream);
         mPushedStream = nullptr;
     }
 
@@ -1362,16 +1399,20 @@ nsHttpChannel::CallOnStartRequest()
         return rv;
       }
       if (listener) {
         mListener = listener;
         mCompressListener = listener;
       }
     }
 
+    rv = EnsureAssocReq();
+    if (NS_FAILED(rv))
+        return rv;
+
     // if this channel is for a download, close off access to the cache.
     if (mCacheEntry && mChannelIsForDownload) {
         mCacheEntry->AsyncDoom(nullptr);
 
         // We must keep the cache entry in case of partial request.
         // Concurrent access is the same, we need the entry in
         // OnStopRequest.
         if (!mCachedContentIsPartial && !mConcurrentCacheAccess)
@@ -2872,16 +2913,130 @@ nsHttpChannel::ResponseWouldVary(nsICach
 // to set a member function ptr to  a base class function.
 void
 nsHttpChannel::HandleAsyncAbort()
 {
     HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
 }
 
 
+nsresult
+nsHttpChannel::EnsureAssocReq()
+{
+    // Confirm Assoc-Req response header on pipelined transactions
+    // per draft-nottingham-http-pipeline-01.txt
+    // of the form: GET http://blah.com/foo/bar?qv
+    // return NS_OK as long as we don't find a violation
+    // (i.e. no header is ok, as are malformed headers, as are
+    // transactions that have not been pipelined (unless those have been
+    // opted in via pragma))
+
+    if (!mResponseHead)
+        return NS_OK;
+
+    nsAutoCString assoc_val;
+    if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_val))) {
+        return NS_OK;
+    }
+
+    if (!mTransaction || !mURI)
+        return NS_OK;
+
+    if (!mTransaction->PipelinePosition()) {
+        // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined
+        // transactions. It is used by test harness.
+
+        nsAutoCString pragma_val;
+        mResponseHead->GetHeader(nsHttp::Pragma, pragma_val);
+        if (pragma_val.IsEmpty() ||
+            !nsHttp::FindToken(pragma_val.get(), "X-Verify-Assoc-Req",
+                               HTTP_HEADER_VALUE_SEPS))
+            return NS_OK;
+    }
+
+    char *method = net_FindCharNotInSet(assoc_val.get(), HTTP_LWS);
+    if (!method)
+        return NS_OK;
+
+    bool equals;
+    char *endofmethod;
+
+    char * assoc_valChar = nullptr;
+    endofmethod = net_FindCharInSet(method, HTTP_LWS);
+    if (endofmethod)
+        assoc_valChar = net_FindCharNotInSet(endofmethod, HTTP_LWS);
+    if (!assoc_valChar)
+        return NS_OK;
+
+    // check the method
+    nsAutoCString methodHead;
+    mRequestHead.Method(methodHead);
+    if ((((int32_t)methodHead.Length()) != (endofmethod - method)) ||
+        PL_strncmp(method,
+                   methodHead.get(),
+                   endofmethod - method)) {
+        LOG(("  Assoc-Req failure Method %s", method));
+        if (mConnectionInfo)
+            gHttpHandler->ConnMgr()->
+                PipelineFeedbackInfo(mConnectionInfo,
+                                     nsHttpConnectionMgr::RedCorruptedContent,
+                                     nullptr, 0);
+
+        nsCOMPtr<nsIConsoleService> consoleService =
+            do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+        if (consoleService) {
+            nsAutoString message
+                (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
+            nsAutoCString assoc_req;
+            mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_req);
+            AppendASCIItoUTF16(assoc_req, message);
+            message += NS_LITERAL_STRING(" expected method ");
+            AppendASCIItoUTF16(methodHead, message);
+            consoleService->LogStringMessage(message.get());
+        }
+
+        if (gHttpHandler->EnforceAssocReq())
+            return NS_ERROR_CORRUPTED_CONTENT;
+        return NS_OK;
+    }
+
+    // check the URL
+    nsCOMPtr<nsIURI> assoc_url;
+    if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_valChar)) ||
+        !assoc_url)
+        return NS_OK;
+
+    mURI->Equals(assoc_url, &equals);
+    if (!equals) {
+        LOG(("  Assoc-Req failure URL %s", assoc_valChar));
+        if (mConnectionInfo)
+            gHttpHandler->ConnMgr()->
+                PipelineFeedbackInfo(mConnectionInfo,
+                                     nsHttpConnectionMgr::RedCorruptedContent,
+                                     nullptr, 0);
+
+        nsCOMPtr<nsIConsoleService> consoleService =
+            do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+        if (consoleService) {
+            nsAutoString message
+                (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
+            nsAutoCString assoc_req;
+            mResponseHead->GetHeader(nsHttp::Assoc_Req, assoc_req);
+            AppendASCIItoUTF16(assoc_req, message);
+            message += NS_LITERAL_STRING(" expected URL ");
+            AppendASCIItoUTF16(mSpec.get(), message);
+            consoleService->LogStringMessage(message.get());
+        }
+
+        if (gHttpHandler->EnforceAssocReq())
+            return NS_ERROR_CORRUPTED_CONTENT;
+    }
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel <byte-range>
 //-----------------------------------------------------------------------------
 
 bool
 nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
                            bool ignoreMissingPartialLen) const
 {
@@ -3151,17 +3306,17 @@ nsHttpChannel::ProcessNotModified()
     MOZ_ASSERT(mCacheEntry);
     NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
 
     // If the 304 response contains a Last-Modified different than the
     // one in our cache that is pretty suspicious and is, in at least the
     // case of bug 716840, a sign of the server having previously corrupted
     // our cache with a bad response. Take the minor step here of just dooming
     // that cache entry so there is a fighting chance of getting things on the
-    // right track.
+    // right track as well as disabling pipelining for that host.
 
     nsAutoCString lastModifiedCached;
     nsAutoCString lastModified304;
 
     rv = mCachedResponseHead->GetHeader(nsHttp::Last_Modified,
                                         lastModifiedCached);
     if (NS_SUCCEEDED(rv)) {
         rv = mResponseHead->GetHeader(nsHttp::Last_Modified,
@@ -3169,16 +3324,21 @@ nsHttpChannel::ProcessNotModified()
     }
 
     if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
         LOG(("Cache Entry and 304 Last-Modified Headers Do Not Match "
              "[%s] and [%s]\n",
              lastModifiedCached.get(), lastModified304.get()));
 
         mCacheEntry->AsyncDoom(nullptr);
+        if (mConnectionInfo)
+            gHttpHandler->ConnMgr()->
+                PipelineFeedbackInfo(mConnectionInfo,
+                                     nsHttpConnectionMgr::RedCorruptedContent,
+                                     nullptr, 0);
         Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
     }
 
     // merge any new headers with the cached response headers
     rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
     if (NS_FAILED(rv)) return rv;
 
     // update the cached response head
@@ -5977,16 +6137,23 @@ nsHttpChannel::BeginConnect()
         }
     }
 
     // If mTimingEnabled flag is not set after OnModifyRequest() then
     // clear the already recorded AsyncOpen value for consistency.
     if (!mTimingEnabled)
         mAsyncOpenTime = TimeStamp();
 
+    // when proxying only use the pipeline bit if ProxyPipelining() allows it.
+    if (!mConnectionInfo->UsingConnect() && mConnectionInfo->UsingHttpProxy()) {
+        mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+        if (gHttpHandler->ProxyPipelining())
+            mCaps |= NS_HTTP_ALLOW_PIPELINING;
+    }
+
     // if this somehow fails we can go on without it
     gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
 
     if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags))
         mCaps |= NS_HTTP_REFRESH_DNS;
 
     if (!mLocalBlocklist && !mConnectionInfo->UsingHttpProxy() &&
         !(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) {
@@ -6008,17 +6175,17 @@ nsHttpChannel::BeginConnect()
         mDNSPrefetch = new nsDNSPrefetch(mURI, this, mTimingEnabled);
         mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
     }
 
     // Adjust mCaps according to our request headers:
     //  - If "Connection: close" is set as a request header, then do not bother
     //    trying to establish a keep-alive connection.
     if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
-        mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE);
+        mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
 
     if (gHttpHandler->CriticalRequestPrioritization()) {
         if (mClassOfService & nsIClassOfService::Leader) {
             mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
         }
         if (mClassOfService & nsIClassOfService::Unblocked) {
             mCaps |= NS_HTTP_LOAD_UNBLOCKED;
         }
@@ -6026,16 +6193,17 @@ nsHttpChannel::BeginConnect()
 
     // Force-Reload should reset the persistent connection pool for this host
     if (mLoadFlags & LOAD_FRESH_CONNECTION) {
         // just the initial document resets the whole pool
         if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
             gHttpHandler->ConnMgr()->ClearAltServiceMappings();
             gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(mConnectionInfo);
         }
+        mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
     }
 
     // We may have been cancelled already, either by on-modify-request
     // listeners or load group observers; in that case, we should not send the
     // request to the server
     if (mCanceled) {
         return mStatus;
     }
@@ -6697,16 +6865,24 @@ nsHttpChannel::OnStopRequest(nsIRequest 
             LOG(("  transaction %p provides connection %p", mTransaction.get(), conn.get()));
             // This is so far a workaround to fix leak when reusing unpersistent
             // connection for authentication retry. See bug 459620 comment 4
             // for details.
             if (conn && !conn->IsPersistent()) {
                 LOG(("  connection is not persistent, not reusing it"));
                 conn = nullptr;
             }
+            // We do not use a sticky connection in case of a nsHttpPipeline as
+            // well (see bug 1337826). This is a quick fix, because
+            // nsHttpPipeline is turned off by default.
+            RefPtr<nsAHttpTransaction> tranConn = do_QueryObject(conn);
+            if (tranConn && tranConn->QueryPipeline()) {
+                LOG(("Do not use this connection, it is a nsHttpPipeline."));
+                conn = nullptr;
+            }
         }
 
         RefPtr<nsAHttpConnection> stickyConn;
         if (mCaps & NS_HTTP_STICKY_CONNECTION) {
             stickyConn = mTransaction->GetConnectionReference();
         }
 
         mTransferSize = mTransaction->GetTransferSize();
@@ -7494,18 +7670,19 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnec
 
     // rewind the upload stream
     if (mUploadStream) {
         nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
         if (seekable)
             seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
     }
 
-    // set sticky connection flag
+    // set sticky connection flag and disable pipelining.
     mCaps |=  NS_HTTP_STICKY_CONNECTION;
+    mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
 
     // and create a new one...
     rv = SetupTransaction();
     if (NS_FAILED(rv)) return rv;
 
     // transfer ownership of connection to transaction
     if (conn)
         mTransaction->SetConnection(conn);
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -17,16 +17,17 @@
 #define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
 #define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
 
 #include "ASpdySession.h"
 #include "mozilla/ChaosMode.h"
 #include "mozilla/Telemetry.h"
 #include "nsHttpConnection.h"
 #include "nsHttpHandler.h"
+#include "nsHttpPipeline.h"
 #include "nsHttpRequestHead.h"
 #include "nsHttpResponseHead.h"
 #include "nsIOService.h"
 #include "nsISocketTransport.h"
 #include "nsSocketTransportService2.h"
 #include "nsISSLSocketControl.h"
 #include "nsISupportsPriority.h"
 #include "nsPreloadedStream.h"
@@ -53,27 +54,29 @@ nsHttpConnection::nsHttpConnection()
     , mMaxBytesRead(0)
     , mTotalBytesRead(0)
     , mTotalBytesWritten(0)
     , mContentBytesWritten(0)
     , mConnectedTransport(false)
     , mKeepAlive(true) // assume to keep-alive by default
     , mKeepAliveMask(true)
     , mDontReuse(false)
+    , mSupportsPipelining(false) // assume low-grade server
     , mIsReused(false)
     , mCompletedProxyConnect(false)
     , mLastTransactionExpectedNoContent(false)
     , mIdleMonitoring(false)
     , mProxyConnectInProgress(false)
     , mExperienced(false)
     , mInSpdyTunnel(false)
     , mForcePlainText(false)
     , mTrafficStamp(false)
     , mHttp1xTransactionCount(0)
     , mRemainingConnectionUses(0xffffffff)
+    , mClassification(nsAHttpTransaction::CLASS_GENERAL)
     , mNPNComplete(false)
     , mSetupSSLCalled(false)
     , mUsingSpdyVersion(0)
     , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
     , mReportedSpdy(false)
     , mEverUsedSpdy(false)
     , mLastHttpResponseVersion(NS_HTTP_VERSION_1_1)
     , mTransactionCaps(0)
@@ -133,16 +136,18 @@ nsHttpConnection::Init(nsHttpConnectionI
 {
     LOG(("nsHttpConnection::Init this=%p", this));
     NS_ENSURE_ARG_POINTER(info);
     NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
 
     mConnectedTransport = connectedTransport;
     mConnInfo = info;
     mLastWriteTime = mLastReadTime = PR_IntervalNow();
+    mSupportsPipelining =
+        gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
     mRtt = rtt;
     mMaxHangTime = PR_SecondsToInterval(maxHangTime);
 
     mSocketTransport = transport;
     mSocketIn = instream;
     mSocketOut = outstream;
 
     // See explanation for non-strictness of this operation in SetSecurityCallbacks.
@@ -268,17 +273,17 @@ nsHttpConnection::StartSpdy(uint8_t spdy
     }
 
     // Setting the connection as reused allows some transactions that fail
     // with NS_ERROR_NET_RESET to be restarted and SPDY uses that code
     // to handle clean rejections (such as those that arrived after
     // a server goaway was generated).
     mIsReused = true;
 
-    // If mTransaction is a muxed object it might represent
+    // If mTransaction is a pipeline object it might represent
     // several requests. If so, we need to unpack that and
     // pack them all into a new spdy session.
 
     nsTArray<RefPtr<nsAHttpTransaction> > list;
     nsresult rv = NS_OK;
     if (!mDid0RTTSpdy) {
         rv = TryTakeSubTransactions(list);
 
@@ -315,16 +320,17 @@ nsHttpConnection::StartSpdy(uint8_t spdy
 
     // Disable TCP Keepalives - use SPDY ping instead.
     rv = DisableTCPKeepalives();
     if (NS_FAILED(rv)) {
         LOG(("nsHttpConnection::StartSpdy [%p] DisableTCPKeepalives failed "
              "rv[0x%" PRIx32 "]", this, static_cast<uint32_t>(rv)));
     }
 
+    mSupportsPipelining = false; // don't use http/1 pipelines with spdy
     mIdleTimeout = gHttpHandler->SpdyTimeout();
 
     if (!mTLSFilter) {
         mTransaction = mSpdySession;
     } else {
         mTLSFilter->SetProxiedTransaction(mSpdySession);
     }
     if (mDontReuse) {
@@ -859,34 +865,48 @@ nsHttpConnection::DontReuse()
     mKeepAliveMask = false;
     mKeepAlive = false;
     mDontReuse = true;
     mIdleTimeout = 0;
     if (mSpdySession)
         mSpdySession->DontReuse();
 }
 
+// Checked by the Connection Manager before scheduling a pipelined transaction
+bool
+nsHttpConnection::SupportsPipelining()
+{
+    if (mTransaction &&
+        mTransaction->PipelineDepth() >= mRemainingConnectionUses) {
+        LOG(("nsHttpConnection::SupportsPipelining this=%p deny pipeline "
+             "because current depth %d exceeds max remaining uses %d\n",
+             this, mTransaction->PipelineDepth(), mRemainingConnectionUses));
+        return false;
+    }
+    return mSupportsPipelining && IsKeepAlive() && !mDontReuse;
+}
+
 bool
 nsHttpConnection::CanReuse()
 {
-    if (mDontReuse || !mRemainingConnectionUses) {
+    if (mDontReuse)
         return false;
-    }
 
-    if ((mTransaction ? (mTransaction->IsDone() ? 0 : 1) : 0) >=
+    if ((mTransaction ? mTransaction->PipelineDepth() : 0) >=
         mRemainingConnectionUses) {
         return false;
     }
 
     bool canReuse;
-    if (mSpdySession) {
+
+    if (mSpdySession)
         canReuse = mSpdySession->CanReuse();
-    } else {
+    else
         canReuse = IsKeepAlive();
-    }
+
     canReuse = canReuse && (IdleTime() < 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.
 
     uint64_t dataSize;
@@ -955,16 +975,74 @@ nsHttpConnection::IsAlive()
         LOG(("pretending socket is still alive to test restart logic\n"));
         alive = true;
     }
 #endif
 
     return alive;
 }
 
+bool
+nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead)
+{
+    // SPDY supports infinite parallelism, so no need to pipeline.
+    if (mUsingSpdyVersion)
+        return false;
+
+    // assuming connection is HTTP/1.1 with keep-alive enabled
+    if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingConnect()) {
+        // XXX check for bad proxy servers...
+        return true;
+    }
+
+    // check for bad origin servers
+    nsAutoCString val;
+    responseHead->GetHeader(nsHttp::Server, val);
+
+    // If there is no server header we will assume it should not be banned
+    // as facebook and some other prominent sites do this
+    if (val.IsEmpty())
+        return true;
+
+    // The blacklist is indexed by the first character. All of these servers are
+    // known to return their identifier as the first thing in the server string,
+    // so we can do a leading match.
+
+    static const char *bad_servers[26][6] = {
+        { nullptr }, { nullptr }, { nullptr }, { nullptr },                 // a - d
+        { "EFAServer/", nullptr },                                       // e
+        { nullptr }, { nullptr }, { nullptr }, { nullptr },                 // f - i
+        { nullptr }, { nullptr }, { nullptr },                             // j - l
+        { "Microsoft-IIS/4.", "Microsoft-IIS/5.", nullptr },             // m
+        { "Netscape-Enterprise/3.", "Netscape-Enterprise/4.",
+          "Netscape-Enterprise/5.", "Netscape-Enterprise/6.", nullptr }, // n
+        { nullptr }, { nullptr }, { nullptr }, { nullptr },                 // o - r
+        { nullptr }, { nullptr }, { nullptr }, { nullptr },                 // s - v
+        { "WebLogic 3.", "WebLogic 4.","WebLogic 5.", "WebLogic 6.",
+          "Winstone Servlet Engine v0.", nullptr },                      // w
+        { nullptr }, { nullptr }, { nullptr }                              // x - z
+    };
+
+    int index = val.get()[0] - 'A'; // the whole table begins with capital letters
+    if ((index >= 0) && (index <= 25))
+    {
+        for (int i = 0; bad_servers[index][i] != nullptr; i++) {
+            if (val.Equals(bad_servers[index][i])) {
+                LOG(("looks like this server does not support pipelining"));
+                gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                    mConnInfo, nsHttpConnectionMgr::RedBannedServer, this , 0);
+                return false;
+            }
+        }
+    }
+
+    // ok, let's allow pipelining to this server
+    return true;
+}
+
 //----------------------------------------------------------------------------
 // nsHttpConnection::nsAHttpConnection compatible methods
 //----------------------------------------------------------------------------
 
 nsresult
 nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans,
                                      nsHttpRequestHead *requestHead,
                                      nsHttpResponseHead *responseHead,
@@ -1012,30 +1090,82 @@ nsHttpConnection::OnHeadersAvailable(nsA
 
         // timeouts that are not caused by persistent connection reuse should
         // not be retried for browser compatibility reasons. bug 907800. The
         // server driven close is implicit in the 408.
         explicitClose = true;
         explicitKeepAlive = false;
     }
 
+    // reset to default (the server may have changed since we last checked)
+    mSupportsPipelining = false;
+
     if ((responseHead->Version() < NS_HTTP_VERSION_1_1) ||
         (requestHead->Version() < NS_HTTP_VERSION_1_1)) {
         // HTTP/1.0 connections are by default NOT persistent
         if (explicitKeepAlive)
             mKeepAlive = true;
         else
             mKeepAlive = false;
+
+        // We need at least version 1.1 to use pipelines
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            mConnInfo, nsHttpConnectionMgr::RedVersionTooLow, this, 0);
     }
     else {
         // HTTP/1.1 connections are by default persistent
-        mKeepAlive = !explicitClose;
+        if (explicitClose) {
+            mKeepAlive = false;
+
+            // persistent connections are required for pipelining to work - if
+            // this close was not pre-announced then generate the negative
+            // BadExplicitClose feedback
+            if (mRemainingConnectionUses > 1)
+                gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                    mConnInfo, nsHttpConnectionMgr::BadExplicitClose, this, 0);
+        }
+        else {
+            mKeepAlive = true;
+
+            // Do not support pipelining when we are establishing
+            // an SSL tunnel though an HTTP proxy. Pipelining support
+            // determination must be based on comunication with the
+            // target server in this case. See bug 422016 for futher
+            // details.
+            if (!mProxyConnectStream)
+              mSupportsPipelining = SupportsPipelining(responseHead);
+        }
     }
     mKeepAliveMask = mKeepAlive;
 
+    // Update the pipelining status in the connection info object
+    // and also read it back. It is possible the ci status is
+    // locked to false if pipelining has been banned on this ci due to
+    // some kind of observed flaky behavior
+    if (mSupportsPipelining) {
+        // report the pipelining-compatible header to the connection manager
+        // as positive feedback. This will undo 1 penalty point the host
+        // may have accumulated in the past.
+
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            mConnInfo, nsHttpConnectionMgr::NeutralExpectedOK, this, 0);
+
+        mSupportsPipelining =
+            gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
+    }
+
+    // If this connection is reserved for revalidations and we are
+    // receiving a document that failed revalidation then switch the
+    // classification to general to avoid pipelining more revalidations behind
+    // it.
+    if (mClassification == nsAHttpTransaction::CLASS_REVALIDATION &&
+        responseStatus != 304) {
+        mClassification = nsAHttpTransaction::CLASS_GENERAL;
+    }
+
     // if this connection is persistent, then the server may send a "Keep-Alive"
     // header specifying the maximum number of times the connection can be
     // 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.
     bool foundKeepAliveMax = false;
@@ -1251,17 +1381,77 @@ nsHttpConnection::ReadTimeoutTick(PRInte
             CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
             return UINT32_MAX;
         }
         nextTickAfter = PR_IntervalToSeconds(mTransaction->ResponseTimeout()) -
                         PR_IntervalToSeconds(initialResponseDelta);
         nextTickAfter = std::max(nextTickAfter, 1U);
     }
 
-    return nextTickAfter;
+    if (!gHttpHandler->GetPipelineRescheduleOnTimeout())
+        return nextTickAfter;
+
+    PRIntervalTime delta = now - mLastReadTime;
+
+    // we replicate some of the checks both here and in OnSocketReadable() as
+    // they will be discovered under different conditions. The ones here
+    // will generally be discovered if we are totally hung and OSR does
+    // not get called at all, however OSR discovers them with lower latency
+    // if the issue is just very slow (but not stalled) reading.
+    //
+    // Right now we only take action if pipelining is involved, but this would
+    // be the place to add general read timeout handling if it is desired.
+
+    uint32_t pipelineDepth = mTransaction->PipelineDepth();
+    if (pipelineDepth > 1) {
+        // if we have pipelines outstanding (not just an idle connection)
+        // then get a fairly quick tick
+        nextTickAfter = 1;
+    }
+
+    if (delta >= gHttpHandler->GetPipelineRescheduleTimeout() &&
+        pipelineDepth > 1) {
+
+        // this just reschedules blocked transactions. no transaction
+        // is aborted completely.
+        LOG(("cancelling pipeline due to a %ums stall - depth %d\n",
+             PR_IntervalToMilliseconds(delta), pipelineDepth));
+
+        nsHttpPipeline *pipeline = mTransaction->QueryPipeline();
+        MOZ_ASSERT(pipeline, "pipelinedepth > 1 without pipeline");
+        // code this defensively for the moment and check for null in opt build
+        // This will reschedule blocked members of the pipeline, but the
+        // blocking transaction (i.e. response 0) will not be changed.
+        if (pipeline) {
+            pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT);
+            LOG(("Rescheduling the head of line blocked members of a pipeline "
+                 "because reschedule-timeout idle interval exceeded"));
+        }
+    }
+
+    if (delta < gHttpHandler->GetPipelineTimeout())
+        return nextTickAfter;
+
+    if (pipelineDepth <= 1 && !mTransaction->PipelinePosition())
+        return nextTickAfter;
+
+    // nothing has transpired on this pipelined socket for many
+    // seconds. Call that a total stall and close the transaction.
+    // There is a chance the transaction will be restarted again
+    // depending on its state.. that will come back araound
+    // without pipelining on, so this won't loop.
+
+    LOG(("canceling transaction stalled for %ums on a pipeline "
+         "of depth %d and scheduled originally at pos %d\n",
+         PR_IntervalToMilliseconds(delta),
+         pipelineDepth, mTransaction->PipelinePosition()));
+
+    // This will also close the connection
+    CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
+    return UINT32_MAX;
 }
 
 void
 nsHttpConnection::UpdateTCPKeepalive(nsITimer *aTimer, void *aClosure)
 {
     MOZ_ASSERT(aTimer);
     MOZ_ASSERT(aClosure);
 
@@ -1741,18 +1931,59 @@ nsHttpConnection::OnSocketReadable()
     if (mKeepAliveMask && (delta >= mMaxHangTime)) {
         LOG(("max hang time exceeded!\n"));
         // give the handler a chance to create a new persistent connection to
         // this host if we've been busy for too long.
         mKeepAliveMask = false;
         gHttpHandler->ProcessPendingQ(mConnInfo);
     }
 
+    // Look for data being sent in bursts with large pauses. If the pauses
+    // are caused by server bottlenecks such as think-time, disk i/o, or
+    // cpu exhaustion (as opposed to network latency) then we generate negative
+    // pipelining feedback to prevent head of line problems
+
     // Reduce the estimate of the time since last read by up to 1 RTT to
     // accommodate exhausted sender TCP congestion windows or minor I/O delays.
+
+    if (delta > mRtt)
+        delta -= mRtt;
+    else
+        delta = 0;
+
+    static const PRIntervalTime k400ms  = PR_MillisecondsToInterval(400);
+
+    if (delta >= (mRtt + gHttpHandler->GetPipelineRescheduleTimeout())) {
+        LOG(("Read delta ms of %u causing slow read major "
+             "event and pipeline cancellation",
+             PR_IntervalToMilliseconds(delta)));
+
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            mConnInfo, nsHttpConnectionMgr::BadSlowReadMajor, this, 0);
+
+        if (gHttpHandler->GetPipelineRescheduleOnTimeout() &&
+            mTransaction->PipelineDepth() > 1) {
+            nsHttpPipeline *pipeline = mTransaction->QueryPipeline();
+            MOZ_ASSERT(pipeline, "pipelinedepth > 1 without pipeline");
+            // code this defensively for the moment and check for null
+            // This will reschedule blocked members of the pipeline, but the
+            // blocking transaction (i.e. response 0) will not be changed.
+            if (pipeline) {
+                pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT);
+                LOG(("Rescheduling the head of line blocked members of a "
+                     "pipeline because reschedule-timeout idle interval "
+                     "exceeded"));
+            }
+        }
+    }
+    else if (delta > k400ms) {
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            mConnInfo, nsHttpConnectionMgr::BadSlowReadMinor, this, 0);
+    }
+
     mLastReadTime = now;
 
     nsresult rv;
     uint32_t n;
     bool again = true;
 
     do {
         if (!mProxyConnectInProgress && !mNPNComplete) {
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -77,16 +77,17 @@ public:
     nsresult Activate(nsAHttpTransaction *, uint32_t caps, int32_t pri);
 
     // Close the underlying socket transport.
     void Close(nsresult reason, bool aIsShutdown = false);
 
     //-------------------------------------------------------------------------
     // XXX document when these are ok to call
 
+    bool SupportsPipelining();
     bool IsKeepAlive()
     {
         return mUsingSpdyVersion || (mKeepAliveMask && mKeepAlive);
     }
     bool CanReuse();   // can this connection be reused?
     bool CanDirectlyActivate();
 
     // Returns time in seconds for how long connection can be reused.
@@ -171,16 +172,22 @@ public:
     // other connections.
     uint32_t  ReadTimeoutTick(PRIntervalTime now);
 
     // For Active and Idle connections, this will be called when
     // mTCPKeepaliveTransitionTimer fires, to check if the TCP keepalive config
     // should move from short-lived (fast-detect) to long-lived.
     static void UpdateTCPKeepalive(nsITimer *aTimer, void *aClosure);
 
+    nsAHttpTransaction::Classifier Classification() { return mClassification; }
+    void Classify(nsAHttpTransaction::Classifier newclass)
+    {
+        mClassification = newclass;
+    }
+
     // When the connection is active this is called every second
     void  ReadTimeoutTick();
 
     int64_t BytesWritten() { return mTotalBytesWritten; } // includes TLS
     int64_t ContentBytesWritten() { return mContentBytesWritten; }
 
     void    SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
     void    PrintDiagnostics(nsCString &log);
@@ -226,16 +233,17 @@ private:
     nsresult OnTransactionDone(nsresult reason);
     nsresult OnSocketWritable();
     nsresult OnSocketReadable();
 
     nsresult SetupProxyConnect();
 
     PRIntervalTime IdleTime();
     bool     IsAlive();
+    bool     SupportsPipelining(nsHttpResponseHead *);
 
     // Makes certain the SSL handshake is complete and NPN negotiation
     // has had a chance to happen
     bool     EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
                                uint32_t &aOut0RTTBytesWritten);
     void     SetupSSL();
 
     // Start the Spdy transaction handler when NPN indicates spdy/*
@@ -295,16 +303,17 @@ private:
     RefPtr<nsIAsyncInputStream>   mInputOverflow;
 
     PRIntervalTime                  mRtt;
 
     bool                            mConnectedTransport;
     bool                            mKeepAlive;
     bool                            mKeepAliveMask;
     bool                            mDontReuse;
+    bool                            mSupportsPipelining;
     bool                            mIsReused;
     bool                            mCompletedProxyConnect;
     bool                            mLastTransactionExpectedNoContent;
     bool                            mIdleMonitoring;
     bool                            mProxyConnectInProgress;
     bool                            mExperienced;
     bool                            mInSpdyTunnel;
     bool                            mForcePlainText;
@@ -317,16 +326,18 @@ private:
     // excludes spdy transactions.
     uint32_t                        mHttp1xTransactionCount;
 
     // Keep-Alive: max="mRemainingConnectionUses" provides the number of future
     // transactions (including the current one) that the server expects to allow
     // on this persistent connection.
     uint32_t                        mRemainingConnectionUses;
 
+    nsAHttpTransaction::Classifier  mClassification;
+
     // SPDY related
     bool                            mNPNComplete;
     bool                            mSetupSSLCalled;
 
     // version level in use, 0 if unused
     uint8_t                         mUsingSpdyVersion;
 
     RefPtr<ASpdySession>            mSpdySession;
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -9,16 +9,17 @@
 // Log on level :5, instead of default :4.
 #undef LOG
 #define LOG(args) LOG5(args)
 #undef LOG_ENABLED
 #define LOG_ENABLED() LOG5_ENABLED()
 
 #include "nsHttpConnectionMgr.h"
 #include "nsHttpConnection.h"
+#include "nsHttpPipeline.h"
 #include "nsHttpHandler.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsNetCID.h"
 #include "nsCOMPtr.h"
 #include "nsNetUtil.h"
 #include "mozilla/net/DNS.h"
 #include "nsISocketTransport.h"
 #include "nsISSLSocketControl.h"
@@ -117,27 +118,31 @@ nsHttpConnectionMgr::EnsureSocketThreadT
 
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::Init(uint16_t maxConns,
                           uint16_t maxPersistConnsPerHost,
                           uint16_t maxPersistConnsPerProxy,
-                          uint16_t maxRequestDelay)
+                          uint16_t maxRequestDelay,
+                          uint16_t maxPipelinedRequests,
+                          uint16_t maxOptimisticPipelinedRequests)
 {
     LOG(("nsHttpConnectionMgr::Init\n"));
 
     {
         ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
         mMaxConns = maxConns;
         mMaxPersistConnsPerHost = maxPersistConnsPerHost;
         mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
         mMaxRequestDelay = maxRequestDelay;
+        mMaxPipelinedRequests = maxPipelinedRequests;
+        mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests;
 
         mIsShuttingDown = false;
     }
 
     return EnsureSocketThreadTarget();
 }
 
 class BoolWrapper : public ARefBase
@@ -928,16 +933,123 @@ nsHttpConnectionMgr::ProcessPendingQForE
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     if (ent)
         return ProcessPendingQForEntry(ent, false);
     return false;
 }
 
+bool
+nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+    if (ent)
+        return ent->SupportsPipelining();
+    return false;
+}
+
+// nsHttpPipelineFeedback used to hold references across events
+
+class nsHttpPipelineFeedback : public ARefBase
+{
+public:
+    nsHttpPipelineFeedback(nsHttpConnectionInfo *ci,
+                           nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+                           nsHttpConnection *conn, uint32_t data)
+        : mConnInfo(ci)
+        , mConn(conn)
+        , mInfo(info)
+        , mData(data)
+        {
+        }
+
+
+    RefPtr<nsHttpConnectionInfo> mConnInfo;
+    RefPtr<nsHttpConnection> mConn;
+    nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo;
+    uint32_t mData;
+private:
+    ~nsHttpPipelineFeedback() {}
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpPipelineFeedback)
+};
+
+void
+nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci,
+                                          PipelineFeedbackInfoType info,
+                                          nsHttpConnection *conn,
+                                          uint32_t data)
+{
+    if (!ci)
+        return;
+
+    // Post this to the socket thread if we are not running there already
+    if (PR_GetCurrentThread() != gSocketThread) {
+        RefPtr<nsHttpPipelineFeedback> fb =
+            new nsHttpPipelineFeedback(ci, info, conn, data);
+        PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, 0, fb);
+        return;
+    }
+
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+    if (ent)
+        ent->OnPipelineFeedbackInfo(info, conn, data);
+}
+
+void
+nsHttpConnectionMgr::ReportFailedToProcess(nsIURI *uri)
+{
+    MOZ_ASSERT(uri);
+
+    nsAutoCString host;
+    int32_t port = -1;
+    nsAutoCString username;
+    bool usingSSL = false;
+    bool isHttp = false;
+
+    nsresult rv = uri->SchemeIs("https", &usingSSL);
+    if (NS_SUCCEEDED(rv) && usingSSL)
+        isHttp = true;
+    if (NS_SUCCEEDED(rv) && !isHttp)
+        rv = uri->SchemeIs("http", &isHttp);
+    if (NS_SUCCEEDED(rv))
+        rv = uri->GetAsciiHost(host);
+    if (NS_SUCCEEDED(rv))
+        rv = uri->GetPort(&port);
+    if (NS_SUCCEEDED(rv))
+        uri->GetUsername(username);
+    if (NS_FAILED(rv) || !isHttp || host.IsEmpty())
+        return;
+
+    // report the event for all the permutations of anonymous and
+    // private versions of this host
+    RefPtr<nsHttpConnectionInfo> ci =
+        new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr,
+                                 OriginAttributes(), usingSSL);
+    ci->SetAnonymous(false);
+    ci->SetPrivate(false);
+    PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+    ci = ci->Clone();
+    ci->SetAnonymous(false);
+    ci->SetPrivate(true);
+    PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+    ci = ci->Clone();
+    ci->SetAnonymous(true);
+    ci->SetPrivate(false);
+    PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+    ci = ci->Clone();
+    ci->SetAnonymous(true);
+    ci->SetPrivate(true);
+    PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+}
 
 // we're at the active connection limit if any one of the following conditions is true:
 //  (1) at max-connections
 //  (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
 //  (3) keep-alive disabled and at max-connections-per-server
 bool
 nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, uint32_t caps)
 {
@@ -1190,16 +1302,150 @@ nsHttpConnectionMgr::MakeNewConnection(n
         if (rv == NS_ERROR_NOT_AVAILABLE)
             rv = NS_ERROR_FAILURE;
         return rv;
     }
 
     return NS_OK;
 }
 
+bool
+nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent,
+                                           nsHttpTransaction *trans,
+                                           nsHttpTransaction::Classifier classification,
+                                           uint16_t depthLimit)
+{
+    if (classification == nsAHttpTransaction::CLASS_SOLO)
+        return false;
+
+    uint32_t maxdepth = ent->MaxPipelineDepth(classification);
+    if (maxdepth == 0) {
+        ent->CreditPenalty();
+        maxdepth = ent->MaxPipelineDepth(classification);
+    }
+
+    if (ent->PipelineState() == PS_RED)
+        return false;
+
+    if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection)
+        return false;
+
+    // The maximum depth of a pipeline in yellow is 1 pipeline of
+    // depth 2 for entire CI. When that transaction completes successfully
+    // we transition to green and that expands the allowed depth
+    // to any number of pipelines of up to depth 4.  When a transaction
+    // queued at position 3 or deeper succeeds we open it all the way
+    // up to depths limited only by configuration. The staggered start
+    // in green is simply because a successful yellow test of depth=2
+    // might really just be a race condition (i.e. depth=1 from the
+    // server's point of view), while depth=3 is a stronger indicator -
+    // keeping the pipelines to a modest depth during that period limits
+    // the damage if something is going to go wrong.
+
+    maxdepth = std::min<uint32_t>(maxdepth, depthLimit);
+
+    if (maxdepth < 2)
+        return false;
+
+    nsAHttpTransaction *activeTrans;
+
+    nsHttpConnection *bestConn = nullptr;
+    uint32_t activeCount = ent->mActiveConns.Length();
+    uint32_t bestConnLength = 0;
+    uint32_t connLength;
+
+    for (uint32_t i = 0; i < activeCount; ++i) {
+        nsHttpConnection *conn = ent->mActiveConns[i];
+        if (!conn->SupportsPipelining())
+            continue;
+
+        if (conn->Classification() != classification)
+            continue;
+
+        activeTrans = conn->Transaction();
+        if (!activeTrans ||
+            activeTrans->IsDone() ||
+            NS_FAILED(activeTrans->Status()))
+            continue;
+
+        connLength = activeTrans->PipelineDepth();
+
+        if (maxdepth <= connLength)
+            continue;
+
+        if (!bestConn || (connLength < bestConnLength)) {
+            bestConn = conn;
+            bestConnLength = connLength;
+        }
+    }
+
+    if (!bestConn)
+        return false;
+
+    activeTrans = bestConn->Transaction();
+    nsresult rv = activeTrans->AddTransaction(trans);
+    if (NS_FAILED(rv))
+        return false;
+
+    LOG(("   scheduling trans %p on pipeline at position %d\n",
+         trans, trans->PipelinePosition()));
+
+    if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1))
+        ent->SetYellowConnection(bestConn);
+
+    if (!trans->GetPendingTime().IsNull()) {
+        if (trans->UsesPipelining())
+            AccumulateTimeDelta(
+                Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
+                trans->GetPendingTime(), TimeStamp::Now());
+        else
+            AccumulateTimeDelta(
+                Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+                trans->GetPendingTime(), TimeStamp::Now());
+        trans->SetPendingTime(false);
+    }
+    return true;
+}
+
+bool
+nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent,
+                                   nsHttpTransaction::Classifier classification)
+{
+    // A connection entry is declared to be "under pressure" if most of the
+    // allowed parallel connections are already used up. In that case we want to
+    // favor existing pipelines over more parallelism so as to reserve any
+    // unused parallel connections for types that don't have existing pipelines.
+    //
+    // The definition of connection pressure is a pretty liberal one here - that
+    // is why we are using the more restrictive maxPersist* counters.
+    //
+    // Pipelines are also favored when the requested classification is already
+    // using 3 or more of the connections. Failure to do this could result in
+    // one class (e.g. images) establishing self replenishing queues on all the
+    // connections that would starve the other transaction types.
+
+    int32_t currentConns = ent->mActiveConns.Length();
+    int32_t maxConns =
+        (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) ?
+        mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost;
+
+    // Leave room for at least 3 distinct types to operate concurrently,
+    // this satisfies the typical {html, js/css, img} page.
+    if (currentConns >= (maxConns - 2))
+        return true;                           /* prefer pipeline */
+
+    int32_t sameClass = 0;
+    for (int32_t i = 0; i < currentConns; ++i)
+        if (classification == ent->mActiveConns[i]->Classification())
+            if (++sameClass == 3)
+                return true;                   /* prefer pipeline */
+
+    return false;                              /* normal behavior */
+}
+
 // returns OK if a connection is found for the transaction
 //   and the transaction is started.
 // returns ERROR_NOT_AVAILABLE if no connection can be found and it
 //   should be queued until circumstances change
 // returns other ERROR when transaction has a hard failure and should
 //   not remain in the pending queue
 nsresult
 nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
@@ -1210,32 +1456,38 @@ nsHttpConnectionMgr::TryDispatchTransact
     LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
          "[trans=%p ci=%p ci=%s caps=%x tunnelprovider=%p onlyreused=%d "
          "active=%" PRIuSIZE " idle=%" PRIuSIZE "]\n", trans,
          ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
          uint32_t(trans->Caps()), trans->TunnelProvider(),
          onlyReusedConnection, ent->mActiveConns.Length(),
          ent->mIdleConns.Length()));
 
+    nsHttpTransaction::Classifier classification = trans->Classification();
     uint32_t caps = trans->Caps();
 
+    // no keep-alive means no pipelines either
+    if (!(caps & NS_HTTP_ALLOW_KEEPALIVE))
+        caps = caps & ~NS_HTTP_ALLOW_PIPELINING;
+
     // 0 - If this should use spdy then dispatch it post haste.
     // 1 - If there is connection pressure then see if we can pipeline this on
     //     a connection of a matching type instead of using a new conn
     // 2 - If there is an idle connection, use it!
     // 3 - if class == reval or script and there is an open conn of that type
     //     then pipeline onto shortest pipeline of that class if limits allow
     // 4 - If we aren't up against our connection limit,
     //     then open a new one
     // 5 - Try a pipeline if we haven't already - this will be unusual because
     //     it implies a low connection pressure situation where
     //     MakeNewConnection() failed.. that is possible, but unlikely, due to
     //     global limits
     // 6 - no connection is available - queue it
 
+    bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING);
     RefPtr<nsHttpConnection> unusedSpdyPersistentConnection;
 
     // step 0
     // look for existing spdy connection - that's always best because it is
     // essentially pipelining without head of line blocking
 
     if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) {
         RefPtr<nsHttpConnection> conn = GetSpdyPreferredConn(ent);
@@ -1271,17 +1523,25 @@ nsHttpConnectionMgr::TryDispatchTransact
     } else {
         // Mark the transaction and its load group as blocking right now to prevent
         // other transactions from being reordered in the queue due to slow syns.
         trans->DispatchedAsBlocking();
     }
 
     // step 1
     // If connection pressure, then we want to favor pipelining of any kind
-    // h1 pipelining has been removed
+    if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) {
+        attemptedOptimisticPipeline = true;
+        if (AddToShortestPipeline(ent, trans,
+                                  classification,
+                                  mMaxOptimisticPipelinedRequests)) {
+            LOG(("   dispatched step 1 trans=%p\n", trans));
+            return NS_OK;
+        }
+    }
 
     // Subject most transactions at high parallelism to rate pacing.
     // It will only be actually submitted to the
     // token bucket once, and if possible it is granted admission synchronously.
     // It is important to leave a transaction in the pending queue when blocked by
     // pacing so it can be found on cancel if necessary.
     // Transactions that cause blocking or bypass it (e.g. js/css) are not rate
     // limited.
@@ -1335,17 +1595,28 @@ nsHttpConnectionMgr::TryDispatchTransact
             DispatchTransaction(ent, trans, conn);
             LOG(("   dispatched step 2 (idle) trans=%p\n", trans));
             return NS_OK;
         }
     }
 
     // step 3
     // consider pipelining scripts and revalidations
-    // h1 pipelining has been removed
+    if (!attemptedOptimisticPipeline &&
+        (classification == nsHttpTransaction::CLASS_REVALIDATION ||
+         classification == nsHttpTransaction::CLASS_SCRIPT)) {
+        // Assignation kept here for documentation purpose; Never read after
+        attemptedOptimisticPipeline = true;
+        if (AddToShortestPipeline(ent, trans,
+                                  classification,
+                                  mMaxOptimisticPipelinedRequests)) {
+            LOG(("   dispatched step 3 (pipeline) trans=%p\n", trans));
+            return NS_OK;
+        }
+    }
 
     // step 4
     if (!onlyReusedConnection) {
         nsresult rv = MakeNewConnection(ent, trans);
         if (NS_SUCCEEDED(rv)) {
             // this function returns NOT_AVAILABLE for asynchronous connects
             LOG(("   dispatched step 4 (async new conn) trans=%p\n", trans));
             return NS_ERROR_NOT_AVAILABLE;
@@ -1360,17 +1631,24 @@ nsHttpConnectionMgr::TryDispatchTransact
         }
     } else if (trans->TunnelProvider() && trans->TunnelProvider()->MaybeReTunnel(trans)) {
         LOG(("   sort of dispatched step 4a tunnel requeue trans=%p\n", trans));
         // the tunnel provider took responsibility for making a new tunnel
         return NS_OK;
     }
 
     // step 5
-    // previously pipelined anything here if allowed but h1 pipelining has been removed
+    if (caps & NS_HTTP_ALLOW_PIPELINING) {
+        if (AddToShortestPipeline(ent, trans,
+                                  classification,
+                                  mMaxPipelinedRequests)) {
+            LOG(("   dispatched step 5 trans=%p\n", trans));
+            return NS_OK;
+        }
+    }
 
     // step 6
     if (unusedSpdyPersistentConnection) {
         // to avoid deadlocks, we need to throw away this perfectly valid SPDY
         // connection to make room for a new one that can service a no KEEPALIVE
         // request
         unusedSpdyPersistentConnection->DontReuse();
     }
@@ -1389,17 +1667,17 @@ nsHttpConnectionMgr::DispatchTransaction
     nsresult rv;
 
     LOG(("nsHttpConnectionMgr::DispatchTransaction "
          "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d]\n",
          ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority));
 
     // It is possible for a rate-paced transaction to be dispatched independent
     // of the token bucket when the amount of parallelization has changed or
-    // when a muxed connection (e.g. h2) becomes available.
+    // when a muxed connection (e.g. spdy or pipelines) becomes available.
     trans->CancelPacing(NS_OK);
 
     if (conn->UsingSpdy()) {
         LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
              "Connection host = %s\n",
              trans->ConnectionInfo()->Origin(),
              conn->ConnectionInfo()->Origin()));
         rv = conn->Activate(trans, caps, priority);
@@ -1410,32 +1688,41 @@ nsHttpConnectionMgr::DispatchTransaction
             trans->SetPendingTime(false);
         }
         return rv;
     }
 
     MOZ_ASSERT(conn && !conn->Transaction(),
                "DispatchTranaction() on non spdy active connection");
 
+    if (!(caps & NS_HTTP_ALLOW_PIPELINING))
+        conn->Classify(nsAHttpTransaction::CLASS_SOLO);
+    else
+        conn->Classify(trans->Classification());
+
     rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
 
     if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
-        AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
-                            trans->GetPendingTime(), TimeStamp::Now());
+        if (trans->UsesPipelining())
+            AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
+                trans->GetPendingTime(), TimeStamp::Now());
+        else
+            AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+                trans->GetPendingTime(), TimeStamp::Now());
         trans->SetPendingTime(false);
     }
     return rv;
 }
 
 //-----------------------------------------------------------------------------
 // ConnectionHandle
 //
 // 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 owns a reference to this handle.  this extra
+// transaction (or pipeline) owns a reference to this handle.  this extra
 // layer of indirection greatly simplifies consumer code, avoiding the
 // need for consumer code to know when to give the connection back to the
 // connection manager.
 //
 class ConnectionHandle : public nsAHttpConnection
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
@@ -1469,45 +1756,90 @@ NS_IMPL_ISUPPORTS0(ConnectionHandle)
 // concrete nsHttpTransaction
 nsresult
 nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent,
                                                  nsAHttpTransaction *aTrans,
                                                  uint32_t caps,
                                                  nsHttpConnection *conn,
                                                  int32_t priority)
 {
-    nsresult rv;
     MOZ_ASSERT(!conn->UsingSpdy(),
                "Spdy Must Not Use DispatchAbstractTransaction");
     LOG(("nsHttpConnectionMgr::DispatchAbstractTransaction "
          "[ci=%s trans=%p caps=%x conn=%p]\n",
          ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
 
-    RefPtr<nsAHttpTransaction> transaction(aTrans);
+    /* Use pipeline datastructure even if connection does not currently qualify
+       to pipeline this transaction because a different pipeline-eligible
+       transaction might be placed on the active connection. Make an exception
+       for CLASS_SOLO as that connection will never pipeline until it goes
+       quiescent */
+
+    RefPtr<nsAHttpTransaction> transaction;
+    nsresult rv;
+    if (conn->Classification() != nsAHttpTransaction::CLASS_SOLO) {
+        LOG(("   using pipeline datastructure.\n"));
+        RefPtr<nsHttpPipeline> pipeline;
+        rv = BuildPipeline(ent, aTrans, getter_AddRefs(pipeline));
+        if (!NS_SUCCEEDED(rv))
+            return rv;
+        transaction = pipeline;
+    }
+    else {
+        LOG(("   not using pipeline datastructure due to class solo.\n"));
+        transaction = aTrans;
+    }
+
     RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
 
     // give the transaction the indirect reference to the connection.
     transaction->SetConnection(handle);
 
     rv = conn->Activate(transaction, caps, priority);
     if (NS_FAILED(rv)) {
       LOG(("  conn->Activate failed [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
         ent->mActiveConns.RemoveElement(conn);
+        if (conn == ent->mYellowConnection)
+            ent->OnYellowComplete();
         DecrementActiveConnCount(conn);
         ConditionallyStopTimeoutTick();
 
         // sever back references to connection, and do so without triggering
         // a call to ReclaimConnection ;-)
         transaction->SetConnection(nullptr);
         handle->Reset(); // destroy the connection
     }
 
+    // As transaction goes out of scope it will drop the last refernece to the
+    // pipeline if activation failed, in which case this will destroy
+    // the pipeline, which will cause each the transactions owned by the
+    // pipeline to be restarted.
+
     return rv;
 }
 
+nsresult
+nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
+                                   nsAHttpTransaction *firstTrans,
+                                   nsHttpPipeline **result)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    /* form a pipeline here even if nothing is pending so that we
+       can stream-feed it as new transactions arrive */
+
+    /* the first transaction can go in unconditionally - 1 transaction
+       on a nsHttpPipeline object is not a real HTTP pipeline */
+
+    RefPtr<nsHttpPipeline> pipeline = new nsHttpPipeline();
+    pipeline->AddTransaction(firstTrans);
+    pipeline.forget(result);
+    return NS_OK;
+}
+
 void
 nsHttpConnectionMgr::ReportProxyTelemetry(nsConnectionEntry *ent)
 {
     enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3, PROXY_HTTPS = 4 };
 
     if (!ent->mConnInfo->UsingProxy())
         Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE);
     else if (ent->mConnInfo->UsingHttpsProxy())
@@ -2106,19 +2438,21 @@ nsHttpConnectionMgr::OnMsgPruneDeadConne
                 // happen after timeToNextExpire.
                 if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
                     PruneDeadConnectionsAfter(timeToNextExpire);
                 }
             } else {
                 ConditionallyStopPruneDeadConnectionsTimer();
             }
 
-            // If this entry is empty, we have too many entries busy then
-            // we can clean it up and restart
-            if (mCT.Count()                >  125 &&
+            // If this entry is empty, we have too many entries, and this
+            // doesn't represent some painfully determined red condition, then
+            // we can clean it up and restart from yellow.
+            if (ent->PipelineState()       != PS_RED &&
+                mCT.Count()                >  125 &&
                 ent->mIdleConns.Length()   == 0 &&
                 ent->mActiveConns.Length() == 0 &&
                 ent->mHalfOpens.Length()   == 0 &&
                 ent->mPendingQ.Length()    == 0 &&
                 (!ent->mUsingSpdy || mCT.Count() > 300)) {
                 LOG(("    removing empty connection entry\n"));
                 iter.Remove();
                 continue;
@@ -2272,16 +2606,19 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
     // a connection that still holds a reference to a transaction was
     // not closed naturally (i.e. it was reset or aborted) and is
     // therefore not something that should be reused.
     if (conn->Transaction()) {
         conn->DontReuse();
     }
 
     if (ent->mActiveConns.RemoveElement(conn)) {
+        if (conn == ent->mYellowConnection) {
+            ent->OnYellowComplete();
+        }
         DecrementActiveConnCount(conn);
         ConditionallyStopTimeoutTick();
     }
 
     if (conn->CanReuse()) {
         LOG(("  adding connection to idle list\n"));
         // Keep The idle connection list sorted with the connections that
         // have moved the largest data pipelines at the front because these
@@ -2354,28 +2691,42 @@ nsHttpConnectionMgr::OnMsgUpdateParam(in
         mMaxPersistConnsPerHost = value;
         break;
     case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
         mMaxPersistConnsPerProxy = value;
         break;
     case MAX_REQUEST_DELAY:
         mMaxRequestDelay = value;
         break;
+    case MAX_PIPELINED_REQUESTS:
+        mMaxPipelinedRequests = value;
+        break;
+    case MAX_OPTIMISTIC_PIPELINED_REQUESTS:
+        mMaxOptimisticPipelinedRequests = value;
+        break;
     default:
         NS_NOTREACHED("unexpected parameter name");
     }
 }
 
 // nsHttpConnectionMgr::nsConnectionEntry
 nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
 {
     MOZ_COUNT_DTOR(nsConnectionEntry);
     gHttpHandler->ConnMgr()->RemovePreferredHash(this);
 }
 
+void
+nsHttpConnectionMgr::OnMsgProcessFeedback(int32_t, ARefBase *param)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    nsHttpPipelineFeedback *fb = static_cast<nsHttpPipelineFeedback *>(param);
+    PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData);
+}
+
 // Read Timeout Tick handlers
 
 void
 nsHttpConnectionMgr::ActivateTimeoutTick()
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() "
          "this=%p mTimeoutTick=%p\n", this, mTimeoutTick.get()));
@@ -3054,20 +3405,22 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
             RefPtr<nsAHttpTransaction> trans;
             if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
                 // null transactions cannot be put in the entry queue, so that
                 // explains why it is not present.
                 mDispatchedMTransaction = true;
                 trans = mTransaction;
             } else {
                 trans = new NullHttpTransaction(mEnt->mConnInfo,
-                                                callbacks, mCaps);
+                                                callbacks,
+                                                mCaps & ~NS_HTTP_ALLOW_PIPELINING);
             }
 
             gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
+            conn->Classify(nsAHttpTransaction::CLASS_SOLO);
             rv = gHttpHandler->ConnMgr()->
                 DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
         } else {
             // otherwise just put this in the persistent connection pool
             LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match "
                  "returning conn %p to pool\n", conn.get()));
             gHttpHandler->ConnMgr()->OnMsgReclaimConnection(0, conn);
         }
@@ -3187,43 +3540,304 @@ already_AddRefed<nsHttpConnection>
 ConnectionHandle::TakeHttpConnection()
 {
     // return our connection object to the caller and clear it internally
     // do not drop our reference - the caller now owns it.
     MOZ_ASSERT(mConn);
     return mConn.forget();
 }
 
+uint32_t
+ConnectionHandle::CancelPipeline(nsresult reason)
+{
+    // no pipeline to cancel
+    return 0;
+}
+
+nsAHttpTransaction::Classifier
+ConnectionHandle::Classification()
+{
+    if (mConn)
+        return mConn->Classification();
+
+    LOG(("ConnectionHandle::Classification this=%p "
+         "has null mConn using CLASS_SOLO default", this));
+    return nsAHttpTransaction::CLASS_SOLO;
+}
 
 // nsConnectionEntry
 
 nsHttpConnectionMgr::
 nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
     : mConnInfo(ci)
+    , mPipelineState(PS_YELLOW)
+    , mYellowGoodEvents(0)
+    , mYellowBadEvents(0)
+    , mYellowConnection(nullptr)
+    , mGreenDepth(kPipelineOpen)
+    , mPipeliningPenalty(0)
     , mUsingSpdy(false)
     , mInPreferredHash(false)
     , mPreferIPv4(false)
     , mPreferIPv6(false)
     , mUsedForConnection(false)
 {
     MOZ_COUNT_CTOR(nsConnectionEntry);
+    if (gHttpHandler->GetPipelineAggressive()) {
+        mGreenDepth = kPipelineUnlimited;
+        mPipelineState = PS_GREEN;
+    }
+    mInitialGreenDepth = mGreenDepth;
+    memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX);
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
 {
     if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
         return true;
     }
 
     return gHttpHandler->ConnMgr()->
         GetSpdyPreferredConn(this) ? true : false;
 }
 
 bool
+nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining()
+{
+    return mPipelineState != nsHttpConnectionMgr::PS_RED;
+}
+
+nsHttpConnectionMgr::PipeliningState
+nsHttpConnectionMgr::nsConnectionEntry::PipelineState()
+{
+    return mPipelineState;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnPipelineFeedbackInfo(
+    nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+    nsHttpConnection *conn,
+    uint32_t data)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    if (mPipelineState == PS_YELLOW) {
+        if (info & kPipelineInfoTypeBad)
+            mYellowBadEvents++;
+        else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood))
+            mYellowGoodEvents++;
+    }
+
+    if (mPipelineState == PS_GREEN && info == GoodCompletedOK) {
+        int32_t depth = data;
+        LOG(("Transaction completed at pipeline depth of %d. Host = %s\n",
+             depth, mConnInfo->Origin()));
+
+        if (depth >= 3)
+            mGreenDepth = kPipelineUnlimited;
+    }
+
+    nsAHttpTransaction::Classifier classification;
+    if (conn)
+        classification = conn->Classification();
+    else if (info == BadInsufficientFraming ||
+             info == BadUnexpectedLarge)
+        classification = (nsAHttpTransaction::Classifier) data;
+    else
+        classification = nsAHttpTransaction::CLASS_SOLO;
+
+    if (gHttpHandler->GetPipelineAggressive() &&
+        info & kPipelineInfoTypeBad &&
+        info != BadExplicitClose &&
+        info != RedVersionTooLow &&
+        info != RedBannedServer &&
+        info != RedCorruptedContent &&
+        info != BadInsufficientFraming) {
+        LOG(("minor negative feedback ignored "
+             "because of pipeline aggressive mode"));
+    }
+    else if (info & kPipelineInfoTypeBad) {
+        if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) {
+            LOG(("transition to red from %d. Host = %s.\n",
+                 mPipelineState, mConnInfo->Origin()));
+            mPipelineState = PS_RED;
+            mPipeliningPenalty = 0;
+        }
+
+        if (mLastCreditTime.IsNull())
+            mLastCreditTime = TimeStamp::Now();
+
+        // Red* events impact the host globally via mPipeliningPenalty, while
+        // Bad* events impact the per class penalty.
+
+        // The individual penalties should be < 16bit-signed-maxint - 25000
+        // (approx 7500). Penalties are paid-off either when something promising
+        // happens (a successful transaction, or promising headers) or when
+        // time goes by at a rate of 1 penalty point every 16 seconds.
+
+        switch (info) {
+        case RedVersionTooLow:
+            mPipeliningPenalty += 1000;
+            break;
+        case RedBannedServer:
+            mPipeliningPenalty += 7000;
+            break;
+        case RedCorruptedContent:
+            mPipeliningPenalty += 7000;
+            break;
+        case RedCanceledPipeline:
+            mPipeliningPenalty += 60;
+            break;
+        case BadExplicitClose:
+            mPipeliningClassPenalty[classification] += 250;
+            break;
+        case BadSlowReadMinor:
+            mPipeliningClassPenalty[classification] += 5;
+            break;
+        case BadSlowReadMajor:
+            mPipeliningClassPenalty[classification] += 25;
+            break;
+        case BadInsufficientFraming:
+            mPipeliningClassPenalty[classification] += 7000;
+            break;
+        case BadUnexpectedLarge:
+            mPipeliningClassPenalty[classification] += 120;
+            break;
+
+        default:
+            MOZ_ASSERT(false, "Unknown Bad/Red Pipeline Feedback Event");
+        }
+
+        const int16_t kPenalty = 25000;
+        mPipeliningPenalty = std::min(mPipeliningPenalty, kPenalty);
+        mPipeliningClassPenalty[classification] =
+          std::min(mPipeliningClassPenalty[classification], kPenalty);
+
+        LOG(("Assessing red penalty to %s class %d for event %d. "
+             "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Origin(),
+             classification, info, mPipeliningPenalty, classification,
+             mPipeliningClassPenalty[classification]));
+    }
+    else {
+        // hand out credits for neutral and good events such as
+        // "headers look ok" events
+
+        mPipeliningPenalty = std::max(mPipeliningPenalty - 1, 0);
+        mPipeliningClassPenalty[classification] = std::max(mPipeliningClassPenalty[classification] - 1, 0);
+    }
+
+    if (mPipelineState == PS_RED && !mPipeliningPenalty)
+    {
+        LOG(("transition %s to yellow\n", mConnInfo->Origin()));
+        mPipelineState = PS_YELLOW;
+        mYellowConnection = nullptr;
+    }
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn)
+{
+    MOZ_ASSERT(!mYellowConnection && mPipelineState == PS_YELLOW,
+               "yellow connection already set or state is not yellow");
+    mYellowConnection = conn;
+    mYellowGoodEvents = mYellowBadEvents = 0;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnYellowComplete()
+{
+    if (mPipelineState == PS_YELLOW) {
+        if (mYellowGoodEvents && !mYellowBadEvents) {
+            LOG(("transition %s to green\n", mConnInfo->Origin()));
+            mPipelineState = PS_GREEN;
+            mGreenDepth = mInitialGreenDepth;
+        }
+        else {
+            // The purpose of the yellow state is to witness at least
+            // one successful pipelined transaction without seeing any
+            // kind of negative feedback before opening the flood gates.
+            // If we haven't confirmed that, then transfer back to red.
+            LOG(("transition %s to red from yellow return\n",
+                 mConnInfo->Origin()));
+            mPipelineState = PS_RED;
+        }
+    }
+
+    mYellowConnection = nullptr;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::CreditPenalty()
+{
+    if (mLastCreditTime.IsNull())
+        return;
+
+    // Decrease penalty values by 1 for every 16 seconds
+    // (i.e 3.7 per minute, or 1000 every 4h20m)
+
+    TimeStamp now = TimeStamp::Now();
+    TimeDuration elapsedTime = now - mLastCreditTime;
+    uint32_t creditsEarned =
+        static_cast<uint32_t>(elapsedTime.ToSeconds()) >> 4;
+
+    bool failed = false;
+    if (creditsEarned > 0) {
+        mPipeliningPenalty =
+            std::max(int32_t(mPipeliningPenalty - creditsEarned), 0);
+        if (mPipeliningPenalty > 0)
+            failed = true;
+
+        for (int32_t i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
+            mPipeliningClassPenalty[i]  =
+                std::max(int32_t(mPipeliningClassPenalty[i] - creditsEarned), 0);
+            failed = failed || (mPipeliningClassPenalty[i] > 0);
+        }
+
+        // update last credit mark to reflect elapsed time
+        mLastCreditTime += TimeDuration::FromSeconds(creditsEarned << 4);
+    }
+    else {
+        failed = true;                         /* just assume this */
+    }
+
+    // If we are no longer red then clear the credit counter - you only
+    // get credits for time spent in the red state
+    if (!failed)
+        mLastCreditTime = TimeStamp();    /* reset to null timestamp */
+
+    if (mPipelineState == PS_RED && !mPipeliningPenalty)
+    {
+        LOG(("transition %s to yellow based on time credit\n",
+             mConnInfo->Origin()));
+        mPipelineState = PS_YELLOW;
+        mYellowConnection = nullptr;
+    }
+}
+
+uint32_t
+nsHttpConnectionMgr::
+nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass)
+{
+    // Still subject to configuration limit no matter return value
+
+    if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0))
+        return 0;
+
+    if (mPipelineState == PS_YELLOW)
+        return kPipelineRestricted;
+
+    return mGreenDepth;
+}
+
+bool
 nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams> *aArg)
 {
     for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
         nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
 
         if (ent->mConnInfo->GetPrivate()) {
             continue;
         }
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -43,29 +43,33 @@ public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIOBSERVER
 
     // parameter names
     enum nsParamName {
         MAX_CONNECTIONS,
         MAX_PERSISTENT_CONNECTIONS_PER_HOST,
         MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
-        MAX_REQUEST_DELAY
+        MAX_REQUEST_DELAY,
+        MAX_PIPELINED_REQUESTS,
+        MAX_OPTIMISTIC_PIPELINED_REQUESTS
     };
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may only be called on the main thread.
     //-------------------------------------------------------------------------
 
     nsHttpConnectionMgr();
 
     nsresult Init(uint16_t maxConnections,
                   uint16_t maxPersistentConnectionsPerHost,
                   uint16_t maxPersistentConnectionsPerProxy,
-                  uint16_t maxRequestDelay);
+                  uint16_t maxRequestDelay,
+                  uint16_t maxPipelinedRequests,
+                  uint16_t maxOptimisticPipelinedRequests);
     nsresult Shutdown();
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called on any thread.
     //-------------------------------------------------------------------------
 
     // Schedules next pruning of dead connection to happen after
     // given time.
@@ -140,16 +144,80 @@ public:
 
     // called from main thread to post a new request token bucket
     // to the socket thread
     nsresult UpdateRequestTokenBucket(EventTokenBucket *aBucket);
 
     // clears the connection history mCT
     nsresult ClearConnectionHistory();
 
+    // Pipielining Interfaces and Datatypes
+
+    const static uint32_t kPipelineInfoTypeMask = 0xffff0000;
+    const static uint32_t kPipelineInfoIDMask   = ~kPipelineInfoTypeMask;
+
+    const static uint32_t kPipelineInfoTypeRed     = 0x00010000;
+    const static uint32_t kPipelineInfoTypeBad     = 0x00020000;
+    const static uint32_t kPipelineInfoTypeNeutral = 0x00040000;
+    const static uint32_t kPipelineInfoTypeGood    = 0x00080000;
+
+    enum PipelineFeedbackInfoType
+    {
+        // Used when an HTTP response less than 1.1 is received
+        RedVersionTooLow = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0001,
+
+        // Used when a HTTP Server response header that is on the banned from
+        // pipelining list is received
+        RedBannedServer = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0002,
+
+        // Used when a response is terminated early, when it fails an
+        // integrity check such as assoc-req or when a 304 contained a Last-Modified
+        // differnet than the entry being validated.
+        RedCorruptedContent = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0004,
+
+        // Used when a pipeline is only partly satisfied - for instance if the
+        // server closed the connection after responding to the first
+        // request but left some requests unprocessed.
+        RedCanceledPipeline = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0005,
+
+        // Used when a connection that we expected to stay persistently open
+        // was closed by the server. Not used when simply timed out.
+        BadExplicitClose = kPipelineInfoTypeBad | 0x0003,
+
+        // Used when there is a gap of around 400 - 1200ms in between data being
+        // read from the server
+        BadSlowReadMinor = kPipelineInfoTypeBad | 0x0006,
+
+        // Used when there is a gap of > 1200ms in between data being
+        // read from the server
+        BadSlowReadMajor = kPipelineInfoTypeBad | 0x0007,
+
+        // Used when a response is received that is not framed with either chunked
+        // encoding or a complete content length.
+        BadInsufficientFraming = kPipelineInfoTypeBad | 0x0008,
+
+        // Used when a very large response is recevied in a potential pipelining
+        // context. Large responses cause head of line blocking.
+        BadUnexpectedLarge = kPipelineInfoTypeBad | 0x000B,
+
+        // Used when a response is received that has headers that appear to support
+        // pipelining.
+        NeutralExpectedOK = kPipelineInfoTypeNeutral | 0x0009,
+
+        // Used when a response is received successfully to a pipelined request.
+        GoodCompletedOK = kPipelineInfoTypeGood | 0x000A
+    };
+
+    // called to provide information relevant to the pipelining manager
+    // may be called from any thread
+    void     PipelineFeedbackInfo(nsHttpConnectionInfo *,
+                                  PipelineFeedbackInfoType info,
+                                  nsHttpConnection *,
+                                  uint32_t);
+
     void ReportFailedToProcess(nsIURI *uri);
 
     // Causes a large amount of connection diagnostic information to be
     // printed to the javascript console
     void PrintDiagnostics();
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called only on the socket thread.
@@ -174,28 +242,47 @@ public:
     // 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);
 
+    bool     SupportsPipelining(nsHttpConnectionInfo *);
+
     bool GetConnectionData(nsTArray<HttpRetParams> *);
 
     void ResetIPFamilyPreference(nsHttpConnectionInfo *);
 
     uint16_t MaxRequestDelay() { return mMaxRequestDelay; }
 
     // public, so that the SPDY/http2 seesions can activate
     void ActivateTimeoutTick();
 
 private:
     virtual ~nsHttpConnectionMgr();
 
+    enum PipeliningState {
+        // Host has proven itself pipeline capable through past experience and
+        // large pipeline depths are allowed on multiple connections.
+        PS_GREEN,
+
+        // Not enough information is available yet with this host to be certain
+        // of pipeline capability. Small pipelines on a single connection are
+        // allowed in order to decide whether or not to proceed to green.
+        PS_YELLOW,
+
+        // One or more bad events has happened that indicate that pipelining
+        // to this host (or a particular type of transaction with this host)
+        // is a bad idea. Pipelining is not currently allowed, but time and
+        // other positive experiences will eventually allow it to try again.
+        PS_RED
+    };
+
     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.
     //
@@ -215,16 +302,64 @@ private:
 
         // calculate the number of half open sockets that have not had at least 1
         // connection complete
         uint32_t UnconnectedHalfOpens();
 
         // Remove a particular half open socket from the mHalfOpens array
         void RemoveHalfOpen(nsHalfOpenSocket *);
 
+        // Pipeline depths for various states
+        const static uint32_t kPipelineUnlimited  = 1024; // fully open - extended green
+        const static uint32_t kPipelineOpen       = 6;    // 6 on each conn - normal green
+        const static uint32_t kPipelineRestricted = 2;    // 2 on just 1 conn in yellow
+
+        nsHttpConnectionMgr::PipeliningState PipelineState();
+        void OnPipelineFeedbackInfo(
+            nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+            nsHttpConnection *, uint32_t);
+        bool SupportsPipelining();
+        uint32_t MaxPipelineDepth(nsAHttpTransaction::Classifier classification);
+        void CreditPenalty();
+
+        nsHttpConnectionMgr::PipeliningState mPipelineState;
+
+        void SetYellowConnection(nsHttpConnection *);
+        void OnYellowComplete();
+        uint32_t                  mYellowGoodEvents;
+        uint32_t                  mYellowBadEvents;
+        nsHttpConnection         *mYellowConnection;
+
+        // initialGreenDepth is the max depth of a pipeline when you first
+        // transition to green. Normally this is kPipelineOpen, but it can
+        // be kPipelineUnlimited in aggressive mode.
+        uint32_t                  mInitialGreenDepth;
+
+        // greenDepth is the current max allowed depth of a pipeline when
+        // in the green state. Normally this starts as kPipelineOpen and
+        // grows to kPipelineUnlimited after a pipeline of depth 3 has been
+        // successfully transacted.
+        uint32_t                  mGreenDepth;
+
+        // pipeliningPenalty is the current amount of penalty points this host
+        // entry has earned for participating in events that are not conducive
+        // to good pipelines - such as head of line blocking, canceled pipelines,
+        // etc.. penalties are paid back either through elapsed time or simply
+        // healthy transactions. Having penalty points means that this host is
+        // not currently eligible for pipelines.
+        int16_t                   mPipeliningPenalty;
+
+        // some penalty points only apply to particular classifications of
+        // transactions - this allows a server that perhaps has head of line
+        // blocking problems on CGI queries to still serve JS pipelined.
+        int16_t                   mPipeliningClassPenalty[nsAHttpTransaction::CLASS_MAX];
+
+        // for calculating penalty repair credits
+        TimeStamp        mLastCreditTime;
+
         // Spdy sometimes resolves the address in the socket manager in order
         // to re-coalesce sharded HTTP hosts. The dotted decimal address is
         // combined with the Anonymous flag from the connection information
         // to build the hash key for hosts in the same ip pool.
         //
         // When a set of hosts are coalesced together one of them is marked
         // mSpdyPreferred. The mapping is maintained in the connection mananger
         // mSpdyPreferred hash.
@@ -359,35 +494,42 @@ private:
     ReentrantMonitor    mReentrantMonitor;
     nsCOMPtr<nsIEventTarget>     mSocketThreadTarget;
 
     // connection limits
     uint16_t mMaxConns;
     uint16_t mMaxPersistConnsPerHost;
     uint16_t mMaxPersistConnsPerProxy;
     uint16_t mMaxRequestDelay; // in seconds
+    uint16_t mMaxPipelinedRequests;
+    uint16_t mMaxOptimisticPipelinedRequests;
     Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
 
     //-------------------------------------------------------------------------
     // NOTE: these members are only accessed on the socket transport thread
     //-------------------------------------------------------------------------
 
     bool     ProcessPendingQForEntry(nsConnectionEntry *, bool considerAll);
+    bool     IsUnderPressure(nsConnectionEntry *ent,
+                             nsHttpTransaction::Classifier classification);
     bool     AtActiveConnectionLimit(nsConnectionEntry *, uint32_t caps);
     nsresult TryDispatchTransaction(nsConnectionEntry *ent,
                                     bool onlyReusedConnection,
                                     nsHttpTransaction *trans);
     nsresult DispatchTransaction(nsConnectionEntry *,
                                  nsHttpTransaction *,
                                  nsHttpConnection *);
     nsresult DispatchAbstractTransaction(nsConnectionEntry *,
                                          nsAHttpTransaction *,
                                          uint32_t,
                                          nsHttpConnection *,
                                          int32_t);
+    nsresult BuildPipeline(nsConnectionEntry *,
+                           nsAHttpTransaction *,
+                           nsHttpPipeline **);
     bool     RestrictConnections(nsConnectionEntry *);
     nsresult ProcessNewTransaction(nsHttpTransaction *);
     nsresult EnsureSocketThreadTarget();
     void     ClosePersistentConnections(nsConnectionEntry *ent);
     void     ReportProxyTelemetry(nsConnectionEntry *ent);
     nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *,
                              uint32_t, bool, bool, bool);
     void     AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
@@ -395,16 +537,20 @@ private:
     void     StartedConnect();
     void     RecvdConnect();
 
     nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *,
                                                   bool allowWildCard);
 
     nsresult MakeNewConnection(nsConnectionEntry *ent,
                                nsHttpTransaction *trans);
+    bool     AddToShortestPipeline(nsConnectionEntry *ent,
+                                   nsHttpTransaction *trans,
+                                   nsHttpTransaction::Classifier classification,
+                                   uint16_t depthLimit);
 
     // Manage the preferred spdy connection entry for this address
     nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry);
     nsConnectionEntry *LookupPreferredHash(nsConnectionEntry *ent);
     void               StorePreferredHash(nsConnectionEntry *ent);
     void               RemovePreferredHash(nsConnectionEntry *ent);
     nsHttpConnection  *GetSpdyPreferredConn(nsConnectionEntry *ent);
     nsDataHashtable<nsCStringHashKey, nsConnectionEntry *>   mSpdyPreferredHash;
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -36,16 +36,17 @@
 #include "ASpdySession.h"
 #include "EventTokenBucket.h"
 #include "Tickler.h"
 #include "nsIXULAppInfo.h"
 #include "nsICookieService.h"
 #include "nsIObserverService.h"
 #include "nsISiteSecurityService.h"
 #include "nsIStreamConverterService.h"
+#include "nsITimer.h"
 #include "nsCRT.h"
 #include "nsIMemoryReporter.h"
 #include "nsIParentalControlsService.h"
 #include "nsPIDOMWindow.h"
 #include "nsINetworkLinkService.h"
 #include "nsHttpChannelAuthProvider.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
@@ -168,31 +169,41 @@ nsHttpHandler::nsHttpHandler()
     , mCapabilities(NS_HTTP_ALLOW_KEEPALIVE)
     , mReferrerLevel(0xff) // by default we always send a referrer
     , mSpoofReferrerSource(false)
     , mHideOnionReferrerSource(false)
     , mReferrerTrimmingPolicy(0)
     , mReferrerXOriginTrimmingPolicy(0)
     , mReferrerXOriginPolicy(0)
     , mFastFallbackToIPv4(false)
+    , mProxyPipelining(true)
     , mIdleTimeout(PR_SecondsToInterval(10))
     , mSpdyTimeout(PR_SecondsToInterval(180))
     , mResponseTimeout(PR_SecondsToInterval(300))
     , mResponseTimeoutEnabled(false)
     , mNetworkChangedTimeout(5000)
     , mMaxRequestAttempts(6)
     , mMaxRequestDelay(10)
     , mIdleSynTimeout(250)
     , mH2MandatorySuiteEnabled(false)
+    , mPipeliningEnabled(false)
     , mMaxConnections(24)
     , mMaxPersistentConnectionsPerServer(2)
     , mMaxPersistentConnectionsPerProxy(4)
+    , mMaxPipelinedRequests(32)
+    , mMaxOptimisticPipelinedRequests(4)
+    , mPipelineAggressive(false)
+    , mMaxPipelineObjectSize(300000)
+    , mPipelineRescheduleOnTimeout(true)
+    , mPipelineRescheduleTimeout(PR_MillisecondsToInterval(1500))
+    , mPipelineReadTimeout(PR_MillisecondsToInterval(30000))
     , mRedirectionLimit(10)
     , mPhishyUserPassLength(1)
     , mQoSBits(0x00)
+    , mPipeliningOverSSL(false)
     , mEnforceAssocReq(false)
     , mLastUniqueID(NowInSeconds())
     , mSessionStartTime(0)
     , mLegacyAppName("Mozilla")
     , mLegacyAppVersion("5.0")
     , mProduct("Gecko")
     , mCompatFirefoxEnabled(false)
     , mUserAgentIsDirty(true)
@@ -253,16 +264,21 @@ nsHttpHandler::~nsHttpHandler()
         mConnMgr->Shutdown();
         mConnMgr = nullptr;
     }
 
     // Note: don't call NeckoChild::DestroyNeckoChild() here, as it's too late
     // and it'll segfault.  NeckoChild will get cleaned up by process exit.
 
     nsHttp::DestroyAtomTable();
+    if (mPipelineTestTimer) {
+        mPipelineTestTimer->Cancel();
+        mPipelineTestTimer = nullptr;
+    }
+
     gHttpHandler = nullptr;
 }
 
 nsresult
 nsHttpHandler::Init()
 {
     nsresult rv;
 
@@ -373,23 +389,21 @@ nsHttpHandler::Init()
         // about shutdown ordering.
         obsService->AddObserver(this, "profile-change-net-teardown", true);
         obsService->AddObserver(this, "profile-change-net-restore", true);
         obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
         obsService->AddObserver(this, "net:clear-active-logins", true);
         obsService->AddObserver(this, "net:prune-dead-connections", true);
         // Sent by the TorButton add-on in the Tor Browser
         obsService->AddObserver(this, "net:prune-all-connections", true);
+        obsService->AddObserver(this, "net:failed-to-process-uri-content", true);
         obsService->AddObserver(this, "last-pb-context-exited", true);
         obsService->AddObserver(this, "browser:purge-session-history", true);
         obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
         obsService->AddObserver(this, "application-background", true);
-
-        // disabled as its a nop right now
-        // obsService->AddObserver(this, "net:failed-to-process-uri-content", true);
     }
 
     MakeNewRequestTokenBucket();
     mWifiTickler = new Tickler();
     if (NS_FAILED(mWifiTickler->Init()))
         mWifiTickler = nullptr;
 
     nsCOMPtr<nsIParentalControlsService> pc = do_CreateInstance("@mozilla.org/parental-controls-service;1");
@@ -424,17 +438,19 @@ nsHttpHandler::InitConnectionMgr()
 
     if (!mConnMgr) {
         mConnMgr = new nsHttpConnectionMgr();
     }
 
     rv = mConnMgr->Init(mMaxConnections,
                         mMaxPersistentConnectionsPerServer,
                         mMaxPersistentConnectionsPerProxy,
-                        mMaxRequestDelay);
+                        mMaxRequestDelay,
+                        mMaxPipelinedRequests,
+                        mMaxOptimisticPipelinedRequests);
     return rv;
 }
 
 nsresult
 nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure)
 {
     nsresult rv;
 
@@ -1136,16 +1152,106 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
             if (!PL_strcmp(httpVersion, "1.1"))
                 mProxyHttpVersion = NS_HTTP_VERSION_1_1;
             else
                 mProxyHttpVersion = NS_HTTP_VERSION_1_0;
             // it does not make sense to issue a HTTP/0.9 request to a proxy server
         }
     }
 
+    if (PREF_CHANGED(HTTP_PREF("pipelining"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("pipelining"), &cVar);
+        if (NS_SUCCEEDED(rv)) {
+            if (cVar)
+                mCapabilities |=  NS_HTTP_ALLOW_PIPELINING;
+            else
+                mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+            mPipeliningEnabled = cVar;
+        }
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("pipelining.maxrequests"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxrequests"), &val);
+        if (NS_SUCCEEDED(rv)) {
+            mMaxPipelinedRequests = clamped(val, 1, 0xffff);
+            if (mConnMgr)
+                mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PIPELINED_REQUESTS,
+                                      mMaxPipelinedRequests);
+        }
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("pipelining.max-optimistic-requests"))) {
+        rv = prefs->
+            GetIntPref(HTTP_PREF("pipelining.max-optimistic-requests"), &val);
+        if (NS_SUCCEEDED(rv)) {
+            mMaxOptimisticPipelinedRequests = clamped(val, 1, 0xffff);
+            if (mConnMgr)
+                mConnMgr->UpdateParam
+                    (nsHttpConnectionMgr::MAX_OPTIMISTIC_PIPELINED_REQUESTS,
+                     mMaxOptimisticPipelinedRequests);
+        }
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("pipelining.aggressive"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("pipelining.aggressive"), &cVar);
+        if (NS_SUCCEEDED(rv))
+            mPipelineAggressive = cVar;
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("pipelining.maxsize"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxsize"), &val);
+        if (NS_SUCCEEDED(rv)) {
+            mMaxPipelineObjectSize =
+                static_cast<int64_t>(clamped(val, 1000, 100000000));
+        }
+    }
+
+    // Determines whether or not to actually reschedule after the
+    // reschedule-timeout has expired
+    if (PREF_CHANGED(HTTP_PREF("pipelining.reschedule-on-timeout"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("pipelining.reschedule-on-timeout"),
+                                &cVar);
+        if (NS_SUCCEEDED(rv))
+            mPipelineRescheduleOnTimeout = cVar;
+    }
+
+    // The amount of time head of line blocking is allowed (in ms)
+    // before the blocked transactions are moved to another pipeline
+    if (PREF_CHANGED(HTTP_PREF("pipelining.reschedule-timeout"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("pipelining.reschedule-timeout"),
+                               &val);
+        if (NS_SUCCEEDED(rv)) {
+            mPipelineRescheduleTimeout =
+                PR_MillisecondsToInterval((uint16_t) clamped(val, 500, 0xffff));
+        }
+    }
+
+    // The amount of time a pipelined transaction is allowed to wait before
+    // being canceled and retried in a non-pipeline connection
+    if (PREF_CHANGED(HTTP_PREF("pipelining.read-timeout"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("pipelining.read-timeout"), &val);
+        if (NS_SUCCEEDED(rv)) {
+            mPipelineReadTimeout =
+                PR_MillisecondsToInterval((uint16_t) clamped(val, 5000,
+                                                             0xffff));
+        }
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("pipelining.ssl"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("pipelining.ssl"), &cVar);
+        if (NS_SUCCEEDED(rv))
+            mPipeliningOverSSL = cVar;
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("proxy.pipelining"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("proxy.pipelining"), &cVar);
+        if (NS_SUCCEEDED(rv))
+            mProxyPipelining = cVar;
+    }
+
     if (PREF_CHANGED(HTTP_PREF("qos"))) {
         rv = prefs->GetIntPref(HTTP_PREF("qos"), &val);
         if (NS_SUCCEEDED(rv))
             mQoSBits = (uint8_t) clamped(val, 0, 0xff);
     }
 
     if (PREF_CHANGED(HTTP_PREF("accept.default"))) {
         nsXPIDLCString accept;
@@ -1469,16 +1575,47 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     if (PREF_CHANGED("network.http.debug-observations")) {
         cVar = false;
         rv = prefs->GetBoolPref("network.http.debug-observations", &cVar);
         if (NS_SUCCEEDED(rv)) {
             mDebugObservations = cVar;
         }
     }
 
+    //
+    // Test HTTP Pipelining (bug796192)
+    // If experiments are allowed and pipelining is Off,
+    // turn it On for just 10 minutes
+    //
+    if (mAllowExperiments && !mPipeliningEnabled &&
+        PREF_CHANGED(HTTP_PREF("pipelining.abtest"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("pipelining.abtest"), &cVar);
+        if (NS_SUCCEEDED(rv)) {
+            // If option is enabled, only test for ~1% of sessions
+            if (cVar && !(rand() % 128)) {
+                mCapabilities |=  NS_HTTP_ALLOW_PIPELINING;
+                if (mPipelineTestTimer)
+                    mPipelineTestTimer->Cancel();
+                mPipelineTestTimer =
+                    do_CreateInstance("@mozilla.org/timer;1", &rv);
+                if (NS_SUCCEEDED(rv)) {
+                    rv = mPipelineTestTimer->InitWithFuncCallback(
+                        TimerCallback, this, 10*60*1000, // 10 minutes
+                        nsITimer::TYPE_ONE_SHOT);
+                }
+            } else {
+                mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+                if (mPipelineTestTimer) {
+                    mPipelineTestTimer->Cancel();
+                    mPipelineTestTimer = nullptr;
+                }
+            }
+        }
+    }
+
     if (PREF_CHANGED(HTTP_PREF("pacing.requests.enabled"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("pacing.requests.enabled"), &cVar);
         if (NS_SUCCEEDED(rv)) {
             mRequestTokenBucketEnabled = cVar;
             requestTokenBucketUpdated = true;
         }
     }
     if (PREF_CHANGED(HTTP_PREF("pacing.requests.min-parallelism"))) {
@@ -1595,16 +1732,27 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
                               !mTCPKeepaliveLongLivedEnabled;
 
 #undef PREF_CHANGED
 #undef MULTI_PREF_CHANGED
 }
 
 
 /**
+ * Static method called by mPipelineTestTimer when it expires.
+ */
+void
+nsHttpHandler::TimerCallback(nsITimer * aTimer, void * aClosure)
+{
+    RefPtr<nsHttpHandler> thisObject = static_cast<nsHttpHandler*>(aClosure);
+    if (!thisObject->mPipeliningEnabled)
+        thisObject->mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
+}
+
+/**
  * Currently, only regularizes the case of subtags.
  */
 static void
 CanonicalizeLanguageTag(char *languageTag)
 {
     char *s = languageTag;
     while (*s != '\0') {
         *s = nsCRT::ToLower(*s);
@@ -1895,16 +2043,22 @@ nsHttpHandler::NewProxiedChannel2(nsIURI
     if (IsNeckoChild()) {
         httpChannel = new HttpChannelChild();
     } else {
         httpChannel = new nsHttpChannel();
     }
 
     uint32_t caps = mCapabilities;
 
+    if (https) {
+        // enable pipelining over SSL if requested
+        if (mPipeliningOverSSL)
+            caps |= NS_HTTP_ALLOW_PIPELINING;
+    }
+
     if (!IsNeckoChild()) {
         // HACK: make sure PSM gets initialized on the main thread.
         net_EnsurePSMInit();
     }
 
     nsID channelId;
     rv = NewChannelId(&channelId);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -2032,22 +2186,21 @@ nsHttpHandler::Observe(nsISupports *subj
         if (mConnMgr) {
             mConnMgr->PruneDeadConnections();
         }
     } else if (!strcmp(topic, "net:prune-all-connections")) {
         if (mConnMgr) {
             mConnMgr->DoShiftReloadConnectionCleanup(nullptr);
             mConnMgr->PruneDeadConnections();
         }
-#if 0
     } else if (!strcmp(topic, "net:failed-to-process-uri-content")) {
-         // nop right now - we used to cancel h1 pipelines based on this,
-         // but those are no longer implemented
-         nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
-#endif
+        nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
+        if (uri && mConnMgr) {
+            mConnMgr->ReportFailedToProcess(uri);
+        }
     } else if (!strcmp(topic, "last-pb-context-exited")) {
         mPrivateAuthCache.ClearAll();
         if (mConnMgr) {
             mConnMgr->ClearAltServiceMappings();
         }
     } else if (!strcmp(topic, "browser:purge-session-history")) {
         if (mConnMgr) {
             if (gSocketTransportService) {
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -23,16 +23,17 @@ class nsIHttpChannel;
 class nsIPrefBranch;
 class nsICancelable;
 class nsICookieService;
 class nsIIOService;
 class nsIRequestContextService;
 class nsISiteSecurityService;
 class nsIStreamConverterService;
 class nsIThrottlingService;
+class nsITimer;
 class nsIUUIDGenerator;
 
 
 namespace mozilla {
 namespace net {
 
 extern Atomic<PRThread*, Relaxed> gSocketThread;
 
@@ -96,16 +97,17 @@ public:
     PRIntervalTime ResponseTimeoutEnabled()  { return mResponseTimeoutEnabled; }
     uint32_t       NetworkChangedTimeout()   { return mNetworkChangedTimeout; }
     uint16_t       MaxRequestAttempts()      { return mMaxRequestAttempts; }
     const char    *DefaultSocketType()       { return mDefaultSocketType.get(); /* ok to return null */ }
     uint32_t       PhishyUserPassLength()    { return mPhishyUserPassLength; }
     uint8_t        GetQoSBits()              { return mQoSBits; }
     uint16_t       GetIdleSynTimeout()       { return mIdleSynTimeout; }
     bool           FastFallbackToIPv4()      { return mFastFallbackToIPv4; }
+    bool           ProxyPipelining()         { return mProxyPipelining; }
     uint32_t       MaxSocketCount();
     bool           EnforceAssocReq()         { return mEnforceAssocReq; }
 
     bool           IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; }
     bool           IsTelemetryEnabled() { return mTelemetryEnabled; }
     bool           AllowExperiments() { return mTelemetryEnabled && mAllowExperiments; }
 
     bool           IsSpdyEnabled() { return mEnableSpdy; }
@@ -312,16 +314,38 @@ public:
         NotifyObservers(chan, NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC);
     }
 
     // Generates the host:port string for use in the Host: header as well as the
     // CONNECT line for proxies. This handles IPv6 literals correctly.
     static nsresult GenerateHostPort(const nsCString& host, int32_t port,
                                      nsACString& hostLine);
 
+    bool GetPipelineAggressive()     { return mPipelineAggressive; }
+    void GetMaxPipelineObjectSize(int64_t *outVal)
+    {
+        *outVal = mMaxPipelineObjectSize;
+    }
+
+    bool GetPipelineEnabled()
+    {
+        return mCapabilities & NS_HTTP_ALLOW_PIPELINING;
+    }
+
+    bool GetPipelineRescheduleOnTimeout()
+    {
+        return mPipelineRescheduleOnTimeout;
+    }
+
+    PRIntervalTime GetPipelineRescheduleTimeout()
+    {
+        return mPipelineRescheduleTimeout;
+    }
+
+    PRIntervalTime GetPipelineTimeout()   { return mPipelineReadTimeout; }
 
     SpdyInformation *SpdyInfo() { return &mSpdyInfo; }
     bool IsH2MandatorySuiteEnabled() { return mH2MandatorySuiteEnabled; }
 
     // Returns true if content-signature test pref is set such that they are
     // NOT enforced on remote newtabs.
     bool NewTabContentSignaturesDisabled()
     {
@@ -372,16 +396,17 @@ private:
     nsresult SetAccept(const char *);
     nsresult SetAcceptLanguages(const char *);
     nsresult SetAcceptEncodings(const char *, bool mIsSecure);
 
     nsresult InitConnectionMgr();
 
     void     NotifyObservers(nsIHttpChannel *chan, const char *event);
 
+    static void TimerCallback(nsITimer * aTimer, void * aClosure);
 private:
 
     // cached services
     nsMainThreadPtrHandle<nsIIOService>              mIOService;
     nsMainThreadPtrHandle<nsIStreamConverterService> mStreamConvSvc;
     nsMainThreadPtrHandle<nsICookieService>          mCookieService;
     nsMainThreadPtrHandle<nsISiteSecurityService>    mSSService;
     nsMainThreadPtrHandle<nsIThrottlingService>      mThrottlingService;
@@ -403,40 +428,51 @@ private:
     uint8_t  mReferrerLevel;
     uint8_t  mSpoofReferrerSource;
     uint8_t  mHideOnionReferrerSource;
     uint8_t  mReferrerTrimmingPolicy;
     uint8_t  mReferrerXOriginTrimmingPolicy;
     uint8_t  mReferrerXOriginPolicy;
 
     bool mFastFallbackToIPv4;
+    bool mProxyPipelining;
     PRIntervalTime mIdleTimeout;
     PRIntervalTime mSpdyTimeout;
     PRIntervalTime mResponseTimeout;
     bool mResponseTimeoutEnabled;
     uint32_t mNetworkChangedTimeout; // milliseconds
     uint16_t mMaxRequestAttempts;
     uint16_t mMaxRequestDelay;
     uint16_t mIdleSynTimeout;
 
     bool     mH2MandatorySuiteEnabled;
+    bool     mPipeliningEnabled;
     uint16_t mMaxConnections;
     uint8_t  mMaxPersistentConnectionsPerServer;
     uint8_t  mMaxPersistentConnectionsPerProxy;
+    uint16_t mMaxPipelinedRequests;
+    uint16_t mMaxOptimisticPipelinedRequests;
+    bool     mPipelineAggressive;
+    int64_t  mMaxPipelineObjectSize;
+    bool     mPipelineRescheduleOnTimeout;
+    PRIntervalTime mPipelineRescheduleTimeout;
+    PRIntervalTime mPipelineReadTimeout;
+    nsCOMPtr<nsITimer> mPipelineTestTimer;
 
     uint8_t  mRedirectionLimit;
 
     // we'll warn the user if we load an URL containing a userpass field
     // unless its length is less than this threshold.  this warning is
     // intended to protect the user against spoofing attempts that use
     // the userpass field of the URL to obscure the actual origin server.
     uint8_t  mPhishyUserPassLength;
 
     uint8_t  mQoSBits;
 
+    bool mPipeliningOverSSL;
     bool mEnforceAssocReq;
 
     nsCString mAccept;
     nsCString mAcceptLanguages;
     nsCString mHttpAcceptEncodings;
     nsCString mHttpsAcceptEncodings;
 
     nsXPIDLCString mDefaultSocketType;
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -0,0 +1,912 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttpPipeline.h"
+#include "nsHttpHandler.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+#include "nsIPipe.h"
+#include "nsCOMPtr.h"
+#include "nsSocketTransportService2.h"
+#include <algorithm>
+
+#ifdef DEBUG
+#include "prthread.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpPushBackWriter
+//-----------------------------------------------------------------------------
+
+class nsHttpPushBackWriter : public nsAHttpSegmentWriter
+{
+public:
+    nsHttpPushBackWriter(const char *buf, uint32_t bufLen)
+        : mBuf(buf)
+        , mBufLen(bufLen)
+        { }
+    virtual ~nsHttpPushBackWriter() {}
+
+    nsresult OnWriteSegment(char *buf, uint32_t count, uint32_t *countWritten)
+    {
+        if (mBufLen == 0)
+            return NS_BASE_STREAM_CLOSED;
+
+        if (count > mBufLen)
+            count = mBufLen;
+
+        memcpy(buf, mBuf, count);
+
+        mBuf += count;
+        mBufLen -= count;
+        *countWritten = count;
+        return NS_OK;
+    }
+
+private:
+    const char *mBuf;
+    uint32_t    mBufLen;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline <public>
+//-----------------------------------------------------------------------------
+
+nsHttpPipeline::nsHttpPipeline()
+    : mStatus(NS_OK)
+    , mRequestIsPartial(false)
+    , mResponseIsPartial(false)
+    , mClosed(false)
+    , mUtilizedPipeline(false)
+    , mPushBackBuf(nullptr)
+    , mPushBackLen(0)
+    , mPushBackMax(0)
+    , mHttp1xTransactionCount(0)
+    , mReceivingFromProgress(0)
+    , mSendingToProgress(0)
+    , mSuppressSendEvents(true)
+{
+}
+
+nsHttpPipeline::~nsHttpPipeline()
+{
+    // make sure we aren't still holding onto any transactions!
+    Close(NS_ERROR_ABORT);
+
+    if (mPushBackBuf)
+        free(mPushBackBuf);
+}
+
+nsresult
+nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans)
+{
+    LOG(("nsHttpPipeline::AddTransaction [this=%p trans=%p]\n", this, trans));
+
+    if (mRequestQ.Length() || mResponseQ.Length())
+        mUtilizedPipeline = true;
+
+    // A reference to the actual transaction is held by the pipeline transaction
+    // in either the request or response queue
+    mRequestQ.AppendElement(trans);
+    uint32_t qlen = PipelineDepth();
+
+    if (qlen != 1) {
+        trans->SetPipelinePosition(qlen);
+    }
+    else {
+        // do it for this case in case an idempotent cancellation
+        // is being repeated and an old value needs to be cleared
+        trans->SetPipelinePosition(0);
+    }
+
+    // trans->SetConnection() needs to be updated to point back at
+    // the pipeline object.
+    trans->SetConnection(this);
+
+    if (mConnection && !mClosed && mRequestQ.Length() == 1)
+        mConnection->ResumeSend();
+
+    return NS_OK;
+}
+
+uint32_t
+nsHttpPipeline::PipelineDepth()
+{
+    return mRequestQ.Length() + mResponseQ.Length();
+}
+
+nsresult
+nsHttpPipeline::SetPipelinePosition(int32_t position)
+{
+    nsAHttpTransaction *trans = Response(0);
+    if (trans)
+        return trans->SetPipelinePosition(position);
+    return NS_OK;
+}
+
+int32_t
+nsHttpPipeline::PipelinePosition()
+{
+    nsAHttpTransaction *trans = Response(0);
+    if (trans)
+        return trans->PipelinePosition();
+
+    // The response queue is empty, so return oldest request
+    if (mRequestQ.Length())
+        return Request(mRequestQ.Length() - 1)->PipelinePosition();
+
+    // No transactions in the pipeline
+    return 0;
+}
+
+nsHttpPipeline *
+nsHttpPipeline::QueryPipeline()
+{
+    return this;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsHttpPipeline)
+NS_IMPL_RELEASE(nsHttpPipeline)
+
+// multiple inheritance fun :-)
+NS_INTERFACE_MAP_BEGIN(nsHttpPipeline)
+    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans,
+                                   nsHttpRequestHead *requestHead,
+                                   nsHttpResponseHead *responseHead,
+                                   bool *reset)
+{
+    LOG(("nsHttpPipeline::OnHeadersAvailable [this=%p]\n", this));
+
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(mConnection, "no connection");
+
+    RefPtr<nsHttpConnectionInfo> ci;
+    GetConnectionInfo(getter_AddRefs(ci));
+    MOZ_ASSERT(ci);
+
+    if (!ci) {
+        return NS_ERROR_UNEXPECTED;
+    }
+
+    bool pipeliningBefore = gHttpHandler->ConnMgr()->SupportsPipelining(ci);
+
+    // trans has now received its response headers; forward to the real connection
+    nsresult rv = mConnection->OnHeadersAvailable(trans,
+                                                  requestHead,
+                                                  responseHead,
+                                                  reset);
+
+    if (!pipeliningBefore && gHttpHandler->ConnMgr()->SupportsPipelining(ci)) {
+        // The received headers have expanded the eligible
+        // pipeline depth for this connection
+        gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci);
+    }
+
+    return rv;
+}
+
+void
+nsHttpPipeline::CloseTransaction(nsAHttpTransaction *aTrans, nsresult reason)
+{
+    LOG(("nsHttpPipeline::CloseTransaction [this=%p trans=%p reason=%" PRIx32 "]\n",
+         this, aTrans, static_cast<uint32_t>(reason)));
+
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(NS_FAILED(reason), "expecting failure code");
+
+    // the specified transaction is to be closed with the given "reason"
+    RefPtr<nsAHttpTransaction> trans(aTrans);
+    int32_t index;
+    bool killPipeline = false;
+
+    if ((index = mRequestQ.IndexOf(trans)) >= 0) {
+        if (index == 0 && mRequestIsPartial) {
+            // the transaction is in the request queue.  check to see if any of
+            // its data has been written out yet.
+            killPipeline = true;
+        }
+        mRequestQ.RemoveElementAt(index);
+    } else if ((index = mResponseQ.IndexOf(trans)) >= 0) {
+        mResponseQ.RemoveElementAt(index);
+        // while we could avoid killing the pipeline if this transaction is the
+        // last transaction in the pipeline, there doesn't seem to be that much
+        // value in doing so.  most likely if this transaction is going away,
+        // the others will be shortly as well.
+        killPipeline = true;
+    }
+
+    // Marking this connection as non-reusable prevents other items from being
+    // added to it and causes it to be torn down soon.
+    DontReuse();
+
+    trans->Close(reason);
+    trans = nullptr;
+
+    if (killPipeline) {
+        // reschedule anything from this pipeline onto a different connection
+        CancelPipeline(reason);
+    }
+
+    // If all the transactions have been removed then we can close the connection
+    // right away.
+    if (!mRequestQ.Length() && !mResponseQ.Length() && mConnection)
+        mConnection->CloseTransaction(this, reason);
+}
+
+nsresult
+nsHttpPipeline::TakeTransport(nsISocketTransport  **aTransport,
+                              nsIAsyncInputStream **aInputStream,
+                              nsIAsyncOutputStream **aOutputStream)
+{
+    return mConnection->TakeTransport(aTransport, aInputStream, aOutputStream);
+}
+
+bool
+nsHttpPipeline::IsPersistent()
+{
+    return true; // pipelining requires this
+}
+
+bool
+nsHttpPipeline::IsReused()
+{
+    if (!mUtilizedPipeline && mConnection)
+        return mConnection->IsReused();
+    return true;
+}
+
+void
+nsHttpPipeline::DontReuse()
+{
+    if (mConnection)
+        mConnection->DontReuse();
+}
+
+nsresult
+nsHttpPipeline::PushBack(const char *data, uint32_t length)
+{
+    LOG(("nsHttpPipeline::PushBack [this=%p len=%u]\n", this, length));
+
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(mPushBackLen == 0, "push back buffer already has data!");
+
+    // If we have no chance for a pipeline (e.g. due to an Upgrade)
+    // then push this data down to original connection
+    if (!mConnection->IsPersistent())
+        return mConnection->PushBack(data, length);
+
+    // PushBack is called recursively from WriteSegments
+
+    // XXX we have a design decision to make here.  either we buffer the data
+    // and process it when we return to WriteSegments, or we attempt to move
+    // onto the next transaction from here.  doing so adds complexity with the
+    // benefit of eliminating the extra buffer copy.  the buffer is at most
+    // 4096 bytes, so it is really unclear if there is any value in the added
+    // complexity.  besides simplicity, buffering this data has the advantage
+    // that we'll call close on the transaction sooner, which will wake up
+    // the HTTP channel sooner to continue with its work.
+
+    if (!mPushBackBuf) {
+        mPushBackMax = length;
+        mPushBackBuf = (char *) malloc(mPushBackMax);
+        if (!mPushBackBuf)
+            return NS_ERROR_OUT_OF_MEMORY;
+    }
+    else if (length > mPushBackMax) {
+        // grow push back buffer as necessary.
+        MOZ_ASSERT(length <= nsIOService::gDefaultSegmentSize, "too big");
+        mPushBackMax = length;
+        mPushBackBuf = (char *) realloc(mPushBackBuf, mPushBackMax);
+        if (!mPushBackBuf)
+            return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    memcpy(mPushBackBuf, data, length);
+    mPushBackLen = length;
+
+    return NS_OK;
+}
+
+already_AddRefed<nsHttpConnection>
+nsHttpPipeline::TakeHttpConnection()
+{
+    if (mConnection)
+        return mConnection->TakeHttpConnection();
+    return nullptr;
+}
+
+nsAHttpTransaction::Classifier
+nsHttpPipeline::Classification()
+{
+    if (mConnection)
+        return mConnection->Classification();
+
+    LOG(("nsHttpPipeline::Classification this=%p "
+         "has null mConnection using CLASS_SOLO default", this));
+    return nsAHttpTransaction::CLASS_SOLO;
+}
+
+void
+nsHttpPipeline::SetProxyConnectFailed()
+{
+    nsAHttpTransaction *trans = Request(0);
+
+    if (trans)
+        trans->SetProxyConnectFailed();
+}
+
+nsHttpRequestHead *
+nsHttpPipeline::RequestHead()
+{
+    nsAHttpTransaction *trans = Request(0);
+
+    if (trans)
+        return trans->RequestHead();
+    return nullptr;
+}
+
+uint32_t
+nsHttpPipeline::Http1xTransactionCount()
+{
+  return mHttp1xTransactionCount;
+}
+
+nsresult
+nsHttpPipeline::TakeSubTransactions(
+    nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
+{
+    LOG(("nsHttpPipeline::TakeSubTransactions [this=%p]\n", this));
+
+    if (mResponseQ.Length() || mRequestIsPartial)
+        return NS_ERROR_ALREADY_OPENED;
+
+    int32_t i, count = mRequestQ.Length();
+    for (i = 0; i < count; ++i) {
+        nsAHttpTransaction *trans = Request(i);
+        // set the transaction connection object back to the underlying
+        // nsHttpConnectionHandle
+        trans->SetConnection(mConnection);
+        outTransactions.AppendElement(trans);
+    }
+    mRequestQ.Clear();
+
+    LOG(("   took %d\n", count));
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpPipeline::nsAHttpTransaction
+//-----------------------------------------------------------------------------
+
+void
+nsHttpPipeline::SetConnection(nsAHttpConnection *conn)
+{
+    LOG(("nsHttpPipeline::SetConnection [this=%p conn=%p]\n", this, conn));
+
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(!conn || !mConnection, "already have a connection");
+
+    mConnection = conn;
+}
+
+nsAHttpConnection *
+nsHttpPipeline::Connection()
+{
+    LOG(("nsHttpPipeline::Connection [this=%p conn=%p]\n", this, mConnection.get()));
+
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    return mConnection;
+}
+
+void
+nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    // depending on timing this could be either the request or the response
+    // that is needed - but they both go to the same host. A request for these
+    // callbacks directly in nsHttpTransaction would not make a distinction
+    // over whether the the request had been transmitted yet.
+    nsAHttpTransaction *trans = Request(0);
+    if (!trans)
+        trans = Response(0);
+    if (trans)
+        trans->GetSecurityCallbacks(result);
+    else {
+        *result = nullptr;
+    }
+}
+
+void
+nsHttpPipeline::OnTransportStatus(nsITransport* transport,
+                                  nsresult status, int64_t progress)
+{
+    LOG(("nsHttpPipeline::OnStatus [this=%p status=%" PRIx32 " progress=%" PRId64 "]\n",
+         this, static_cast<uint32_t>(status), progress));
+
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    nsAHttpTransaction *trans;
+    int32_t i, count;
+
+    switch (status) {
+
+    case NS_NET_STATUS_RESOLVING_HOST:
+    case NS_NET_STATUS_RESOLVED_HOST:
+    case NS_NET_STATUS_CONNECTING_TO:
+    case NS_NET_STATUS_CONNECTED_TO:
+        // These should only appear at most once per pipeline.
+        // Deliver to the first transaction.
+
+        trans = Request(0);
+        if (!trans)
+            trans = Response(0);
+        if (trans)
+            trans->OnTransportStatus(transport, status, progress);
+
+        break;
+
+    case NS_NET_STATUS_SENDING_TO:
+        // This is generated by the socket transport when (part) of
+        // a transaction is written out
+        //
+        // In pipelining this is generated out of FillSendBuf(), but it cannot do
+        // so until the connection is confirmed by CONNECTED_TO.
+        // See patch for bug 196827.
+        //
+
+        if (mSuppressSendEvents) {
+            mSuppressSendEvents = false;
+
+            // catch up by sending the event to all the transactions that have
+            // moved from request to response and any that have been partially
+            // sent. Also send WAITING_FOR to those that were completely sent
+            count = mResponseQ.Length();
+            for (i = 0; i < count; ++i) {
+                Response(i)->OnTransportStatus(transport,
+                                               NS_NET_STATUS_SENDING_TO,
+                                               progress);
+                Response(i)->OnTransportStatus(transport,
+                                               NS_NET_STATUS_WAITING_FOR,
+                                               progress);
+            }
+            if (mRequestIsPartial && Request(0))
+                Request(0)->OnTransportStatus(transport,
+                                              NS_NET_STATUS_SENDING_TO,
+                                              progress);
+            mSendingToProgress = progress;
+        }
+        // otherwise ignore it
+        break;
+
+    case NS_NET_STATUS_WAITING_FOR:
+        // Created by nsHttpConnection when request pipeline has been totally
+        // sent. Ignore it here because it is simulated in FillSendBuf() when
+        // a request is moved from request to response.
+
+        // ignore it
+        break;
+
+    case NS_NET_STATUS_RECEIVING_FROM:
+        // Forward this only to the transaction currently recieving data. It is
+        // normally generated by the socket transport, but can also
+        // be repeated by the pushbackwriter if necessary.
+        mReceivingFromProgress = progress;
+        if (Response(0))
+            Response(0)->OnTransportStatus(transport, status, progress);
+        break;
+
+    default:
+        // forward other notifications to all request transactions
+        count = mRequestQ.Length();
+        for (i = 0; i < count; ++i)
+            Request(i)->OnTransportStatus(transport, status, progress);
+        break;
+    }
+}
+
+nsHttpConnectionInfo *
+nsHttpPipeline::ConnectionInfo()
+{
+    nsAHttpTransaction *trans = Request(0) ? Request(0) : Response(0);
+    if (!trans) {
+        return nullptr;
+    }
+    return trans->ConnectionInfo();
+}
+
+bool
+nsHttpPipeline::IsDone()
+{
+    bool done = true;
+
+    uint32_t i, count = mRequestQ.Length();
+    for (i = 0; done && (i < count); i++)
+        done = Request(i)->IsDone();
+
+    count = mResponseQ.Length();
+    for (i = 0; done && (i < count); i++)
+        done = Response(i)->IsDone();
+
+    return done;
+}
+
+nsresult
+nsHttpPipeline::Status()
+{
+    return mStatus;
+}
+
+uint32_t
+nsHttpPipeline::Caps()
+{
+    nsAHttpTransaction *trans = Request(0);
+    if (!trans)
+        trans = Response(0);
+
+    return trans ? trans->Caps() : 0;
+}
+
+void
+nsHttpPipeline::SetDNSWasRefreshed()
+{
+    nsAHttpTransaction *trans = Request(0);
+    if (!trans)
+        trans = Response(0);
+
+    if (trans)
+      trans->SetDNSWasRefreshed();
+}
+
+uint64_t
+nsHttpPipeline::Available()
+{
+    uint64_t result = 0;
+
+    int32_t i, count = mRequestQ.Length();
+    for (i=0; i<count; ++i)
+        result += Request(i)->Available();
+    return result;
+}
+
+nsresult
+nsHttpPipeline::ReadFromPipe(nsIInputStream *stream,
+                             void *closure,
+                             const char *buf,
+                             uint32_t offset,
+                             uint32_t count,
+                             uint32_t *countRead)
+{
+    nsHttpPipeline *self = (nsHttpPipeline *) closure;
+    return self->mReader->OnReadSegment(buf, count, countRead);
+}
+
+nsresult
+nsHttpPipeline::ReadSegments(nsAHttpSegmentReader *reader,
+                             uint32_t count,
+                             uint32_t *countRead)
+{
+    LOG(("nsHttpPipeline::ReadSegments [this=%p count=%u]\n", this, count));
+
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    if (mClosed) {
+        *countRead = 0;
+        return mStatus;
+    }
+
+    nsresult rv;
+    uint64_t avail = 0;
+    if (mSendBufIn) {
+        rv = mSendBufIn->Available(&avail);
+        if (NS_FAILED(rv)) return rv;
+    }
+
+    if (avail == 0) {
+        rv = FillSendBuf();
+        if (NS_FAILED(rv)) return rv;
+
+        rv = mSendBufIn->Available(&avail);
+        if (NS_FAILED(rv)) return rv;
+
+        // return EOF if send buffer is empty
+        if (avail == 0) {
+            *countRead = 0;
+            return NS_OK;
+        }
+    }
+
+    // read no more than what was requested
+    if (avail > count)
+        avail = count;
+
+    mReader = reader;
+
+    // avail is under 4GB, so casting to uint32_t is safe
+    rv = mSendBufIn->ReadSegments(ReadFromPipe, this, (uint32_t)avail, countRead);
+
+    mReader = nullptr;
+    return rv;
+}
+
+nsresult
+nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer,
+                              uint32_t count,
+                              uint32_t *countWritten)
+{
+    LOG(("nsHttpPipeline::WriteSegments [this=%p count=%u]\n", this, count));
+
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    if (mClosed)
+        return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
+
+    nsAHttpTransaction *trans;
+    nsresult rv;
+
+    trans = Response(0);
+    // This code deals with the establishment of a CONNECT tunnel through
+    // an HTTP proxy. It allows the connection to do the CONNECT/200
+    // HTTP transaction to establish a tunnel as a precursor to the
+    // actual pipeline of regular HTTP transactions.
+    if (!trans && mRequestQ.Length() &&
+        mConnection->IsProxyConnectInProgress()) {
+        LOG(("nsHttpPipeline::WriteSegments [this=%p] Forced Delegation\n",
+             this));
+        trans = Request(0);
+    }
+
+    if (!trans) {
+        if (mRequestQ.Length() > 0)
+            rv = NS_BASE_STREAM_WOULD_BLOCK;
+        else
+            rv = NS_BASE_STREAM_CLOSED;
+    } else {
+        //
+        // ask the transaction to consume data from the connection.
+        // PushBack may be called recursively.
+        //
+        rv = trans->WriteSegments(writer, count, countWritten);
+
+        if (rv == NS_BASE_STREAM_CLOSED || trans->IsDone()) {
+            trans->Close(NS_OK);
+
+            // Release the transaction if it is not IsProxyConnectInProgress()
+            if (trans == Response(0)) {
+                mResponseQ.RemoveElementAt(0);
+                mResponseIsPartial = false;
+                ++mHttp1xTransactionCount;
+            }
+
+            // ask the connection manager to add additional transactions
+            // to our pipeline.
+            RefPtr<nsHttpConnectionInfo> ci;
+            GetConnectionInfo(getter_AddRefs(ci));
+            if (ci)
+                gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci);
+        }
+        else
+            mResponseIsPartial = true;
+    }
+
+    if (mPushBackLen) {
+        nsHttpPushBackWriter pushBackWriter(mPushBackBuf, mPushBackLen);
+        uint32_t len = mPushBackLen, n;
+        mPushBackLen = 0;
+
+        // This progress notification has previously been sent from
+        // the socket transport code, but it was delivered to the
+        // previous transaction on the pipeline.
+        nsITransport *transport = Transport();
+        if (transport)
+            OnTransportStatus(transport, NS_NET_STATUS_RECEIVING_FROM,
+                              mReceivingFromProgress);
+
+        // the push back buffer is never larger than NS_HTTP_SEGMENT_SIZE,
+        // so we are guaranteed that the next response will eat the entire
+        // push back buffer (even though it might again call PushBack).
+        rv = WriteSegments(&pushBackWriter, len, &n);
+    }
+
+    return rv;
+}
+
+uint32_t
+nsHttpPipeline::CancelPipeline(nsresult originalReason)
+{
+    uint32_t i, reqLen, respLen, total;
+    nsAHttpTransaction *trans;
+
+    reqLen = mRequestQ.Length();
+    respLen = mResponseQ.Length();
+    total = reqLen + respLen;
+
+    // don't count the first response, if presnet
+    if (respLen)
+        total--;
+
+    if (!total)
+        return 0;
+
+    // any pending requests can ignore this error and be restarted
+    // unless it is during a CONNECT tunnel request
+    for (i = 0; i < reqLen; ++i) {
+        trans = Request(i);
+        if (mConnection && mConnection->IsProxyConnectInProgress())
+            trans->Close(originalReason);
+        else
+            trans->Close(NS_ERROR_NET_RESET);
+    }
+    mRequestQ.Clear();
+
+    // any pending responses can be restarted except for the first one,
+    // that we might want to finish on this pipeline or cancel individually.
+    // Higher levels of callers ensure that we don't process non-idempotent
+    // tranasction with the NS_HTTP_ALLOW_PIPELINING bit set
+    for (i = 1; i < respLen; ++i) {
+        trans = Response(i);
+        trans->Close(NS_ERROR_NET_RESET);
+    }
+
+    if (respLen > 1)
+        mResponseQ.TruncateLength(1);
+
+    DontReuse();
+    Classify(nsAHttpTransaction::CLASS_SOLO);
+
+    return total;
+}
+
+void
+nsHttpPipeline::Close(nsresult reason)
+{
+    LOG(("nsHttpPipeline::Close [this=%p reason=%" PRIx32 "]\n",
+         this, static_cast<uint32_t>(reason)));
+
+    if (mClosed) {
+        LOG(("  already closed\n"));
+        return;
+    }
+
+    // the connection is going away!
+    mStatus = reason;
+    mClosed = true;
+
+    RefPtr<nsHttpConnectionInfo> ci;
+    GetConnectionInfo(getter_AddRefs(ci));
+    uint32_t numRescheduled = CancelPipeline(reason);
+
+    // numRescheduled can be 0 if there is just a single response in the
+    // pipeline object. That isn't really a meaningful pipeline that
+    // has been forced to be rescheduled so it does not need to generate
+    // negative feedback.
+    if (ci && numRescheduled)
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            ci, nsHttpConnectionMgr::RedCanceledPipeline, nullptr, 0);
+
+    nsAHttpTransaction *trans = Response(0);
+    if (!trans)
+        return;
+
+    // The current transaction can be restarted via reset
+    // if the response has not started to arrive and the reason
+    // for failure is innocuous (e.g. not an SSL error)
+    if (!mResponseIsPartial &&
+        (reason == NS_ERROR_NET_RESET ||
+         reason == NS_OK ||
+         reason == NS_ERROR_NET_TIMEOUT ||
+         reason == NS_BASE_STREAM_CLOSED)) {
+        trans->Close(NS_ERROR_NET_RESET);
+    }
+    else {
+        trans->Close(reason);
+    }
+
+    mResponseQ.Clear();
+}
+
+nsresult
+nsHttpPipeline::OnReadSegment(const char *segment,
+                              uint32_t count,
+                              uint32_t *countRead)
+{
+    return mSendBufOut->Write(segment, count, countRead);
+}
+
+nsresult
+nsHttpPipeline::FillSendBuf()
+{
+    // reads from request queue, moving transactions to response queue
+    // when they have been completely read.
+
+    nsresult rv;
+
+    if (!mSendBufIn) {
+        // allocate a single-segment pipe
+        rv = NS_NewPipe(getter_AddRefs(mSendBufIn),
+                        getter_AddRefs(mSendBufOut),
+                        nsIOService::gDefaultSegmentSize,  /* segment size */
+                        nsIOService::gDefaultSegmentSize,  /* max size */
+                        true, true);
+        if (NS_FAILED(rv)) return rv;
+    }
+
+    uint32_t n;
+    uint64_t avail;
+    RefPtr<nsAHttpTransaction> trans;
+    nsITransport *transport = Transport();
+
+    while ((trans = Request(0)) != nullptr) {
+        avail = trans->Available();
+        if (avail) {
+            // if there is already a response in the responseq then this
+            // new data comprises a pipeline. Update the transaction in the
+            // response queue to reflect that if necessary. We are now sending
+            // out a request while we haven't received all responses.
+            nsAHttpTransaction *response = Response(0);
+            if (response && !response->PipelinePosition())
+                response->SetPipelinePosition(1);
+            rv = trans->ReadSegments(this, (uint32_t)std::min(avail, (uint64_t)UINT32_MAX), &n);
+            if (NS_FAILED(rv)) return rv;
+
+            if (n == 0) {
+                LOG(("send pipe is full"));
+                break;
+            }
+
+            mSendingToProgress += n;
+            if (!mSuppressSendEvents && transport) {
+                // Simulate a SENDING_TO event
+                trans->OnTransportStatus(transport,
+                                         NS_NET_STATUS_SENDING_TO,
+                                         mSendingToProgress);
+            }
+        }
+
+        avail = trans->Available();
+        if (avail == 0) {
+            // move transaction from request queue to response queue
+            mRequestQ.RemoveElementAt(0);
+            mResponseQ.AppendElement(trans);
+            mRequestIsPartial = false;
+
+            if (!mSuppressSendEvents && transport) {
+                // Simulate a WAITING_FOR event
+                trans->OnTransportStatus(transport,
+                                         NS_NET_STATUS_WAITING_FOR,
+                                         mSendingToProgress);
+            }
+
+            // It would be good to re-enable data read handlers via ResumeRecv()
+            // except the read handler code can be synchronously dispatched on
+            // the stack.
+        }
+        else
+            mRequestIsPartial = true;
+    }
+    return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpPipeline.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHttpPipeline_h__
+#define nsHttpPipeline_h__
+
+#include "nsAHttpConnection.h"
+#include "nsAHttpTransaction.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla { namespace net {
+
+class nsHttpPipeline final : public nsAHttpConnection
+                           , public nsAHttpTransaction
+                           , public nsAHttpSegmentReader
+{
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSAHTTPCONNECTION(mConnection)
+    NS_DECL_NSAHTTPTRANSACTION
+    NS_DECL_NSAHTTPSEGMENTREADER
+
+    nsHttpPipeline();
+
+  bool ResponseTimeoutEnabled() const override final {
+    return true;
+  }
+
+private:
+    virtual ~nsHttpPipeline();
+
+    nsresult FillSendBuf();
+
+    static nsresult ReadFromPipe(nsIInputStream *, void *, const char *,
+                                 uint32_t, uint32_t, uint32_t *);
+
+    // convenience functions
+    nsAHttpTransaction *Request(int32_t i)
+    {
+        if (mRequestQ.Length() == 0)
+            return nullptr;
+
+        return mRequestQ[i];
+    }
+    nsAHttpTransaction *Response(int32_t i)
+    {
+        if (mResponseQ.Length() == 0)
+            return nullptr;
+
+        return mResponseQ[i];
+    }
+
+    // overload of nsAHttpTransaction::QueryPipeline()
+    nsHttpPipeline *QueryPipeline() override;
+
+    RefPtr<nsAHttpConnection>   mConnection;
+    nsTArray<RefPtr<nsAHttpTransaction> > mRequestQ;
+    nsTArray<RefPtr<nsAHttpTransaction> > mResponseQ;
+    nsresult                      mStatus;
+
+    // these flags indicate whether or not the first request or response
+    // is partial.  a partial request means that Request(0) has been
+    // partially written out to the socket.  a partial response means
+    // that Response(0) has been partially read in from the socket.
+    bool mRequestIsPartial;
+    bool mResponseIsPartial;
+
+    // indicates whether or not the pipeline has been explicitly closed.
+    bool mClosed;
+
+    // indicates whether or not a true pipeline (more than 1 request without
+    // a synchronous response) has been formed.
+    bool mUtilizedPipeline;
+
+    // used when calling ReadSegments/WriteSegments on a transaction.
+    nsAHttpSegmentReader *mReader;
+
+    // send buffer
+    nsCOMPtr<nsIInputStream>  mSendBufIn;
+    nsCOMPtr<nsIOutputStream> mSendBufOut;
+
+    // the push back buffer.  not exceeding nsIOService::gDefaultSegmentSize bytes.
+    char     *mPushBackBuf;
+    uint32_t  mPushBackLen;
+    uint32_t  mPushBackMax;
+
+    // The number of transactions completed on this pipeline.
+    uint32_t  mHttp1xTransactionCount;
+
+    // For support of OnTransportStatus()
+    int64_t  mReceivingFromProgress;
+    int64_t  mSendingToProgress;
+    bool     mSuppressSendEvents;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpPipeline_h__
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -99,16 +99,18 @@ nsHttpTransaction::nsHttpTransaction()
     , mInvalidResponseBytesRead(0)
     , mPushedStream(nullptr)
     , mInitialRwin(0)
     , mChunkedDecoder(nullptr)
     , mStatus(NS_OK)
     , mPriority(0)
     , mRestartCount(0)
     , mCaps(0)
+    , mClassification(CLASS_GENERAL)
+    , mPipelinePosition(0)
     , mHttpVersion(NS_HTTP_VERSION_UNKNOWN)
     , mHttpResponseCode(0)
     , mCurrentHttpResponseHeaderSize(0)
     , mCapsToClear(0)
     , mResponseIsComplete(false)
     , mClosed(false)
     , mConnected(false)
     , mHaveStatusLine(false)
@@ -138,16 +140,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mSubmittedRatePacing(false)
     , mPassedRatePacing(false)
     , mSynchronousRatePaceRequest(false)
     , mClassOfService(0)
     , m0RTTInProgress(false)
     , mTransportStatus(NS_OK)
 {
     LOG(("Creating nsHttpTransaction @%p\n", this));
+    gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize);
 
 #ifdef MOZ_VALGRIND
     memset(&mSelfAddr, 0, sizeof(NetAddr));
     memset(&mPeerAddr, 0, sizeof(NetAddr));
 #endif
     mSelfAddr.raw.family = PR_AF_UNSPEC;
     mPeerAddr.raw.family = PR_AF_UNSPEC;
 }
@@ -173,16 +176,55 @@ nsHttpTransaction::~nsHttpTransaction()
     mConnection = nullptr;
 
     delete mResponseHead;
     delete mForTakeResponseHead;
     delete mChunkedDecoder;
     ReleaseBlockingTransaction();
 }
 
+nsHttpTransaction::Classifier
+nsHttpTransaction::Classify()
+{
+    if (!(mCaps & NS_HTTP_ALLOW_PIPELINING))
+        return (mClassification = CLASS_SOLO);
+
+    if (mRequestHead->HasHeader(nsHttp::If_Modified_Since) ||
+        mRequestHead->HasHeader(nsHttp::If_None_Match))
+        return (mClassification = CLASS_REVALIDATION);
+
+    nsAutoCString accept;
+    bool hasAccept = NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Accept, accept));
+    if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("image/"))) {
+        return (mClassification = CLASS_IMAGE);
+    }
+
+    if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("text/css"))) {
+        return (mClassification = CLASS_SCRIPT);
+    }
+
+    mClassification = CLASS_GENERAL;
+
+    nsAutoCString requestURI;
+    mRequestHead->RequestURI(requestURI);
+    int32_t queryPos = requestURI.FindChar('?');
+    if (queryPos == kNotFound) {
+        if (StringEndsWith(requestURI,
+                           NS_LITERAL_CSTRING(".js")))
+            mClassification = CLASS_SCRIPT;
+    }
+    else if (queryPos >= 3 &&
+             Substring(requestURI, queryPos - 3, 3).
+             EqualsLiteral(".js")) {
+        mClassification = CLASS_SCRIPT;
+    }
+
+    return mClassification;
+}
+
 nsresult
 nsHttpTransaction::Init(uint32_t caps,
                         nsHttpConnectionInfo *cinfo,
                         nsHttpRequestHead *requestHead,
                         nsIInputStream *requestBody,
                         bool requestBodyHasHeaders,
                         nsIEventTarget *target,
                         nsIInterfaceRequestor *callbacks,
@@ -368,16 +410,18 @@ nsHttpTransaction::Init(uint32_t caps,
     if (NS_FAILED(rv)) return rv;
 
 #ifdef WIN32 // bug 1153929
     MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
     uint32_t * vtable = (uint32_t *) mPipeOut.get();
     MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
 #endif // WIN32
 
+    Classify();
+
     nsCOMPtr<nsIAsyncInputStream> tmp(mPipeIn);
     tmp.forget(responseBody);
     return NS_OK;
 }
 
 // This method should only be used on the socket thread
 nsAHttpConnection *
 nsHttpTransaction::Connection()
@@ -394,17 +438,17 @@ nsHttpTransaction::GetConnectionReferenc
     return connection.forget();
 }
 
 nsHttpResponseHead *
 nsHttpTransaction::TakeResponseHead()
 {
     MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
 
-    // Lock TakeResponseHead() against main thread
+    // Lock RestartInProgress() and TakeResponseHead() against main thread
     MutexAutoLock lock(*nsHttp::GetLock());
 
     mResponseHeadTaken = true;
 
     // Prefer mForTakeResponseHead over mResponseHead. It is always a complete
     // set of headers.
     nsHttpResponseHead *head;
     if (mForTakeResponseHead) {
@@ -577,25 +621,26 @@ nsHttpTransaction::OnTransportStatus(nsI
             (status == NS_NET_STATUS_WAITING_FOR))
             mActivityDistributor->ObserveActivity(
                 mChannel,
                 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
                 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT,
                 PR_Now(), 0, EmptyCString());
 
         // report the status and progress
-        mActivityDistributor->ObserveActivity(
-            mChannel,
-            NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
-            static_cast<uint32_t>(status),
-            PR_Now(),
-            progress,
-            EmptyCString());
+        if (!mRestartInProgressVerifier.IsDiscardingContent())
+            mActivityDistributor->ObserveActivity(
+                mChannel,
+                NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
+                static_cast<uint32_t>(status),
+                PR_Now(),
+                progress,
+                EmptyCString());
     }
-    
+
     // nsHttpChannel synthesizes progress events in OnDataAvailable
     if (status == NS_NET_STATUS_RECEIVING_FROM)
         return;
 
     int64_t progressMax;
 
     if (status == NS_NET_STATUS_SENDING_TO) {
         // suppress progress when only writing request headers
@@ -979,19 +1024,34 @@ nsHttpTransaction::Close(nsresult reason
 
         if (reason == psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
             (!mReceivedData &&
             ((mRequestHead && mRequestHead->IsSafeMethod()) ||
              !reallySentData || connReused))) {
             // if restarting fails, then we must proceed to close the pipe,
             // which will notify the channel that the transaction failed.
 
+            if (mPipelinePosition) {
+                gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                    mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline,
+                    nullptr, 0);
+            }
             if (NS_SUCCEEDED(Restart()))
                 return;
         }
+        else if (!mResponseIsComplete && mPipelinePosition &&
+                 reason == NS_ERROR_NET_RESET) {
+            // due to unhandled rst on a pipeline - safe to
+            // restart as only idempotent is found there
+
+            gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, nullptr, 0);
+            if (NS_SUCCEEDED(RestartInProgress()))
+                return;
+        }
     }
 
     if ((mChunkedDecoder || (mContentLength >= int64_t(0))) &&
         (NS_SUCCEEDED(reason) && !mResponseIsComplete)) {
 
         NS_WARNING("Partial transfer, incomplete HTTP response received");
 
         if ((mHttpResponseCode / 100 == 2) &&
@@ -1012,16 +1072,30 @@ nsHttpTransaction::Close(nsresult reason
             // whether or not we generate an error for the transaction
             // bad framing means we don't want a pconn
             mConnection->DontReuse();
         }
     }
 
     bool relConn = true;
     if (NS_SUCCEEDED(reason)) {
+        if (!mResponseIsComplete) {
+            // The response has not been delimited with a high-confidence
+            // algorithm like Content-Length or Chunked Encoding. We
+            // need to use a strong framing mechanism to pipeline.
+            gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
+                nullptr, mClassification);
+        }
+        else if (mPipelinePosition) {
+            // report this success as feedback
+            gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                mConnInfo, nsHttpConnectionMgr::GoodCompletedOK,
+                nullptr, mPipelinePosition);
+        }
 
         // the server has not sent the final \r\n terminating the header
         // section, and there may still be a header line unparsed.  let's make
         // sure we parse the remaining header line, and then hopefully, the
         // response will be usable (see bug 88792).
         if (!mHaveAllHeaders) {
             char data = '\n';
             uint32_t unused;
@@ -1080,16 +1154,41 @@ nsHttpTransaction::Close(nsresult reason
 }
 
 nsHttpConnectionInfo *
 nsHttpTransaction::ConnectionInfo()
 {
     return mConnInfo.get();
 }
 
+nsresult
+nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+nsHttpTransaction::PipelineDepth()
+{
+    return IsDone() ? 0 : 1;
+}
+
+nsresult
+nsHttpTransaction::SetPipelinePosition(int32_t position)
+{
+    mPipelinePosition = position;
+    return NS_OK;
+}
+
+int32_t
+nsHttpTransaction::PipelinePosition()
+{
+    return mPipelinePosition;
+}
+
 bool // NOTE BASE CLASS
 nsAHttpTransaction::ResponseTimeoutEnabled() const
 {
     return false;
 }
 
 PRIntervalTime // NOTE BASE CLASS
 nsAHttpTransaction::ResponseTimeout()
@@ -1103,16 +1202,81 @@ nsHttpTransaction::ResponseTimeoutEnable
     return mResponseTimeoutEnabled;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpTransaction <private>
 //-----------------------------------------------------------------------------
 
 nsresult
+nsHttpTransaction::RestartInProgress()
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    if ((mRestartCount + 1) >= gHttpHandler->MaxRequestAttempts()) {
+        LOG(("nsHttpTransaction::RestartInProgress() "
+             "reached max request attempts, failing transaction %p\n", this));
+        return NS_ERROR_NET_RESET;
+    }
+
+    // Lock RestartInProgress() and TakeResponseHead() against main thread
+    MutexAutoLock lock(*nsHttp::GetLock());
+
+    // Don't try and RestartInProgress() things that haven't gotten a response
+    // header yet. Those should be handled under the normal restart() path if
+    // they are eligible.
+    if (!mHaveAllHeaders)
+        return NS_ERROR_NET_RESET;
+
+    if (mCaps & NS_HTTP_STICKY_CONNECTION) {
+        return NS_ERROR_NET_RESET;
+    }
+
+    // don't try and restart 0.9 or non 200/Get HTTP/1
+    if (!mRestartInProgressVerifier.IsSetup())
+        return NS_ERROR_NET_RESET;
+
+    LOG(("Will restart transaction %p and skip first %" PRId64 " bytes, "
+         "old Content-Length %" PRId64,
+         this, mContentRead, mContentLength));
+
+    mRestartInProgressVerifier.SetAlreadyProcessed(
+        std::max(mRestartInProgressVerifier.AlreadyProcessed(), mContentRead));
+
+    if (!mResponseHeadTaken && !mForTakeResponseHead) {
+        // TakeResponseHeader() has not been called yet and this
+        // is the first restart. Store the resp headers exclusively
+        // for TakeResponseHead() which is called from the main thread and
+        // could happen at any time - so we can't continue to modify those
+        // headers (which restarting will effectively do)
+        mForTakeResponseHead = mResponseHead;
+        mResponseHead = nullptr;
+    }
+
+    if (mResponseHead) {
+        mResponseHead->Reset();
+    }
+
+    mContentRead = 0;
+    mContentLength = -1;
+    delete mChunkedDecoder;
+    mChunkedDecoder = nullptr;
+    mHaveStatusLine = false;
+    mHaveAllHeaders = false;
+    mHttpResponseMatched = false;
+    mResponseIsComplete = false;
+    mDidContentStart = false;
+    mNoContent = false;
+    mSentData = false;
+    mReceivedData = false;
+
+    return Restart();
+}
+
+nsresult
 nsHttpTransaction::Restart()
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     // limit the number of restart attempts - bug 92224
     if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
         LOG(("reached max request attempts, failing transaction @%p\n", this));
         return NS_ERROR_NET_RESET;
@@ -1135,16 +1299,22 @@ nsHttpTransaction::Restart()
         MutexAutoLock lock(mLock);
         mConnection = nullptr;
     }
 
     // Reset this to our default state, since this may change from one restart
     // to the next
     mReuseOnRestart = false;
 
+    // disable pipelining for the next attempt in case pipelining caused the
+    // reset.  this is being overly cautious since we don't know if pipelining
+    // was the problem here.
+    mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
+    SetPipelinePosition(0);
+
     if (!mConnInfo->GetRoutedHost().IsEmpty()) {
         MutexAutoLock lock(*nsHttp::GetLock());
         RefPtr<nsHttpConnectionInfo> ci;
          mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
          mConnInfo = ci;
         if (mRequestHead) {
             mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, NS_LITERAL_CSTRING("0"));
         }
@@ -1264,16 +1434,19 @@ nsHttpTransaction::ParseLineSegment(char
         // not a continuation of the previous or if we haven't
         // parsed the status line yet, then parse the contents
         // of mLineBuf.
         mLineBuf.Truncate(mLineBuf.Length() - 1);
         if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
             nsresult rv = ParseLine(mLineBuf);
             mLineBuf.Truncate();
             if (NS_FAILED(rv)) {
+                gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                    mConnInfo, nsHttpConnectionMgr::RedCorruptedContent,
+                    nullptr, 0);
                 return rv;
             }
         }
     }
 
     // append segment to mLineBuf...
     mLineBuf.Append(segment, len);
 
@@ -1442,17 +1615,18 @@ nsHttpTransaction::HandleContentStart()
 
         // Save http version, mResponseHead isn't available anymore after
         // TakeResponseHead() is called
         mHttpVersion = mResponseHead->Version();
         mHttpResponseCode = mResponseHead->Status();
 
         // notify the connection, give it a chance to cause a reset.
         bool reset = false;
-        mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset);
+        if (!mRestartInProgressVerifier.IsSetup())
+            mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset);
 
         // looks like we should ignore this response, resetting...
         if (reset) {
             LOG(("resetting transaction's response head\n"));
             mHaveAllHeaders = false;
             mHaveStatusLine = false;
             mReceivedData = false;
             mSentData = false;
@@ -1487,23 +1661,31 @@ nsHttpTransaction::HandleContentStart()
         }
 
         if (mResponseHead->Status() == 200 &&
             mConnection->IsProxyConnectInProgress()) {
             // successful CONNECTs do not have response bodies
             mNoContent = true;
         }
         mConnection->SetLastTransactionExpectedNoContent(mNoContent);
+        if (mInvalidResponseBytesRead)
+            gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
+                nullptr, mClassification);
 
-        if (mNoContent) {
+        if (mNoContent)
             mContentLength = 0;
-        } else {
+        else {
             // grab the content-length from the response headers
             mContentLength = mResponseHead->ContentLength();
 
+            if ((mClassification != CLASS_SOLO) &&
+                (mContentLength > mMaxPipelineObjectSize))
+                CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
+
             // handle chunked encoding here, so we'll know immediately when
             // we're done with the socket.  please note that _all_ other
             // decoding is done when the channel receives the content data
             // so as not to block the socket transport thread too much.
             if (mResponseHead->Version() >= NS_HTTP_VERSION_1_0 &&
                 mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
                 // we only support the "chunked" transfer encoding right now.
                 mChunkedDecoder = new nsHttpChunkedDecoder();
@@ -1515,19 +1697,30 @@ nsHttpTransaction::HandleContentStart()
                     if (mConnection) {
                         mConnection->DontReuse();
                     }
                 }
             }
             else if (mContentLength == int64_t(-1))
                 LOG(("waiting for the server to close the connection.\n"));
         }
+        if (mRestartInProgressVerifier.IsSetup() &&
+            !mRestartInProgressVerifier.Verify(mContentLength, mResponseHead)) {
+            LOG(("Restart in progress subsequent transaction failed to match"));
+            return NS_ERROR_ABORT;
+        }
     }
 
     mDidContentStart = true;
+
+    // The verifier only initializes itself once (from the first iteration of
+    // a transaction that gets far enough to have response headers)
+    if (mRequestHead->IsGet())
+        mRestartInProgressVerifier.Set(mContentLength, mResponseHead);
+
     return NS_OK;
 }
 
 // called on the socket thread
 nsresult
 nsHttpTransaction::HandleContent(char *buf,
                                  uint32_t count,
                                  uint32_t *contentRead,
@@ -1578,24 +1771,48 @@ nsHttpTransaction::HandleContent(char *b
         }
     }
     else {
         // when we are just waiting for the server to close the connection...
         // (no explicit content-length given)
         *contentRead = count;
     }
 
+    int64_t toReadBeforeRestart =
+        mRestartInProgressVerifier.ToReadBeforeRestart();
+
+    if (toReadBeforeRestart && *contentRead) {
+        uint32_t ignore =
+            static_cast<uint32_t>(std::min<int64_t>(toReadBeforeRestart, UINT32_MAX));
+        ignore = std::min(*contentRead, ignore);
+        LOG(("Due To Restart ignoring %d of remaining %" PRId64,
+             ignore, toReadBeforeRestart));
+        *contentRead -= ignore;
+        mContentRead += ignore;
+        mRestartInProgressVerifier.HaveReadBeforeRestart(ignore);
+        memmove(buf, buf + ignore, *contentRead + *contentRemaining);
+    }
+
     if (*contentRead) {
         // update count of content bytes read and report progress...
         mContentRead += *contentRead;
     }
 
     LOG(("nsHttpTransaction::HandleContent [this=%p count=%u read=%u mContentRead=%" PRId64 " mContentLength=%" PRId64 "]\n",
         this, count, *contentRead, mContentRead, mContentLength));
 
+    // Check the size of chunked responses. If we exceed the max pipeline size
+    // for this response reschedule the pipeline
+    if ((mClassification != CLASS_SOLO) &&
+        mChunkedDecoder &&
+        ((mContentRead + mChunkedDecoder->GetChunkRemaining()) >
+         mMaxPipelineObjectSize)) {
+        CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
+    }
+
     // check for end-of-file
     if ((mContentRead == mContentLength) ||
         (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
         // the transaction is done with a complete response.
         mTransactionDone = true;
         mResponseIsComplete = true;
         ReleaseBlockingTransaction();
 
@@ -1678,17 +1895,17 @@ nsHttpTransaction::ProcessData(char *buf
         // buf layout:
         //
         // +--------------------------------------+----------------+-----+
         // |              countRead               | countRemaining |     |
         // +--------------------------------------+----------------+-----+
         //
         // count          : bytes read from the socket
         // countRead      : bytes corresponding to this transaction
-        // countRemaining : bytes corresponding to next transaction on conn
+        // countRemaining : bytes corresponding to next pipelined transaction
         //
         // NOTE:
         // count > countRead + countRemaining <==> chunked transfer encoding
         //
         rv = HandleContent(buf, count, countRead, &countRemaining);
         if (NS_FAILED(rv)) return rv;
         // we may have read more than our share, in which case we must give
         // the excess bytes back to the connection
@@ -1703,25 +1920,43 @@ nsHttpTransaction::ProcessData(char *buf
             mContentDecodingCheck = true;
         }
     }
 
     return NS_OK;
 }
 
 void
+nsHttpTransaction::CancelPipeline(uint32_t reason)
+{
+    // reason is casted through a uint to avoid compiler header deps
+    gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+        mConnInfo,
+        static_cast<nsHttpConnectionMgr::PipelineFeedbackInfoType>(reason),
+        nullptr, mClassification);
+
+    mConnection->CancelPipeline(NS_ERROR_ABORT);
+
+    // Avoid pipelining this transaction on restart by classifying it as solo.
+    // This also prevents BadUnexpectedLarge from being reported more
+    // than one time per transaction.
+    mClassification = CLASS_SOLO;
+}
+
+
+void
 nsHttpTransaction::SetRequestContext(nsIRequestContext *aRequestContext)
 {
     LOG(("nsHttpTransaction %p SetRequestContext %p\n", this, aRequestContext));
     mRequestContext = aRequestContext;
 }
 
 // Called when the transaction marked for blocking is associated with a connection
-// (i.e. added to a new h1 conn, an idle http connection, etc..)
-// It is safe to call this multiple times with it only
+// (i.e. added to a new h1 conn, an idle http connection, or placed into
+// a http pipeline). It is safe to call this multiple times with it only
 // having an effect once.
 void
 nsHttpTransaction::DispatchedAsBlocking()
 {
     if (mDispatchedAsBlocking)
         return;
 
     LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
@@ -2087,16 +2322,105 @@ nsHttpTransaction::OnOutputStreamReady(n
         mConnection->TransactionHasDataToRecv(this);
         nsresult rv = mConnection->ResumeRecv();
         if (NS_FAILED(rv))
             NS_ERROR("ResumeRecv failed");
     }
     return NS_OK;
 }
 
+// nsHttpTransaction::RestartVerifier
+
+static bool
+matchOld(nsHttpResponseHead *newHead, nsCString &old,
+         nsHttpAtom headerAtom)
+{
+    nsAutoCString val;
+
+    newHead->GetHeader(headerAtom, val);
+    if (!val.IsEmpty() && old.IsEmpty())
+        return false;
+    if (val.IsEmpty() && !old.IsEmpty())
+        return false;
+    if (!val.IsEmpty() && !old.Equals(val))
+        return false;
+    return true;
+}
+
+bool
+nsHttpTransaction::RestartVerifier::Verify(int64_t contentLength,
+                                           nsHttpResponseHead *newHead)
+{
+    if (mContentLength != contentLength)
+        return false;
+
+    if (newHead->Status() != 200)
+        return false;
+
+    if (!matchOld(newHead, mContentRange, nsHttp::Content_Range))
+        return false;
+
+    if (!matchOld(newHead, mLastModified, nsHttp::Last_Modified))
+        return false;
+
+    if (!matchOld(newHead, mETag, nsHttp::ETag))
+        return false;
+
+    if (!matchOld(newHead, mContentEncoding, nsHttp::Content_Encoding))
+        return false;
+
+    if (!matchOld(newHead, mTransferEncoding, nsHttp::Transfer_Encoding))
+        return false;
+
+    return true;
+}
+
+void
+nsHttpTransaction::RestartVerifier::Set(int64_t contentLength,
+                                        nsHttpResponseHead *head)
+{
+    if (mSetup)
+        return;
+
+    // If mSetup does not transition to true RestartInPogress() is later
+    // forbidden
+
+    // Only RestartInProgress with 200 response code
+    if (!head || (head->Status() != 200)) {
+        return;
+    }
+
+    mContentLength = contentLength;
+
+    nsAutoCString val;
+    if (NS_SUCCEEDED(head->GetHeader(nsHttp::ETag, val))) {
+        mETag = val;
+    }
+    if (NS_SUCCEEDED(head->GetHeader(nsHttp::Last_Modified, val))) {
+        mLastModified = val;
+    }
+    if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Range, val))) {
+        mContentRange = val;
+    }
+    if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Encoding, val))) {
+        mContentEncoding = val;
+    }
+    if (NS_SUCCEEDED(head->GetHeader(nsHttp::Transfer_Encoding, val))) {
+        mTransferEncoding = val;
+    }
+
+    // We can only restart with any confidence if we have a stored etag or
+    // last-modified header
+    if (mETag.IsEmpty() && mLastModified.IsEmpty()) {
+        return;
+    }
+
+    mSetup = true;
+}
+
 void
 nsHttpTransaction::GetNetworkAddresses(NetAddr &self, NetAddr &peer)
 {
     MutexAutoLock lock(mLock);
     self = mSelfAddr;
     peer = mPeerAddr;
 }
 
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -114,21 +114,24 @@ public:
 
     void EnableKeepAlive() { mCaps |= NS_HTTP_ALLOW_KEEPALIVE; }
     void MakeSticky() { mCaps |= NS_HTTP_STICKY_CONNECTION; }
 
     // SetPriority() may only be used by the connection manager.
     void    SetPriority(int32_t priority) { mPriority = priority; }
     int32_t    Priority()                 { return mPriority; }
 
+    enum Classifier Classification() { return mClassification; }
+
     void PrintDiagnostics(nsCString &log);
 
     // Sets mPendingTime to the current time stamp or to a null time stamp (if now is false)
     void SetPendingTime(bool now = true) { mPendingTime = now ? TimeStamp::Now() : TimeStamp(); }
     const TimeStamp GetPendingTime() { return mPendingTime; }
+    bool UsesPipelining() const { return mCaps & NS_HTTP_ALLOW_PIPELINING; }
 
     // overload of nsAHttpTransaction::RequestContext()
     nsIRequestContext *RequestContext() override { return mRequestContext.get(); }
     void SetRequestContext(nsIRequestContext *aRequestContext);
     void DispatchedAsBlocking();
     void RemoveDispatchedAsBlocking();
 
     nsHttpTransaction *QueryHttpTransaction() override { return this; }
@@ -166,27 +169,31 @@ public:
 
     bool Do0RTT() override;
     nsresult Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) override;
 private:
     friend class DeleteHttpTransaction;
     virtual ~nsHttpTransaction();
 
     nsresult Restart();
+    nsresult RestartInProgress();
     char    *LocateHttpStart(char *buf, uint32_t len,
                              bool aAllowPartialMatch);
     nsresult ParseLine(nsACString &line);
     nsresult ParseLineSegment(char *seg, uint32_t len);
     nsresult ParseHead(char *, uint32_t count, uint32_t *countRead);
     nsresult HandleContentStart();
     nsresult HandleContent(char *, uint32_t count, uint32_t *contentRead, uint32_t *contentRemaining);
     nsresult ProcessData(char *, uint32_t, uint32_t *);
     void     DeleteSelfOnConsumerThread();
     void     ReleaseBlockingTransaction();
 
+    Classifier Classify();
+    void       CancelPipeline(uint32_t reason);
+
     static nsresult ReadRequestSegment(nsIInputStream *, void *, const char *,
                                        uint32_t, uint32_t, uint32_t *);
     static nsresult WritePipeSegment(nsIOutputStream *, void *, char *,
                                      uint32_t, uint32_t, uint32_t *);
 
     bool TimingEnabled() const { return mCaps & NS_HTTP_TIMING_ENABLED; }
 
     bool ResponseTimeoutEnabled() const final;
@@ -267,16 +274,19 @@ private:
     TimingStruct                    mTimings;
 
     nsresult                        mStatus;
 
     int16_t                         mPriority;
 
     uint16_t                        mRestartCount;        // the number of times this transaction has been restarted
     uint32_t                        mCaps;
+    enum Classifier                 mClassification;
+    int32_t                         mPipelinePosition;
+    int64_t                         mMaxPipelineObjectSize;
 
     nsHttpVersion                   mHttpVersion;
     uint16_t                        mHttpResponseCode;
 
     uint32_t                        mCurrentHttpResponseHeaderSize;
 
     // mCapsToClear holds flags that should be cleared in mCaps, e.g. unset
     // NS_HTTP_REFRESH_DNS when DNS refresh request has completed to avoid
@@ -323,16 +333,78 @@ private:
 
     // protected by nsHttp::GetLock()
     nsHttpResponseHead             *mForTakeResponseHead;
     bool                            mResponseHeadTaken;
 
     // The time when the transaction was submitted to the Connection Manager
     TimeStamp                       mPendingTime;
 
+    class RestartVerifier
+    {
+
+        // When a idemptotent transaction has received part of its response body
+        // and incurs an error it can be restarted. To do this we mark the place
+        // where we stopped feeding the body to the consumer and start the
+        // network call over again. If everything we track (headers, length, etc..)
+        // matches up to the place where we left off then the consumer starts being
+        // fed data again with the new information. This can be done N times up
+        // to the normal restart (i.e. with no response info) limit.
+
+    public:
+        RestartVerifier()
+            : mContentLength(-1)
+            , mAlreadyProcessed(0)
+            , mToReadBeforeRestart(0)
+            , mSetup(false)
+        {}
+        ~RestartVerifier() {}
+
+        void Set(int64_t contentLength, nsHttpResponseHead *head);
+        bool Verify(int64_t contentLength, nsHttpResponseHead *head);
+        bool IsDiscardingContent() { return mToReadBeforeRestart != 0; }
+        bool IsSetup() { return mSetup; }
+        int64_t AlreadyProcessed() { return mAlreadyProcessed; }
+        void SetAlreadyProcessed(int64_t val) {
+            mAlreadyProcessed = val;
+            mToReadBeforeRestart = val;
+        }
+        int64_t ToReadBeforeRestart() { return mToReadBeforeRestart; }
+        void HaveReadBeforeRestart(uint32_t amt)
+        {
+            MOZ_ASSERT(amt <= mToReadBeforeRestart,
+                       "too large of a HaveReadBeforeRestart deduction");
+            mToReadBeforeRestart -= amt;
+        }
+
+    private:
+        // This is the data from the first complete response header
+        // used to make sure that all subsequent response headers match
+
+        int64_t                         mContentLength;
+        nsCString                       mETag;
+        nsCString                       mLastModified;
+        nsCString                       mContentRange;
+        nsCString                       mContentEncoding;
+        nsCString                       mTransferEncoding;
+
+        // This is the amount of data that has been passed to the channel
+        // from previous iterations of the transaction and must therefore
+        // be skipped in the new one.
+        int64_t                         mAlreadyProcessed;
+
+        // The amount of data that must be discarded in the current iteration
+        // (where iteration > 0) to reach the mAlreadyProcessed high water
+        // mark.
+        int64_t                         mToReadBeforeRestart;
+
+        // true when ::Set has been called with a response header
+        bool                            mSetup;
+    } mRestartInProgressVerifier;
+
 // For Rate Pacing via an EventTokenBucket
 public:
     // called by the connection manager to run this transaction through the
     // token bucket. If the token bucket admits the transaction immediately it
     // returns true. The function is called repeatedly until it returns true.
     bool TryToRunPacedRequest();
 
     // ATokenBucketEvent pure virtual implementation. Called by the token bucket
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -201,17 +201,26 @@ interface nsIHttpChannel : nsIChannel
      * behavior. Don't do it!
      *
      * @param aVisitor
      *        the header visitor instance.
      */
     void visitNonDefaultRequestHeaders(in nsIHttpHeaderVisitor aVisitor);
 
     /**
-     * This attribute no longer has any effect, it remains for backwards compat
+     * This attribute is a hint to the channel to indicate whether or not
+     * the underlying HTTP transaction should be allowed to be pipelined
+     * with other transactions.  This should be set to FALSE, for example,
+     * if the application knows that the corresponding document is likely
+     * to be very large.
+     *
+     * This attribute is true by default, though other factors may prevent
+     * pipelining.
+     *
+     * This attribute may only be set before the channel is opened.
      *
      * @throws NS_ERROR_FAILURE if set after the channel has been opened.
      */
     attribute boolean allowPipelining;
 
     /**
      * This attribute of the channel indicates whether or not
      * the underlying HTTP transaction should be honor stored Strict Transport
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_assoc.js
@@ -0,0 +1,102 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var httpserver = new HttpServer();
+var currentTestIndex = 0;
+
+XPCOMUtils.defineLazyGetter(this, "port", function() {
+    return httpserver.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "tests", function() {
+    return [
+            // this is valid
+            {url: "/assoc/assoctest?valid",
+             responseheader: ["Assoc-Req: GET http://localhost:" + port +
+                              "/assoc/assoctest?valid",
+                              "Pragma: X-Verify-Assoc-Req"],
+             flags: 0},
+
+            // this is invalid because the method is wrong
+            {url: "/assoc/assoctest?invalid",
+             responseheader: ["Assoc-Req: POST http://localhost:" + port +
+                              "/assoc/assoctest?invalid",
+                              "Pragma: X-Verify-Assoc-Req"],
+             flags: CL_EXPECT_LATE_FAILURE},
+
+            // this is invalid because the url is wrong
+            {url: "/assoc/assoctest?notvalid",
+             responseheader: ["Assoc-Req: GET http://localhost:" + port +
+                              "/wrongpath/assoc/assoctest?notvalid",
+                              "Pragma: X-Verify-Assoc-Req"],
+             flags: CL_EXPECT_LATE_FAILURE},
+
+             // this is invalid because the space between method and URL is missing
+            {url: "/assoc/assoctest?invalid2",
+             responseheader: ["Assoc-Req: GEThttp://localhost:" + port +
+                              "/assoc/assoctest?invalid2",
+                              "Pragma: X-Verify-Assoc-Req"],
+             flags: CL_EXPECT_LATE_FAILURE},
+    ];
+});
+
+var oldPrefVal;
+var domBranch;
+
+function setupChannel(url)
+{
+  return NetUtil.newChannel({
+    uri: "http://localhost:" + port + url,
+    loadUsingSystemPrincipal: true
+  });
+}
+
+function startIter()
+{
+    var channel = setupChannel(tests[currentTestIndex].url);
+    channel.asyncOpen2(new ChannelListener(completeIter,
+                                          channel, tests[currentTestIndex].flags));
+}
+
+function completeIter(request, data, ctx)
+{
+    if (++currentTestIndex < tests.length ) {
+        startIter();
+    } else {
+        domBranch.setBoolPref("enforce", oldPrefVal);
+        httpserver.stop(do_test_finished);
+    }
+}
+
+function run_test()
+{
+    var prefService =
+        Components.classes["@mozilla.org/preferences-service;1"]
+        .getService(Components.interfaces.nsIPrefService);
+    domBranch = prefService.getBranch("network.http.assoc-req.");
+    oldPrefVal = domBranch.getBoolPref("enforce");
+    domBranch.setBoolPref("enforce", true);
+
+    httpserver.registerPathHandler("/assoc/assoctest", handler);
+    httpserver.start(-1);
+
+    startIter();
+    do_test_pending();
+}
+
+function handler(metadata, response)
+{
+    var body = "thequickbrownfox";
+    response.setHeader("Content-Type", "text/plain", false);
+
+    var header = tests[currentTestIndex].responseheader;
+    if (header != undefined) {
+        for (var i = 0; i < header.length; i++) {
+            var splitHdr = header[i].split(": ");
+            response.setHeader(splitHdr[0], splitHdr[1], false);
+        }
+    }
+    
+    response.setStatusLine(metadata.httpVersion, 200, "OK");
+    response.bodyOutputStream.write(body, body.length);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -85,16 +85,17 @@ skip-if = true
 [test_NetUtil.js]
 [test_URIs.js]
 # Intermittent time-outs on Android, bug 1285020
 requesttimeoutfactor = 2
 [test_URIs2.js]
 # Intermittent time-outs on Android, bug 1285020
 requesttimeoutfactor = 2
 [test_aboutblank.js]
+[test_assoc.js]
 [test_auth_jar.js]
 [test_auth_proxy.js]
 [test_authentication.js]
 [test_authpromptwrapper.js]
 [test_auth_dialog_permission.js]
 [test_backgroundfilesaver.js]
 # Runs for a long time, causing intermittent time-outs on Android, bug 995686
 requesttimeoutfactor = 2
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -2565,16 +2565,23 @@
   },
   "TRANSACTION_WAIT_TIME_HTTP": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 5000,
     "n_buckets": 100,
     "description": "Time from submission to dispatch of HTTP transaction (ms)"
   },
+  "TRANSACTION_WAIT_TIME_HTTP_PIPELINES": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": 5000,
+    "n_buckets": 100,
+    "description": "Time from submission to dispatch of HTTP with pipelines transaction (ms)"
+  },
   "TRANSACTION_WAIT_TIME_SPDY": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 5000,
     "n_buckets": 100,
     "description": "Time from submission to dispatch of SPDY transaction (ms)"
   },
   "HTTP_SAW_QUIC_ALT_PROTOCOL": {
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -661,16 +661,17 @@
     "TAP_TO_LOAD_IMAGE_SIZE",
     "THUNDERBIRD_CONVERSATIONS_TIME_TO_2ND_GLODA_QUERY_MS",
     "THUNDERBIRD_GLODA_SIZE_MB",
     "THUNDERBIRD_INDEXING_RATE_MSG_PER_S",
     "TLS_ERROR_REPORT_UI",
     "TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED",
     "TOUCH_ENABLED_DEVICE",
     "TRANSACTION_WAIT_TIME_HTTP",
+    "TRANSACTION_WAIT_TIME_HTTP_PIPELINES",
     "TRANSACTION_WAIT_TIME_SPDY",
     "TRANSLATED_CHARACTERS",
     "TRANSLATED_PAGES",
     "TRANSLATED_PAGES_BY_LANGUAGE",
     "TRANSLATION_OPPORTUNITIES",
     "TRANSLATION_OPPORTUNITIES_BY_LANGUAGE",
     "VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG",
     "VIDEO_CANPLAYTYPE_H264_LEVEL",
@@ -1581,16 +1582,17 @@
     "THUNDERBIRD_INDEXING_RATE_MSG_PER_S",
     "TLS_ERROR_REPORT_UI",
     "TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED",
     "TOTAL_CONTENT_PAGE_LOAD_TIME",
     "TOTAL_COUNT_HIGH_ERRORS",
     "TOTAL_COUNT_LOW_ERRORS",
     "TOUCH_ENABLED_DEVICE",
     "TRANSACTION_WAIT_TIME_HTTP",
+    "TRANSACTION_WAIT_TIME_HTTP_PIPELINES",
     "TRANSACTION_WAIT_TIME_SPDY",
     "TRANSLATED_CHARACTERS",
     "TRANSLATED_PAGES",
     "TRANSLATED_PAGES_BY_LANGUAGE",
     "TRANSLATION_OPPORTUNITIES",
     "TRANSLATION_OPPORTUNITIES_BY_LANGUAGE",
     "UPDATE_CANNOT_STAGE_EXTERNAL",
     "UPDATE_CANNOT_STAGE_NOTIFY",