bug 1204614 - use h2 per stream flow control to deal with suspended channels r=hurley
authorPatrick McManus <mcmanus@ducksong.com>
Fri, 11 Sep 2015 15:53:27 -0400
changeset 297243 0bf012ea842fa811f1057355fb11259b7bc20487
parent 297242 074f8c86cc2d32bc9960b05a19d00de3211700b7
child 297244 dfd4e61be0624dba05fb0562f453fa93027a1663
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley
bugs1204614
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1204614 - use h2 per stream flow control to deal with suspended channels r=hurley
modules/libpref/init/all.js
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/protocol/http/ASpdySession.h
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/Http2Session.h
netwerk/protocol/http/Http2Stream.cpp
netwerk/protocol/http/Http2Stream.h
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/nsAHttpConnection.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
netwerk/protocol/http/nsIHttpChannelInternal.idl
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1404,17 +1404,18 @@ pref("network.http.spdy.enforce-tls-prof
 pref("network.http.spdy.chunk-size", 16000);
 pref("network.http.spdy.timeout", 180);
 pref("network.http.spdy.coalesce-hostnames", true);
 pref("network.http.spdy.persistent-settings", false);
 pref("network.http.spdy.ping-threshold", 58);
 pref("network.http.spdy.ping-timeout", 8);
 pref("network.http.spdy.send-buffer-size", 131072);
 pref("network.http.spdy.allow-push", true);
-pref("network.http.spdy.push-allowance", 131072);
+pref("network.http.spdy.push-allowance", 131072);   // 128KB
+pref("network.http.spdy.pull-allowance", 12582912); // 12MB
 pref("network.http.spdy.default-concurrent", 100);
 
 // alt-svc allows separation of transport routing from
 // the origin host without using a proxy.
 pref("network.http.altsvc.enabled", true);
 pref("network.http.altsvc.oe", true);
 
 pref("network.http.diagnostics", false);
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -103,16 +103,17 @@ struct HttpChannelOpenArgs
   bool                        allowAltSvc;
   OptionalFileDescriptorSet   fds;
   OptionalLoadInfoArgs        loadInfo;
   OptionalHttpResponseHead    synthesizedResponseHead;
   nsCString                   synthesizedSecurityInfoSerialization;
   uint32_t                    cacheKey;
   nsCString                   schedulingContextID;
   OptionalCorsPreflightArgs   preflightArgs;
+  uint32_t                    initialRwin;
 };
 
 struct HttpChannelConnectArgs
 {
   uint32_t channelId;
   bool shouldIntercept;
 };
 
--- a/netwerk/protocol/http/ASpdySession.h
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -46,21 +46,19 @@ public:
     return true;
   }
 
   virtual void SendPing() = 0;
 
   const static uint32_t kSendingChunkSize = 4095;
   const static uint32_t kTCPSendBufferSize = 131072;
 
