Bug 1337791 - Part 2: http/2 origin frame extension. r=nwgh
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 03 Apr 2017 17:23:55 -0400
changeset 351183 fbb9934d1a90a696f659b71c08886895b2edbbdd
parent 351182 7f8f5cada5b7e2ae6f26ef4a2eecee3f6b33767d
child 351184 71b228f12fc112f938250b2fd8418d0370a9e147
push id88808
push userryanvm@gmail.com
push dateTue, 04 Apr 2017 18:42:26 +0000
treeherdermozilla-inbound@71b228f12fc1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnwgh
bugs1337791
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 1337791 - Part 2: http/2 origin frame extension. r=nwgh
modules/libpref/init/all.js
netwerk/protocol/http/ASpdySession.h
netwerk/protocol/http/ConnectionDiagnostics.cpp
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/Http2Session.h
netwerk/protocol/http/Http2Stream.cpp
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1591,16 +1591,19 @@ pref("network.http.spdy.default-hpack-bu
 // 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);
 
 // Turn on 0RTT data for TLS 1.3
 pref("security.tls.enable_0rtt_data", true);
 
+// the origin extension impacts h2 coalescing
+pref("network.http.originextension", true);
+
 pref("network.http.diagnostics", false);
 
 pref("network.http.pacing.requests.enabled", true);
 pref("network.http.pacing.requests.min-parallelism", 6);
 pref("network.http.pacing.requests.hz", 80);
 pref("network.http.pacing.requests.burst", 10);
 
 // TCP Keepalive config for HTTP connections.
--- a/netwerk/protocol/http/ASpdySession.h
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -27,16 +27,19 @@ public:
   virtual bool RoomForMoreStreams() = 0;
   virtual PRIntervalTime IdleTime() = 0;
   virtual uint32_t ReadTimeoutTick(PRIntervalTime now) = 0;
   virtual void DontReuse() = 0;
   virtual uint32_t SpdyVersion() = 0;
 
   static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *, bool);
 
+  virtual bool TestJoinConnection(const nsACString &hostname, int32_t port) = 0;
+  virtual bool JoinConnection(const nsACString &hostname, int32_t port) = 0;
+
   // MaybeReTunnel() is called by the connection manager when it cannot
   // dispatch a tunneled transaction. That might be because the tunnels it
   // expects to see are dead (and we may or may not be able to make more),
   // or it might just need to wait longer for one of them to become free.
   //
   // return true if the session takes back ownership of the transaction from
   // the connection manager.
   virtual bool MaybeReTunnel(nsAHttpTransaction *) = 0;
--- a/netwerk/protocol/http/ConnectionDiagnostics.cpp
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -62,18 +62,17 @@ nsHttpConnectionMgr::OnMsgPrintDiagnosti
     mLogData.AppendPrintf("   Active Conns Length = %" PRIuSIZE "\n",
                           ent->mActiveConns.Length());
     mLogData.AppendPrintf("   Idle Conns Length = %" PRIuSIZE "\n",
                           ent->mIdleConns.Length());
     mLogData.AppendPrintf("   Half Opens Length = %" PRIuSIZE "\n",
                           ent->mHalfOpens.Length());
     mLogData.AppendPrintf("   Coalescing Keys Length = %" PRIuSIZE "\n",
                           ent->mCoalescingKeys.Length());
-    mLogData.AppendPrintf("   Spdy using = %d, preferred = %d\n",
-                          ent->mUsingSpdy, ent->mInPreferredHash);
+    mLogData.AppendPrintf("   Spdy using = %d\n", ent->mUsingSpdy);
 
     uint32_t i;
     for (i = 0; i < ent->mActiveConns.Length(); ++i) {
       mLogData.AppendPrintf("   :: Active Connection #%u\n", i);
       ent->mActiveConns[i]->PrintDiagnostics(mLogData);
     }
     for (i = 0; i < ent->mIdleConns.Length(); ++i) {
       mLogData.AppendPrintf("   :: Idle Connection #%u\n", i);
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -106,16 +106,17 @@ Http2Session::Http2Session(nsISocketTran
   , mOutputQueueSent(0)
   , mLastReadEpoch(PR_IntervalNow())
   , mPingSentEpoch(0)
   , mPreviousUsed(false)
   , mWaitingForSettingsAck(false)
   , mGoAwayOnPush(false)
   , mUseH2Deps(false)
   , mAttemptingEarlyData(attemptingEarlyData)
+  , mOriginFrameActivated(false)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   static uint64_t sSerial;
   mSerial = ++sSerial;
 
   LOG3(("Http2Session::Http2Session %p serial=0x%" PRIX64 "\n", this, mSerial));
 
@@ -219,17 +220,18 @@ static Http2ControlFx sControlFunctions[
   Http2Session::RecvPriority,
   Http2Session::RecvRstStream,
   Http2Session::RecvSettings,
   Http2Session::RecvPushPromise,
   Http2Session::RecvPing,
   Http2Session::RecvGoAway,
   Http2Session::RecvWindowUpdate,
   Http2Session::RecvContinuation,
-  Http2Session::RecvAltSvc // extension for type 0x0A
+  Http2Session::RecvAltSvc, // extension for type 0x0A
+  Http2Session::RecvOrigin  // extension for type 0x0B
 };
 
 bool
 Http2Session::RoomForMoreConcurrent()
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   return (mConcurrent < mMaxConcurrent);
 }
@@ -1793,36 +1795,33 @@ Http2Session::RecvPushPromise(Http2Sessi
   nsAutoCString key;
   if (!pushedStream->GetHashKey(key)) {
     LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n"));
     self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR);
     self->ResetDownstreamState();
     return NS_OK;
   }
 
-  RefPtr<nsStandardURL> associatedURL, pushedURL;
-  rv = Http2Stream::MakeOriginURL(associatedStream->Origin(), associatedURL);
-  if (NS_SUCCEEDED(rv)) {
-    rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedURL);
-  }
-  LOG3(("Http2Session::RecvPushPromise %p checking %s == %s", self,
-        associatedStream->Origin().get(), pushedStream->Origin().get()));
-  bool match = false;
+  // does the pushed origin belong on this connection?
+  LOG3(("Http2Session::RecvPushPromise %p origin check %s", self,
+        pushedStream->Origin().get()));
+  RefPtr<nsStandardURL> pushedURL;
+  rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedURL);
+  nsAutoCString pushedHostName;
+  int32_t pushedPort = -1;
   if (NS_SUCCEEDED(rv)) {
-    rv = associatedURL->Equals(pushedURL, &match);
+    rv = pushedURL->GetHost(pushedHostName);
+  }
+  if (NS_SUCCEEDED(rv)) {
+    rv = pushedURL->GetPort(&pushedPort);
   }
