Bug 1377208 - Move background h2 stream into background group. r=hurley
authorKershaw Chang <kechang@mozilla.com>
Wed, 30 Aug 2017 02:19:00 -0400
changeset 377683 f8e3ab3bb127fa915958f135ad044723404022e3
parent 377682 72b2e3517b04384ecfd738115aa8e99567ef2f27
child 377684 02b9376bea86bd86e86d1747509e6660af545928
push id32414
push userkwierso@gmail.com
push dateThu, 31 Aug 2017 00:10:19 +0000
treeherdermozilla-central@d9b405d82cff [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley
bugs1377208
milestone57.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 1377208 - Move background h2 stream into background group. r=hurley
netwerk/protocol/http/Http2Push.cpp
netwerk/protocol/http/Http2Push.h
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/Http2Session.h
netwerk/protocol/http/Http2Stream.cpp
netwerk/protocol/http/Http2Stream.h
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
--- a/netwerk/protocol/http/Http2Push.cpp
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -58,18 +58,19 @@ private:
 
 //////////////////////////////////////////
 // Http2PushedStream
 //////////////////////////////////////////
 
 Http2PushedStream::Http2PushedStream(Http2PushTransactionBuffer *aTransaction,
                                      Http2Session *aSession,
                                      Http2Stream *aAssociatedStream,
-                                     uint32_t aID)
-  :Http2Stream(aTransaction, aSession, 0)
+                                     uint32_t aID,
+                                     uint64_t aCurrentForegroundTabOuterContentWindowId)
+  :Http2Stream(aTransaction, aSession, 0, aCurrentForegroundTabOuterContentWindowId)
   , mConsumerStream(nullptr)
   , mAssociatedTransaction(aAssociatedStream->Transaction())
   , mBufferedPush(aTransaction)
   , mStatus(NS_OK)
   , mPushCompleted(false)
   , mDeferCleanupOnSuccess(true)
   , mDeferCleanupOnPush(false)
   , mOnPushFailed(false)
--- a/netwerk/protocol/http/Http2Push.h
+++ b/netwerk/protocol/http/Http2Push.h
@@ -27,17 +27,18 @@ namespace net {
 class Http2PushTransactionBuffer;
 
 class Http2PushedStream final : public Http2Stream
 {
 public:
   Http2PushedStream(Http2PushTransactionBuffer *aTransaction,
                     Http2Session *aSession,
                     Http2Stream *aAssociatedStream,
-                    uint32_t aID);
+                    uint32_t aID,
+                    uint64_t aCurrentForegroundTabOuterContentWindowId);
   virtual ~Http2PushedStream() {}
 
   bool GetPushComplete();
 
   // The consumer stream is the synthetic pull stream hooked up to this push
   virtual Http2Stream *GetConsumerStream() override { return mConsumerStream; };
 
   void SetConsumerStream(Http2Stream *aStream);
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -135,16 +135,18 @@ Http2Session::Http2Session(nsISocketTran
   mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
   mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
   SendHello();
 
   mLastDataReadEpoch = mLastReadEpoch;
 
   mPingThreshold = gHttpHandler->SpdyPingThreshold();
   mPreviousPingThreshold = mPingThreshold;
+  mCurrentForegroundTabOuterContentWindowId =
+    gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId();
 }
 
 void
 Http2Session::Shutdown()
 {
   for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
     nsAutoPtr<Http2Stream> &stream = iter.Data();
 
@@ -413,17 +415,21 @@ Http2Session::AddStream(nsAHttpTransacti
 
   if (aUseTunnel) {
     LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
           this, aHttpTransaction));
     DispatchOnTunnel(aHttpTransaction, aCallbacks);
     return true;
   }
 
-  Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority);
+  Http2Stream *stream =
+    new Http2Stream(aHttpTransaction,
+                    this,
+                    aPriority,
+                    mCurrentForegroundTabOuterContentWindowId);
 
   LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " "
         "NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID));
 
   mStreamTransactionHash.Put(aHttpTransaction, stream);
 
   mReadyForWrite.Push(stream);
   SetWriteCallbacks();