-  // until we have an API that can push back on receiving data (right now
-  // WriteSegments is obligated to accept data and buffer) there is no
-  // reason to throttle with the rwin other than in server push
-  // scenarios.
-  const static uint32_t kInitialRwin = 256 * 1024 * 1024;
+  // This is roughly the amount of data a suspended channel will have to
+  // buffer before h2 flow control kicks in.
+  const static uint32_t kInitialRwin = 12 * 1024 * 1024; // 12MB
 
   const static uint32_t kDefaultMaxConcurrent = 100;
 
   // soft errors are errors that terminate a stream without terminating the
   // connection. In general non-network errors are stream errors as well
   // as network specific items like cancels.
   bool SoftStreamError(nsresult code)
   {
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -98,16 +98,17 @@ Http2Session::Http2Session(nsISocketTran
   , mPeerGoAwayReason(UNASSIGNED)
   , mGoAwayID(0)
   , mOutgoingGoAwayID(0)
   , mConcurrent(0)
   , mServerPushedResources(0)
   , mServerInitialStreamWindow(kDefaultRwin)
   , mLocalSessionWindow(kDefaultRwin)
   , mServerSessionWindow(kDefaultRwin)
+  , mInitialRwin(ASpdySession::kInitialRwin)
   , mOutputQueueSize(kDefaultQueueSize)
   , mOutputQueueUsed(0)
   , mOutputQueueSent(0)
   , mLastReadEpoch(PR_IntervalNow())
   , mPingSentEpoch(0)
   , mPreviousUsed(false)
   , mWaitingForSettingsAck(false)
   , mGoAwayOnPush(false)
@@ -121,16 +122,17 @@ Http2Session::Http2Session(nsISocketTran
   LOG3(("Http2Session::Http2Session %p serial=0x%X\n", this, mSerial));
 
   mInputFrameBuffer = new char[mInputFrameBufferSize];
   mOutputQueueBuffer = new char[mOutputQueueSize];
   mDecompressBuffer.SetCapacity(kDefaultBufferSize);
   mDecompressor.SetCompressor(&mCompressor);
 
   mPushAllowance = gHttpHandler->SpdyPushAllowance();
+  mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
   mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
   mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
   SendHello();
 
   mLastDataReadEpoch = mLastReadEpoch;
 
   mPingThreshold = gHttpHandler->SpdyPingThreshold();
   mPreviousPingThreshold = mPingThreshold;
@@ -886,20 +888,20 @@ Http2Session::SendHello()
   MOZ_ASSERT(numberOfEntries <= maxSettings);
   uint32_t dataLen = 6 * numberOfEntries;
   CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
   mOutputQueueUsed += kFrameHeaderBytes + dataLen;
 
   LogIO(this, nullptr, "Generate Settings", packet, kFrameHeaderBytes + dataLen);
 
   // now bump the local session window from 64KB
-  uint32_t sessionWindowBump = ASpdySession::kInitialRwin - kDefaultRwin;
-  if (kDefaultRwin < ASpdySession::kInitialRwin) {
+  uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin;
+  if (kDefaultRwin < mInitialRwin) {
     // send a window update for the session (Stream 0) for something large
-    mLocalSessionWindow = ASpdySession::kInitialRwin;
+    mLocalSessionWindow = mInitialRwin;
 
     packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
     CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
     mOutputQueueUsed += kFrameHeaderBytes + 4;
     NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump);
 
     LOG3(("Session Window increase at start of session %p %u\n",
           this, sessionWindowBump));
@@ -1101,17 +1103,18 @@ static void RemoveStreamFromQueue(Http2S
   }
 }
 
 void
 Http2Session::RemoveStreamFromQueues(Http2Stream *aStream)
 {
   RemoveStreamFromQueue(aStream, mReadyForWrite);
   RemoveStreamFromQueue(aStream, mQueuedStreams);
-  RemoveStreamFromQueue(aStream, mReadyForRead);
+  RemoveStreamFromQueue(aStream, mPushesReadyForRead);
+  RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
 }
 
 void
 Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   LOG3(("Http2Session::CloseStream %p %p 0x%x %X\n",
         this, aStream, aStream->StreamID(), aResult));
@@ -2451,45 +2454,30 @@ Http2Session::WriteSegments(nsAHttpSegme
     return rv;
 
   SetWriteCallbacks();
 
   // If there are http transactions attached to a push stream with filled buffers
   // trigger that data pump here. This only reads from buffers (not the network)
   // so mDownstreamState doesn't matter.
   Http2Stream *pushConnectedStream =
-    static_cast<Http2Stream *>(mReadyForRead.PopFront());
+    static_cast<Http2Stream *>(mPushesReadyForRead.PopFront());
   if (pushConnectedStream) {
-    LOG3(("Http2Session::WriteSegments %p processing pushed stream 0x%X\n",
-          this, pushConnectedStream->StreamID()));
-    mSegmentWriter = writer;
-    rv = pushConnectedStream->WriteSegments(this, count, countWritten);
-    mSegmentWriter = nullptr;
-
-    // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
-    // so we need this check to determine the truth.
-    if (NS_SUCCEEDED(rv) && !*countWritten &&
-        pushConnectedStream->PushSource() &&
-        pushConnectedStream->PushSource()->GetPushComplete()) {
-      rv = NS_BASE_STREAM_CLOSED;
-    }
-
-    if (rv == NS_BASE_STREAM_CLOSED) {
-      CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
-      rv = NS_OK;
-    }
-
-    // if we return OK to nsHttpConnection it will use mSocketInCondition
-    // to determine whether to schedule more reads, incorrectly
-    // assuming that nsHttpConnection::OnSocketWrite() was called.
-    if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
-      rv = NS_BASE_STREAM_WOULD_BLOCK;
-      ResumeRecv();
-    }
-
+    return ProcessConnectedPush(pushConnectedStream, writer, count, countWritten);
+  }
+
+  // feed gecko channels that previously stopped consuming data
+  // only take data from stored buffers
+  Http2Stream *slowConsumer =
+    static_cast<Http2Stream *>(mSlowConsumersReadyForRead.PopFront());
+  if (slowConsumer) {
+    internalStateType savedState = mDownstreamState;
+    mDownstreamState = NOT_USING_NETWORK;
+    rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten);
+    mDownstreamState = savedState;
     return rv;
   }
 
   // The BUFFERING_OPENING_SETTINGS state is just like any BUFFERING_FRAME_HEADER
   // except the only frame type it will allow is SETTINGS
 
   // The session layer buffers the leading 8 byte header of every frame.
   // Non-Data frames are then buffered for their full length, but data
@@ -2828,16 +2816,79 @@ Http2Session::WriteSegments(nsAHttpSegme
              mDownstreamState != BUFFERING_CONTROL_FRAME,
              "Control Handler returned OK but did not change state");
 
   if (mShouldGoAway && !mStreamTransactionHash.Count())
     Close(NS_OK);
   return rv;
 }
 
+nsresult
+Http2Session::ProcessConnectedPush(Http2Stream *pushConnectedStream,
+                                   nsAHttpSegmentWriter * writer,
+                                   uint32_t count, uint32_t *countWritten)
+{
+  LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n",
+        this, pushConnectedStream->StreamID()));
+  mSegmentWriter = writer;
+  nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
+  mSegmentWriter = nullptr;
+
+  // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
+  // so we need this check to determine the truth.
+  if (NS_SUCCEEDED(rv) && !*countWritten &&
+      pushConnectedStream->PushSource() &&
+      pushConnectedStream->PushSource()->GetPushComplete()) {
+    rv = NS_BASE_STREAM_CLOSED;
+  }
+
+  if (rv == NS_BASE_STREAM_CLOSED) {
+    CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
+    rv = NS_OK;
+  }
+
+  // if we return OK to nsHttpConnection it will use mSocketInCondition
+  // to determine whether to schedule more reads, incorrectly
+  // assuming that nsHttpConnection::OnSocketWrite() was called.
+  if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
+    rv = NS_BASE_STREAM_WOULD_BLOCK;
+    ResumeRecv();
+  }
+  return rv;
+}
+
+nsresult
+Http2Session::ProcessSlowConsumer(Http2Stream *slowConsumer,
+                                  nsAHttpSegmentWriter * writer,
+                                  uint32_t count, uint32_t *countWritten)
+{
+  LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n",
+        this, slowConsumer->StreamID()));
+  mSegmentWriter = writer;
+  nsresult rv = slowConsumer->WriteSegments(this, count, countWritten);
+  mSegmentWriter = nullptr;
+  LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %X %d\n",
+        this, slowConsumer->StreamID(), rv, *countWritten));
+  if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) {
+    rv = NS_BASE_STREAM_CLOSED;
+  }
+
+  if (NS_SUCCEEDED(rv)) {
+    UpdateLocalRwin(slowConsumer, 0);
+    ConnectSlowConsumer(slowConsumer);
+  }
+
+  if (rv == NS_BASE_STREAM_CLOSED) {
+    CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR);
+    rv = NS_OK;
+  }
+
+  return rv;
+}
+
 void
 Http2Session::UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes)
 {
   if (!stream) // this is ok - it means there was a data frame for a rst stream
     return;
 
   // If this data packet was not for a valid or live stream then there
   // is no reason to mess with the flow control
@@ -2902,22 +2953,22 @@ Http2Session::UpdateLocalSessionWindow(u
 
   mLocalSessionWindow -= bytes;
 
   LOG3(("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
         "localWindow=%lld\n", this, bytes, mLocalSessionWindow));
 
   // Don't necessarily ack every data packet. Only do it
   // after a significant amount of data.
-  if ((mLocalSessionWindow > (ASpdySession::kInitialRwin - kMinimumToAck)) &&
+  if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) &&
       (mLocalSessionWindow > kEmergencyWindowThreshold))
     return;
 
   // Only send max  bits of window updates at a time.
-  uint64_t toack64 = ASpdySession::kInitialRwin - mLocalSessionWindow;
+  uint64_t toack64 = mInitialRwin - mLocalSessionWindow;
   uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
 
   LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n",
         this, toack));
   mLocalSessionWindow += toack;
 
   if (toack == 0) {
     // Ensure we never send an illegal 0 window update
@@ -3118,16 +3169,20 @@ Http2Session::OnWriteSegment(char *buf,
   nsresult rv;
 
   if (!mSegmentWriter) {
     // the only way this could happen would be if Close() were called on the
     // stack with WriteSegments()
     return NS_ERROR_FAILURE;
   }
 
+  if (mDownstreamState == NOT_USING_NETWORK) {
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+
   if (mDownstreamState == PROCESSING_DATA_FRAME) {
 
     if (mInputFrameFinal &&
         mInputFrameDataRead == mInputFrameDataSize) {
       *countWritten = 0;
       SetNeedsCleanup();
       return NS_BASE_STREAM_CLOSED;
     }
@@ -3215,17 +3270,26 @@ Http2Session::SetNeedsCleanup()
   MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
   mNeedsCleanup = mInputFrameDataStream;
   ResetDownstreamState();
 }
 
 void
 Http2Session::ConnectPushedStream(Http2Stream *stream)
 {
-  mReadyForRead.Push(stream);
+  mPushesReadyForRead.Push(stream);
+  ForceRecv();
+}
+
+void
+Http2Session::ConnectSlowConsumer(Http2Stream *stream)
+{
+  LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n",
+        this, stream->StreamID()));
+  mSlowConsumersReadyForRead.Push(stream);
   ForceRecv();
 }
 
 uint32_t
 Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   uint32_t rv = 0;
@@ -3471,17 +3535,35 @@ Http2Session::TransactionHasDataToWrite(
 
   mReadyForWrite.Push(stream);
   SetWriteCallbacks();
 
   // NSPR poll will not poll the network if there are non system PR_FileDesc's
   // that are ready - so we can get into a deadlock waiting for the system IO
   // to come back here if we don't force the send loop manually.
   ForceSend();
-
+}
+
+void
+Http2Session::TransactionHasDataToRecv(nsAHttpTransaction *caller)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));
+
+  // a signal from the http transaction to the connection that it will consume more
+  Http2Stream *stream = mStreamTransactionHash.Get(caller);
+  if (!stream || !VerifyStream(stream)) {
+    LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found",
+          this, caller));
+    return;
+  }
+
+  LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n",
+        this, stream->StreamID()));
+  ConnectSlowConsumer(stream);
 }
 
 void
 Http2Session::TransactionHasDataToWrite(Http2Stream *stream)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
         this, stream, stream->StreamID()));
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -187,47 +187,50 @@ public:
   void CreateFrameHeader(charType dest, uint16_t frameLength,
                          uint8_t frameType, uint8_t frameFlags,
                          uint32_t streamID);
 
   // For writing the data stream to LOG4
   static void LogIO(Http2Session *, Http2Stream *, const char *,
                     const char *, uint32_t);
 