-  if (NS_FAILED(rv)) {
-    // Fallback to string equality of origins. This won't be guaranteed to be as
-    // liberal as we want it to be, but it will at least be safe
-    match = associatedStream->Origin().Equals(pushedStream->Origin());
-  }
-  if (!match) {
-    LOG3(("Http2Session::RecvPushPromise %p pushed stream mismatched origin "
-          "associated origin %s .. pushed origin %s\n", self,
-          associatedStream->Origin().get(), pushedStream->Origin().get()));
+  if (NS_FAILED(rv) ||
+      !self->TestJoinConnection(pushedHostName, pushedPort)) {
+    LOG3(("Http2Session::RecvPushPromise %p pushed stream mismatched origin %s\n",
+          self, pushedStream->Origin().get()));
     self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
     self->ResetDownstreamState();
     return NS_OK;
   }
 
   if (pushedStream->TryOnPush()) {
     LOG3(("Http2Session::RecvPushPromise %p channel implements nsIHttpPushListener "
           "stream %p will not be placed into session cache.\n", self, pushedStream));
@@ -2286,16 +2285,121 @@ Http2Session::RecvAltSvc(Http2Session *s
 
   RefPtr<UpdateAltSvcEvent> event =
     new UpdateAltSvcEvent(altSvcFieldValue, origin, ci, irCallbacks);
   NS_DispatchToMainThread(event);
   self->ResetDownstreamState();
   return NS_OK;
 }
 
+void
+Http2Session::Received421(nsHttpConnectionInfo *ci)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("Http2Session::Recevied421 %p %d\n", this, mOriginFrameActivated));
+  if (!mOriginFrameActivated || !ci) {
+    return;
+  }
+
+  nsAutoCString key(ci->GetOrigin());
+  key.Append(':');
+  key.AppendInt(ci->OriginPort());
+  mOriginFrame.Remove(key);
+  LOG3(("Http2Session::Received421 %p key %s removed\n", this, key.get()));
+}
+
+// defined as an http2 extension - origin
+// defines receipt of frame type 0x0b.. http://httpwg.org/http-extensions/origin-frame.html
+// as this is an extension, never generate protocol error - just ignore problems
+nsresult
+Http2Session::RecvOrigin(Http2Session *self)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ORIGIN);
+  LOG3(("Http2Session::RecvOrigin %p Flags 0x%X id 0x%X\n", self,
+        self->mInputFrameFlags, self->mInputFrameID));
+
+  if (self->mInputFrameFlags & 0x0F) {
+    LOG3(("Http2Session::RecvOrigin %p leading flags must be 0", self));
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
+
+  if (self->mInputFrameID) {
+    LOG3(("Http2Session::RecvOrigin %p not stream 0", self));
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
+
+  if (self->ConnectionInfo()->UsingProxy()) {
+    LOG3(("Http2Session::RecvOrigin %p must not use proxy", self));
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
+
+  if (!gHttpHandler->AllowOriginExtension()) {
+    LOG3(("Http2Session::RecvOrigin %p origin extension pref'd off", self));
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
+
+  uint32_t offset = 0;
+  self->mOriginFrameActivated = true;
+
+  while (self->mInputFrameDataSize >= (offset + 2U)) {
+
+    uint16_t originLen = NetworkEndian::readUint16(
+      self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset);
+    LOG3(("Http2Session::RecvOrigin %p origin extension defined as %d bytes\n", self, originLen));
+    if (originLen + 2U + offset > self->mInputFrameDataSize) {
+      LOG3(("Http2Session::RecvOrigin %p origin len too big for frame", self));
+      break;
+    }
+
+    nsAutoCString originString;
+    RefPtr<nsStandardURL> originURL;
+    originString.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2, originLen);
+    offset += originLen + 2;
+    if (NS_FAILED(Http2Stream::MakeOriginURL(originString, originURL))){
+      LOG3(("Http2Session::RecvOrigin %p origin frame string %s failed to parse\n", self, originString.get()));
+      continue;
+    }
+
+    LOG3(("Http2Session::RecvOrigin %p origin frame string %s parsed OK\n", self, originString.get()));
+    bool isHttps = false;
+    if (NS_FAILED(originURL->SchemeIs("https", &isHttps)) || !isHttps) {
+      LOG3(("Http2Session::RecvOrigin %p origin frame not https\n", self));
+      continue;
+    }
+
+    int32_t port = -1;
+    originURL->GetPort(&port);
+    if (port == -1) {
+      port = 443;
+    }
+    // dont use ->GetHostPort because we want explicit 443
+    nsAutoCString host;
+    originURL->GetHost(host);
+    nsAutoCString key(host);
+    key.Append(':');
+    key.AppendInt(port);
+    if (!self->mOriginFrame.Get(key)) {
+      self->mOriginFrame.Put(key, true);
+      RefPtr<nsHttpConnection> conn(self->HttpConnection());
+      MOZ_ASSERT(conn.get());
+      gHttpHandler->ConnMgr()->RegisterOriginCoalescingKey(conn, host, port);
+    } else {
+      LOG3(("Http2Session::RecvOrigin %p origin frame already in set\n", self));
+    }
+  }
+
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsAHttpTransaction. It is expected that nsHttpConnection is the caller
 // of these methods
 //-----------------------------------------------------------------------------
 
 void
 Http2Session::OnTransportStatus(nsITransport* aTransport,
                                 nsresult aStatus, int64_t aProgress)
@@ -3843,16 +3947,25 @@ Http2Session::TakeTransport(nsISocketTra
 
 already_AddRefed<nsHttpConnection>
 Http2Session::TakeHttpConnection()
 {
   MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
   return nullptr;
 }
 
+already_AddRefed<nsHttpConnection>
+Http2Session::HttpConnection()
+{
+  if (mConnection) {
+    return mConnection->HttpConnection();
+  }
+  return nullptr;
+}
+
 void
 Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **aOut)
 {
   *aOut = nullptr;
 }
 
 //-----------------------------------------------------------------------------
 // unused methods of nsAHttpTransaction
@@ -4000,10 +4113,107 @@ Http2Session::SendPing()
     mPreviousPingThreshold = mPingThreshold;
     mPreviousUsed = true;
     mPingThreshold = gHttpHandler->NetworkChangedTimeout();
   }
   GeneratePing(false);
   Unused << ResumeRecv();
 }
 
+bool
+Http2Session::TestOriginFrame(const nsACString &hostname, int32_t port)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(mOriginFrameActivated);
+
+  nsAutoCString key(hostname);
+  key.Append (':');
+  key.AppendInt(port);
+  bool rv = mOriginFrame.Get(key);
+  LOG3(("TestOriginFrame() %p %s %d\n", this, key.get(), rv));
+  return rv;
+}
+
+bool
+Http2Session::TestJoinConnection(const nsACString &hostname, int32_t port)
+{
+  if (!mConnection || mClosed || mShouldGoAway) {
+    return false;
+  }
+
+  if (mOriginFrameActivated) {
+    bool originFrameResult = TestOriginFrame(hostname, port);
+    if (!originFrameResult) {
+      return false;
+    }
+  } else {
+    LOG3(("TestJoinConnection %p no origin frame check used.\n", this));
+  }
+
+  nsresult rv;
+  bool isJoined = false;
+
+  nsCOMPtr<nsISupports> securityInfo;
+  nsCOMPtr<nsISSLSocketControl> sslSocketControl;
+
+  mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+  sslSocketControl = do_QueryInterface(securityInfo, &rv);
+  if (NS_FAILED(rv) || !sslSocketControl) {
+    return false;
+  }
+
+  // try all the coalescable versions we support.
+  const SpdyInformation *info = gHttpHandler->SpdyInfo();
+  for (uint32_t index = SpdyInformation::kCount; index > 0; --index) {
+    if (info->ProtocolEnabled(index - 1)) {
+      rv = sslSocketControl->TestJoinConnection(info->VersionString[index - 1],
+                                                hostname, port, &isJoined);
+      if (NS_SUCCEEDED(rv) && isJoined) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool
+Http2Session::JoinConnection(const nsACString &hostname, int32_t port)
+{
+  if (!mConnection || mClosed || mShouldGoAway) {
+    return false;
+  }
+  if (mOriginFrameActivated) {
+    bool originFrameResult = TestOriginFrame(hostname, port);
+    if (!originFrameResult) {
+      return false;
+    }
+  } else {
+    LOG3(("JoinConnection %p no origin frame check used.\n", this));
+  }
+
+  nsresult rv;
+  bool isJoined = false;
+
+  nsCOMPtr<nsISupports> securityInfo;
+  nsCOMPtr<nsISSLSocketControl> sslSocketControl;
+
+  mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+  sslSocketControl = do_QueryInterface(securityInfo, &rv);
+  if (NS_FAILED(rv) || !sslSocketControl) {
+    return false;
+  }
+
+  // try all the coalescable versions we support.
+  const SpdyInformation *info = gHttpHandler->SpdyInfo();
+  for (uint32_t index = SpdyInformation::kCount; index > 0; --index) {
+    if (info->ProtocolEnabled(index - 1)) {
+      rv = sslSocketControl->JoinConnection(info->VersionString[index - 1],
+                                            hostname, port, &isJoined);
+      if (NS_SUCCEEDED(rv) && isJoined) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -45,16 +45,18 @@ public:
 
  Http2Session(nsISocketTransport *, uint32_t version, bool attemptingEarlyData);
 
   MOZ_MUST_USE bool AddStream(nsAHttpTransaction *, int32_t,
                               bool, nsIInterfaceRequestor *) override;
   bool CanReuse() override { return !mShouldGoAway && !mClosed; }
   bool RoomForMoreStreams() override;
   uint32_t SpdyVersion() override;
+  bool TestJoinConnection(const nsACString &hostname, int32_t port) override;
+  bool JoinConnection(const nsACString &hostname, int32_t port) override;
 
   // When the connection is active this is called up to once every 1 second
   // return the interval (in seconds) that the connection next wants to
   // have this invoked. It might happen sooner depending on the needs of
   // other connections.
   uint32_t  ReadTimeoutTick(PRIntervalTime now) override;
 
   // Idle time represents time since "goodput".. e.g. a data or header frame
@@ -84,17 +86,18 @@ public:
     FRAME_TYPE_RST_STREAM    = 0x3,
     FRAME_TYPE_SETTINGS      = 0x4,
     FRAME_TYPE_PUSH_PROMISE  = 0x5,
     FRAME_TYPE_PING          = 0x6,
     FRAME_TYPE_GOAWAY        = 0x7,
     FRAME_TYPE_WINDOW_UPDATE = 0x8,
     FRAME_TYPE_CONTINUATION  = 0x9,
     FRAME_TYPE_ALTSVC        = 0xA,
-    FRAME_TYPE_LAST          = 0xB
+    FRAME_TYPE_ORIGIN        = 0xB,
+    FRAME_TYPE_LAST          = 0xC
   };
 
   // NO_ERROR is a macro defined on windows, so we'll name the HTTP2 goaway
   // code NO_ERROR to be NO_HTTP_ERROR
   enum errorType {
     NO_HTTP_ERROR = 0,
     PROTOCOL_ERROR = 1,
     INTERNAL_ERROR = 2,
@@ -180,16 +183,17 @@ public:
   static nsresult RecvRstStream(Http2Session *);
   static nsresult RecvSettings(Http2Session *);
   static nsresult RecvPushPromise(Http2Session *);
   static nsresult RecvPing(Http2Session *);
   static nsresult RecvGoAway(Http2Session *);
   static nsresult RecvWindowUpdate(Http2Session *);
   static nsresult RecvContinuation(Http2Session *);
   static nsresult RecvAltSvc(Http2Session *);
+  static nsresult RecvOrigin(Http2Session *);
 
   char       *EnsureOutputBuffer(uint32_t needed);
 
   template<typename charType>
   void CreateFrameHeader(charType dest, uint16_t frameLength,
                          uint8_t frameType, uint8_t frameFlags,
                          uint32_t streamID);
 
@@ -238,16 +242,19 @@ public:
   bool UseH2Deps() { return mUseH2Deps; }
 
   // overload of nsAHttpTransaction
   MOZ_MUST_USE nsresult ReadSegmentsAgain(nsAHttpSegmentReader *, uint32_t, uint32_t *, bool *) override final;
   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;
 
+  // For use by an HTTP2Stream
+  void Received421(nsHttpConnectionInfo *ci);
+
 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,
@@ -503,16 +510,20 @@ private:
   bool mGoAwayOnPush;
 
   bool mUseH2Deps;
 
   bool mAttemptingEarlyData;
   // The ID(s) of the stream(s) that we are getting 0RTT data from.
   nsTArray<uint32_t> m0RTTStreams;
 
+  bool TestOriginFrame(const nsACString &name, int32_t port);
+  bool mOriginFrameActivated;
+  nsDataHashtable<nsCStringHashKey, bool> mOriginFrame;
+
 private:
 /// connect tunnels
   void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *);
   void CreateTunnel(nsHttpTransaction *, nsHttpConnectionInfo *, nsIInterfaceRequestor *);
   void RegisterTunnel(Http2Stream *);
   void UnRegisterTunnel(Http2Stream *);
   uint32_t FindTunnelCount(nsHttpConnectionInfo *);
   nsDataHashtable<nsCStringHashKey, uint32_t> mTunnelHash;
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -1017,29 +1017,35 @@ Http2Stream::ConvertResponseHeaders(Http
   decompressor->GetStatus(statusString);
   if (statusString.IsEmpty()) {
     LOG3(("Http2Stream::ConvertResponseHeaders %p Error - no status\n", this));
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
   nsresult errcode;
   httpResponseCode = statusString.ToInteger(&errcode);
+  LOG3(("Http2Stream::ConvertResponseHeaders %p response code %d\n", this, httpResponseCode));
   if (mIsTunnel) {
     LOG3(("Http2Stream %p Tunnel Response code %d", this, httpResponseCode));
     if ((httpResponseCode / 100) != 2) {
       MapStreamToPlainText();
     }
   }
 
   if (httpResponseCode == 101) {
     // 8.1.1 of h2 disallows 101.. throw PROTOCOL_ERROR on stream
     LOG3(("Http2Stream::ConvertResponseHeaders %p Error - status == 101\n", this));
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
+  if (httpResponseCode == 421) {
+    // Origin Frame requires 421 to remove this origin from the origin set
+    mSession->Received421(mTransaction->ConnectionInfo());
+  }
+
   if (aHeadersIn.Length() && aHeadersOut.Length()) {
     Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
     uint32_t ratio =
       aHeadersIn.Length() * 100 / aHeadersOut.Length();
     Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
   }
 
   // The decoding went ok. Now we can customize and clean up.
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -124,16 +124,19 @@ public:
     // the same connection and work around buggy servers.
     virtual bool LastTransactionExpectedNoContent() = 0;
     virtual void   SetLastTransactionExpectedNoContent(bool) = 0;
 
     // Transfer the base http connection object along with a
     // reference to it to the caller.
     virtual already_AddRefed<nsHttpConnection> TakeHttpConnection() = 0;
 
+    // Like TakeHttpConnection() but do not drop our own ref
+    virtual already_AddRefed<nsHttpConnection> HttpConnection() = 0;
+
     // Get the nsISocketTransport used by the connection without changing
     //  references or ownership.
     virtual nsISocketTransport *Transport() = 0;
 
     // The number of transaction bytes written out on this HTTP Connection, does
     // not count CONNECT tunnel setup
     virtual int64_t BytesWritten() = 0;
 
@@ -156,17 +159,18 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpCon
     MOZ_MUST_USE nsresult TakeTransport(nsISocketTransport **,    \
                                         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; \
+    /*                                                                  \
        Thes methods below have automatic definitions that just forward the \
        function to a lower level connection object        \
     */                                                    \
     void GetConnectionInfo(nsHttpConnectionInfo **result) \
       override                                            \
     {                                                     \
       if (!(fwdObject)) {                                 \
           *result = nullptr;                              \
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -879,16 +879,34 @@ nsHttpConnection::DontReuse()
     mKeepAlive = false;
     mDontReuse = true;
     mIdleTimeout = 0;
     if (mSpdySession)
         mSpdySession->DontReuse();
 }
 
 bool
+nsHttpConnection::TestJoinConnection(const nsACString &hostname, int32_t port)
+{
+    if (mSpdySession && CanDirectlyActivate()) {
+        return mSpdySession->TestJoinConnection(hostname, port);
+    }
+    return false;
+}
+
+bool
+nsHttpConnection::JoinConnection(const nsACString &hostname, int32_t port)
+{
+    if (mSpdySession && CanDirectlyActivate()) {
+        return mSpdySession->JoinConnection(hostname, port);
+    }
+    return false;
+}
+
+bool
 nsHttpConnection::CanReuse()
 {
     if (mDontReuse || !mRemainingConnectionUses) {
         return false;
     }
 
     if ((mTransaction ? (mTransaction->IsDone() ? 0U : 1U) : 0U) >=
         mRemainingConnectionUses) {
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -217,16 +217,19 @@ public:
     // connection since CheckForTraffic() was called.
     bool NoTraffic() {
         return mTrafficStamp &&
             (mTrafficCount == (mTotalBytesWritten + mTotalBytesRead));
     }
     // override of nsAHttpConnection
     virtual uint32_t Version();
 
+    bool TestJoinConnection(const nsACString &hostname, int32_t port);
+    bool JoinConnection(const nsACString &hostname, int32_t port);
+
 private:
     // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
     enum TCPKeepaliveConfig {
       kTCPKeepaliveDisabled = 0,
       kTCPKeepaliveShortLivedConfig,
       kTCPKeepaliveLongLivedConfig
     };
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -95,16 +95,17 @@ nsHttpConnectionMgr::nsHttpConnectionMgr
     , mCurrentTopLevelOuterContentWindowId(0)
 {
     LOG(("Creating nsHttpConnectionMgr @%p\n", this));
 }
 
 nsHttpConnectionMgr::~nsHttpConnectionMgr()
 {
     LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
+    MOZ_ASSERT(mCoalescingHash.Count() == 0);
     if (mTimeoutTick)
         mTimeoutTick->Cancel();
 }
 
 nsresult
 nsHttpConnectionMgr::EnsureSocketThreadTarget()
 {
     nsresult rv;
@@ -554,123 +555,28 @@ nsHttpConnectionMgr::ClearConnectionHist
             ent->PendingQLength()       == 0) {
             iter.Remove();
         }
     }
 
     return NS_OK;
 }
 
-
-nsHttpConnectionMgr::nsConnectionEntry *
-nsHttpConnectionMgr::LookupPreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
-{
-    nsConnectionEntry *preferred = nullptr;
-    uint32_t len = ent->mCoalescingKeys.Length();
-    for (uint32_t i = 0; !preferred && (i < len); ++i) {
-        preferred = mSpdyPreferredHash.Get(ent->mCoalescingKeys[i]);
-    }
-    return preferred;
-}
-
-void
-nsHttpConnectionMgr::StorePreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
-{
-    if (ent->mCoalescingKeys.IsEmpty()) {
-        return;
-    }
-
-    ent->mInPreferredHash = true;
-    uint32_t len = ent->mCoalescingKeys.Length();
-    for (uint32_t i = 0; i < len; ++i) {
-        mSpdyPreferredHash.Put(ent->mCoalescingKeys[i], ent);
-    }
-}
-
-void
-nsHttpConnectionMgr::RemovePreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
-{
-    if (!ent->mInPreferredHash || ent->mCoalescingKeys.IsEmpty()) {
-        return;
-    }
-
-    ent->mInPreferredHash = false;
-    uint32_t len = ent->mCoalescingKeys.Length();
-    for (uint32_t i = 0; i < len; ++i) {
-        mSpdyPreferredHash.Remove(ent->mCoalescingKeys[i]);
-    }
-}
-
-// Given a nsHttpConnectionInfo find the connection entry object that
-// contains either the nshttpconnection or nshttptransaction parameter.
-// Normally this is done by the hashkey lookup of connectioninfo,
-// but if spdy coalescing is in play it might be found in a redirected
-// entry
-nsHttpConnectionMgr::nsConnectionEntry *
-nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci,
-                                           nsHttpConnection *conn,
-                                           nsHttpTransaction *trans)
-{
-    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
-    if (!ci)
-        return nullptr;
-
-    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
-
-    // If there is no sign of coalescing (or it is disabled) then just
-    // return the primary hash lookup
-    if (!ent || !ent->mUsingSpdy || ent->mCoalescingKeys.IsEmpty())
-        return ent;
-
-    // If there is no preferred coalescing entry for this host (or the
-    // preferred entry is the one that matched the mCT hash lookup) then
-    // there is only option
-    nsConnectionEntry *preferred = LookupPreferredHash(ent);
-    if (!preferred || (preferred == ent))
-        return ent;
-
-    if (conn) {
-        // The connection could be either in preferred or ent. It is most
-        // likely the only active connection in preferred - so start with that.
-        if (preferred->mActiveConns.Contains(conn))
-            return preferred;
-        if (preferred->mIdleConns.Contains(conn))
-            return preferred;
-    }
-
-    if (trans) {
-        if (preferred->mUrgentStartQ.Contains(trans, PendingComparator())) {
-            return preferred;
-        }
-
-        nsTArray<RefPtr<PendingTransactionInfo>> *infoArray;
-        if (preferred->mPendingTransactionTable.Get(
-            trans->TopLevelOuterContentWindowId(), &infoArray)) {
-            if (infoArray->Contains(trans, PendingComparator())) {
-                return preferred;
-            }
-        }
-    }
-
-    // Neither conn nor trans found in preferred, use the default entry
-    return ent;
-}
-
 nsresult
 nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p",
          this, conn));
 
-    if (!conn->ConnectionInfo())
+    if (!conn->ConnectionInfo()) {
         return NS_ERROR_UNEXPECTED;
-
-    nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
-                                                   conn, nullptr);
+    }
+
+    nsConnectionEntry *ent = mCT.Get(conn->ConnectionInfo()->HashKey());
 
     RefPtr<nsHttpConnection> deleteProtector(conn);
     if (!ent || !ent->mIdleConns.RemoveElement(conn))
         return NS_ERROR_UNEXPECTED;
 
     conn->Close(NS_ERROR_ABORT);
     mNumIdleConns--;
     ConditionallyStopPruneDeadConnectionsTimer();
@@ -684,237 +590,244 @@ nsHttpConnectionMgr::RemoveIdleConnectio
 
     LOG(("nsHttpConnectionMgr::RemoveIdleConnection %p conn=%p",
          this, conn));
 
     if (!conn->ConnectionInfo()) {
         return NS_ERROR_UNEXPECTED;
     }
 
-    nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
-                                                   conn, nullptr);
+    nsConnectionEntry *ent = mCT.Get(conn->ConnectionInfo()->HashKey());
 
     if (!ent || !ent->mIdleConns.RemoveElement(conn)) {
         return NS_ERROR_UNEXPECTED;
     }
 
     mNumIdleConns--;
     return NS_OK;
 }
 
+nsHttpConnection *
+nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(nsConnectionEntry *ent,
+                                                        const nsCString &key,
+                                                        bool justKidding)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(ent->mConnInfo);
+    nsHttpConnectionInfo *ci = ent->mConnInfo;
+
+    nsTArray<nsWeakPtr> *listOfWeakConns =  mCoalescingHash.Get(key);
+    if (!listOfWeakConns) {
+        return nullptr;
+    }
+
+    uint32_t listLen = listOfWeakConns->Length();
+    for (uint32_t j = 0; j < listLen; ) {
+
+        RefPtr<nsHttpConnection> potentialMatch = do_QueryReferent(listOfWeakConns->ElementAt(j));
+        if (!potentialMatch) {
+            // This is a connection that needs to be removed from the list
+            LOG(("FindCoalescableConnectionByHashKey() found old conn %p that has null weak ptr - removing\n",
+                 listOfWeakConns->ElementAt(j).get()));
+            if (j != listLen - 1) {
+                listOfWeakConns->Elements()[j] = listOfWeakConns->Elements()[listLen - 1];
+            }
+            listOfWeakConns->RemoveElementAt(listLen - 1);
+            MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1);
+            listLen--;
+            continue; // without adjusting iterator
+        }
+
+        bool couldJoin;
+        if (justKidding) {
+            couldJoin = potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort());
+        } else {
+            couldJoin = potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort());
+        }
+        if (couldJoin) {
+            LOG(("FindCoalescableConnectionByHashKey() found hashtable match %p for key %s of CI %s join ok\n",
+                 potentialMatch.get(), key.get(), ci->HashKey().get()));
+            return potentialMatch.get();
+        } else {
+            LOG(("FindCoalescableConnectionByHashKey() found hashtable match %p for key %s of CI %s join failed\n",
+                 potentialMatch.get(), key.get(), ci->HashKey().get()));
+        }
+        ++j; // bypassed by continue when weakptr fails
+    }
+
+    if (!listLen) { // shrunk to 0 while iterating
+        LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n"));
+        mCoalescingHash.Remove(key);
+    }
+    return nullptr;
+}
+
+static void
+BuildOriginFrameHashKey(nsACString &newKey, nsHttpConnectionInfo *ci,
+                        const nsACString &host, int32_t port)
+{
+    newKey.Assign(host);
+    if (ci->GetAnonymous()) {
+        newKey.AppendLiteral("~A:");
+    } else {
+        newKey.AppendLiteral("~.:");
+    }
+    newKey.AppendInt(port);
+    newKey.AppendLiteral("/[");
+    nsAutoCString suffix;
+    ci->GetOriginAttributes().CreateSuffix(suffix);
+    newKey.Append(suffix);
+    newKey.AppendLiteral("]viaORIGIN.FRAME");
+}
+
+nsHttpConnection *
+nsHttpConnectionMgr::FindCoalescableConnection(nsConnectionEntry *ent,
+                                               bool justKidding)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(ent->mConnInfo);
+    nsHttpConnectionInfo *ci = ent->mConnInfo;
+    LOG(("FindCoalescableConnection %s\n", ci->HashKey().get()));
+    // First try and look it up by origin frame
+    nsCString newKey;
+    BuildOriginFrameHashKey(newKey, ci, ci->GetOrigin(), ci->OriginPort());
+    nsHttpConnection *conn =
+        FindCoalescableConnectionByHashKey(ent, newKey, justKidding);
+    if (conn) {
+        LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
+             ci->HashKey().get(), conn, newKey.get()));
+        return conn;
+    }
+
+    // now check for DNS based keys
+    // deleted conns (null weak pointers) are removed from list
+    uint32_t keyLen = ent->mCoalescingKeys.Length();
+    for (uint32_t i = 0; i < keyLen; ++i) {
+        conn = FindCoalescableConnectionByHashKey(ent, ent->mCoalescingKeys[i], justKidding);
+        if (conn) {
+            LOG(("FindCoalescableConnection(%s) match conn %p on dns key %s\n",
+                 ci->HashKey().get(), conn, ent->mCoalescingKeys[i].get()));
+            return conn;
+        }
+    }
+
+    LOG(("FindCoalescableConnection(%s) no matching conn\n", ci->HashKey().get()));
+    return nullptr;
+}
+
+void
+nsHttpConnectionMgr::UpdateCoalescingForNewConn(nsHttpConnection *conn,
+                                                nsConnectionEntry *ent)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(conn);
+    MOZ_ASSERT(conn->ConnectionInfo());
+    MOZ_ASSERT(ent);
+    MOZ_ASSERT(mCT.Get(conn->ConnectionInfo()->HashKey()) == ent);
+
+    nsHttpConnection *existingConn = FindCoalescableConnection(ent, true);
+    if (existingConn) {
+        LOG(("UpdateCoalescingForNewConn() found active conn that could have served newConn\n"
+             "graceful close of conn=%p to migrate to %p\n", conn, existingConn));
+        conn->DontReuse();
+        return;
+    }
+
+    // This connection might go into the mCoalescingHash for new transactions to be coalesced onto
+    // if it can accept new transactions
+    if (!conn->CanDirectlyActivate()) {
+        return;
+    }
+
+    uint32_t keyLen = ent->mCoalescingKeys.Length();
+    for (uint32_t i = 0;i < keyLen; ++i) {
+        LOG(("UpdateCoalescingForNewConn() registering conn %p %s under key %s\n",
+             conn, conn->ConnectionInfo()->HashKey().get(), ent->mCoalescingKeys[i].get()));
+        nsTArray<nsWeakPtr> *listOfWeakConns =  mCoalescingHash.Get(ent->mCoalescingKeys[i]);
+        if (!listOfWeakConns) {
+            LOG(("UpdateCoalescingForNewConn() need new list element\n"));
+            listOfWeakConns = new nsTArray<nsWeakPtr>(1);
+            mCoalescingHash.Put(ent->mCoalescingKeys[i], listOfWeakConns);
+        }
+        listOfWeakConns->AppendElement(
+            do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn)));
+    }
+
+    // Cancel any other pending connections - their associated transactions
+    // are in the pending queue and will be dispatched onto this new connection
+    for (int32_t index = ent->mHalfOpens.Length() - 1; index >= 0; --index) {
+        LOG(("UpdateCoalescingForNewConn() forcing halfopen abandon %p\n",
+             ent->mHalfOpens[index]));
+        ent->mHalfOpens[index]->Abandon();
+    }
+
+    if (ent->mActiveConns.Length() > 1) {
+        // this is a new connection that can be coalesced onto. hooray!
+        // if there are other connection to this entry (e.g.
+        // some could still be handshaking, shutting down, etc..) then close
+        // them down after any transactions that are on them are complete.
+        // This probably happened due to the parallel connection algorithm
+        // that is used only before the host is known to speak h2.
+        for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+            nsHttpConnection *otherConn = ent->mActiveConns[index];
+            if (otherConn != conn) {
+                LOG(("UpdateCoalescingForNewConn() shutting down connection (%p) because new "
+                     "spdy connection (%p) takes precedence\n", otherConn, conn));
+                otherConn->DontReuse();
+            }
+        }
+    }
+}
+
 // This function lets a connection, after completing the NPN phase,
 // report whether or not it is using spdy through the usingSpdy
 // argument. It would not be necessary if NPN were driven out of
 // the connection manager. The connection entry associated with the
 // connection is then updated to indicate whether or not we want to use
