Bug 1365307 - Throttling of HTTP transactions. r=mcmanus
authorHonza Bambas <honzab.moz@firemni.cz>
Thu, 01 Jun 2017 12:16:00 -0400
changeset 410024 d0a62851c33d0ca77d986654d1d9aa201eb8852a
parent 410023 ecad8cb9f20c5c4a3e768ebb96ec52917474680d
child 410025 60752371afbb6a9e0631c55ea09a05c0d767d04b
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs1365307
milestone55.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 1365307 - Throttling of HTTP transactions. r=mcmanus
modules/libpref/init/all.js
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2081,21 +2081,25 @@ pref("network.auth.subresource-img-cross
 // for credentials from the user explicitly.
 // If set to true, and a server URL conforms other conditions for sending default
 // credentials, those will be sent automatically in Private Browsing windows.
 //
 // This preference has no effect when the browser is set to "Never Remember History",
 // in that case default credentials will always be used.
 pref("network.auth.private-browsing-sso", false);
 
-// Control how the throttling service works - number of ms that each
+// Control how throttling of http responses works - number of ms that each
 // suspend and resume period lasts (prefs named appropriately)
-pref("network.throttle.suspend-for", 3000);
-pref("network.throttle.resume-for", 200);
-pref("network.throttle.enable", true);
+pref("network.http.throttle.enable", true);
+pref("network.http.throttle.suspend-for", 3000);
+pref("network.http.throttle.resume-for", 200);
+// Delay we resume throttled background responses after the last unthrottled
+// response has finished.  Prevents resuming too soon during an active page load
+// at which sub-resource reqeusts quickly come and go.
+pref("network.http.throttle.resume-background-in", 400);
 
 pref("permissions.default.image",           1); // 1-Accept, 2-Deny, 3-dontAcceptForeign
 
 pref("network.proxy.type",                  5);
 pref("network.proxy.ftp",                   "");
 pref("network.proxy.ftp_port",              0);
 pref("network.proxy.http",                  "");
 pref("network.proxy.http_port",             0);
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -399,16 +399,17 @@ Http2Session::AddStream(nsAHttpTransacti
               "transaction (%08x).\n", this, aHttpTransaction, trans,
               static_cast<uint32_t>(rv)));
       }
       return true;
     }
   }
 
   aHttpTransaction->SetConnection(this);
+  aHttpTransaction->OnActivated(true);
 
   if (aUseTunnel) {
     LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
           this, aHttpTransaction));
     DispatchOnTunnel(aHttpTransaction, aCallbacks);
     return true;
   }
 
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -40,16 +40,20 @@ class SpdyConnectTransaction;
 class nsAHttpTransaction : public nsSupportsWeakReference
 {
 public:
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_AHTTPTRANSACTION_IID)
 
     // called by the connection when it takes ownership of the transaction.
     virtual void SetConnection(nsAHttpConnection *) = 0;
 
+    // called by the connection after a successfull activation of this transaction
+    // in other words, tells the transaction it transitioned to the "active" state.
+    virtual void OnActivated(bool h2) {}
+
     // used to obtain the connection associated with this transaction
     virtual nsAHttpConnection *Connection() = 0;
 
     // called by the connection to get security callbacks to set on the
     // socket transport.
     virtual void GetSecurityCallbacks(nsIInterfaceRequestor **) = 0;
 
     // called to report socket status (see nsITransportEventSink)
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -706,20 +706,16 @@ nsHttpChannel::ContinueConnect()
 
     rv = mTransactionPump->AsyncRead(this, nullptr);
     if (NS_FAILED(rv)) return rv;
 
     uint32_t suspendCount = mSuspendCount;
     while (suspendCount--)
         mTransactionPump->Suspend();
 