-  // an overload of nsAHttpConnection
+  // overload of nsAHttpConnection
   void TransactionHasDataToWrite(nsAHttpTransaction *) override;
+  void TransactionHasDataToRecv(nsAHttpTransaction *) override;
 
   // a similar version for Http2Stream
   void TransactionHasDataToWrite(Http2Stream *);
 
   // an overload of nsAHttpSegementReader
   virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment) override;
   nsresult BufferOutput(const char *, uint32_t, uint32_t *);
   void     FlushOutputQueue();
   uint32_t AmountOfOutputBuffered() { return mOutputQueueUsed - mOutputQueueSent; }
 
   uint32_t GetServerInitialStreamWindow() { return mServerInitialStreamWindow; }
 
   bool TryToActivate(Http2Stream *stream);
   void ConnectPushedStream(Http2Stream *stream);
+  void ConnectSlowConsumer(Http2Stream *stream);
 
   nsresult ConfirmTLSProfile();
   static bool ALPNCallback(nsISupports *securityInfo);
 
   uint64_t Serial() { return mSerial; }
 
   void PrintDiagnostics (nsCString &log) override;
 
   // Streams need access to these
   uint32_t SendingChunkSize() { return mSendingChunkSize; }
   uint32_t PushAllowance() { return mPushAllowance; }
   Http2Compressor *Compressor() { return &mCompressor; }
   nsISocketTransport *SocketTransport() { return mSocketTransport; }
   int64_t ServerSessionWindow() { return mServerSessionWindow; }
   void DecrementServerSessionWindow (uint32_t bytes) { mServerSessionWindow -= bytes; }
+  uint32_t InitialRwin() { return mInitialRwin; }
 
   void SendPing() override;
   bool MaybeReTunnel(nsAHttpTransaction *) override;
   bool UseH2Deps() { return mUseH2Deps; }
 
 private:
 
   // These internal states do not correspond to the states of the HTTP/2 specification
@@ -235,17 +238,18 @@ private:
     BUFFERING_OPENING_SETTINGS,
     BUFFERING_FRAME_HEADER,
     BUFFERING_CONTROL_FRAME,
     PROCESSING_DATA_FRAME_PADDING_CONTROL,
     PROCESSING_DATA_FRAME,
     DISCARDING_DATA_FRAME_PADDING,
     DISCARDING_DATA_FRAME,
     PROCESSING_COMPLETE_HEADERS,
-    PROCESSING_CONTROL_RST_STREAM
+    PROCESSING_CONTROL_RST_STREAM,
+    NOT_USING_NETWORK
   };
 
   static const uint8_t kMagicHello[24];
 
   nsresult    ResponseHeadersComplete();
   uint32_t    GetWriteQueueSize();
   void        ChangeDownstreamState(enum internalStateType);
   void        ResetDownstreamState();
@@ -262,16 +266,21 @@ private:
   void        SendHello();
   void        RemoveStreamFromQueues(Http2Stream *);
   nsresult    ParsePadding(uint8_t &, uint16_t &);
 
   void        SetWriteCallbacks();
   void        RealignOutputQueue();
 
   void        ProcessPending();
+  nsresult    ProcessConnectedPush(Http2Stream *, nsAHttpSegmentWriter *,
+                                   uint32_t, uint32_t *);
+  nsresult    ProcessSlowConsumer(Http2Stream *, nsAHttpSegmentWriter *,
+                                  uint32_t, uint32_t *);
+
   nsresult    SetInputFrameDataStream(uint32_t);
   void        CreatePriorityNode(uint32_t, uint32_t, uint8_t, const char *);
   bool        VerifyStream(Http2Stream *, uint32_t);
   void        SetNeedsCleanup();
 
   void        UpdateLocalRwin(Http2Stream *stream, uint32_t bytes);
   void        UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes);
   void        UpdateLocalSessionWindow(uint32_t bytes);