-// spdy with that host and update the preliminary preferred host
+// spdy with that host and update the coalescing hash
 // entries used for de-sharding hostsnames.
 void
 nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
                                           bool usingSpdy)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
-
-    nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
-                                                   conn, nullptr);
-
-    if (!ent)
+    if (!conn->ConnectionInfo()) {
         return;
-
-    if (!usingSpdy)
+    }
+    nsConnectionEntry *ent = mCT.Get(conn->ConnectionInfo()->HashKey());
+    if (!ent || !usingSpdy) {
         return;
+    }
 
     ent->mUsingSpdy = true;
     mNumSpdyActiveConns++;
 
+    // adjust timeout timer
     uint32_t ttl = conn->TimeToLive();
     uint64_t timeOfExpire = NowInSeconds() + ttl;
-    if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
+    if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) {
         PruneDeadConnectionsAfter(ttl);
-
-    // Lookup preferred directly from the hash instead of using
-    // GetSpdyPreferredEnt() because we want to avoid the cert compatibility
-    // check at this point because the cert is never part of the hash
-    // lookup. Filtering on that has to be done at the time of use
-    // rather than the time of registration (i.e. now).
-    nsConnectionEntry *joinedConnection;
-    nsConnectionEntry *preferred = LookupPreferredHash(ent);
-
-    LOG(("ReportSpdyConnection %p,%s conn %p prefers %p,%s\n",
-         ent, ent->mConnInfo->Origin(), conn, preferred,
-         preferred ? preferred->mConnInfo->Origin() : ""));
-
-    if (!preferred) {
-        // this becomes the preferred entry
-        StorePreferredHash(ent);
-        preferred = ent;
-    } else if ((preferred != ent) &&
-               (joinedConnection = GetSpdyPreferredEnt(ent)) &&
-               (joinedConnection != ent)) {
-        //
-        // A connection entry (e.g. made with a different hostname) with
-        // the same IP address is preferred for future transactions over this
-        // connection entry. Gracefully close down the connection to help
-        // new transactions migrate over.
-
-        LOG(("ReportSpdyConnection graceful close of conn=%p ent=%p to "
-                 "migrate to preferred (desharding)\n", conn, ent));
-        conn->DontReuse();
-    } else if (preferred != ent) {
-        LOG (("ReportSpdyConnection preferred host may be in false start or "
-              "may have insufficient cert. Leave mapping in place but do not "
-              "abandon this connection yet."));
     }
 
-    if ((preferred == ent) && conn->CanDirectlyActivate()) {
-        // this is a new spdy connection to the preferred entry
-
-        // Cancel any other pending connections - their associated transactions
-        // are in the pending queue and will be dispatched onto this connection
-        for (int32_t index = ent->mHalfOpens.Length() - 1;
-             index >= 0; --index) {
-            LOG(("ReportSpdyConnection forcing halfopen abandon %p\n",
-                 ent->mHalfOpens[index]));
-            ent->mHalfOpens[index]->Abandon();
-        }
-
-        if (ent->mActiveConns.Length() > 1) {
-            // this is a new connection to an established preferred spdy host.
-            // if there is more than 1 live and established spdy connection (e.g.
-            // some could still be handshaking, shutting down, etc..) then close
-            // this one down after any transactions that are on it are complete.
-            // This probably happened due to the parallel connection algorithm
-            // that is used only before the host is known to speak spdy.
-            for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
-                nsHttpConnection *otherConn = ent->mActiveConns[index];
-                if (otherConn != conn) {
-                    LOG(("ReportSpdyConnection shutting down connection (%p) because new "
-                         "spdy connection (%p) takes precedence\n", otherConn, conn));
-                    otherConn->DontReuse();
-                }
-            }
-        }
-    }
+    UpdateCoalescingForNewConn(conn, ent);
 
     nsresult rv = ProcessPendingQ(ent->mConnInfo);
     if (NS_FAILED(rv)) {
         LOG(("ReportSpdyConnection conn=%p ent=%p "
              "failed to process pending queue (%08x)\n", conn, ent,
              static_cast<uint32_t>(rv)));
     }
     rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
     if (NS_FAILED(rv)) {
         LOG(("ReportSpdyConnection conn=%p ent=%p "
              "failed to post event (%08x)\n", conn, ent,
              static_cast<uint32_t>(rv)));
     }
 }
 