@@ -792,24 +798,19 @@ Http2Session::GenerateSettingsAck()
 
 void
 Http2Session::GeneratePriority(uint32_t aID, uint8_t aPriorityWeight)
 {
   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
   LOG3(("Http2Session::GeneratePriority %p %X %X\n",
         this, aID, aPriorityWeight));
 
-  uint32_t frameSize = kFrameHeaderBytes + 5;
-  char *packet = EnsureOutputBuffer(frameSize);
-  mOutputQueueUsed += frameSize;
-
-  CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, aID);
-  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, 0);
-  memcpy(packet + frameSize - 1, &aPriorityWeight, 1);
-  LogIO(this, nullptr, "Generate Priority", packet, frameSize);
+  char *packet = CreatePriorityFrame(aID, 0, aPriorityWeight);
+
+  LogIO(this, nullptr, "Generate Priority", packet, kFrameHeaderBytes + 5);
   FlushOutputQueue();
 }
 
 void
 Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
 {
   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
@@ -976,24 +977,48 @@ Http2Session::SendHello()
     // certainly need to change the lookup of the stream/ID hash in
     // Http2Session::OnTransportStatus. Yeah, that's right. YOU!
   }
 
   FlushOutputQueue();
 }
 
 void
-Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, uint8_t weight,
-                                 const char *label)
+Http2Session::SendPriorityFrame(uint32_t streamID,
+                                uint32_t dependsOn,
+                                uint8_t weight)
 {
-  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  LOG3(("Http2Session::SendPriorityFrame %p Frame 0x%X depends on 0x%X "
+        "weight %d\n", this, streamID, dependsOn, weight));
+
+  char *packet = CreatePriorityFrame(streamID, dependsOn, weight);
+
+  LogIO(this, nullptr, "SendPriorityFrame", packet, kFrameHeaderBytes + 5);
+  FlushOutputQueue();
+}
+
+char *
+Http2Session::CreatePriorityFrame(uint32_t streamID,
+                                  uint32_t dependsOn,
+                                  uint8_t weight)
+{
+  char *packet = EnsureOutputBuffer(kFrameHeaderBytes + 5);
   CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
   mOutputQueueUsed += kFrameHeaderBytes + 5;
   NetworkEndian::writeUint32(packet + kFrameHeaderBytes, dependsOn); // depends on
   packet[kFrameHeaderBytes + 4] = weight; // weight
+  return packet;
+}
+
+void
+Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, uint8_t weight,
+                                 const char *label)
+{
+  char *packet = CreatePriorityFrame(streamID, dependsOn, weight);
 
   LOG3(("Http2Session %p generate Priority Frame 0x%X depends on 0x%X "
         "weight %d for %s class\n", this, streamID, dependsOn, weight, label));
   LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5);
 }
 
 // perform a bunch of integrity checks on the stream.
 // returns true if passed, false (plus LOG and ABORT) if failed.
@@ -1750,17 +1775,21 @@ Http2Session::RecvPushPromise(Http2Sessi
     Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS, self->mAggregatedHeaderSize);
   }
 
   // Create the buffering transaction and push stream
   RefPtr<Http2PushTransactionBuffer> transactionBuffer =
     new Http2PushTransactionBuffer();
   transactionBuffer->SetConnection(self);
   Http2PushedStream *pushedStream =
-    new Http2PushedStream(transactionBuffer, self, associatedStream, promisedID);
+    new Http2PushedStream(transactionBuffer,
+                          self,
+                          associatedStream,
+                          promisedID,
+                          self->mCurrentForegroundTabOuterContentWindowId);
 
   rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
                                         self->mDecompressBuffer,
                                         pushedStream->GetRequestString());
 
   if (rv == NS_ERROR_NOT_IMPLEMENTED) {
     LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
     self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
@@ -4459,10 +4488,22 @@ Http2Session::RealJoinConnection(const n
     key2.AppendInt(port);
     if (!mJoinConnectionCache.Get(key2)) {
       mJoinConnectionCache.Put(key2, joinedReturn);
     }
   }
   return joinedReturn;
 }
 
+void
+Http2Session::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
+{
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+  mCurrentForegroundTabOuterContentWindowId = windowId;
+
+  for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+    iter.Data()->TopLevelOuterContentWindowIdChanged(windowId);
+  }
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -253,16 +253,18 @@ public:
   MOZ_MUST_USE nsresult WriteSegmentsAgain(nsAHttpSegmentWriter *, uint32_t , uint32_t *, bool *) override final;
   MOZ_MUST_USE bool Do0RTT() override final { return true; }
   MOZ_MUST_USE nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) override final;
   void SetFastOpenStatus(uint8_t aStatus) override final;
 
   // For use by an HTTP2Stream
   void Received421(nsHttpConnectionInfo *ci);
 