@@ -330,17 +339,18 @@ private:
   // by the nsClassHashtable implementation when they are removed from
   // the transaction hash.
   nsDataHashtable<nsUint32HashKey, Http2Stream *>     mStreamIDHash;
   nsClassHashtable<nsPtrHashKey<nsAHttpTransaction>,
     Http2Stream>                                      mStreamTransactionHash;
 
   nsDeque                                             mReadyForWrite;
   nsDeque                                             mQueuedStreams;
-  nsDeque                                             mReadyForRead;
+  nsDeque                                             mPushesReadyForRead;
+  nsDeque                                             mSlowConsumersReadyForRead;
   nsTArray<Http2PushedStream *>                       mPushedStreams;
 
   // Compression contexts for header transport.
   // HTTP/2 compresses only HTTP headers and does not reset the context in between
   // frames. Even data that is not associated with a stream (e.g invalid
   // stream ID) is passed through these contexts to keep the compression
   // context correct.
   Http2Compressor     mCompressor;
@@ -446,16 +456,19 @@ private:
   // signed because asynchronous changes via SETTINGS can drive it negative.
   int64_t              mLocalSessionWindow;
 
   // The Remote Session Window is how much data the client is allowed to send
   // (across all streams) without receiving a window update to stream 0. It is
   // signed because asynchronous changes via SETTINGS can drive it negative.
   int64_t              mServerSessionWindow;
 
+  // The initial value of the local stream and session window
+  uint32_t             mInitialRwin;
+
   // This is a output queue of bytes ready to be written to the SSL stream.
   // When that streams returns WOULD_BLOCK on direct write the bytes get
   // coalesced together here. This results in larger writes to the SSL layer.
   // The buffer is not dynamically grown to accomodate stream writes, but
   // does expand to accept infallible session wide frames like GoAway and RST.
   uint32_t             mOutputQueueSize;
   uint32_t             mOutputQueueUsed;
   uint32_t             mOutputQueueSent;
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -22,16 +22,17 @@
 #include "TunnelUtils.h"
 
 #include "mozilla/Telemetry.h"
 #include "nsAlgorithm.h"
 #include "nsHttp.h"
 #include "nsHttpHandler.h"
 #include "nsHttpRequestHead.h"
 #include "nsIClassOfService.h"
+#include "nsIPipe.h"
 #include "nsISocketTransport.h"
 #include "nsStandardURL.h"
 #include "prnetdb.h"
 
 #ifdef DEBUG
 // defined by the socket transport service while active
 extern PRThread *gSocketThread;
 #endif
@@ -58,16 +59,17 @@ Http2Stream::Http2Stream(nsAHttpTransact
   , mRequestBlockedOnRead(0)
   , mRecvdFin(0)
   , mRecvdReset(0)
   , mSentReset(0)
   , mCountAsActive(0)
   , mSentFin(0)
   , mSentWaitingFor(0)
   , mSetTCPSocketBuffer(0)
+  , mBypassInputBuffer(0)
   , mTxInlineFrameSize(Http2Session::kDefaultBufferSize)
   , mTxInlineFrameUsed(0)
   , mTxStreamFrameSize(0)
   , mRequestBodyLenRemaining(0)
   , mLocalUnacked(0)
   , mBlockedOnRwin(false)
   , mTotalSent(0)
   , mTotalRead(0)
@@ -231,16 +233,79 @@ Http2Stream::ReadSegments(nsAHttpSegment
   default:
     MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state");
     break;
   }
 
   return rv;
 }
 
+static bool
+IsDataAvailable(nsIInputStream *stream)
+{
+  if (!stream) {
+    return false;
+  }
+  uint64_t avail;
+  if (NS_FAILED(stream->Available(&avail))) {
+    return false;
+  }
+  return (avail > 0);
+}
+
+uint64_t
+Http2Stream::LocalUnAcked()
+{
+  // reduce unacked by the amount of undelivered data
+  // to help assert flow control
+  uint64_t undelivered = 0;
+  if (mInputBufferIn) {
+    mInputBufferIn->Available(&undelivered);
+  }
+
+  if (undelivered > mLocalUnacked) {
+    return 0;
+  }
+  return mLocalUnacked - undelivered;
+}
+
+nsresult
+Http2Stream::BufferInput(uint32_t count, uint32_t *countWritten)
+{
+  static const uint32_t segmentSize = 32768;
+  char buf[segmentSize];
+
+  count = std::min(segmentSize, count);
+  if (!mInputBufferOut) {
+    NS_NewPipe(getter_AddRefs(mInputBufferIn), getter_AddRefs(mInputBufferOut),
+               segmentSize, UINT32_MAX);
+    if (!mInputBufferOut) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+  mBypassInputBuffer = 1;
+  nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+  mBypassInputBuffer = 0;
+  if (NS_SUCCEEDED(rv)) {
+    uint32_t buffered;
+    rv = mInputBufferOut->Write(buf, *countWritten, &buffered);
+    if (NS_SUCCEEDED(rv) && (buffered != *countWritten)) {
+      rv = NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+  return rv;
+}
+
+bool
+Http2Stream::DeferCleanup(nsresult status)
+{
+  // do not cleanup a stream that has data buffered for the transaction
+  return (NS_SUCCEEDED(status) && IsDataAvailable(mInputBufferIn));
+}
+
 // WriteSegments() is used to read data off the socket. Generally this is
 // just a call through to the associated nsHttpTransaction for this stream
 // for the remaining data bytes indicated by the current DATA frame.
 
 nsresult
 Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer,
                            uint32_t count,
                            uint32_t *countWritten)
@@ -248,18 +313,26 @@ Http2Stream::WriteSegments(nsAHttpSegmen
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
 
   LOG3(("Http2Stream::WriteSegments %p count=%d state=%x",
         this, count, mUpstreamState));
 
   mSegmentWriter = writer;
   nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
+
+  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+    // consuming transaction won't take data. but we need to read it into a buffer so that it
+    // won't block other streams. but we should not advance the flow control window
+    // so that we'll eventually push back on the sender.
+
+    // stash this data
+    rv = BufferInput(count, countWritten);
+  }
   mSegmentWriter = nullptr;
-
   return rv;
 }
 
 nsresult
 Http2Stream::MakeOriginURL(const nsACString &origin, nsRefPtr<nsStandardURL> &url)
 {
   nsAutoCString scheme;
   nsresult rv = net_ExtractURLScheme(origin, nullptr, nullptr, &scheme);
@@ -618,36 +691,47 @@ Http2Stream::AdjustInitialWindow()
       return;
   }
 
   if (stream->mState == RESERVED_BY_REMOTE) {
     // h2-14 prevents sending a window update in this state
     return;
   }
 
-  MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin);
-  uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow;
+  // right now mClientReceiveWindow is the lower push limit
+  // bump it up to the pull limit set by the channel or session
+  // don't allow windows less than push
+  uint32_t bump = 0;
+  nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
+  if (trans && trans->InitialRwin()) {
+    bump = (trans->InitialRwin() > mClientReceiveWindow) ?
+      (trans->InitialRwin() - mClientReceiveWindow) : 0;
+  } else {
+    MOZ_ASSERT(mSession->InitialRwin() >= mClientReceiveWindow);
+    bump = mSession->InitialRwin() - mClientReceiveWindow;
+  }
+
+  LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n",
+        this, stream->mStreamID, bump));
   if (!bump) { // nothing to do
     return;
   }
 
   uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
   EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4,
                mTxInlineFrameUsed, mTxInlineFrameSize);
   mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4;
 
   mSession->CreateFrameHeader(packet, 4,
                               Http2Session::FRAME_TYPE_WINDOW_UPDATE,
                               0, stream->mStreamID);
 
   mClientReceiveWindow += bump;
   bump = PR_htonl(bump);
   memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