-nsHttpConnectionMgr::nsConnectionEntry *
-nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry)
-{
-    if (!gHttpHandler->IsSpdyEnabled() ||
-        !gHttpHandler->CoalesceSpdy() ||
-        aOriginalEntry->mConnInfo->GetNoSpdy() ||
-        aOriginalEntry->mCoalescingKeys.IsEmpty()) {
-        return nullptr;
-    }
-
-    nsConnectionEntry *preferred = LookupPreferredHash(aOriginalEntry);
-
-    // if there is no redirection no cert validation is required
-    if (preferred == aOriginalEntry)
-        return aOriginalEntry;
-
-    // if there is no preferred host or it is no longer using spdy
-    // then skip pooling
-    if (!preferred || !preferred->mUsingSpdy)
-        return nullptr;
-
-    // if there is not an active spdy session in this entry then
-    // we cannot pool because the cert upon activation may not
-    // be the same as the old one. Active sessions are prohibited
-    // from changing certs.
-
-    nsHttpConnection *activeSpdy = nullptr;
-
-    for (uint32_t index = 0; index < preferred->mActiveConns.Length(); ++index) {
-        if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
-            activeSpdy = preferred->mActiveConns[index];
-            break;
-        }
-    }
-
-    if (!activeSpdy) {
-        // remove the preferred status of this entry if it cannot be
-        // used for pooling.
-        RemovePreferredHash(preferred);
-        LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
-             "preferred host mapping %s to %s removed due to inactivity.\n",
-             aOriginalEntry->mConnInfo->Origin(),
-             preferred->mConnInfo->Origin()));
-
-        return nullptr;
-    }
-
-    // Check that the server cert supports redirection
-    nsresult rv;
-    bool isJoined = false;
-
-    nsCOMPtr<nsISupports> securityInfo;
-    nsCOMPtr<nsISSLSocketControl> sslSocketControl;
-    nsAutoCString negotiatedNPN;
-
-    activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo));
-    if (!securityInfo) {
-        NS_WARNING("cannot obtain spdy security info");
-        return nullptr;
-    }
-
-    sslSocketControl = do_QueryInterface(securityInfo, &rv);
-    if (NS_FAILED(rv)) {
-        NS_WARNING("sslSocketControl QI Failed");
-        return nullptr;
-    }
-
-    // try all the spdy versions we support.
-    const SpdyInformation *info = gHttpHandler->SpdyInfo();
-    for (uint32_t index = SpdyInformation::kCount;
-         NS_SUCCEEDED(rv) && index > 0; --index) {
-        if (info->ProtocolEnabled(index - 1)) {
-            rv = sslSocketControl->JoinConnection(info->VersionString[index - 1],
-                                                  aOriginalEntry->mConnInfo->GetOrigin(),
-                                                  aOriginalEntry->mConnInfo->OriginPort(),
-                                                  &isJoined);
-            if (NS_SUCCEEDED(rv) && isJoined) {
-                break;
-            }
-        }
-    }
-
-    if (NS_FAILED(rv) || !isJoined) {
-        LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
-             "Host %s cannot be confirmed to be joined "
-             "with %s connections. rv=%" PRIx32 " isJoined=%d",
-             preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
-             static_cast<uint32_t>(rv), isJoined));
-        Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, false);
-        return nullptr;
-    }
-
-    // IP pooling confirmed
-    LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
-         "Host %s has cert valid for %s connections, "
-         "so %s will be coalesced with %s",
-         preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
-         aOriginalEntry->mConnInfo->Origin(), preferred->mConnInfo->Origin()));
-    Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, true);
-    return preferred;
-}
-
 //-----------------------------------------------------------------------------
 bool
 nsHttpConnectionMgr::DispatchPendingQ(nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo> > &pendingQ,
                                       nsConnectionEntry *ent,
                                       bool considerAll)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