-    if (mClassOfService & nsIClassOfService::Throttleable) {
-        gHttpHandler->ThrottleTransaction(mTransaction, true);
-    }
-
     return NS_OK;
 }
 
 void
 nsHttpChannel::SpeculativeConnect()
 {
     // Before we take the latency hit of dealing with the cache, try and
     // get the TCP (and SSL) handshakes going so they can overlap.
@@ -6617,20 +6613,18 @@ nsHttpChannel::ContinueBeginConnect()
 
 //-----------------------------------------------------------------------------
 // HttpChannel::nsIClassOfService
 //-----------------------------------------------------------------------------
 
 void
 nsHttpChannel::OnClassOfServiceUpdated()
 {
-    bool throttleable = !!(mClassOfService & nsIClassOfService::Throttleable);
-
     if (mTransaction) {
-        gHttpHandler->ThrottleTransaction(mTransaction, throttleable);
+        gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction, mClassOfService);
     }
 }
 
 NS_IMETHODIMP
 nsHttpChannel::SetClassFlags(uint32_t inFlags)
 {
     uint32_t previous = mClassOfService;
     mClassOfService = inFlags;
@@ -8004,20 +7998,16 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnec
 
     rv = mTransactionPump->AsyncRead(this, nullptr);
     if (NS_FAILED(rv)) return rv;
 
     uint32_t suspendCount = mSuspendCount;
     while (suspendCount--)
         mTransactionPump->Suspend();
 
-    if (mSuspendCount && mClassOfService & nsIClassOfService::Throttleable) {
-        gHttpHandler->ThrottleTransaction(mTransaction, true);
-    }
-
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIApplicationCacheChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
@@ -8714,20 +8704,16 @@ nsHttpChannel::SuspendInternal()
     NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
 
     LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
 
     ++mSuspendCount;
 
     if (mSuspendCount == 1) {
         mSuspendTimestamp = TimeStamp::NowLoRes();
-
-        if (mClassOfService & nsIClassOfService::Throttleable && mTransaction) {
-            gHttpHandler->ThrottleTransaction(mTransaction, true);
-        }
     }
 
     nsresult rvTransaction = NS_OK;
     if (mTransactionPump) {
         rvTransaction = mTransactionPump->Suspend();
     }
     nsresult rvCache = NS_OK;
     if (mCachePump) {
@@ -8743,20 +8729,16 @@ nsHttpChannel::ResumeInternal()
     NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
 
     LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
 
     if (--mSuspendCount == 0) {
         mSuspendTotalTime += (TimeStamp::NowLoRes() - mSuspendTimestamp).
                                ToMilliseconds();
 
-        if (mClassOfService & nsIClassOfService::Throttleable && mTransaction) {
-            gHttpHandler->ThrottleTransaction(mTransaction, false);
-        }
-
         if (mCallOnResume) {
             nsresult rv = AsyncCall(mCallOnResume);
             mCallOnResume = nullptr;
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     nsresult rvTransaction = NS_OK;
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -659,16 +659,18 @@ nsHttpConnection::Activate(nsAHttpTransa
     }
 
     if (mTLSFilter) {
         rv = mTLSFilter->SetProxiedTransaction(trans);
         NS_ENSURE_SUCCESS(rv, rv);
         mTransaction = mTLSFilter;
     }
 
+    trans->OnActivated(false);
+
     rv = OnOutputStreamReady(mSocketOut);
 
 failed_activation:
     if (NS_FAILED(rv)) {
         mTransaction = nullptr;
     }
 
     return rv;
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -110,26 +110,34 @@ nsHttpConnectionMgr::InsertTransactionSo
 //-----------------------------------------------------------------------------
 
 nsHttpConnectionMgr::nsHttpConnectionMgr()
     : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
     , mMaxUrgentExcessiveConns(0)
     , mMaxConns(0)
     , mMaxPersistConnsPerHost(0)
     , mMaxPersistConnsPerProxy(0)
+    , mMaxRequestDelay(0)
+    , mThrottleEnabled(false)
+    , mThrottleSuspendFor(0)
+    , mThrottleResumeFor(0)
+    , mThrottleResumeIn(0)
     , mIsShuttingDown(false)
     , mNumActiveConns(0)
     , mNumIdleConns(0)
     , mNumSpdyActiveConns(0)
     , mNumHalfOpenConns(0)
     , mTimeOfNextWakeUp(UINT64_MAX)
     , mPruningNoTraffic(false)
     , mTimeoutTickArmed(false)
     , mTimeoutTickNext(1)
     , mCurrentTopLevelOuterContentWindowId(0)
+    , mThrottlingInhibitsReading(false)
+    , mActiveTabTransactionsExist(false)
+    , mActiveTabUnthrottledTransactionsExist(false)
 {
     LOG(("Creating nsHttpConnectionMgr @%p\n", this));
 }
 
 nsHttpConnectionMgr::~nsHttpConnectionMgr()
 {
     LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
     MOZ_ASSERT(mCoalescingHash.Count() == 0);
@@ -159,29 +167,38 @@ nsHttpConnectionMgr::EnsureSocketThreadT
     return sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
 }
 
 nsresult
 nsHttpConnectionMgr::Init(uint16_t maxUrgentExcessiveConns,
                           uint16_t maxConns,
                           uint16_t maxPersistConnsPerHost,
                           uint16_t maxPersistConnsPerProxy,
-                          uint16_t maxRequestDelay)
+                          uint16_t maxRequestDelay,
+                          bool throttleEnabled,
+                          uint32_t throttleSuspendFor,
+                          uint32_t throttleResumeFor,
+                          uint32_t throttleResumeIn)
 {
     LOG(("nsHttpConnectionMgr::Init\n"));
 
     {
         ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
         mMaxUrgentExcessiveConns = maxUrgentExcessiveConns;
         mMaxConns = maxConns;
         mMaxPersistConnsPerHost = maxPersistConnsPerHost;
         mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
         mMaxRequestDelay = maxRequestDelay;
 
+        mThrottleEnabled = throttleEnabled;
+        mThrottleSuspendFor = throttleSuspendFor;
+        mThrottleResumeFor = throttleResumeFor;
+        mThrottleResumeIn = throttleResumeIn;
+
         mIsShuttingDown = false;
     }
 
     return EnsureSocketThreadTarget();
 }
 
 class BoolWrapper : public ARefBase
 {
@@ -339,23 +356,25 @@ nsHttpConnectionMgr::Observe(nsISupports
                              const char16_t *data)
 {
     LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
 
     if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
         nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
         if (timer == mTimer) {
             Unused << PruneDeadConnections();
-        }
-        else if (timer == mTimeoutTick) {
+        } else if (timer == mTimeoutTick) {
             TimeoutTick();
         } else if (timer == mTrafficTimer) {
             Unused << PruneNoTraffic();
-        }
-        else {
+        } else if (timer == mThrottleTicker) {
+            ThrottlerTick();
+        } else if (timer == mDelayedResumeReadTimer) {
+            ResumeBackgroundThrottledTransactions();
+        } else {
             MOZ_ASSERT(false, "unexpected timer-callback");
             LOG(("Unexpected timer object\n"));
             return NS_ERROR_UNEXPECTED;
         }
     }
 
     return NS_OK;
 }
@@ -373,22 +392,22 @@ nsHttpConnectionMgr::AddTransaction(nsHt
 nsresult
 nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
 {
     LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans, priority));
     return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
 }
 
 void
-nsHttpConnectionMgr::ThrottleTransaction(nsHttpTransaction *trans, bool throttle)
+nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(nsHttpTransaction *trans, uint32_t classOfService)
 {
-    LOG(("nsHttpConnectionMgr::ThrottleTransaction [trans=%p throttle=%" PRIx32 "]\n",
-         trans, static_cast<uint32_t>(throttle)));
-    Unused << PostEvent(&nsHttpConnectionMgr::OnMsgThrottleTransaction,
-                        static_cast<int32_t>(throttle), trans);
+    LOG(("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p classOfService=%" PRIu32 "]\n",
+         trans, static_cast<uint32_t>(classOfService)));
+    Unused << PostEvent(&nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction,
+                        static_cast<int32_t>(classOfService), trans);
 }
 
 nsresult
 nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
 {
     LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n",
          trans, static_cast<uint32_t>(reason)));
     return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
@@ -2152,16 +2171,20 @@ nsHttpConnectionMgr::OnMsgShutdown(int32
     if (mTimer) {
       mTimer->Cancel();
       mTimer = nullptr;
     }
     if (mTrafficTimer) {
       mTrafficTimer->Cancel();
       mTrafficTimer = nullptr;
     }
+    DestroyThrottleTicker();
+    mActiveTransactions[false].Clear();
+    mActiveTransactions[true].Clear();
+
     mCoalescingHash.Clear();
 
     // signal shutdown complete
     nsCOMPtr<nsIRunnable> runnable =
         new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
                       0, param);
     NS_DispatchToMainThread(runnable);
 }
@@ -2219,25 +2242,25 @@ nsHttpConnectionMgr::OnMsgReschedTransac
         if (index >= 0) {
             RefPtr<PendingTransactionInfo> pendingTransInfo = (*pendingQ)[index];
             pendingQ->RemoveElementAt(index);
             InsertTransactionSorted(*pendingQ, pendingTransInfo);
         }
     }
 }
 
-void nsHttpConnectionMgr::OnMsgThrottleTransaction(int32_t arg, ARefBase *param)
+void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(int32_t arg, ARefBase *param)
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
-    LOG(("nsHttpConnectionMgr::OnMsgThrottleTransaction [trans=%p]\n", param));
-
-    bool throttle = static_cast<bool>(arg);
+    LOG(("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction [trans=%p]\n", param));
+
+    uint32_t cos = static_cast<uint32_t>(arg);
     nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
 
-    trans->ThrottleResponse(throttle);
+    trans->SetClassOfService(cos);
 }
 
 void
 nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
     LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
 