-  LOG3(("AdjustInitialwindow increased flow control window %p 0x%X\n",
-        this, stream->mStreamID));
 }
 
 void
 Http2Stream::AdjustPushedPriority()
 {
   // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams.
   // 0 is the sink for a pushed stream.
 
@@ -1313,26 +1397,44 @@ Http2Stream::OnWriteSegment(char *buf,
                             uint32_t *countWritten)
 {
   LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n",
         this, count, mUpstreamState, mStreamID));
 
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   MOZ_ASSERT(mSegmentWriter);
 
-  if (!mPushSource)
-    return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+  if (mPushSource) {
+    nsresult rv;
+    rv = mPushSource->GetBufferedData(buf, count, countWritten);
+    if (NS_FAILED(rv))
+      return rv;
+
+    mSession->ConnectPushedStream(this);
+    return NS_OK;
+  }
 
-  nsresult rv;
-  rv = mPushSource->GetBufferedData(buf, count, countWritten);
-  if (NS_FAILED(rv))
+  // sometimes we have read data from the network and stored it in a pipe
+  // so that other streams can proceed when the gecko caller is not processing
+  // data events fast enough and flow control hasn't caught up yet. This
+  // gets the stored data out of that pipe
+  if (!mBypassInputBuffer && IsDataAvailable(mInputBufferIn)) {
+    nsresult rv = mInputBufferIn->Read(buf, count, countWritten);
+    LOG3(("Http2Stream::OnWriteSegment read from flow control buffer %p %x %d\n",
+          this, mStreamID, *countWritten));
+    if (!IsDataAvailable(mInputBufferIn)) {
+      // drop the pipe if we don't need it anymore
+      mInputBufferIn = nullptr;
+      mInputBufferOut = nullptr;
+    }
     return rv;
+  }
 
-  mSession->ConnectPushedStream(this);
-  return NS_OK;
+  // read from the network
+  return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
 }
 
 /// connect tunnels
 
 void
 Http2Stream::ClearTransactionsBlockedOnTunnel()
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
--- a/netwerk/protocol/http/Http2Stream.h
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -47,17 +47,17 @@ public:
   uint32_t StreamID() { return mStreamID; }
   Http2PushedStream *PushSource() { return mPushSource; }
 
   stateType HTTPState() { return mState; }
   void SetHTTPState(stateType val) { mState = val; }
 
   virtual nsresult ReadSegments(nsAHttpSegmentReader *,  uint32_t, uint32_t *);
   virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *);
-  virtual bool DeferCleanup(nsresult status) { return false; }
+  virtual bool DeferCleanup(nsresult status);
 
   // The consumer stream is the synthetic pull stream hooked up to this stream
   // http2PushedStream overrides it
   virtual Http2Stream *GetConsumerStream() { return nullptr; };
 
   const nsAFlatCString &Origin() const { return mOrigin; }
   const nsAFlatCString &Host() const { return mHeaderHost; }
   const nsAFlatCString &Path() const { return mHeaderPath; }
@@ -119,17 +119,17 @@ public:
     mLocalUnacked += delta;
   }
 
   void IncrementClientReceiveWindow(uint32_t delta) {
     mClientReceiveWindow += delta;
     mLocalUnacked -= delta;
   }
 
-  uint64_t LocalUnAcked() { return mLocalUnacked; }
+  uint64_t LocalUnAcked();
   int64_t  ClientReceiveWindow()  { return mClientReceiveWindow; }
 
   bool     BlockedOnRwin() { return mBlockedOnRwin; }
 
   uint32_t Priority() { return mPriority; }
   void SetPriority(uint32_t);
   void SetPriorityDependency(uint32_t, uint8_t, bool);
   void UpdatePriorityDependency();
@@ -205,16 +205,18 @@ private:
   nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *);
   nsresult GenerateOpen();
 
   void     AdjustPushedPriority();
   void     AdjustInitialWindow();
   nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment);
   void     GenerateDataFrameHeader(uint32_t, bool);
 
+  nsresult BufferInput(uint32_t , uint32_t *);
+
   // The underlying HTTP transaction. This pointer is used as the key
   // in the Http2Session mStreamTransactionHash so it is important to
   // keep a reference to it as long as this stream is a member of that hash.
   // (i.e. don't change it or release it after it is set in the ctor).
   nsRefPtr<nsAHttpTransaction> mTransaction;
 
   // The underlying socket transport object is needed to propogate some events
   nsISocketTransport         *mSocketTransport;
@@ -253,16 +255,20 @@ private:
   uint32_t                     mSentFin              : 1;
 
   // Flag is set after the WAITING_FOR Transport event has been generated
   uint32_t                     mSentWaitingFor       : 1;
 
   // Flag is set after TCP send autotuning has been disabled
   uint32_t                     mSetTCPSocketBuffer   : 1;
 