@@ -1042,16 +955,19 @@ nsHttpConnectionMgr::ProcessPendingQForE
 
     LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry "
          "[ci=%s ent=%p active=%" PRIuSIZE " idle=%" PRIuSIZE " urgent-start queue=%" PRIuSIZE
          "queued=%" PRIuSIZE "]\n",
          ent->mConnInfo->HashKey().get(), ent, ent->mActiveConns.Length(),
          ent->mIdleConns.Length(), ent->mUrgentStartQ.Length(),
          ent->PendingQLength()));
 
+    if (!ent->mUrgentStartQ.Length() && !ent->mPendingTransactionTable.Count()) {
+        return false;
+    }
     ProcessSpdyPendingQ(ent);
 
     bool dispatchedSuccessfully = false;
 
     if (!ent->mUrgentStartQ.IsEmpty()) {
         dispatchedSuccessfully = DispatchPendingQ(ent->mUrgentStartQ,
                                                   ent,
                                                   considerAll);
@@ -1453,17 +1369,17 @@ nsHttpConnectionMgr::TryDispatchTransact
 
     RefPtr<nsHttpConnection> unusedSpdyPersistentConnection;
 
     // step 0
     // look for existing spdy connection - that's always best because it is
     // essentially pipelining without head of line blocking
 
     if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) {
-        RefPtr<nsHttpConnection> conn = GetSpdyPreferredConn(ent);
+        RefPtr<nsHttpConnection> conn = GetSpdyActiveConn(ent);
         if (conn) {
             if ((caps & NS_HTTP_ALLOW_KEEPALIVE) || !conn->IsExperienced()) {
                 LOG(("   dispatch to spdy: [conn=%p]\n", conn.get()));
                 trans->RemoveDispatchedAsBlocking();  /* just in case */
                 nsresult rv = DispatchTransaction(ent, trans, conn);
                 NS_ENSURE_SUCCESS(rv, rv);
                 return NS_OK;
             }
@@ -1771,27 +1687,16 @@ nsHttpConnectionMgr::ProcessNewTransacti
 
     nsresult rv = NS_OK;
     nsHttpConnectionInfo *ci = trans->ConnectionInfo();
     MOZ_ASSERT(ci);
 
     nsConnectionEntry *ent =
         GetOrCreateConnectionEntry(ci, !!trans->TunnelProvider());
 
-    // SPDY coalescing of hostnames means we might redirect from this
-    // connection entry onto the preferred one.
-    nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
-    if (preferredEntry && (preferredEntry != ent)) {
-        LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
-             "redirected via coalescing from %s to %s\n", trans,
-             ent->mConnInfo->Origin(), preferredEntry->mConnInfo->Origin()));
-
-        ent = preferredEntry;
-    }
-
     ReportProxyTelemetry(ent);
 
     // Check if the transaction already has a sticky reference to a connection.
     // If so, then we can just use it directly by transferring its reference
     // to the new connection variable instead of searching for a new one
 
     nsAHttpConnection *wrappedConnection = trans->Connection();
     RefPtr<nsHttpConnection> conn;