@@ -2725,16 +2748,28 @@ nsHttpConnectionMgr::OnMsgUpdateParam(in
         mMaxPersistConnsPerHost = value;
         break;
     case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
         mMaxPersistConnsPerProxy = value;
         break;
     case MAX_REQUEST_DELAY:
         mMaxRequestDelay = value;
         break;
+    case THROTTLING_ENABLED:
+        SetThrottlingEnabled(!!value);
+        break;
+    case THROTTLING_SUSPEND_FOR:
+        mThrottleSuspendFor = value;
+        break;
+    case THROTTLING_RESUME_FOR:
+        mThrottleResumeFor = value;
+        break;
+    case THROTTLING_RESUME_IN:
+        mThrottleResumeIn = value;
+        break;
     default:
         NS_NOTREACHED("unexpected parameter name");
     }
 }
 
 // nsHttpConnectionMgr::nsConnectionEntry
 nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
 {
@@ -2798,26 +2833,490 @@ nsHttpConnectionMgr::UpdateCurrentTopLev
 {
     RefPtr<UINT64Wrapper> windowIdWrapper = new UINT64Wrapper(aWindowId);
     return PostEvent(
         &nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId,
         0,
         windowIdWrapper);
 }
 
+void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable)
+{
+    LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
+
+    mThrottleEnabled = aEnable;
+
+    if (mThrottleEnabled) {
+        EnsureThrottleTickerIfNeeded();
+    } else {
+        DestroyThrottleTicker();
+        ResumeReadOf(mActiveTransactions[false]);
+        ResumeReadOf(mActiveTransactions[true]);
+    }
+}
+
+void nsHttpConnectionMgr::LogActiveTransactions(char operation)
+{
+    if (!LOG_ENABLED()) {
+        return;
+    }
+
+    nsTArray<RefPtr<nsHttpTransaction>> *trs = nullptr;
+    uint32_t au, at, bu = 0, bt = 0;
+
+    trs = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
+    au = trs ? trs->Length() : 0;
+    trs = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
+    at = trs ? trs->Length() : 0;
+
+    for (auto iter = mActiveTransactions[false].Iter(); !iter.Done(); iter.Next()) {
+        bu += iter.UserData()->Length();
+    }
+    bu -= au;
+    for (auto iter = mActiveTransactions[true].Iter(); !iter.Done(); iter.Next()) {
+        bt += iter.UserData()->Length();
+    }
+    bt -= at;
+
+    // Shows counts of:
+    // - unthrottled transaction for the active tab
+    // - throttled transaction for the active tab
+    // - unthrottled transaction for background tabs
+    // - throttled transaction for background tabs
+    LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt));
+}
+
 void
-nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
-    int32_t, ARefBase *param)
+nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
+
+    nsTArray<RefPtr<nsHttpTransaction>> *transactions =
+        mActiveTransactions[aThrottled].LookupOrAdd(tabId);
+
+    MOZ_ASSERT(!transactions->Contains(aTrans));
+
+    transactions->AppendElement(aTrans);
+
+    LOG(("nsHttpConnectionMgr::AddActiveTransaction    t=%p tabid=%" PRIx64 "(%d) thr=%d",
+          aTrans, tabId, tabId == mCurrentTopLevelOuterContentWindowId, aThrottled));
+    LogActiveTransactions('+');
+
+    if (tabId == mCurrentTopLevelOuterContentWindowId) {
+        mActiveTabTransactionsExist = true;
+        if (!aThrottled) {
+            mActiveTabUnthrottledTransactionsExist = true;
+        }
+    }
+
+    if (!mThrottleEnabled) {
+        return;
+    }
+
+    EnsureThrottleTickerIfNeeded();
+}
+
+void
+nsHttpConnectionMgr::RemoveActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
+    bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
+
+    nsTArray<RefPtr<nsHttpTransaction>> *transactions =
+        mActiveTransactions[aThrottled].Get(tabId);
+
+    if (!transactions || !transactions->RemoveElement(aTrans)) {
+        // Was not tracked as active, probably just ignore.
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64 "(%d) thr=%d",
+          aTrans, tabId, forActiveTab, aThrottled));
+
+    if (!transactions->IsEmpty()) {
+        // There are still transactions of the type, hence nothing in the throttling conditions
+        // has changed and we don't need to update "Exists" caches nor we need to wake any now
+        // throttled transactions.
+        LogActiveTransactions('-');
+        return;
+    }
+
+    // To optimize the following logic, always remove the entry when the array is empty.
+    mActiveTransactions[aThrottled].Remove(tabId);
+    LogActiveTransactions('-');
+
+    if (forActiveTab) {
+        // Update caches of the active tab transaction existence, since it's now affected
+        if (!aThrottled) {
+            mActiveTabUnthrottledTransactionsExist = false;
+        }
+        if (mActiveTabTransactionsExist) {
+            mActiveTabTransactionsExist = mActiveTransactions[!aThrottled].Contains(tabId);
+        }
+    }
+
+    if (!mThrottleEnabled) {
+        return;
+    }
+
+    bool unthrottledExist = !mActiveTransactions[false].IsEmpty();
+    bool throttledExist = !mActiveTransactions[true].IsEmpty();
+
+    if (!unthrottledExist && !throttledExist) {
+        // Nothing active globally, just get rid of the timer completely and we are done.
+        MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
+        MOZ_ASSERT(!mActiveTabTransactionsExist);
+
+        DestroyThrottleTicker();
+        return;
+    }
+
+    if (!mThrottlingInhibitsReading) {
+        // There is then nothing to wake up.  Affected transactions will not be put
+        // to sleep automatically on next tick.
+        LOG(("  reading not currently inhibited"));
+        return;
+    }
+
+    if (mActiveTabUnthrottledTransactionsExist) {
+        // There are still unthrottled transactions for the active tab, hence the state
+        // is unaffected and we don't need to do anything (nothing to wake).
+        LOG(("  there are unthrottled for the active tab"));
+        return;
+    }
+
+    if (mActiveTabTransactionsExist) {
+        // There are only trottled transactions for the active tab.
+        // If the last transaction we just removed was a non-throttled for the active tab
+        // we can wake the throttled transactions for the active tab.
+        if (forActiveTab && !aThrottled) {
+            LOG(("  resuming throttled for active tab"));
+            ResumeReadOf(mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId));
+        }
+        return;
+    }
+
+    if (!unthrottledExist) {
+        // There are no unthrottled transactions for any tab.  Resume all throttled,
+        // all are only for background tabs.
+        LOG(("  delay resuming throttled for background tabs"));
+        DelayedResumeBackgroundThrottledTransactions();
+        return;
+    }
+
+    if (forActiveTab) {
+        // Removing the last transaction for the active tab frees up the unthrottled
+        // background tabs transactions.
+        LOG(("  resuming unthrottled for background tabs"));
+        ResumeReadOf(mActiveTransactions[false]);
+        return;
+    }
+
+    LOG(("  not resuming anything"));
+}
+
+void
+nsHttpConnectionMgr::MoveActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
+{
+    LOG(("nsHttpConnectionMgr::MoveActiveTransaction ENTER t=%p", aTrans));
+    AddActiveTransaction(aTrans, aThrottled);
+    RemoveActiveTransaction(aTrans, !aThrottled);
+    LOG(("nsHttpConnectionMgr::MoveActiveTransaction EXIT t=%p", aTrans));
+}
+
+bool
+nsHttpConnectionMgr::ShouldStopReading(nsHttpTransaction * aTrans, bool aThrottled)
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
-    mCurrentTopLevelOuterContentWindowId =
-        static_cast<UINT64Wrapper*>(param)->GetValue();
+
+    if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
+        return false;
+    }
+
+    uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
+
+    if (mActiveTabTransactionsExist) {
+        if (!tabId) {
+            // Chrome initiated and unidentified transactions just respect
+            // their throttle flag, when something for the active tab is happening.
+            return aThrottled;
+        }
+        if (tabId != mCurrentTopLevelOuterContentWindowId) {
+            // This is a background tab request, we want them to always throttle.
+            return true;
+        }
+        if (mActiveTabUnthrottledTransactionsExist) {
+            // Unthrottled transactions for the active tab take precedence
+            return aThrottled;
+        }
+        // This is a throttled transaction for the active tab and there are no
+        // unthrottled for the active tab, just let go on full fuel.
+        return false;
+    }
+
+    if (!mActiveTransactions[false].IsEmpty()) {
+        // This means there are unthrottled active transactions for background tabs.
+        // If we are here, there can't be any transactions for the active tab.
+        // (If there is no transaction for a tab id, there is no entry for it in the hashtable.)
+        return aThrottled;
+    }
+
+    // There are only unthrottled transactions for background tabs: don't throttle.
+    return false;
+}
+
+bool nsHttpConnectionMgr::IsThrottleTickerNeeded()
+{
+    LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
+
+    if (mActiveTabUnthrottledTransactionsExist &&
+        mActiveTransactions[false].Count() > 1) {
+        LOG(("  there are unthrottled transactions for both active and bck"));
+        return true;
+    }
+
+    if (mActiveTabTransactionsExist &&
+        mActiveTransactions[true].Count() > 1) {
+        LOG(("  there are throttled transactions for both active and bck"));
+        return true;
+    }
+
+    if (!mActiveTransactions[true].IsEmpty() &&
+        !mActiveTransactions[false].IsEmpty()) {
+        LOG(("  there are both throttled and unthrottled transactions"));
+        return true;
+    }
+
+    LOG(("  nothing to throttle"));
+    return false;
+}
+
+void
+nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded()
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
+    if (!IsThrottleTickerNeeded()) {
+        return;
+    }
+
+    // There is a new demand to throttle, hence unschedule delayed resume
+    // of background throttled transastions.
+    CancelDelayedResumeBackgroundThrottledTransactions();
+
+    if (mThrottleTicker) {
+        return;
+    }
+
+    MOZ_ASSERT(!mThrottlingInhibitsReading);
+
+    mThrottleTicker = do_CreateInstance("@mozilla.org/timer;1");
+    if (mThrottleTicker) {
+        mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
+        mThrottlingInhibitsReading = true;
+    }
+
+    LogActiveTransactions('^');
+}
+
+void
+nsHttpConnectionMgr::DestroyThrottleTicker()
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    // Nothing to throttle, hence no need for this timer anymore.
+    CancelDelayedResumeBackgroundThrottledTransactions();
+
+    MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded());
+
+    if (!mThrottleTicker) {
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
+    mThrottleTicker->Cancel();
+    mThrottleTicker = nullptr;
+    mThrottlingInhibitsReading = false;
+
+    LogActiveTransactions('v');
+}
+
+void
+nsHttpConnectionMgr::ThrottlerTick()
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
+
+    LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d", mThrottlingInhibitsReading));
+
+    if (!mThrottlingInhibitsReading && !IsThrottleTickerNeeded()) {
+        mThrottleTicker = nullptr;
+    } else {
+        mThrottleTicker = do_CreateInstance("@mozilla.org/timer;1");
+    }
+
+    if (mThrottlingInhibitsReading) {
+        if (mThrottleTicker) {
+            mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
+        }
+    } else {
+        // Resume by the ticker happens sooner than delayed resume, no need
+        // for the delayed resume timer
+        CancelDelayedResumeBackgroundThrottledTransactions();
+
+        if (mThrottleTicker) {
+            mThrottleTicker->Init(this, mThrottleResumeFor, nsITimer::TYPE_ONE_SHOT);
+        }
+
+        ResumeReadOf(mActiveTransactions[false], true);
+        ResumeReadOf(mActiveTransactions[true]);
+    }
+}
+
+void
+nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions()
+{
+    if (mDelayedResumeReadTimer) {
+        return;
+    }
+
+    mDelayedResumeReadTimer = do_CreateInstance("@mozilla.org/timer;1");
+    if (!mDelayedResumeReadTimer) {
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
+    mDelayedResumeReadTimer->Init(this, mThrottleResumeIn, nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions()
+{
+    if (!mDelayedResumeReadTimer) {
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions"));
+    mDelayedResumeReadTimer->Cancel();
+    mDelayedResumeReadTimer = nullptr;
+}
+
+void
+nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions()
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
+    mDelayedResumeReadTimer = nullptr;
+
+    // We can destroy the ticker, since only transactions to resume now
+    // are background throttled.  If 'higher class' transactions have
+    // been added, we don't get here - the ticker has been scheduled
+    // and hence the delayed resume timer canceled.
+    MOZ_ASSERT(mActiveTransactions[false].IsEmpty() &&
+                          !mActiveTabTransactionsExist);
+
+    DestroyThrottleTicker();
+    ResumeReadOf(mActiveTransactions[true], true);
+}
+
+void
+nsHttpConnectionMgr::ResumeReadOf(
+    nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>& hashtable,
+    bool excludeForActiveTab)
+{
+    for (auto iter = hashtable.Iter(); !iter.Done(); iter.Next()) {
+        if (excludeForActiveTab && iter.Key() == mCurrentTopLevelOuterContentWindowId) {
+            // These have never been throttled (never stopped reading)
+            continue;
+        }
+        ResumeReadOf(iter.UserData());
+    }
+}
+
+void
+nsHttpConnectionMgr::ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>* transactions)
+{
+    MOZ_ASSERT(transactions);
+
+    for (auto trans : *transactions) {
+        trans->ResumeReading();
+    }
+}
+
+void
+nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
+    int32_t aLoading, ARefBase *param)
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    uint64_t winId = static_cast<UINT64Wrapper*>(param)->GetValue();
+
+    if (mCurrentTopLevelOuterContentWindowId == winId) {
+        // duplicate notification
+        return;
+    }
+
+    bool activeTabWasLoading = mActiveTabTransactionsExist;
+    bool activeTabIdChanged = mCurrentTopLevelOuterContentWindowId != winId;
+
+    mCurrentTopLevelOuterContentWindowId = winId;
+
     LOG(("nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId"
-         " id=%" PRIu64 "\n",
+         " id=%" PRIx64 "\n",
          mCurrentTopLevelOuterContentWindowId));
+
+    nsTArray<RefPtr<nsHttpTransaction>> *transactions = nullptr;
+
+    if (activeTabIdChanged) {
+        // Update the "Exists" caches and resume any transactions that now deserve it,
+        // changing the active tab changes the conditions for throttling.
+        transactions = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
+        mActiveTabUnthrottledTransactionsExist = !!transactions;
+
+        if (!mActiveTabUnthrottledTransactionsExist) {
+            transactions = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
+        }
+        mActiveTabTransactionsExist = !!transactions;
+    }
+
+    if (transactions) {
+        // This means there are some transactions for this newly activated tab, resume them
+        // but anything else.
+        LOG(("  resuming newly activated tab transactions"));
+        ResumeReadOf(transactions);
+        return;
+    }
+
+    if (!activeTabWasLoading) {
+        // There were no transactions for the previously active tab, hence
+        // all remaning transactions, if there were, were all unthrottled,
+        // no need to wake them.
+        return;
+    }
+
+    if (!mActiveTransactions[false].IsEmpty()) {
+        LOG(("  resuming unthrottled background transactions"));
+        ResumeReadOf(mActiveTransactions[false]);
+        return;
+    }
+
+    if (!mActiveTransactions[true].IsEmpty()) {
+        LOG(("  delayed resuming throttled background transactions"));
+        DelayedResumeBackgroundThrottledTransactions();
+        return;
+    }
+
+    DestroyThrottleTicker();
 }
 
 void
 nsHttpConnectionMgr::TimeoutTick()
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
     MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -50,30 +50,38 @@ public:
     NS_DECL_NSIOBSERVER
 
     // parameter names
     enum nsParamName {
         MAX_URGENT_START_Q,
         MAX_CONNECTIONS,
         MAX_PERSISTENT_CONNECTIONS_PER_HOST,
         MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
-        MAX_REQUEST_DELAY
+        MAX_REQUEST_DELAY,
+        THROTTLING_ENABLED,
+        THROTTLING_SUSPEND_FOR,
+        THROTTLING_RESUME_FOR,
+        THROTTLING_RESUME_IN
     };
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may only be called on the main thread.
     //-------------------------------------------------------------------------
 
     nsHttpConnectionMgr();
 
     MOZ_MUST_USE nsresult Init(uint16_t maxUrgentExcessiveConns,
                                uint16_t maxConnections,
                                uint16_t maxPersistentConnectionsPerHost,
                                uint16_t maxPersistentConnectionsPerProxy,
-                               uint16_t maxRequestDelay);
+                               uint16_t maxRequestDelay,
+                               bool throttleEnabled,
+                               uint32_t throttleSuspendFor,
+                               uint32_t throttleResumeFor,
+                               uint32_t throttleResumeIn);
     MOZ_MUST_USE nsresult Shutdown();
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called on any thread.
     //-------------------------------------------------------------------------
 
     // Schedules next pruning of dead connection to happen after
     // given time.