+  // Flag is set when OnWriteSegment is being called directly from stream instead
+  // of transaction
+  uint32_t                     mBypassInputBuffer   : 1;
+
   // The InlineFrame and associated data is used for composing control
   // frames and data frame headers.
   nsAutoArrayPtr<uint8_t>      mTxInlineFrame;
   uint32_t                     mTxInlineFrameSize;
   uint32_t                     mTxInlineFrameUsed;
 
   // mTxStreamFrameSize tracks the progress of
   // transmitting a request body data frame. The data frame itself
@@ -307,16 +313,21 @@ private:
 
   // For Progress Events
   uint64_t                     mTotalSent;
   uint64_t                     mTotalRead;
 
   // For Http2Push
   Http2PushedStream *mPushSource;
 
+  // A pipe used to store stream data when the transaction cannot keep up
+  // and flow control has not yet kicked in.
+  nsCOMPtr<nsIInputStream> mInputBufferIn;
+  nsCOMPtr<nsIOutputStream> mInputBufferOut;
+
 /// connect tunnels
 public:
   bool IsTunnel() { return mIsTunnel; }
 private:
   void ClearTransactionsBlockedOnTunnel();
   void MapStreamToPlainText();
   void MapStreamToHttpConnection();
 
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -75,16 +75,17 @@ HttpBaseChannel::HttpBaseChannel()
   , mAllowSpdy(true)
   , mAllowAltSvc(true)
   , mResponseTimeoutEnabled(true)
   , mAllRedirectsSameOrigin(true)
   , mAllRedirectsPassTimingAllowCheck(true)
   , mForceNoIntercept(false)
   , mResponseCouldBeSynthesized(false)
   , mSuspendCount(0)
+  , mInitialRwin(0)
   , mProxyResolveFlags(0)
   , mProxyURI(nullptr)
   , mContentDispositionHint(UINT32_MAX)
   , mHttpHandler(gHttpHandler)
   , mReferrerPolicy(REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE)
   , mRedirectCount(0)
   , mForcePending(false)
   , mCorsIncludeCredentials(false)