+  void SendPriorityFrame(uint32_t streamID, uint32_t dependsOn, uint8_t weight);
+
 private:
 
   // These internal states do not correspond to the states of the HTTP/2 specification
   enum internalStateType {
     BUFFERING_OPENING_SETTINGS,
     BUFFERING_FRAME_HEADER,
     BUFFERING_CONTROL_FRAME,
     PROCESSING_DATA_FRAME_PADDING_CONTROL,
@@ -302,16 +304,17 @@ private:
                                              nsAHttpSegmentWriter *,
                                              uint32_t, uint32_t *);
   MOZ_MUST_USE nsresult ProcessSlowConsumer(Http2Stream *,
                                             nsAHttpSegmentWriter *,
                                             uint32_t, uint32_t *);
 
   MOZ_MUST_USE nsresult SetInputFrameDataStream(uint32_t);
   void        CreatePriorityNode(uint32_t, uint32_t, uint8_t, const char *);
+  char        *CreatePriorityFrame(uint32_t, uint32_t, uint8_t);
   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);
 
   void        MaybeDecrementConcurrent(Http2Stream *stream);
@@ -528,16 +531,18 @@ private:
 
   bool RealJoinConnection(const nsACString &hostname, int32_t port, bool jk);
   bool TestOriginFrame(const nsACString &name, int32_t port);
   bool mOriginFrameActivated;
   nsDataHashtable<nsCStringHashKey, bool> mOriginFrame;
 
   nsDataHashtable<nsCStringHashKey, bool> mJoinConnectionCache;
 