@@ -90,23 +98,19 @@ public:
     // adds a transaction to the list of managed transactions.
     MOZ_MUST_USE nsresult AddTransaction(nsHttpTransaction *, int32_t priority);
 
     // called to reschedule the given transaction.  it must already have been
     // added to the connection manager via AddTransaction.
     MOZ_MUST_USE nsresult RescheduleTransaction(nsHttpTransaction *,
                                                 int32_t priority);
 
-    // tells the transaction to stop receiving the response when |throttle|
-    // is true.  to start receiving again, this must be called with |throttle|
-    // set to false.  calling multiple times with the same value of |throttle|
-    // has no effect.  called by nsHttpChannels with the Throttleable class set
-    // and controlled by net::ThrottlingService.
-    // there is nothing to do when this fails, hence the void result.
-    void ThrottleTransaction(nsHttpTransaction *, bool throttle);
+    // TOOD
+    void UpdateClassOfServiceOnTransaction(nsHttpTransaction *,
+                                           uint32_t classOfService);
 
     // cancels a transaction w/ the given reason.
     MOZ_MUST_USE nsresult CancelTransaction(nsHttpTransaction *,
                                             nsresult reason);
     MOZ_MUST_USE nsresult CancelTransactions(nsHttpConnectionInfo *,
                                              nsresult reason);
 
     // called to force the connection manager to prune its list of idle
@@ -206,16 +210,27 @@ public:
 
     uint16_t MaxRequestDelay() { return mMaxRequestDelay; }
 
     // public, so that the SPDY/http2 seesions can activate
     void ActivateTimeoutTick();
 
     nsresult UpdateCurrentTopLevelOuterContentWindowId(uint64_t aWindowId);
 
+    // tracks and untracks active transactions according their throttle status
+    void AddActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
+    void RemoveActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
+    void MoveActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
+
+    // called by nsHttpTransaction::WriteSegments.  decides whether the transaction
+    // should stop reading data based on: the throttling ticker status, overall
+    // status of all active transactions regarding active tab and respective
+    // throttling state.
+    bool ShouldStopReading(nsHttpTransaction* aTrans, bool aThrottled);
+
 private:
     virtual ~nsHttpConnectionMgr();
 
     class nsHalfOpenSocket;
     class PendingTransactionInfo;
 
     // nsConnectionEntry
     //
@@ -481,16 +496,20 @@ private:
     nsCOMPtr<nsIEventTarget>     mSocketThreadTarget;
 
     // connection limits
     uint16_t mMaxUrgentExcessiveConns;
     uint16_t mMaxConns;
     uint16_t mMaxPersistConnsPerHost;
     uint16_t mMaxPersistConnsPerProxy;
     uint16_t mMaxRequestDelay; // in seconds
+    bool mThrottleEnabled;
+    uint32_t mThrottleSuspendFor;
+    uint32_t mThrottleResumeFor;
+    uint32_t mThrottleResumeIn;
     Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
 
     //-------------------------------------------------------------------------
     // NOTE: these members are only accessed on the socket transport thread
     //-------------------------------------------------------------------------
 
     MOZ_MUST_USE bool ProcessPendingQForEntry(nsConnectionEntry *,
                                               bool considerAll);