@@ -2051,16 +2052,34 @@ HttpBaseChannel::GetResponseTimeoutEnabl
 NS_IMETHODIMP
 HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable)
 {
   mResponseTimeoutEnabled = aEnable;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+HttpBaseChannel::GetInitialRwin(uint32_t *aRwin)
+{
+  if (NS_WARN_IF(!aRwin)) {
+    return NS_ERROR_NULL_POINTER;
+  }
+  *aRwin = mInitialRwin;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitialRwin(uint32_t aRwin)
+{
+  ENSURE_CALLED_BEFORE_CONNECT();
+  mInitialRwin = aRwin;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 HttpBaseChannel::ForcePending(bool aForcePending)
 {
   mForcePending = aForcePending;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime)
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -183,16 +183,18 @@ public:
   NS_IMETHOD SetAllowSpdy(bool aAllowSpdy) override;
   NS_IMETHOD GetAllowAltSvc(bool *aAllowAltSvc) override;
   NS_IMETHOD SetAllowAltSvc(bool aAllowAltSvc) override;
   NS_IMETHOD GetApiRedirectToURI(nsIURI * *aApiRedirectToURI) override;
   virtual nsresult AddSecurityMessage(const nsAString &aMessageTag, const nsAString &aMessageCategory);
   NS_IMETHOD TakeAllSecurityMessages(nsCOMArray<nsISecurityConsoleMessage> &aMessages) override;
   NS_IMETHOD GetResponseTimeoutEnabled(bool *aEnable) override;
   NS_IMETHOD SetResponseTimeoutEnabled(bool aEnable) override;
+  NS_IMETHOD GetInitialRwin(uint32_t* aRwin) override;
+  NS_IMETHOD SetInitialRwin(uint32_t aRwin) override;
   NS_IMETHOD GetNetworkInterfaceId(nsACString& aNetworkInterfaceId) override;
   NS_IMETHOD SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId) override;
   NS_IMETHOD ForcePending(bool aForcePending) override;
   NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override;
   NS_IMETHOD ForceNoIntercept() override;
   NS_IMETHOD GetCorsIncludeCredentials(bool* aInclude) override;
   NS_IMETHOD SetCorsIncludeCredentials(bool aInclude) override;
   NS_IMETHOD GetCorsMode(uint32_t* aCorsMode) override;
@@ -394,16 +396,19 @@ protected:
   uint32_t                          mForceNoIntercept           : 1;
 
   // True if this channel was intercepted and could receive a synthesized response.
   uint32_t                          mResponseCouldBeSynthesized : 1;
 
   // Current suspension depth for this channel object
   uint32_t                          mSuspendCount;
 
+  // Per channel transport window override (0 means no override)
+  uint32_t                          mInitialRwin;
+
   nsCOMPtr<nsIURI>                  mAPIRedirectToURI;
   nsAutoPtr<nsTArray<nsCString> >   mRedirectedCachekeys;
 
   uint32_t                          mProxyResolveFlags;
   nsCOMPtr<nsIURI>                  mProxyURI;
 
   uint32_t                          mContentDispositionHint;
   nsAutoPtr<nsString>               mContentDispositionFilename;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -1714,16 +1714,17 @@ HttpChannelChild::ContinueAsyncOpen()
   openArgs.thirdPartyFlags() = mThirdPartyFlags;
   openArgs.resumeAt() = mSendResumeAt;
   openArgs.startPos() = mStartPos;
   openArgs.entityID() = mEntityID;
   openArgs.chooseApplicationCache() = mChooseApplicationCache;
   openArgs.appCacheClientID() = appCacheClientId;
   openArgs.allowSpdy() = mAllowSpdy;
   openArgs.allowAltSvc() = mAllowAltSvc;
+  openArgs.initialRwin() = mInitialRwin;
 
   uint32_t cacheKey = 0;
   if (mCacheKey) {
     nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(mCacheKey);
     if (!container) {
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -122,17 +122,18 @@ HttpChannelParent::Init(const HttpChanne
                        a.requestMethod(), a.uploadStream(),
                        a.uploadStreamHasHeaders(), a.priority(), a.classOfService(),
                        a.redirectionLimit(), a.allowPipelining(), a.allowSTS(),
                        a.thirdPartyFlags(), a.resumeAt(), a.startPos(),
                        a.entityID(), a.chooseApplicationCache(),
                        a.appCacheClientID(), a.allowSpdy(), a.allowAltSvc(), a.fds(),
                        a.loadInfo(), a.synthesizedResponseHead(),
                        a.synthesizedSecurityInfoSerialization(),
-                       a.cacheKey(), a.schedulingContextID(), a.preflightArgs());
+                       a.cacheKey(), a.schedulingContextID(), a.preflightArgs(),
+                       a.initialRwin());
   }
   case HttpChannelCreationArgs::THttpChannelConnectArgs:
   {
     const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
     return ConnectChannel(cArgs.channelId(), cArgs.shouldIntercept());
   }
   default:
     NS_NOTREACHED("unknown open type");
@@ -281,17 +282,18 @@ HttpChannelParent::DoAsyncOpen(  const U
                                  const bool&                allowSpdy,
                                  const bool&                allowAltSvc,
                                  const OptionalFileDescriptorSet& aFds,
                                  const OptionalLoadInfoArgs& aLoadInfoArgs,
                                  const OptionalHttpResponseHead& aSynthesizedResponseHead,
                                  const nsCString&           aSecurityInfoSerialization,
                                  const uint32_t&            aCacheKey,
                                  const nsCString&           aSchedulingContextID,
-                                 const OptionalCorsPreflightArgs& aCorsPreflightArgs)
+                                 const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+                                 const uint32_t&            aInitialRwin)
 {
   nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
   if (!uri) {
     // URIParams does MOZ_ASSERT if null, but we need to protect opt builds from
     // null deref here.
     return false;
   }
   nsCOMPtr<nsIURI> originalUri = DeserializeURI(aOriginalURI);
@@ -446,16 +448,17 @@ HttpChannelParent::DoAsyncOpen(  const U
     mChannel->SetClassFlags(classOfService);
   }
   mChannel->SetRedirectionLimit(redirectionLimit);
   mChannel->SetAllowPipelining(allowPipelining);
   mChannel->SetAllowSTS(allowSTS);
   mChannel->SetThirdPartyFlags(thirdPartyFlags);
   mChannel->SetAllowSpdy(allowSpdy);
   mChannel->SetAllowAltSvc(allowAltSvc);
+  mChannel->SetInitialRwin(aInitialRwin);
 
   nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
     do_QueryObject(mChannel);
   nsCOMPtr<nsIApplicationCacheService> appCacheService =
     do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
 
   bool setChooseApplicationCache = chooseApplicationCache;
   if (appCacheChan && appCacheService) {
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -116,17 +116,18 @@ protected:
                    const bool&                allowSpdy,
                    const bool&                allowAltSvc,
                    const OptionalFileDescriptorSet& aFds,
                    const OptionalLoadInfoArgs& aLoadInfoArgs,
                    const OptionalHttpResponseHead& aSynthesizedResponseHead,
                    const nsCString&           aSecurityInfoSerialization,
                    const uint32_t&            aCacheKey,
                    const nsCString&           aSchedulingContextID,
-                   const OptionalCorsPreflightArgs& aCorsPreflightArgs);
+                   const OptionalCorsPreflightArgs& aCorsPreflightArgs,
+                   const uint32_t&            aInitialRwin);
 
   virtual bool RecvSetPriority(const uint16_t& priority) override;
   virtual bool RecvSetClassOfService(const uint32_t& cos) override;
   virtual bool RecvSetCacheTokenCachedCharset(const nsCString& charset) override;
   virtual bool RecvSuspend() override;
   virtual bool RecvResume() override;
   virtual bool RecvCancel(const nsresult& status) override;
   virtual bool RecvRedirect2Verify(const nsresult& result,
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -65,17 +65,25 @@ public:
     // 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
+    // when a gecko caller has called ResumeRecv() after being paused
+    virtual void TransactionHasDataToRecv(nsAHttpTransaction *)
+    {
+        // by default do nothing - only multiplexed protocols need to overload
+        return;
+    }
+
     // called by the connection manager to close a transaction being processed
     // by this connection.
     //
     // @param transaction
     //        the transaction being closed.
     // @param reason
     //        the reason for closing the transaction.  NS_BASE_STREAM_CLOSED
     //        is equivalent to NS_OK.
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -192,16 +192,17 @@ nsHttpHandler::nsHttpHandler()
     , mCoalesceSpdy(true)
     , mSpdyPersistentSettings(false)
     , mAllowPush(true)
     , mEnableAltSvc(false)
     , mEnableAltSvcOE(false)
     , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
     , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize)
     , mSpdyPushAllowance(32768)
+    , mSpdyPullAllowance(ASpdySession::kInitialRwin)
     , mDefaultSpdyConcurrent(ASpdySession::kDefaultMaxConcurrent)
     , mSpdyPingThreshold(PR_SecondsToInterval(58))
     , mSpdyPingTimeout(PR_SecondsToInterval(8))
     , mConnectTimeout(90000)
     , mParallelSpeculativeConnectLimit(6)
     , mRequestTokenBucketEnabled(true)
     , mRequestTokenBucketMinParallelism(6)
     , mRequestTokenBucketHz(100)
@@ -1310,16 +1311,24 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
         rv = prefs->GetIntPref(HTTP_PREF("spdy.push-allowance"), &val);
         if (NS_SUCCEEDED(rv)) {
             mSpdyPushAllowance =
                 static_cast<uint32_t>
                 (clamped(val, 1024, static_cast<int32_t>(ASpdySession::kInitialRwin)));
         }
     }
 
