Bug 1337791 - http/2 origin frame extension 2/3 r=nwgh draft
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 03 Apr 2017 17:23:55 -0400
changeset 555240 d3605a438c7397726c100b26db082adb87eeac10
parent 555239 0078ede4db4f9b681e9c080dfb7b3c410f58db1d
child 555241 4693886d91800b807f40523f1459d0cbbcf20d45
push id52202
push userbmo:mcmanus@ducksong.com
push dateMon, 03 Apr 2017 21:42:06 +0000
reviewersnwgh
bugs1337791
milestone55.0a1
Bug 1337791 - http/2 origin frame extension 2/3 r=nwgh MozReview-Commit-ID: Ez6F7agN1r3
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,137 @@ 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()));
+}
+
+void
+Http2Session::InitializeOriginSet()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("Http2Session::InitializeOriginSet %d\n", mOriginFrameActivated));
+  if (!mOriginFrameActivated) {
+    mOriginFrameActivated = true;
+    // The SNI is implicitly in the origin set - but we don't implement that
+    // as coalescing, so you don't need to explicitly put it here
+  }
+}
+
+// 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 & 0xF0) {
+    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;
+  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));
+      self->ResetDownstreamState();
+      return NS_OK;
+    }
+
+    nsAutoCString originString;
+    RefPtr<nsStandardURL> originURL;
+    originString.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2, originLen);
+    if (NS_FAILED(Http2Stream::MakeOriginURL(originString, originURL))){
+      LOG3(("Http2Session::RecvOrigin %p origin frame string %s failed to parse\n", self, originString.get()));
+      self->ResetDownstreamState();
+      return NS_OK;
+    }
+
+    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));
+      self->ResetDownstreamState();
+      return NS_OK;
+    }
+
+    self->InitializeOriginSet();
+    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));
+    }
+    offset += originLen + 2;
+  }
+  // init in case the loop did not run and we didn't hit early return on first iter
+  self->InitializeOriginSet();
+
+  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 +3963,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 +4129,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,21 @@ 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;
 
+  void InitializeOriginSet();
+  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
@@ -554,123 +554,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 +589,241 @@ 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) { // shurnk 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 match 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
+        // 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(("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 +951,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 +1365,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 +1683,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 +1898,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 +1935,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 +2077,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 +2117,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 +2164,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 +2509,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 +2648,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 +2874,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 +3473,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 +3577,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 +3678,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 +3760,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 +3954,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)
@@ -1370,16 +1371,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
@@ -119,16 +119,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; }
@@ -498,16 +499,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;