+  uint64_t mCurrentForegroundTabOuterContentWindowId;
+
   class CachePushCheckCallback final : public nsICacheEntryOpenCallback
   {
   public:
     CachePushCheckCallback(Http2Session *session, uint32_t promisedID, const nsACString &requestString);
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSICACHEENTRYOPENCALLBACK
 
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -33,17 +33,18 @@
 #include "nsStandardURL.h"
 #include "prnetdb.h"
 
 namespace mozilla {
 namespace net {
 
 Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
                          Http2Session *session,
-                         int32_t priority)
+                         int32_t priority,
+                         uint64_t windowId)
   : mStreamID(0)
   , mSession(session)
   , mSegmentReader(nullptr)
   , mSegmentWriter(nullptr)
   , mUpstreamState(GENERATING_HEADERS)
   , mState(IDLE)
   , mRequestHeadersDone(0)
   , mOpenGenerated(0)
@@ -67,16 +68,18 @@ Http2Stream::Http2Stream(nsAHttpTransact
   , mTxStreamFrameSize(0)
   , mRequestBodyLenRemaining(0)
   , mLocalUnacked(0)
   , mBlockedOnRwin(false)
   , mTotalSent(0)
   , mTotalRead(0)
   , mPushSource(nullptr)
   , mAttempting0RTT(false)
+  , mCurrentForegroundTabOuterContentWindowId(windowId)
+  , mTransactionTabId(0)
   , mIsTunnel(false)
   , mPlainTextTunnel(false)
 {
   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
   LOG3(("Http2Stream::Http2Stream %p", this));
 
   mServerReceiveWindow = session->GetServerInitialStreamWindow();
@@ -95,16 +98,21 @@ Http2Stream::Http2Stream(nsAHttpTransact
     httpPriority = kWorstPriority;
   } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
     httpPriority = kBestPriority;
   } else {
     httpPriority = kNormalPriority + priority;
   }
   MOZ_ASSERT(httpPriority >= 0);
   SetPriority(static_cast<uint32_t>(httpPriority));
+
+  nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
+  if (trans) {
+    mTransactionTabId = trans->TopLevelOuterContentWindowId();
+  }
 }
 
 Http2Stream::~Http2Stream()
 {
   ClearTransactionsBlockedOnTunnel();
   mStreamID = Http2Session::kDeadStreamID;
 
   LOG3(("Http2Stream::~Http2Stream %p", this));
@@ -1197,16 +1205,50 @@ Http2Stream::SetPriorityDependency(uint3
                                    bool exclusive)
 {
   // undefined what it means when the server sends a priority frame. ignore it.
   LOG3(("Http2Stream::SetPriorityDependency %p 0x%X received dependency=0x%X "
         "weight=%u exclusive=%d", this, mStreamID, newDependency, newWeight,
         exclusive));
 }
 
+static uint32_t
+GetPriorityDependencyFromTransaction(nsHttpTransaction *trans)
+{
+  MOZ_ASSERT(trans);
+
+  uint32_t classFlags = trans->ClassOfService();
+
+  if (classFlags & nsIClassOfService::UrgentStart) {
+    return Http2Session::kUrgentStartGroupID;
+  }
+
+  if (classFlags & nsIClassOfService::Leader) {
+    return Http2Session::kLeaderGroupID;
+  }
+
+  if (classFlags & nsIClassOfService::Follower) {
+    return Http2Session::kFollowerGroupID;
+  }
+
+  if (classFlags & nsIClassOfService::Speculative) {
+    return Http2Session::kSpeculativeGroupID;
+  }
+
+  if (classFlags & nsIClassOfService::Background) {
+    return Http2Session::kBackgroundGroupID;
+  }
+
+  if (classFlags & nsIClassOfService::Unblocked) {
+    return Http2Session::kOtherGroupID;
+  }
+
+  return Http2Session::kFollowerGroupID; // unmarked followers
+}
+
 void
 Http2Stream::UpdatePriorityDependency()
 {
   if (!mSession->UseH2Deps()) {
     return;
   }
 
   nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
@@ -1225,37 +1267,66 @@ Http2Stream::UpdatePriorityDependency()
   //
   // streams for leaders (html, js, css) depend on 3
   // streams for folowers (images) depend on b
   // default streams (xhr, async js) depend on 5
   // explicit bg streams (beacon, etc..) depend on 7
   // spculative bg streams depend on 9
   // urgent-start streams depend on d
 
-  uint32_t classFlags = trans->ClassOfService();
+  mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
 
-  if (classFlags & nsIClassOfService::Leader) {
-    mPriorityDependency = Http2Session::kLeaderGroupID;
-  } else if (classFlags & nsIClassOfService::Follower) {
-    mPriorityDependency = Http2Session::kFollowerGroupID;
-  } else if (classFlags & nsIClassOfService::Speculative) {
-    mPriorityDependency = Http2Session::kSpeculativeGroupID;
-  } else if (classFlags & nsIClassOfService::Background) {
+  if (mTransactionTabId != mCurrentForegroundTabOuterContentWindowId &&
+      mPriorityDependency != Http2Session::kUrgentStartGroupID) {
+    LOG3(("Http2Stream::UpdatePriorityDependency %p "
+          " depends on background group for trans %p\n",
+          this, trans));
     mPriorityDependency = Http2Session::kBackgroundGroupID;
-  } else if (classFlags & nsIClassOfService::Unblocked) {
-    mPriorityDependency = Http2Session::kOtherGroupID;
-  } else if (classFlags & nsIClassOfService::UrgentStart) {
-    mPriorityDependency = Http2Session::kUrgentStartGroupID;
-  } else {
-    mPriorityDependency = Http2Session::kFollowerGroupID; // unmarked followers
   }
 
   LOG3(("Http2Stream::UpdatePriorityDependency %p "
-        "classFlags %X depends on stream 0x%X\n",
-        this, classFlags, mPriorityDependency));
+        "depends on stream 0x%X\n",
+        this, mPriorityDependency));
+}
+
+void
+Http2Stream::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
+{
+  LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged "
+        "%p windowId=%" PRIx64 "\n",
+        this, windowId));
+
+  mCurrentForegroundTabOuterContentWindowId = windowId;
+
+  if (!mSession->UseH2Deps()) {
+    return;
+  }
+
+  // Urgent start takes an absolute precedence, so don't
+  // change mPriorityDependency here.
+  if (mPriorityDependency == Http2Session::kUrgentStartGroupID) {
+    return;
+  }
+
+  if (mTransactionTabId != mCurrentForegroundTabOuterContentWindowId) {
+    mPriorityDependency = Http2Session::kBackgroundGroupID;
+    LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged %p "
+          "move into background group.\n", this));
+  } else {
+    nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
+    if (!trans) {
+      return;
+    }
+
+    mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
+    LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged %p "
+          "depends on stream 0x%X\n", this, mPriorityDependency));
+  }
+
+  mSession->SendPriorityFrame(mStreamID, mPriorityDependency, mPriorityWeight);
 }
 
 void
 Http2Stream::SetRecvdFin(bool aStatus)
 {
   mRecvdFin = aStatus ? 1 : 0;
   if (!aStatus)
     return;
--- a/netwerk/protocol/http/Http2Stream.h
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -47,17 +47,17 @@ public:
     CLOSED_BY_REMOTE,
     CLOSED
   };
 
   const static int32_t kNormalPriority = 0x1000;
   const static int32_t kWorstPriority = kNormalPriority + nsISupportsPriority::PRIORITY_LOWEST;
   const static int32_t kBestPriority = kNormalPriority + nsISupportsPriority::PRIORITY_HIGHEST;
 