@@ -1997,24 +1902,23 @@ nsHttpConnectionMgr::DispatchSpdyPending
     // Put the leftovers back in the pending queue and get rid of the
     // transactions we dispatched
     leftovers.SwapElements(pendingQ);
     leftovers.Clear();
 }
 
 // This function tries to dispatch the pending spdy transactions on
 // the connection entry sent in as an argument. It will do so on the
-// active spdy connection either in that same entry or in the
-// redirected 'preferred' entry for the same coalescing hash key if
-// coalescing is enabled.
+// active spdy connection either in that same entry or from the
+// coalescing hash table
 
 void
 nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
 {
-    nsHttpConnection *conn = GetSpdyPreferredConn(ent);
+    nsHttpConnection *conn = GetSpdyActiveConn(ent);
     if (!conn || !conn->CanDirectlyActivate()) {
         return;
     }
 
     DispatchSpdyPendingQ(ent->mUrgentStartQ, ent, conn);
     if (!conn->CanDirectlyActivate()) {
         return;
     }
@@ -2035,70 +1939,78 @@ nsHttpConnectionMgr::OnMsgProcessAllSpdy
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
     for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
         ProcessSpdyPendingQ(iter.Data());
     }
 }
 
+// Given a connection entry, return an active h2 connection
+// that can be directly activated or null
 nsHttpConnection *
-nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent)
+nsHttpConnectionMgr::GetSpdyActiveConn(nsConnectionEntry *ent)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     MOZ_ASSERT(ent);
 