@@ -574,17 +593,17 @@ private:
         const nsConnectionEntry *ent,
         nsresult reason);
 
     // message handlers
     void OnMsgShutdown             (int32_t, ARefBase *);
     void OnMsgShutdownConfirm      (int32_t, ARefBase *);
     void OnMsgNewTransaction       (int32_t, ARefBase *);
     void OnMsgReschedTransaction   (int32_t, ARefBase *);
-    void OnMsgThrottleTransaction  (int32_t, ARefBase *);
+    void OnMsgUpdateClassOfServiceOnTransaction  (int32_t, ARefBase *);
     void OnMsgCancelTransaction    (int32_t, ARefBase *);
     void OnMsgCancelTransactions   (int32_t, ARefBase *);
     void OnMsgProcessPendingQ      (int32_t, ARefBase *);
     void OnMsgPruneDeadConnections (int32_t, ARefBase *);
     void OnMsgSpeculativeConnect   (int32_t, ARefBase *);
     void OnMsgReclaimConnection    (int32_t, ARefBase *);
     void OnMsgCompleteUpgrade      (int32_t, ARefBase *);
     void OnMsgUpdateParam          (int32_t, ARefBase *);
@@ -635,16 +654,68 @@ private:
     // Read Timeout Tick handlers
     void TimeoutTick();
 
     // For diagnostics
     void OnMsgPrintDiagnostics(int32_t, ARefBase *);
 
     nsCString mLogData;
     uint64_t mCurrentTopLevelOuterContentWindowId;
+
+    // Called on a pref change
+    void SetThrottlingEnabled(bool aEnable);
+
+    // Two hashtalbes keeping track of active transactions regarding window id and throttling.
+    // Used by the throttling algorithm to obtain number of transactions for the active tab
+    // and for inactive tabs according their throttle status.
+    // mActiveTransactions[0] are all unthrottled transactions, mActiveTransactions[1] throttled.
+    nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>> mActiveTransactions[2];
+
+    // Whether we are inside the "stop reading" interval, altered by the throttle ticker
+    bool mThrottlingInhibitsReading;
+
+    // ticker for the 'stop reading'/'resume reading' signal
+    nsCOMPtr<nsITimer> mThrottleTicker;
+    // Checks if the combination of active transactions requires the ticker.
+    bool IsThrottleTickerNeeded();
+    // The method also unschedules the delayed resume of background tabs timer
+    // if the ticker was about to be scheduled.
+    void EnsureThrottleTickerIfNeeded();
+    // Drops also the mThrottlingInhibitsReading flag.  Immediate or delayed resume
+    // of currently throttled transactions is not affected by this method.
+    void DestroyThrottleTicker();
+    // Handler for the ticker: alters the mThrottlingInhibitsReading flag.
+    void ThrottlerTick();
+
+    // mechanism to delay immediate resume of background tabs and chrome initiated
+    // throttled transactions after the last transaction blocking their unthrottle
+    // has been removed.  Needs to be delayed because during a page load there is
+    // a number of intervals when there is no transaction that would cause throttling.
+    // Hence, throttling of long standing responses, like downloads, would be mostly
+    // ineffective if resumed during every such interval.
+    nsCOMPtr<nsITimer> mDelayedResumeReadTimer;
+    // Schedule the resume
+    void DelayedResumeBackgroundThrottledTransactions();
+    // Simply destroys the timer
+    void CancelDelayedResumeBackgroundThrottledTransactions();
+    // Handler for the timer: resumes all background throttled transactions
+    void ResumeBackgroundThrottledTransactions();
+
+    // Simple helpers, iterates the given hash/array and resume.
+    // @param excludeActive: skip active tabid transactions.
+    void ResumeReadOf(nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&,
+                      bool excludeActive = false);
+    void ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>*);
+
+    // Cached status of the active tab active transactions existence,
+    // saves a lot of hashtable lookups
+    bool mActiveTabTransactionsExist;
+    bool mActiveTabUnthrottledTransactionsExist;
+
+    void LogActiveTransactions(char);
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnectionMgr::nsHalfOpenSocket, NS_HALFOPENSOCKET_IID)
 
 } // namespace net
 } // namespace mozilla
 
 #endif // !nsHttpConnectionMgr_h__
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -186,16 +186,20 @@ nsHttpHandler::nsHttpHandler()
     , mMaxRequestAttempts(6)
     , mMaxRequestDelay(10)
     , mIdleSynTimeout(250)
     , mH2MandatorySuiteEnabled(false)
     , mMaxUrgentExcessiveConns(3)
     , mMaxConnections(24)
     , mMaxPersistentConnectionsPerServer(2)
     , mMaxPersistentConnectionsPerProxy(4)
+    , mThrottleEnabled(true)
+    , mThrottleSuspendFor(3000)
+    , mThrottleResumeFor(200)
+    , mThrottleResumeIn(400)
     , mRedirectionLimit(10)
     , mPhishyUserPassLength(1)
     , mQoSBits(0x00)
     , mEnforceAssocReq(false)
     , mLastUniqueID(NowInSeconds())
     , mSessionStartTime(0)
     , mLegacyAppName("Mozilla")
     , mLegacyAppVersion("5.0")