-  Http2Stream(nsAHttpTransaction *, Http2Session *, int32_t);
+  Http2Stream(nsAHttpTransaction *, Http2Session *, int32_t, uint64_t);
 
   uint32_t StreamID() { return mStreamID; }
   Http2PushedStream *PushSource() { return mPushSource; }
 
   stateType HTTPState() { return mState; }
   void SetHTTPState(stateType val) { mState = val; }
 
   virtual MOZ_MUST_USE nsresult ReadSegments(nsAHttpSegmentReader *,
@@ -165,16 +165,18 @@ public:
                                              RefPtr<nsStandardURL> &url);
 
   // Mirrors nsAHttpTransaction
   bool Do0RTT();
   nsresult Finish0RTT(bool aRestart, bool aAlpnIgnored);
 
   nsresult GetOriginAttributes(mozilla::OriginAttributes *oa);
 
+  void TopLevelOuterContentWindowIdChanged(uint64_t windowId);
+
 protected:
   static void CreatePushHashKey(const nsCString &scheme,
                                 const nsCString &hostHeader,
                                 const mozilla::OriginAttributes &originAttributes,
                                 uint64_t serial,
                                 const nsACString& pathInfo,
                                 nsCString &outOrigin,
                                 nsCString &outKey);
@@ -342,16 +344,20 @@ private:
   Http2PushedStream *mPushSource;
 
   // Used to store stream data when the transaction channel cannot keep up
   // and flow control has not yet kicked in.
   SimpleBuffer mSimpleBuffer;
 
   bool mAttempting0RTT;
 
+  uint64_t mCurrentForegroundTabOuterContentWindowId;
+
+  uint64_t mTransactionTabId;
+
 /// connect tunnels
 public:
   bool IsTunnel() { return mIsTunnel; }
 private:
   void ClearTransactionsBlockedOnTunnel();
   void MapStreamToPlainText();
   void MapStreamToHttpConnection();
 
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -139,16 +139,19 @@ public:
     virtual int64_t BytesWritten() = 0;
 
     // Update the callbacks used to provide security info. May be called on
     // any thread.
     virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
 
     // nsHttp.h version
     virtual uint32_t Version() = 0;
+
+    // A notification of the current active tab id change.
+    virtual void TopLevelOuterContentWindowIdChanged(uint64_t windowId) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
 
 #define NS_DECL_NSAHTTPCONNECTION(fwdObject)                    \
     MOZ_MUST_USE nsresult OnHeadersAvailable(nsAHttpTransaction *,  \
                                              nsHttpRequestHead *,   \
                                              nsHttpResponseHead *,  \
@@ -158,16 +161,17 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpCon
                                         nsIAsyncInputStream **,   \
                                         nsIAsyncOutputStream **) override; \
     bool IsPersistent() override;                         \
     bool IsReused() override;                             \
     void DontReuse() override;                            \
     MOZ_MUST_USE nsresult PushBack(const char *, uint32_t) override; \
     already_AddRefed<nsHttpConnection> TakeHttpConnection() override; \
     already_AddRefed<nsHttpConnection> HttpConnection() override; \