-    nsConnectionEntry *preferred = GetSpdyPreferredEnt(ent);
-    // this entry is spdy-enabled if it is involved in a redirect
-    if (preferred) {
-        // all new connections for this entry will use spdy too
-        ent->mUsingSpdy = true;
-    } else {
-        preferred = ent;
-    }
-
-    if (!preferred->mUsingSpdy) {
-        return nullptr;
-    }
-
-    nsHttpConnection *rv = nullptr;
-    uint32_t activeLen = preferred->mActiveConns.Length();
+    nsHttpConnection *experienced = nullptr;
+    nsHttpConnection *noExperience = nullptr;
+    uint32_t activeLen = ent->mActiveConns.Length();
+    nsHttpConnectionInfo *ci = ent->mConnInfo;
     uint32_t index;
 
     // activeLen should generally be 1.. this is a setup race being resolved
     // take a conn who can activate and is experienced
     for (index = 0; index < activeLen; ++index) {
-        nsHttpConnection *tmp = preferred->mActiveConns[index];
-        if (tmp->CanDirectlyActivate() && tmp->IsExperienced()) {
-            rv = tmp;
-            break;
+        nsHttpConnection *tmp = ent->mActiveConns[index];
+        if (tmp->CanDirectlyActivate()) {
+            if (tmp->IsExperienced()) {
+                experienced = tmp;
+                break;
+            }
+            noExperience = tmp; // keep looking for a better option
         }
     }
 
-    // if that worked, cleanup anything else
-    if (rv) {
+    // if that worked, cleanup anything else and exit
+    if (experienced) {
         for (index = 0; index < activeLen; ++index) {
-            nsHttpConnection *tmp = preferred->mActiveConns[index];
+            nsHttpConnection *tmp = ent->mActiveConns[index];
             // in the case where there is a functional h2 session, drop the others
-            if (tmp != rv) {
+            if (tmp != experienced) {
                 tmp->DontReuse();
             }
         }
-        return rv;
+        LOG(("GetSpdyActiveConn() request for ent %p %s "
+             "found an active experienced connection %p in native connection entry\n",
+             ent, ci->HashKey().get(), experienced));
+        return experienced;
+    }
+
+    if (noExperience) {
+        LOG(("GetSpdyActiveConn() request for ent %p %s "
+             "found an active but inexperienced connection %p in native connection entry\n",
+             ent, ci->HashKey().get(), noExperience));
+        return noExperience;
     }
 
-    // take a conn who can activate and leave the rest alone
-    for (index = 0; index < activeLen; ++index) {
-        nsHttpConnection *tmp = preferred->mActiveConns[index];
-        if (tmp->CanDirectlyActivate()) {
-            rv = tmp;
-            break;
-        }
+    // there was no active spdy connection in the connection entry, but
+    // there might be one in the hash table for coalescing
+    nsHttpConnection *existingConn = FindCoalescableConnection(ent, false);
+    if (existingConn) {
+        LOG(("GetSpdyActiveConn() request for ent %p %s "
+             "found an active connection %p in the coalescing hashtable\n",
+             ent, ci->HashKey().get(), existingConn));
+        return existingConn;
     }
-    return rv;
+
+    LOG(("GetSpdyActiveConn() request for ent %p %s "
+         "did not find an active connection\n", ent, ci->HashKey().get()));
+    return nullptr;
 }
 
 //-----------------------------------------------------------------------------
 
 void
 nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -2169,16 +2081,17 @@ nsHttpConnectionMgr::OnMsgShutdown(int32
     if (mTimer) {
       mTimer->Cancel();
       mTimer = nullptr;
     }
     if (mTrafficTimer) {
       mTrafficTimer->Cancel();
       mTrafficTimer = nullptr;
     }
+    mCoalescingHash.Clear();
 
     // signal shutdown complete
     nsCOMPtr<nsIRunnable> runnable =
         new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
                       0, param);
     NS_DispatchToMainThread(runnable);
 }
 
@@ -2208,18 +2121,20 @@ void
 nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
 
     RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction *>(param);
     trans->SetPriority(priority);
 
-    nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(),
-                                                   nullptr, trans);
+    if (!trans->ConnectionInfo()) {
+        return;
+    }
+    nsConnectionEntry *ent = mCT.Get(trans->ConnectionInfo()->HashKey());
 
     if (ent) {
         int32_t caps = trans->Caps();
         nsTArray<RefPtr<PendingTransactionInfo>> *pendingQ = nullptr;
         if (caps & NS_HTTP_URGENT_START) {
             pendingQ = &(ent->mUrgentStartQ);
         } else {
             pendingQ =
@@ -2253,19 +2168,20 @@ nsHttpConnectionMgr::OnMsgCancelTransact
     // if the transaction owns a connection and the transaction is not done,
     // then ask the connection to close the transaction.  otherwise, close the
     // transaction directly (removing it from the pending queue first).
     //
     RefPtr<nsAHttpConnection> conn(trans->Connection());
     if (conn && !trans->IsDone()) {
         conn->CloseTransaction(trans, closeCode);
     } else {
-        nsConnectionEntry *ent =
-            LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans);
-
+        nsConnectionEntry *ent = nullptr;
+        if (trans->ConnectionInfo()) {
+            ent = mCT.Get(trans->ConnectionInfo()->HashKey());
+        }
         if (ent) {
             uint32_t caps = trans->Caps();
             int32_t transIndex;
             // We will abandon all half-open sockets belonging to the given
             // transaction.
             nsTArray<RefPtr<PendingTransactionInfo>> *infoArray;
             RefPtr<PendingTransactionInfo> pendingTransInfo;
             if (caps & NS_HTTP_URGENT_START) {
@@ -2597,18 +2513,19 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
     nsHttpConnection *conn = static_cast<nsHttpConnection *>(param);
 
     //
     // 1) remove the connection from the active list
     // 2) if keep-alive, add connection to idle list
     // 3) post event to process the pending transaction queue
     //
 
-    nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
-                                                   conn, nullptr);
+    nsConnectionEntry *ent = conn->ConnectionInfo() ?
+        mCT.Get(conn->ConnectionInfo()->HashKey()) : nullptr;
+
     if (!ent) {
         // this can happen if the connection is made outside of the
         // connection manager and is being "reclaimed" for use with
         // future transactions. HTTP/2 tunnels work like this.
         ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true);
         LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
              "forced new hash entry %s\n",
              conn, conn->ConnectionInfo()->HashKey().get()));
@@ -2735,17 +2652,16 @@ nsHttpConnectionMgr::OnMsgUpdateParam(in
         NS_NOTREACHED("unexpected parameter name");
     }
 }
 
 // nsHttpConnectionMgr::nsConnectionEntry
 nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
 {
     MOZ_COUNT_DTOR(nsConnectionEntry);
-    gHttpHandler->ConnMgr()->RemovePreferredHash(this);
 }
 
 // Read Timeout Tick handlers
 
 void
 nsHttpConnectionMgr::ActivateTimeoutTick()
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -2962,23 +2878,16 @@ nsHttpConnectionMgr::OnMsgSpeculativeCon
     SpeculativeConnectArgs *args = static_cast<SpeculativeConnectArgs *>(param);
 
     LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
          args->mTrans->ConnectionInfo()->HashKey().get()));
 
     nsConnectionEntry *ent =
         GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);
 
-    // If spdy has previously made a preferred entry for this host via
-    // the ip pooling rules. If so, connect to the preferred host instead of
-    // the one directly passed in here.
-    nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
-    if (preferredEntry)
-        ent = preferredEntry;
-
     uint32_t parallelSpeculativeConnectLimit =
         gHttpHandler->ParallelSpeculativeConnectLimit();
     bool ignoreIdle = false;
     bool isFromPredictor = false;
     bool allow1918 = false;
 
     if (args->mOverridesOK) {
         parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
@@ -3568,16 +3477,43 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
                 }
             }
         }
     }
 
     return rv;
 }
 
+// register a connection to receive CanJoinConnection() for particular
+// origin keys
+void
+nsHttpConnectionMgr::RegisterOriginCoalescingKey(nsHttpConnection *conn,
+                                                 const nsACString &host,
+                                                 int32_t port)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    nsHttpConnectionInfo *ci = conn ? conn->ConnectionInfo() : nullptr;
+    if (!ci || !conn->CanDirectlyActivate()) {
+        return;
+    }
+
+    nsCString newKey;
+    BuildOriginFrameHashKey(newKey, ci, host, port);
+    nsTArray<nsWeakPtr> *listOfWeakConns =  mCoalescingHash.Get(newKey);
+    if (!listOfWeakConns) {
+        listOfWeakConns = new nsTArray<nsWeakPtr>(1);
+        mCoalescingHash.Put(newKey, listOfWeakConns);
+    }
+    listOfWeakConns->AppendElement(do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn)));
+
+    LOG(("nsHttpConnectionMgr::RegisterOriginCoalescingKey "
+         "Established New Coalescing Key %s to %p %s\n",
+         newKey.get(), conn, ci->HashKey().get()));
+}
+
 // method for nsITransportEventSink
 NS_IMETHODIMP
 nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
                                                          nsresult status,
                                                          int64_t progress,
                                                          int64_t progressMax)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -3645,17 +3581,17 @@ nsHttpConnectionMgr::nsHalfOpenSocket::O
                 } else {
                     newKey->AppendLiteral("~.:");
                 }
                 newKey->AppendInt(mEnt->mConnInfo->OriginPort());
                 newKey->AppendLiteral("/[");
                 nsAutoCString suffix;
                 mEnt->mConnInfo->GetOriginAttributes().CreateSuffix(suffix);
                 newKey->Append(suffix);
-                newKey->Append(']');
+                newKey->AppendLiteral("]viaDNS");
                 LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus "
                      "STATUS_CONNECTING_TO Established New Coalescing Key # %d for host "
                      "%s [%s]", i, mEnt->mConnInfo->Origin(), newKey->get()));
             }
             gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
         }
     }
 
@@ -3746,40 +3682,45 @@ already_AddRefed<nsHttpConnection>
 ConnectionHandle::TakeHttpConnection()
 {
     // return our connection object to the caller and clear it internally
     // do not drop our reference - the caller now owns it.
     MOZ_ASSERT(mConn);
     return mConn.forget();
 }
 