@@ -544,17 +548,21 @@ nsHttpHandler::InitConnectionMgr()
     if (!mConnMgr) {
         mConnMgr = new nsHttpConnectionMgr();
     }
 
     rv = mConnMgr->Init(mMaxUrgentExcessiveConns,
                         mMaxConnections,
                         mMaxPersistentConnectionsPerServer,
                         mMaxPersistentConnectionsPerProxy,
-                        mMaxRequestDelay);
+                        mMaxRequestDelay,
+                        mThrottleEnabled,
+                        mThrottleSuspendFor,
+                        mThrottleResumeFor,
+                        mThrottleResumeIn);
     return rv;
 }
 
 nsresult
 nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure)
 {
     nsresult rv;
 
@@ -1541,16 +1549,51 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
 
     if (PREF_CHANGED(HTTP_PREF("max_response_header_size"))) {
         rv = prefs->GetIntPref(HTTP_PREF("max_response_header_size"), &val);
         if (NS_SUCCEEDED(rv)) {
             mMaxHttpResponseHeaderSize = val;
         }
     }
 
+    if (PREF_CHANGED("network.http.throttle.enable")) {
+        rv = prefs->GetBoolPref("network.http.throttle.enable", &mThrottleEnabled);
+        if (NS_SUCCEEDED(rv) && mConnMgr) {
+            Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_ENABLED,
+                                            static_cast<int32_t>(mThrottleEnabled));
+        }
+    }
+
+    if (PREF_CHANGED("network.http.throttle.suspend-for")) {
+        rv = prefs->GetIntPref("network.http.throttle.suspend-for", &val);
+        mThrottleSuspendFor = (uint32_t)clamped(val, 0, 120000);
+        if (NS_SUCCEEDED(rv) && mConnMgr) {
+            Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_SUSPEND_FOR,
+                                            mThrottleSuspendFor);
+        }
+    }
+
+    if (PREF_CHANGED("network.http.throttle.resume-for")) {
+        rv = prefs->GetIntPref("network.http.throttle.resume-for", &val);
+        mThrottleResumeFor = (uint32_t)clamped(val, 0, 120000);
+        if (NS_SUCCEEDED(rv) && mConnMgr) {
+            Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_RESUME_FOR,
+                                            mThrottleResumeFor);
+        }
+    }
+
+    if (PREF_CHANGED("network.http.throttle.resume-background-in")) {
+        rv = prefs->GetIntPref("network.http.throttle.resume-background-in", &val);
+        mThrottleResumeIn = (uint32_t)clamped(val, 0, 120000);
+        if (NS_SUCCEEDED(rv) && mConnMgr) {
+            Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_RESUME_IN,
+                                            mThrottleResumeIn);
+        }
+    }
+
     if (PREF_CHANGED(HTTP_PREF("focused_window_transaction_ratio"))) {
         float ratio = 0;
         rv = prefs->GetFloatPref(HTTP_PREF("focused_window_transaction_ratio"), &ratio);
         if (NS_SUCCEEDED(rv)) {
             if (ratio > 0 && ratio < 1) {
                 mFocusedWindowTransactionRatio = ratio;
             } else {
                 NS_WARNING("Wrong value for focused_window_transaction_ratio");
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -219,20 +219,20 @@ public:
     // Called to change the priority of an existing transaction that has
     // already been initiated.
     MOZ_MUST_USE nsresult RescheduleTransaction(nsHttpTransaction *trans,
                                                 int32_t priority)
     {
         return mConnMgr->RescheduleTransaction(trans, priority);
     }
 
-    void ThrottleTransaction(nsHttpTransaction *trans,
-                                              bool throttle)
+    void UpdateClassOfServiceOnTransaction(nsHttpTransaction *trans,
+                                           uint32_t classOfService)
     {
-        mConnMgr->ThrottleTransaction(trans, throttle);
+        mConnMgr->UpdateClassOfServiceOnTransaction(trans, classOfService);
     }
 
     // Called to cancel a transaction, which may or may not be assigned to
     // a connection.  Callable from any thread.
     MOZ_MUST_USE nsresult CancelTransaction(nsHttpTransaction *trans,
                                             nsresult reason)
     {
         return mConnMgr->CancelTransaction(trans, reason);
@@ -443,16 +443,21 @@ private:
     uint16_t mIdleSynTimeout;
 
     bool     mH2MandatorySuiteEnabled;
     uint16_t mMaxUrgentExcessiveConns;
     uint16_t mMaxConnections;
     uint8_t  mMaxPersistentConnectionsPerServer;
     uint8_t  mMaxPersistentConnectionsPerProxy;
 
+    bool mThrottleEnabled;
+    uint32_t mThrottleSuspendFor;
+    uint32_t mThrottleResumeFor;
+    uint32_t mThrottleResumeIn;
+
     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;
 
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -26,16 +26,17 @@
 #include "nsISeekableStream.h"
 #include "nsMultiplexInputStream.h"
 #include "nsStringStream.h"
 
 #include "nsComponentManagerUtils.h" // do_CreateInstance
 #include "nsIHttpActivityObserver.h"
 #include "nsSocketTransportService2.h"
 #include "nsICancelable.h"
+#include "nsIClassOfService.h"
 #include "nsIEventTarget.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIInputStream.h"
 #include "nsIThrottledInputChannel.h"
 #include "nsITransport.h"
 #include "nsIOService.h"
 #include "nsIRequestContext.h"
 #include "nsIHttpAuthenticator.h"
@@ -100,19 +101,21 @@ nsHttpTransaction::nsHttpTransaction()
     , mPriority(0)
     , mRestartCount(0)
     , mCaps(0)
     , mHttpVersion(NS_HTTP_VERSION_UNKNOWN)
     , mHttpResponseCode(0)
     , mCurrentHttpResponseHeaderSize(0)
     , mCapsToClear(0)
     , mResponseIsComplete(false)
-    , mThrottleResponse(false)
+    , mReadingStopped(false)
     , mClosed(false)
     , mConnected(false)
+    , mActivated(false)
+    , mActivatedAsH2(false)
     , mHaveStatusLine(false)
     , mHaveAllHeaders(false)
     , mTransactionDone(false)
     , mDidContentStart(false)
     , mNoContent(false)
     , mSentData(false)
     , mReceivedData(false)
     , mStatusEventPending(false)
@@ -146,21 +149,53 @@ nsHttpTransaction::nsHttpTransaction()
 #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;
 }
 
-void nsHttpTransaction::ThrottleResponse(bool aThrottle)
+void nsHttpTransaction::ResumeReading()
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
-    mThrottleResponse = aThrottle;
+    if (!mReadingStopped) {
+        return;
+    }
+
+    LOG(("nsHttpTransaction::ResumeReading %p", this));
+
+    mReadingStopped = false;
+    if (mConnection) {
+        nsresult rv = mConnection->ResumeRecv();
+        if (NS_FAILED(rv)) {
+            LOG(("  resume failed with rv=%" PRIx32, static_cast<uint32_t>(rv)));
+        }
+    }
+}
+
+void nsHttpTransaction::SetClassOfService(uint32_t cos)
+{
+    bool wasThrottling = mClassOfService & nsIClassOfService::Throttleable;
+
+    mClassOfService = cos;
+
+    bool isThrottling = mClassOfService & nsIClassOfService::Throttleable;
+
+    if (mConnection && wasThrottling != isThrottling) {
+        // Do nothing until we are actually activated.  For now
+        // only remember the throttle flag.  Call to MoveActiveTransaction
+        // would add this transaction to the list too early.
+        gHttpHandler->ConnMgr()->MoveActiveTransaction(this, isThrottling);
+
+        if (mReadingStopped && !isThrottling) {
+            ResumeReading();
+        }
+    }
 }
 
 nsHttpTransaction::~nsHttpTransaction()
 {
     LOG(("Destroying nsHttpTransaction @%p\n", this));
     if (mTransactionObserver) {
         mTransactionObserver->Complete(this, NS_OK);
     }
@@ -468,16 +503,31 @@ nsHttpTransaction::SetConnection(nsAHttp
 {
     {
         MutexAutoLock lock(mLock);
         mConnection = conn;
     }
 }
 
 void
+nsHttpTransaction::OnActivated(bool h2)
+{
+    MOZ_ASSERT(OnSocketThread());
+
+    mActivatedAsH2 = h2;
+    if (mActivated) {
+        return;
+    }
+
+    mActivated = true;
+    gHttpHandler->ConnMgr()->AddActiveTransaction(
+        this, mClassOfService & nsIClassOfService::Throttleable);
+}
+
+void
 nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb)
 {
     MutexAutoLock lock(mLock);
     nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
     tmp.forget(cb);
 }
 
 void
@@ -769,38 +819,49 @@ nsHttpTransaction::WritePipeSegment(nsIO
 
     return rv; // failure code only stops WriteSegments; it is not propagated.
 }
 
 nsresult
 nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
                                  uint32_t count, uint32_t *countWritten)
 {
-    static bool reentrantFlag = false;
-    LOG(("nsHttpTransaction::WriteSegments %p reentrantFlag=%d",
-         this, reentrantFlag));
-    MOZ_DIAGNOSTIC_ASSERT(!reentrantFlag);
-    reentrantFlag = true;
+    LOG(("nsHttpTransaction::WriteSegments %p", this));
+
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
     if (mTransactionDone) {
-        reentrantFlag = false;
         return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
     }
 
+    // Throttling feature is now disabled for http/2 transactions
+    // because of bug 1367861.  The logic around mActivatedAsH2
+    // will be removed when that is fixed
+    if (!mActivatedAsH2 &&
+        gHttpHandler->ConnMgr()->ShouldStopReading(this,
+            mClassOfService & nsIClassOfService::Throttleable)) {
+        LOG(("nsHttpTransaction::WriteSegments %p response throttled", this));
+        // Must remember that we have to call ResumeRecv() on our connection when
+        // called back by the conn manager to resume reading.
+        mReadingStopped = true;
+        // This makes the underlaying connection or stream wait for explicit resume.
+        // For h1 this means we stop reading from the socket.
+        // For h2 this means we stop updating recv window for the stream.
+        return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+
     mWriter = writer;
 
 #ifdef WIN32 // bug 1153929
     MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
     uint32_t * vtable = (uint32_t *) mPipeOut.get();
     MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
 #endif // WIN32
 
     if (!mPipeOut) {
-        reentrantFlag = false;
         return NS_ERROR_UNEXPECTED;
     }
 
     nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
 
     mWriter = nullptr;
 
     if (mForceRestart) {
@@ -822,26 +883,31 @@ nsHttpTransaction::WriteSegments(nsAHttp
             mPipeOut->AsyncWait(this, 0, 0, target);
             mWaitingOnPipeOut = true;
         } else {
             NS_ERROR("no socket thread event target");
             rv = NS_ERROR_UNEXPECTED;
         }
     }
 
-    reentrantFlag = false;
     return rv;
 }
 
 void
 nsHttpTransaction::Close(nsresult reason)
 {
     LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n",
          this, static_cast<uint32_t>(reason)));
 
+    if (!mClosed) {
+        gHttpHandler->ConnMgr()->RemoveActiveTransaction(
+            this, mClassOfService & nsIClassOfService::Throttleable);
+        mActivated = false;
+    }
+
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
     if (reason == NS_BINDING_RETARGETED) {
         LOG(("  close %p skipped due to ERETARGETED\n", this));
         return;
     }
 
     if (mClosed) {
         LOG(("  already closed\n"));
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -84,16 +84,18 @@ public:
                                uint64_t               reqContentLength,
                                bool                   reqBodyIncludesHeaders,
                                nsIEventTarget        *consumerTarget,
                                nsIInterfaceRequestor *callbacks,
                                nsITransportEventSink *eventsink,
                                uint64_t               topLevelOuterContentWindowId,
                                nsIAsyncInputStream  **responseBody);
 
+    void OnActivated(bool h2) override;
+
     // attributes
     nsHttpResponseHead    *ResponseHead()   { return mHaveAllHeaders ? mResponseHead : nullptr; }
     nsISupports           *SecurityInfo()   { return mSecurityInfo; }
 
     nsIEventTarget        *ConsumerTarget() { return mConsumerTarget; }
     nsISupports           *HttpChannel()    { return mChannel; }
 
     void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
@@ -302,24 +304,27 @@ private:
     // redundant requests on the network. The member itself is atomic, but
     // access to it from the networking thread may happen either before or
     // after the main thread modifies it. To deal with raciness, only unsetting
     // bitfields should be allowed: 'lost races' will thus err on the
     // conservative side, e.g. by going ahead with a 2nd DNS refresh.
     Atomic<uint32_t>                mCapsToClear;
     Atomic<bool, ReleaseAcquire>    mResponseIsComplete;
 
-    // If true, this transaction was asked to stop receiving the response.
-    // NOTE: this flag is currently unused.  A useful remnant of an old throttling algorithm.
-    bool                            mThrottleResponse;
+    // True iff WriteSegments was called while this transaction should be throttled (stop reading)
+    // Used to resume read on unblock of reading.  Conn manager is responsible for calling back
+    // to resume reading.
+    bool                            mReadingStopped;
 
     // state flags, all logically boolean, but not packed together into a
     // bitfield so as to avoid bitfield-induced races.  See bug 560579.
     bool                            mClosed;
     bool                            mConnected;
+    bool                            mActivated;
+    bool                            mActivatedAsH2;
     bool                            mHaveStatusLine;
     bool                            mHaveAllHeaders;
     bool                            mTransactionDone;
     bool                            mDidContentStart;
     bool                            mNoContent; // expecting an empty entity body
     bool                            mSentData;
     bool                            mReceivedData;
     bool                            mStatusEventPending;
@@ -368,27 +373,27 @@ public:
     void OnTokenBucketAdmitted() override; // ATokenBucketEvent
 
     // CancelPacing() can be used to tell the token bucket to remove this
     // transaction from the list of pending transactions. This is used when a
     // transaction is believed to be HTTP/1 (and thus subject to rate pacing)
     // but later can be dispatched via spdy (not subject to rate pacing).
     void CancelPacing(nsresult reason);
 
-    // Called only on the socket thread.  Updates the flag whether the transaction
-    // should make the underlying connection or session stop reading from the socket.
-    void ThrottleResponse(bool aThrottle);
+    // Called by the connetion manager on the socket thread when reading for this
+    // previously throttled transaction has to be resumed.
+    void ResumeReading();
 
 private:
     bool mSubmittedRatePacing;
     bool mPassedRatePacing;
     bool mSynchronousRatePaceRequest;
     nsCOMPtr<nsICancelable> mTokenBucketCancel;
 public:
-    void     SetClassOfService(uint32_t cos) { mClassOfService = cos; }
+    void     SetClassOfService(uint32_t cos);
     uint32_t ClassOfService() { return mClassOfService; }
 private:
     uint32_t mClassOfService;
 
 public:
     // setting TunnelProvider to non-null means the transaction should only
     // be dispatched on a specific ConnectionInfo Hash Key (as opposed to a
     // generic wild card one). That means in the specific case of carrying this