+    void TopLevelOuterContentWindowIdChanged(uint64_t windowId) override; \
     /*                                                                  \
        Thes methods below have automatic definitions that just forward the \
        function to a lower level connection object        \
     */                                                    \
     void GetConnectionInfo(nsHttpConnectionInfo **result) \
       override                                            \
     {                                                     \
       if (!(fwdObject)) {                                 \
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -3409,33 +3409,77 @@ nsHttpConnectionMgr::ResumeReadOf(nsTArr
     MOZ_ASSERT(transactions);
 
     for (auto trans : *transactions) {
         trans->ResumeReading();
     }
 }
 
 void
+nsHttpConnectionMgr::NotifyConnectionOfWindowIdChange(uint64_t previousWindowId)
+{
+    MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+    nsTArray<RefPtr<nsHttpTransaction>> *transactions = nullptr;
+    nsTArray<RefPtr<nsAHttpConnection>> connections;
+
+    auto addConnectionHelper =
+        [&connections](nsTArray<RefPtr<nsHttpTransaction>> *trans) {
+            if (!trans) {
+                return;
+            }
+
+            for (auto t : *trans) {
+                RefPtr<nsAHttpConnection> conn = t->Connection();
+                if (conn && !connections.Contains(conn)) {
+                    connections.AppendElement(conn);
+                }
+            }
+        };
+
+    // Get unthrottled transactions with the previous and current window id.
+    transactions = mActiveTransactions[false].Get(previousWindowId);
+    addConnectionHelper(transactions);
+    transactions =
+        mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
+    addConnectionHelper(transactions);
+
+    // Get throttled transactions with the previous and current window id.
+    transactions = mActiveTransactions[true].Get(previousWindowId);
+    addConnectionHelper(transactions);
+    transactions =
+        mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
+    addConnectionHelper(transactions);
+
+    for (auto conn : connections) {
+        conn->TopLevelOuterContentWindowIdChanged(mCurrentTopLevelOuterContentWindowId);
+    }
+}
+
+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;
 
+    uint64_t previousWindowId = mCurrentTopLevelOuterContentWindowId;
     mCurrentTopLevelOuterContentWindowId = winId;
 
+    NotifyConnectionOfWindowIdChange(previousWindowId);
+
     LOG(("nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId"
          " id=%" PRIx64 "\n",
          mCurrentTopLevelOuterContentWindowId));
 
     nsTArray<RefPtr<nsHttpTransaction>> *transactions = nullptr;
 
     if (activeTabIdChanged) {
         // Update the "Exists" caches and resume any transactions that now deserve it,
@@ -4893,16 +4937,22 @@ ConnectionHandle::TakeHttpConnection()
 
 already_AddRefed<nsHttpConnection>
 ConnectionHandle::HttpConnection()
 {
     RefPtr<nsHttpConnection> rv(mConn);
     return rv.forget();
 }
 
+void
+ConnectionHandle::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
+{
+  // Do nothing.
+}
+
 // nsConnectionEntry
 
 nsHttpConnectionMgr::
 nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
     : mConnInfo(ci)
     , mUsingSpdy(false)
     , mPreferIPv4(false)
     , mPreferIPv6(false)
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -237,16 +237,21 @@ public:
     void TouchThrottlingTimeWindow(bool aEnsureTicker = true);
 
     // return true iff the connection has pending transactions for the active tab.
     // it's mainly used to disallow throttling (stop reading) of a response
     // belonging to the same conn info to free up a connection ASAP.
     // NOTE: relatively expensive to call, there are two hashtable lookups.
     bool IsConnEntryUnderPressure(nsHttpConnectionInfo*);
 
+    uint64_t CurrentTopLevelOuterContentWindowId()
+    {
+        return mCurrentTopLevelOuterContentWindowId;
+    }
+
 private:
     virtual ~nsHttpConnectionMgr();
 
     class nsHalfOpenSocket;
     class PendingTransactionInfo;
 
     // nsConnectionEntry
     //
@@ -755,16 +760,21 @@ private:
     bool mActiveTabTransactionsExist;
     bool mActiveTabUnthrottledTransactionsExist;
 
     void LogActiveTransactions(char);
 
     nsTArray<RefPtr<PendingTransactionInfo>>*
     GetTransactionPendingQHelper(nsConnectionEntry *ent, nsAHttpTransaction *trans);
 
+    // When current active tab is changed, this function uses
+    // |previousWindowId| to select background transactions and
+    // mCurrentTopLevelOuterContentWindowId| to select foreground transactions.
+    // Then, it notifies selected transactions' connection of the new active tab id.
+    void NotifyConnectionOfWindowIdChange(uint64_t previousWindowId);
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnectionMgr::nsHalfOpenSocket, NS_HALFOPENSOCKET_IID)
 
 } // namespace net
 } // namespace mozilla
 
 #endif // !nsHttpConnectionMgr_h__