+already_AddRefed<nsHttpConnection>
+ConnectionHandle::HttpConnection()
+{
+    RefPtr<nsHttpConnection> rv(mConn);
+    return rv.forget();
+}
 
 // nsConnectionEntry
 
 nsHttpConnectionMgr::
 nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
     : mConnInfo(ci)
     , mUsingSpdy(false)
-    , mInPreferredHash(false)
     , mPreferIPv4(false)
     , mPreferIPv6(false)
     , mUsedForConnection(false)
 {
     MOZ_COUNT_CTOR(nsConnectionEntry);
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
 {
     if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
         return true;
     }
 
     return gHttpHandler->ConnMgr()->
-        GetSpdyPreferredConn(this) ? true : false;
+        GetSpdyActiveConn(this) ? true : false;
 }
 
 bool
 nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams> *aArg)
 {
     for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
         nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
 
@@ -3823,19 +3764,20 @@ nsHttpConnectionMgr::GetConnectionData(n
 
     return true;
 }
 
 void
 nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo *ci)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
-    nsConnectionEntry *ent = LookupConnectionEntry(ci, nullptr, nullptr);
-    if (ent)
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+    if (ent) {
         ent->ResetIPFamilyPreference();
+    }
 }
 
 uint32_t
 nsHttpConnectionMgr::
 nsConnectionEntry::UnconnectedHalfOpens()
 {
     uint32_t unconnectedHalfOpens = 0;
     for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
@@ -4016,17 +3958,17 @@ nsHttpConnectionMgr::MoveToWildCardConnE
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     MOZ_ASSERT(specificCI->UsingHttpsProxy());
 
     LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
          "change CI from %s to %s\n", proxyConn, specificCI->HashKey().get(),
          wildCardCI->HashKey().get()));
 
-    nsConnectionEntry *ent = LookupConnectionEntry(specificCI, proxyConn, nullptr);
+    nsConnectionEntry *ent = mCT.Get(specificCI->HashKey());
     LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy %d)\n",
          proxyConn, ent, ent ? ent->mUsingSpdy : 0));
 
     if (!ent || !ent->mUsingSpdy) {
         return;
     }
 
     nsConnectionEntry *wcEnt = GetOrCreateConnectionEntry(wildCardCI, true);
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -239,32 +239,26 @@ private:
         // connection complete
         uint32_t UnconnectedHalfOpens();
 
         // Remove a particular half open socket from the mHalfOpens array
         void RemoveHalfOpen(nsHalfOpenSocket *);
 
         // Spdy sometimes resolves the address in the socket manager in order
         // to re-coalesce sharded HTTP hosts. The dotted decimal address is
-        // combined with the Anonymous flag from the connection information
+        // combined with the Anonymous flag and OA from the connection information
         // to build the hash key for hosts in the same ip pool.
         //
-        // When a set of hosts are coalesced together one of them is marked
-        // mSpdyPreferred. The mapping is maintained in the connection mananger
-        // mSpdyPreferred hash.
-        //
         nsTArray<nsCString> mCoalescingKeys;
 
         // To have the UsingSpdy flag means some host with the same connection
         // entry has done NPN=spdy/* at some point. It does not mean every
         // connection is currently using spdy.
         bool mUsingSpdy : 1;
 
-        bool mInPreferredHash : 1;
-
         // Flags to remember our happy-eyeballs decision.
         // Reset only by Ctrl-F5 reload.
         // True when we've first connected an IPv4 server for this host,
         // initially false.
         bool mPreferIPv4 : 1;
         // True when we've first connected an IPv6 server for this host,
         // initially false.
         bool mPreferIPv6 : 1;
@@ -302,16 +296,17 @@ private:
             uint32_t maxCount = 0);
 
         // Remove the empty pendingQ in |mPendingTransactionTable|.
         void RemoveEmptyPendingQ();
     };
 
 public:
     static nsAHttpConnection *MakeConnectionHandle(nsHttpConnection *aWrapped);
+    void RegisterOriginCoalescingKey(nsHttpConnection *, const nsACString &host, int32_t port);
 
 private:
 
     // nsHalfOpenSocket is used to hold the state of an opening TCP socket
     // while we wait for it to establish and bind it to a connection
 
     class nsHalfOpenSocket final : public nsIOutputStreamCallback,
                                    public nsITransportEventSink,
@@ -513,26 +508,24 @@ private:
                                  PendingTransactionInfo *pendingTransInfo);
 
     nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *,
                                                   bool allowWildCard);
 
     MOZ_MUST_USE nsresult MakeNewConnection(nsConnectionEntry *ent,
                                             PendingTransactionInfo *pendingTransInfo);
 
-    // Manage the preferred spdy connection entry for this address
-    nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry);
-    nsConnectionEntry *LookupPreferredHash(nsConnectionEntry *ent);
-    void               StorePreferredHash(nsConnectionEntry *ent);
-    void               RemovePreferredHash(nsConnectionEntry *ent);
-    nsHttpConnection  *GetSpdyPreferredConn(nsConnectionEntry *ent);
-    nsDataHashtable<nsCStringHashKey, nsConnectionEntry *>   mSpdyPreferredHash;
-    nsConnectionEntry *LookupConnectionEntry(nsHttpConnectionInfo *ci,
-                                             nsHttpConnection *conn,
-                                             nsHttpTransaction *trans);
+    // Manage h2 connection coalescing
+    // The hashtable contains arrays of weak pointers to nsHttpConnections
+    nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > mCoalescingHash;
+
+    nsHttpConnection *FindCoalescableConnection(nsConnectionEntry *ent, bool justKidding);
+    nsHttpConnection *FindCoalescableConnectionByHashKey(nsConnectionEntry *ent, const nsCString &key, bool justKidding);
+    void UpdateCoalescingForNewConn(nsHttpConnection *conn, nsConnectionEntry *ent);
+    nsHttpConnection *GetSpdyActiveConn(nsConnectionEntry *ent);
 
     void               ProcessSpdyPendingQ(nsConnectionEntry *ent);
     void               DispatchSpdyPendingQ(nsTArray<RefPtr<PendingTransactionInfo>> &pendingQ,
                                             nsConnectionEntry *ent,
                                             nsHttpConnection *conn);
     // used to marshall events to the socket transport thread.
     MOZ_MUST_USE nsresult PostEvent(nsConnEventHandler  handler,
                                     int32_t             iparam = 0,
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -210,16 +210,17 @@ nsHttpHandler::nsHttpHandler()
     , mHttp2Enabled(true)
     , mUseH2Deps(true)
     , mEnforceHttp2TlsProfile(true)
     , mCoalesceSpdy(true)
     , mSpdyPersistentSettings(false)
     , mAllowPush(true)
     , mEnableAltSvc(false)
     , mEnableAltSvcOE(false)
+    , mEnableOriginExtension(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)
@@ -1376,16 +1377,23 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
 
     if (PREF_CHANGED(HTTP_PREF("altsvc.oe"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("altsvc.oe"),
                                 &cVar);
         if (NS_SUCCEEDED(rv))
             mEnableAltSvcOE = cVar;
     }
 
+    if (PREF_CHANGED(HTTP_PREF("originextension"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("originextension"),
+                                &cVar);
+        if (NS_SUCCEEDED(rv))
+            mEnableOriginExtension = cVar;
+    }
+
     if (PREF_CHANGED(HTTP_PREF("spdy.push-allowance"))) {
         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)));
         }
     }
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -118,16 +118,17 @@ public:
     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; }
+    bool           AllowOriginExtension() { return mEnableOriginExtension; }
     uint32_t       ConnectTimeout()  { return mConnectTimeout; }
     uint32_t       ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
     bool           CriticalRequestPrioritization() { return mCriticalRequestPrioritization; }
     bool           UseH2Deps() { return mUseH2Deps; }
 
     uint32_t       MaxConnectionsPerOrigin() { return mMaxPersistentConnectionsPerServer; }
     bool           UseRequestTokenBucket() { return mRequestTokenBucketEnabled; }
     uint16_t       RequestTokenBucketMinParallelism() { return mRequestTokenBucketMinParallelism; }
@@ -497,16 +498,17 @@ private:
     uint32_t           mHttp2Enabled : 1;
     uint32_t           mUseH2Deps : 1;
     uint32_t           mEnforceHttp2TlsProfile : 1;
     uint32_t           mCoalesceSpdy : 1;
     uint32_t           mSpdyPersistentSettings : 1;
     uint32_t           mAllowPush : 1;
     uint32_t           mEnableAltSvc : 1;
     uint32_t           mEnableAltSvcOE : 1;
+    uint32_t           mEnableOriginExtension : 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;