+    if (PREF_CHANGED(HTTP_PREF("spdy.pull-allowance"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("spdy.pull-allowance"), &val);
+        if (NS_SUCCEEDED(rv)) {
+            mSpdyPullAllowance =
+                static_cast<uint32_t>(clamped(val, 1024, 0x7fffffff));
+        }
+    }
+
     if (PREF_CHANGED(HTTP_PREF("spdy.default-concurrent"))) {
         rv = prefs->GetIntPref(HTTP_PREF("spdy.default-concurrent"), &val);
         if (NS_SUCCEEDED(rv)) {
             mDefaultSpdyConcurrent =
                 static_cast<uint32_t>(std::max<int32_t>(std::min<int32_t>(val, 9999), 1));
         }
     }
 
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -106,16 +106,17 @@ public:
     bool           IsSpdyV31Enabled() { return mSpdyV31; }
     bool           IsHttp2Enabled() { return mHttp2Enabled; }
     bool           EnforceHttp2TlsProfile() { return mEnforceHttp2TlsProfile; }
     bool           CoalesceSpdy() { return mCoalesceSpdy; }
     bool           UseSpdyPersistentSettings() { return mSpdyPersistentSettings; }
     uint32_t       SpdySendingChunkSize() { return mSpdySendingChunkSize; }
     uint32_t       SpdySendBufferSize()      { return mSpdySendBufferSize; }
     uint32_t       SpdyPushAllowance()       { return mSpdyPushAllowance; }
+    uint32_t       SpdyPullAllowance()       { return mSpdyPullAllowance; }
     uint32_t       DefaultSpdyConcurrent()   { return mDefaultSpdyConcurrent; }
     PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
     PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
     bool           AllowPush()   { return mAllowPush; }
     bool           AllowAltSvc() { return mEnableAltSvc; }
     bool           AllowAltSvcOE() { return mEnableAltSvcOE; }
     uint32_t       ConnectTimeout()  { return mConnectTimeout; }
     uint32_t       ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
@@ -497,16 +498,17 @@ private:
     uint32_t           mEnableAltSvcOE : 1;
 
     // Try to use SPDY features instead of HTTP/1.1 over SSL
     SpdyInformation    mSpdyInfo;
 
     uint32_t       mSpdySendingChunkSize;
     uint32_t       mSpdySendBufferSize;
     uint32_t       mSpdyPushAllowance;
+    uint32_t       mSpdyPullAllowance;
     uint32_t       mDefaultSpdyConcurrent;
     PRIntervalTime mSpdyPingThreshold;
     PRIntervalTime mSpdyPingTimeout;
 
     // The maximum amount of time to wait for socket transport to be
     // established. In milliseconds.
     uint32_t       mConnectTimeout;
 
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -93,16 +93,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mRequestSize(0)
     , mConnection(nullptr)
     , mRequestHead(nullptr)
     , mResponseHead(nullptr)
     , mContentLength(-1)
     , mContentRead(0)
     , mInvalidResponseBytesRead(0)
     , mPushedStream(nullptr)
+    , mInitialRwin(0)
     , mChunkedDecoder(nullptr)
     , mStatus(NS_OK)
     , mPriority(0)
     , mRestartCount(0)
     , mCaps(0)
     , mClassification(CLASS_GENERAL)
     , mPipelinePosition(0)
     , mCapsToClear(0)
@@ -264,16 +265,17 @@ nsHttpTransaction::Init(uint32_t caps,
     nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
         do_QueryInterface(eventsink);
     if (httpChannelInternal) {
         rv = httpChannelInternal->GetResponseTimeoutEnabled(
             &mResponseTimeoutEnabled);
         if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
         }
+        httpChannelInternal->GetInitialRwin(&mInitialRwin);
     }
 
     // create transport event sink proxy. it coalesces all events if and only
     // if the activity observer is not active. when the observer is active
     // we need not to coalesce any events to get all expected notifications
     // of the transaction state, necessary for correct debugging and logging.
     rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink),
                                         eventsink, target,
@@ -2165,16 +2167,17 @@ nsHttpTransaction::OnInputStreamReady(ns
 // nsHttpTransaction::nsIOutputStreamCallback
 //-----------------------------------------------------------------------------
 
 // called on the socket thread
 NS_IMETHODIMP
 nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out)
 {
     if (mConnection) {
+        mConnection->TransactionHasDataToRecv(this);
         nsresult rv = mConnection->ResumeRecv();
         if (NS_FAILED(rv))
             NS_ERROR("ResumeRecv failed");
     }
     return NS_OK;
 }
 
 // nsHttpTransaction::RestartVerifier
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -135,16 +135,17 @@ public:
     Http2PushedStream *GetPushedStream() { return mPushedStream; }
     Http2PushedStream *TakePushedStream()
     {
         Http2PushedStream *r = mPushedStream;
         mPushedStream = nullptr;
         return r;
     }
     void SetPushedStream(Http2PushedStream *push) { mPushedStream = push; }
+    uint32_t InitialRwin() const { return mInitialRwin; };
 
     // Locked methods to get and set timing info
     const TimingStruct Timings();
     void SetDomainLookupStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetDomainLookupEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetConnectStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetConnectEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
     void SetRequestStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false);
@@ -243,16 +244,17 @@ private:
     // After a 304/204 or other "no-content" style response we will skip over
     // up to MAX_INVALID_RESPONSE_BODY_SZ bytes when looking for the next
     // response header to deal with servers that actually sent a response
     // body where they should not have. This member tracks how many bytes have
     // so far been skipped.
     uint32_t                        mInvalidResponseBytesRead;
 
     Http2PushedStream               *mPushedStream;
+    uint32_t                        mInitialRwin;
 
     nsHttpChunkedDecoder            *mChunkedDecoder;
 
     TimingStruct                    mTimings;
 
     nsresult                        mStatus;
 
     int16_t                         mPriority;
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -34,17 +34,17 @@ interface nsIHttpUpgradeListener : nsISu
                               in nsIAsyncOutputStream aSocketOut);
 };
 
 /**
  * Dumping ground for http.  This interface will never be frozen.  If you are
  * using any feature exposed by this interface, be aware that this interface
  * will change and you will be broken.  You have been warned.
  */
-[scriptable, uuid(46ef729f-4c9b-4084-b9e2-498992a31aee)]
+[scriptable, uuid(5f019a6f-6f6f-4b5c-8994-70cdbc40258e)]
 
 interface nsIHttpChannelInternal : nsISupports
 {
     /**
      * An http channel can own a reference to the document URI
      */
     attribute nsIURI documentURI;
 
@@ -198,16 +198,23 @@ interface nsIHttpChannelInternal : nsISu
 
     /**
      * This attribute en/disables the timeout for the first byte of an HTTP
      * response. Enabled by default.
      */
     attribute boolean responseTimeoutEnabled;
 
     /**
+     * If the underlying transport supports RWIN manipulation, this is the
+     * intiial window value for the channel. HTTP/2 implements this.
+     * 0 means no override from system default. Set before opening channel.
+     */
+    attribute unsigned long initialRwin;
+
+    /**
      * Get value of the URI passed to nsIHttpChannel.redirectTo() if any.
      * May return null when redirectTo() has not been called.
      */
     readonly attribute nsIURI apiRedirectToURI;
 
     /**
      * Enable/Disable use of Alternate Services with this channel.
      * The network.http.altsvc.enabled preference is still a pre-requisite.