bug 1003448 - HTTP/2 Alternate Service and Opportunistic Security [2/2 necko] r=hurley
☠☠ backed out by 916f2837201c ☠ ☠
authorPatrick McManus <mcmanus@ducksong.com>
Thu, 21 Aug 2014 10:50:17 -0400
changeset 208790 61f98b1d29f99d995c6482347cff94fe5f7e59af
parent 208789 8e947d1636f10aa16abfe578f96405325dda730c
child 208791 527003361b21f2919be95f0d0d3973ce8583a4e3
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewershurley
bugs1003448
milestone35.0a1
bug 1003448 - HTTP/2 Alternate Service and Opportunistic Security [2/2 necko] r=hurley
modules/libpref/init/all.js
netwerk/base/public/nsISpeculativeConnect.idl
netwerk/base/src/Predictor.cpp
netwerk/protocol/http/AlternateServices.cpp
netwerk/protocol/http/AlternateServices.h
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/Http2Session.h
netwerk/protocol/http/NullHttpTransaction.cpp
netwerk/protocol/http/NullHttpTransaction.h
netwerk/protocol/http/SpdySession3.cpp
netwerk/protocol/http/SpdySession31.cpp
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttp.cpp
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpAtomList.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionInfo.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpRequestHead.cpp
netwerk/protocol/http/nsHttpRequestHead.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
netwerk/test/unit/test_http2.js
toolkit/components/telemetry/Histograms.json
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1237,16 +1237,26 @@ pref("network.http.spdy.timeout", 180);
 pref("network.http.spdy.coalesce-hostnames", true);
 pref("network.http.spdy.persistent-settings", false);
 pref("network.http.spdy.ping-threshold", 58);
 pref("network.http.spdy.ping-timeout", 8);
 pref("network.http.spdy.send-buffer-size", 131072);
 pref("network.http.spdy.allow-push", true);
 pref("network.http.spdy.push-allowance", 131072);
 
+// alt-svc allows separation of transport routing from
+// the origin host without using a proxy.
+#ifdef RELEASE_BUILD
+pref("network.http.altsvc.enabled", false);
+pref("network.http.altsvc.oe", false);
+#else
+pref("network.http.altsvc.enabled", true);
+pref("network.http.altsvc.oe", true);
+#endif
+
 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", 100);
 pref("network.http.pacing.requests.burst", 32);
 
 // TCP Keepalive config for HTTP connections.
--- a/netwerk/base/public/nsISpeculativeConnect.idl
+++ b/netwerk/base/public/nsISpeculativeConnect.idl
@@ -30,17 +30,17 @@ interface nsISpeculativeConnect : nsISup
 
 };
 
 /**
  * This is used to override the default values for various values (documented
  * inline) to determine whether or not to actually make a speculative
  * connection.
  */
-[builtinclass, uuid(a9cdd875-2ef8-4d53-95d6-e4e18f65e0db)]
+[builtinclass, uuid(f6a0d1e5-369f-4abc-81ae-d370d36e4006)]
 interface nsISpeculativeConnectionOverrider : nsISupports
 {
     /**
      * Used to determine the maximum number of unused speculative connections
      * we will have open for a host at any one time
      */
     [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit;
 
@@ -58,9 +58,14 @@ interface nsISpeculativeConnectionOverri
      */
     [infallible] readonly attribute boolean ignoreIdle;
 
     /*
      * Used by the Predictor to gather telemetry data on speculative connection
      * usage.
      */
     [infallible] readonly attribute boolean isFromPredictor;
+
+    /**
+     * by default speculative connections are not made to RFC 1918 addresses
+     */
+    [infallible] readonly attribute boolean allow1918;
 };
--- a/netwerk/base/src/Predictor.cpp
+++ b/netwerk/base/src/Predictor.cpp
@@ -390,16 +390,23 @@ Predictor::GetParallelSpeculativeConnect
 
 NS_IMETHODIMP
 Predictor::GetIsFromPredictor(bool *isFromPredictor)
 {
   *isFromPredictor = true;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+Predictor::GetAllow1918(bool *allow1918)
+{
+  *allow1918 = false;
+  return NS_OK;
+}
+
 // Predictor::nsIInterfaceRequestor
 
 NS_IMETHODIMP
 Predictor::GetInterface(const nsIID &iid, void **result)
 {
   return QueryInterface(iid, result);
 }
 
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -0,0 +1,452 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HttpLog.h"
+
+#include "AlternateServices.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpHandler.h"
+#include "nsThreadUtils.h"
+#include "NullHttpTransaction.h"
+#include "nsISSLStatusProvider.h"
+#include "nsISSLStatus.h"
+#include "nsISSLSocketControl.h"
+
+namespace mozilla {
+namespace net {
+
+AltSvcMapping::AltSvcMapping(const nsACString &originScheme,
+                             const nsACString &originHost,
+                             int32_t originPort,
+                             const nsACString &username,
+                             bool privateBrowsing,
+                             uint32_t expiresAt,
+                             const nsACString &alternateHost,
+                             int32_t alternatePort,
+                             const nsACString &npnToken)
+  : mAlternateHost(alternateHost)
+  , mAlternatePort(alternatePort)
+  , mOriginHost(originHost)
+  , mOriginPort(originPort)
+  , mUsername(username)
+  , mPrivate(privateBrowsing)
+  , mExpiresAt(expiresAt)
+  , mValidated(false)
+  , mRunning(false)
+  , mNPNToken(npnToken)
+{
+  mHttps = originScheme.Equals("https");
+
+  if (mAlternatePort == -1) {
+    mAlternatePort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+  }
+  if (mOriginPort == -1) {
+    mOriginPort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+  }
+
+  LOG(("AltSvcMapping ctor %p %s://%s:%d to %s:%d\n", this,
+       nsCString(originScheme).get(), mOriginHost.get(), mOriginPort,
+       mAlternateHost.get(), mAlternatePort));
+
+  if (mAlternateHost.IsEmpty()) {
+    mAlternateHost = mOriginHost;
+  }
+  MakeHashKey(mHashKey, originScheme, mOriginHost, mOriginPort, mPrivate);
+}
+
+void
+AltSvcMapping::MakeHashKey(nsCString &outKey,
+                           const nsACString &originScheme,
+                           const nsACString &originHost,
+                           int32_t originPort,
+                           bool privateBrowsing)
+{
+  if (originPort == -1) {
+    bool isHttps = originScheme.Equals("https");
+    originPort = isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
+  }
+
+  outKey.Append(originScheme);
+  outKey.Append(':');
+  outKey.Append(originHost);
+  outKey.Append(':');
+  outKey.AppendInt(originPort);
+  outKey.Append(':');
+  outKey.Append(privateBrowsing ? 'P' : '.');
+}
+
+int32_t
+AltSvcMapping::TTL()
+{
+  return mExpiresAt - NowInSeconds();
+}
+
+void
+AltSvcMapping::SetExpired()
+{
+  mExpiresAt = NowInSeconds() - 1;
+}
+
+bool
+AltSvcMapping::RouteEquals(AltSvcMapping *map)
+{
+  MOZ_ASSERT(map->mHashKey.Equals(mHashKey));
+  return mAlternateHost.Equals(map->mAlternateHost) &&
+    (mAlternatePort == map->mAlternatePort) &&
+    mNPNToken.Equals(map->mNPNToken);
+
+  return false;
+}
+
+void
+AltSvcMapping::GetConnectionInfo(nsHttpConnectionInfo **outCI,
+                                 nsProxyInfo *pi)
+{
+  nsRefPtr<nsHttpConnectionInfo> ci =
+    new nsHttpConnectionInfo(mAlternateHost, mAlternatePort, mNPNToken,
+                             mUsername, pi, mOriginHost, mOriginPort);
+  if (!mHttps) {
+    ci->SetRelaxed(true);
+  }
+  ci->SetPrivate(mPrivate);
+  ci.forget(outCI);
+}
+
+// This is the asynchronous null transaction used to validate
+// an alt-svc advertisement
+class AltSvcTransaction MOZ_FINAL : public NullHttpTransaction
+{
+public:
+    AltSvcTransaction(AltSvcMapping *map,
+                      nsHttpConnectionInfo *ci,
+                      nsIInterfaceRequestor *callbacks,
+                      uint32_t caps)
+    : NullHttpTransaction(ci, callbacks, caps)
+    , mMapping(map)
+    , mRunning(false)
+    , mTriedToValidate(false)
+    , mTriedToWrite(false)
+  {
+    MOZ_ASSERT(mMapping);
+    LOG(("AltSvcTransaction ctor %p map %p [%s -> %s]",
+         this, map, map->OriginHost().get(), map->AlternateHost().get()));
+  }
+
+  ~AltSvcTransaction()
+  {
+    LOG(("AltSvcTransaction dtor %p map %p running %d",
+         this, mMapping.get(), mRunning));
+
+    if (mRunning) {
+      MOZ_ASSERT(mMapping->IsRunning());
+      MaybeValidate(NS_OK);
+    }
+    if (!mMapping->Validated()) {
+      // try again later
+      mMapping->SetExpiresAt(NowInSeconds() + 2);
+    }
+    LOG(("AltSvcTransaction dtor %p map %p validated %d [%s]",
+         this, mMapping.get(), mMapping->Validated(),
+         mMapping->HashKey().get()));
+    mMapping->SetRunning(false);
+  }
+
+  void StartTransaction()
+  {
+    LOG(("AltSvcTransaction::StartTransaction() %p", this));
+
+    MOZ_ASSERT(!mRunning);
+    MOZ_ASSERT(!mMapping->IsRunning());
+    mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
+    mRunning = true;
+    mMapping->SetRunning(true);
+  }
+
+  void MaybeValidate(nsresult reason)
+  {
+    if (mTriedToValidate) {
+      return;
+    }
+    mTriedToValidate = true;
+
+    LOG(("AltSvcTransaction::MaybeValidate() %p reason=%x running=%d conn=%p write=%d",
+         this, reason, mRunning, mConnection.get(), mTriedToWrite));
+
+    if (mTriedToWrite && reason == NS_BASE_STREAM_CLOSED) {
+      // The normal course of events is to cause the transaction to fail with CLOSED
+      // on a write - so that's a success that means the HTTP/2 session is setup.
+      reason = NS_OK;
+    }
+
+    if (NS_FAILED(reason) || !mRunning || !mConnection) {
+      LOG(("AltSvcTransaction::MaybeValidate %p Failed due to precondition", this));
+      return;
+    }
+
+    // insist on spdy/3* or >= http/2
+    uint32_t version = mConnection->Version();
+    LOG(("AltSvcTransaction::MaybeValidate() %p version %d\n", this, version));
+    if ((version < HTTP_VERSION_2) &&
+        (version != SPDY_VERSION_31) && (version != SPDY_VERSION_3)) {
+      LOG(("AltSvcTransaction::MaybeValidate %p Failed due to protocol version", this));
+      return;
+    }
+
+    nsCOMPtr<nsISupports> secInfo;
+    mConnection->GetSecurityInfo(getter_AddRefs(secInfo));
+    nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
+    bool bypassAuth = false;
+
+    if (!socketControl ||
+        NS_FAILED(socketControl->GetBypassAuthentication(&bypassAuth))) {
+      bypassAuth = false;
+    }
+
+    LOG(("AltSvcTransaction::MaybeValidate() %p socketControl=%p bypass=%d",
+         this, socketControl.get(), bypassAuth));
+
+    if (bypassAuth) {
+      LOG(("AltSvcTransaction::MaybeValidate() %p "
+           "validating alternate service because relaxed", this));
+      mMapping->SetValidated(true);
+      return;
+    }
+
+    if (socketControl->GetFailedVerification()) {
+      LOG(("AltSvcTransaction::MaybeValidate() %p "
+           "not validated due to auth error", this));
+      return;
+    }
+
+    LOG(("AltSvcTransaction::MaybeValidate() %p "
+         "validating alternate service with auth check", this));
+    mMapping->SetValidated(true);
+  }
+
+  void Close(nsresult reason) MOZ_OVERRIDE
+  {
+    LOG(("AltSvcTransaction::Close() %p reason=%x running %d",
+         this, reason, mRunning));
+
+    MaybeValidate(reason);
+    if (!mMapping->Validated() && mConnection) {
+      mConnection->DontReuse();
+    }
+    NullHttpTransaction::Close(reason);
+  }
+
+  nsresult ReadSegments(nsAHttpSegmentReader *reader,
+                        uint32_t count, uint32_t *countRead) MOZ_OVERRIDE
+  {
+    LOG(("AltSvcTransaction::ReadSegements() %p\n"));
+    mTriedToWrite = true;
+    return NullHttpTransaction::ReadSegments(reader, count, countRead);
+  }
+
+private:
+  nsRefPtr<AltSvcMapping> mMapping;
+  uint32_t                mRunning : 1;
+  uint32_t                mTriedToValidate : 1;
+  uint32_t                mTriedToWrite : 1;
+};
+
+void
+AltSvcCache::UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
+                                     nsIInterfaceRequestor *aCallbacks,
+                                     uint32_t caps)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  AltSvcMapping *existing = mHash.GetWeak(map->mHashKey);
+  LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p existing %p %s",
+       this, map, existing, map->AlternateHost().get()));
+
+  if (existing && (existing->TTL() <= 0)) {
+    LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p is expired",
+         this, map));
+    existing = nullptr;
+    mHash.Remove(map->mHashKey);
+  }
+
+  if (existing && existing->mValidated) {
+    if (existing->RouteEquals(map)) {
+      // update expires
+      LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p updates ttl of %p\n",
+           this, map, existing));
+      existing->SetExpiresAt(map->GetExpiresAt());
+      return;
+    }
+
+    LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p overwrites %p\n",
+         this, map, existing));
+    existing = nullptr;
+    mHash.Remove(map->mHashKey);
+  }
+
+  if (existing) {
+    LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p ignored because %p "
+         "still in progress\n", this, map, existing));
+    return;
+  }
+
+  mHash.Put(map->mHashKey, map);
+
+  nsRefPtr<nsHttpConnectionInfo> ci;
+  map->GetConnectionInfo(getter_AddRefs(ci), pi);
+  caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+
+  nsCOMPtr<nsIInterfaceRequestor> callbacks = new AltSvcOverride(aCallbacks);
+
+  nsRefPtr<AltSvcTransaction> nullTransaction =
+    new AltSvcTransaction(map, ci, aCallbacks, caps);
+  nullTransaction->StartTransaction();
+  gHttpHandler->ConnMgr()->SpeculativeConnect(ci, callbacks, caps, nullTransaction);
+}
+
+AltSvcMapping *
+AltSvcCache::GetAltServiceMapping(const nsACString &scheme, const nsACString &host,
+                                  int32_t port, bool privateBrowsing)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!gHttpHandler->AllowAltSvc()) {
+    return nullptr;
+  }
+  if (!gHttpHandler->AllowAltSvcOE() && scheme.Equals(NS_LITERAL_CSTRING("http"))) {
+    return nullptr;
+  }
+
+  nsAutoCString key;
+  AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing);
+  AltSvcMapping *existing = mHash.GetWeak(key);
+  LOG(("AltSvcCache::GetAltServiceMapping %p key=%s "
+       "existing=%p validated=%d running=%d ttl=%d",
+       this, key.get(), existing, existing ? existing->mValidated : 0,
+       existing ? existing->mRunning : 0,
+       existing ? existing->TTL() : 0));
+  if (existing && (existing->TTL() <= 0)) {
+    LOG(("AltSvcCache::GetAltServiceMapping %p map %p is expired", this, existing));
+    mHash.Remove(existing->mHashKey);
+    existing = nullptr;
+  }
+  if (existing && existing->mValidated)
+    return existing;
+  return nullptr;
+}
+
+class ProxyClearHostMapping : public nsRunnable {
+public:
+  explicit ProxyClearHostMapping(const nsACString &host, int32_t port)
+    : mHost(host)
+    , mPort(port)
+    {}
+
+    NS_IMETHOD Run()
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+      gHttpHandler->ConnMgr()->ClearHostMapping(mHost, mPort);
+      return NS_OK;
+    }
+private:
+    nsCString mHost;
+    int32_t mPort;
+};
+
+void
+AltSvcCache::ClearHostMapping(const nsACString &host, int32_t port)
+{
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> event = new ProxyClearHostMapping(host, port);
+    if (event) {
+      NS_DispatchToMainThread(event);
+    }
+    return;
+  }
+
+  nsAutoCString key;
+
+  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, true);
+  AltSvcMapping *existing = mHash.GetWeak(key);
+  if (existing) {
+    existing->SetExpired();
+  }
+
+  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, true);
+  existing = mHash.GetWeak(key);
+  if (existing) {
+    existing->SetExpired();
+  }
+
+  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, false);
+  existing = mHash.GetWeak(key);
+  if (existing) {
+    existing->SetExpired();
+  }
+
+  AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, false);
+  existing = mHash.GetWeak(key);
+  if (existing) {
+    existing->SetExpired();
+  }
+}
+
+void
+AltSvcCache::ClearAltServiceMappings()
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    mHash.Clear();
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetInterface(const nsIID &iid, void **result)
+{
+  if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
+    return NS_OK;
+  }
+  return mCallbacks->GetInterface(iid, result);
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIgnoreIdle(bool *ignoreIdle)
+{
+  *ignoreIdle = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIgnorePossibleSpdyConnections(bool *ignorePossibleSpdyConnections)
+{
+  *ignorePossibleSpdyConnections = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetParallelSpeculativeConnectLimit(
+  uint32_t *parallelSpeculativeConnectLimit)
+{
+  *parallelSpeculativeConnectLimit = 32;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetIsFromPredictor(bool *isFromPredictor)
+{
+  *isFromPredictor = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AltSvcOverride::GetAllow1918(bool *allow)
+{
+  // normally we don't do speculative connects to 1918.. and we use
+  // speculative connects for the mapping validation, so override
+  // that default here for alt-svc
+  *allow = true;
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(AltSvcOverride, nsIInterfaceRequestor, nsISpeculativeConnectionOverrider)
+
+} // namespace mozilla::net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+Alt-Svc allows separation of transport routing from the origin host without
+using a proxy. See https://httpwg.github.io/http-extensions/alt-svc.html
+
+ Nice To Have Future Enhancements::
+ * flush on network change event when we have an indicator
+ * use established https channel for http instead separate of conninfo hash
+ * pin via http-tls header
+ * clear based on origin when a random fail happens not just 421
+ * upon establishment of channel, cancel and retry trans that have not yet written anything
+ * persistent storage (including private browsing filter)
+ * memory reporter for cache, but this is rather tiny
+*/
+
+#ifndef mozilla_net_AlternateServices_h
+#define mozilla_net_AlternateServices_h
+
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsISpeculativeConnect.h"
+
+class nsProxyInfo;
+
+namespace mozilla { namespace net {
+
+class nsHttpConnectionInfo;
+
+class AltSvcMapping
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcMapping)
+  friend class AltSvcCache;
+
+public:
+  AltSvcMapping(const nsACString &originScheme,
+                const nsACString &originHost,
+                int32_t originPort,
+                const nsACString &username,
+                bool privateBrowsing,
+                uint32_t expiresAt,
+                const nsACString &alternateHost,
+                int32_t alternatePort,
+                const nsACString &npnToken);
+
+  const nsCString &AlternateHost() const { return mAlternateHost; }
+  const nsCString &OriginHost() const { return mOriginHost; }
+  const nsCString &HashKey() const { return mHashKey; }
+  uint32_t AlternatePort() const { return mAlternatePort; }
+  bool Validated() { return mValidated; }
+  void SetValidated(bool val) { mValidated = val; }
+  bool IsRunning() { return mRunning; }
+  void SetRunning(bool val) { mRunning = val; }
+  int32_t GetExpiresAt() { return mExpiresAt; }
+  void SetExpiresAt(int32_t val) { mExpiresAt = val; }
+  void SetExpired();
+  bool RouteEquals(AltSvcMapping *map);
+
+  void GetConnectionInfo(nsHttpConnectionInfo **outCI, nsProxyInfo *pi);
+  int32_t TTL();
+
+private:
+  virtual ~AltSvcMapping() {};
+  static void MakeHashKey(nsCString &outKey,
+                          const nsACString &originScheme,
+                          const nsACString &originHost,
+                          int32_t originPort,
+                          bool privateBrowsing);
+
+  nsCString mHashKey;
+
+  nsCString mAlternateHost;
+  int32_t mAlternatePort;
+
+  nsCString mOriginHost;
+  int32_t mOriginPort;
+
+  nsCString mUsername;
+  bool mPrivate;
+
+  uint32_t mExpiresAt;
+
+  bool mValidated;
+  bool mRunning;
+  bool mHttps;
+
+  nsCString mNPNToken;
+};
+
+class AltSvcOverride : public nsIInterfaceRequestor
+                     , public nsISpeculativeConnectionOverrider
+{
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+    NS_DECL_NSIINTERFACEREQUESTOR
+
+    AltSvcOverride(nsIInterfaceRequestor *aRequestor)
+      : mCallbacks(aRequestor) {}
+
+private:
+    virtual ~AltSvcOverride() {}
+    nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+class AltSvcCache
+{
+public:
+  void UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
+                               nsIInterfaceRequestor *, uint32_t caps); // main thread
+  AltSvcMapping *GetAltServiceMapping(const nsACString &scheme,
+                                      const nsACString &host,
+                                      int32_t port, bool pb);
+  void ClearAltServiceMappings();
+  void ClearHostMapping(const nsACString &host, int32_t port);
+
+private:
+  nsRefPtrHashtable<nsCStringHashKey, AltSvcMapping> mHash;
+};
+
+}} // namespace mozilla::net
+
+#endif // include guard
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -253,17 +253,18 @@ static Http2ControlFx sControlFunctions[
   Http2Session::RecvHeaders,
   Http2Session::RecvPriority,
   Http2Session::RecvRstStream,
   Http2Session::RecvSettings,
   Http2Session::RecvPushPromise,
   Http2Session::RecvPing,
   Http2Session::RecvGoAway,
   Http2Session::RecvWindowUpdate,
-  Http2Session::RecvContinuation
+  Http2Session::RecvContinuation,
+  Http2Session::RecvAltSvc // extension for type 0x0A
 };
 
 bool
 Http2Session::RoomForMoreConcurrent()
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   return (mConcurrent < mMaxConcurrent);
 }
@@ -437,17 +438,18 @@ Http2Session::AddStream(nsAHttpTransacti
     LOG3(("Http2Session::AddStream %p stream %p activated immediately.",
           this, stream));
     ActivateStream(stream);
   } else {
     LOG3(("Http2Session::AddStream %p stream %p queued.", this, stream));
     mQueuedStreams.Push(stream);
   }
 
-  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) {
+  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
+      !aHttpTransaction->IsNullTransaction()) {
     LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
           this, aHttpTransaction));
     DontReuse();
   }
 
   return true;
 }
 
@@ -1853,16 +1855,218 @@ Http2Session::RecvContinuation(Http2Sess
   // continued push promise
   if (self->mInputFrameFlags & kFlag_END_HEADERS) {
     self->mInputFrameFlags &= ~kFlag_END_HEADERS;
     self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
   }
   return RecvPushPromise(self);
 }
 
+class UpdateAltSvcEvent : public nsRunnable
+{
+public:
+  UpdateAltSvcEvent(const nsCString &host, const uint16_t port,
+                    const nsCString &npnToken, const uint32_t expires,
+                    const nsCString &aOrigin,
+                    nsHttpConnectionInfo *aCI,
+                    nsIInterfaceRequestor *callbacks)
+    : mHost(host)
+    , mPort(port)
+    , mNPNToken(npnToken)
+    , mExpires(expires)
+    , mOrigin(aOrigin)
+    , mCI(aCI)
+    , mCallbacks(callbacks)
+  {
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCString originScheme;
+    nsCString originHost;
+    int32_t originPort = -1;
+
+    nsCOMPtr<nsIURI> uri;
+    if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) {
+      LOG(("UpdateAltSvcEvent origin does not parse %s\n",
+           mOrigin.get()));
+      return NS_OK;
+    }
+    uri->GetScheme(originScheme);
+    uri->GetHost(originHost);
+    uri->GetPort(&originPort);
+
+    const char *username = mCI->Username();
+    const bool privateBrowsing = mCI->GetPrivate();
+
+    LOG(("UpdateAltSvcEvent location=%s:%u protocol=%s expires=%u "
+         "origin=%s://%s:%u user=%s private=%d", mHost.get(), mPort,
+         mNPNToken.get(), mExpires, originScheme.get(), originHost.get(),
+         originPort, username, privateBrowsing));
+    nsRefPtr<AltSvcMapping> mapping = new AltSvcMapping(
+      nsDependentCString(originScheme.get()),
+      nsDependentCString(originHost.get()),
+      originPort, nsDependentCString(username), privateBrowsing, mExpires,
+      mHost, mPort, mNPNToken);
+
+    nsProxyInfo *proxyInfo = mCI->ProxyInfo();
+    gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, mCallbacks, 0);
+    return NS_OK;
+  }
+
+private:
+  nsCString mHost;
+  uint16_t mPort;
+  nsCString mNPNToken;
+  uint32_t mExpires;
+  nsCString mOrigin;
+  nsRefPtr<nsHttpConnectionInfo> mCI;
+  nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+// defined as an http2 extension - alt-svc
+// defines receipt of frame type 0x0A.. See AlternateSevices.h
+nsresult
+Http2Session::RecvAltSvc(Http2Session *self)
+{
+  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC);
+  LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
+        self->mInputFrameFlags, self->mInputFrameID));
+
+  if (self->mInputFrameDataSize < 8) {
+    LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
+    RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
+  }
+
+  uint32_t maxAge =
+    PR_ntohl(*reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get() + kFrameHeaderBytes));
+  uint16_t portRoute =
+    PR_ntohs(*reinterpret_cast<uint16_t *>(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4));
+  uint8_t protoLen = self->mInputFrameBuffer.get()[kFrameHeaderBytes + 6];
+  LOG3(("Http2Session::RecvAltSvc %p maxAge=%d port=%d protoLen=%d", self,
+        maxAge, portRoute, protoLen));
+
+  if (self->mInputFrameDataSize < (8U + protoLen)) {
+    LOG3(("Http2Session::RecvAltSvc %p frame too small for protocol", self));
+    RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
+  }
+  nsAutoCString protocol;
+  protocol.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 7, protoLen);
+
+  uint32_t spdyIndex;
+  SpdyInformation *spdyInfo = gHttpHandler->SpdyInfo();
+  if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(protocol, &spdyIndex)) &&
+        spdyInfo->ProtocolEnabled(spdyIndex))) {
+    LOG3(("Http2Session::RecvAltSvc %p unknown protocol %s, ignoring", self,
+          protocol.BeginReading()));
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
+
+  uint8_t hostLen = self->mInputFrameBuffer.get()[kFrameHeaderBytes + 7 + protoLen];
+  if (self->mInputFrameDataSize < (8U + protoLen + hostLen)) {
+    LOG3(("Http2Session::RecvAltSvc %p frame too small for host", self));
+    RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
+  }
+
+  nsRefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
+  if (!self->mConnection || !ci) {
+    LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self,
+          self->mInputFrameID));
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
+
+  nsAutoCString hostRoute;
+  hostRoute.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 8 + protoLen, hostLen);
+
+  uint32_t originLen = self->mInputFrameDataSize - 8 - protoLen - hostLen;
+  nsAutoCString specifiedOrigin;
+  if (originLen) {
+    if (self->mInputFrameID) {
+      LOG3(("Http2Session::RecvAltSvc %p got frame w/origin on non zero stream", self));
+      self->ResetDownstreamState();
+      return NS_OK;
+    }
+    specifiedOrigin.Assign(
+      self->mInputFrameBuffer.get() + kFrameHeaderBytes + 8 + protoLen + hostLen,
+      originLen);
+
+    bool okToReroute = true;
+    nsCOMPtr<nsISupports> securityInfo;
+    self->mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+    nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
+    if (!ssl) {
+      okToReroute = false;
+    }
+
+    // a little off main thread origin parser. This is a non critical function because
+    // any alternate route created has to be verified anyhow
+    nsAutoCString specifiedOriginHost;
+    if (specifiedOrigin.EqualsIgnoreCase("https://", 8)) {
+      specifiedOriginHost.Assign(specifiedOrigin.get() + 8,
+                                 specifiedOrigin.Length() - 8);
+      if (ci->GetRelaxed()) {
+        // technically this is ok because it will still be confirmed before being used
+        // but let's not support it.
+        okToReroute = false;
+      }
+    } else if (specifiedOrigin.EqualsIgnoreCase("http://", 7)) {
+      specifiedOriginHost.Assign(specifiedOrigin.get() + 7,
+                                 specifiedOrigin.Length() - 7);
+    }
+
+    int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
+    if (colonOffset != kNotFound) {
+      specifiedOriginHost.Truncate(colonOffset);
+    }
+
+    if (okToReroute) {
+      ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
+    }
+    if (!okToReroute) {
+      LOG3(("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin %s",
+            self, specifiedOrigin.BeginReading()));
+      self->ResetDownstreamState();
+      return NS_OK;
+    }
+  } else {
+    // no origin specified in frame. We need to have an active pull stream to match
+    // this up to as if it were a response header.
+    if (!(self->mInputFrameID & 0x1) ||
+        NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
+        !self->mInputFrameDataStream->Transaction() ||
+        !self->mInputFrameDataStream->Transaction()->RequestHead()) {
+      LOG3(("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", self));
+      self->ResetDownstreamState();
+      return NS_OK;
+    }
+
+    specifiedOrigin.Assign(
+      self->mInputFrameDataStream->Transaction()->RequestHead()->Origin());
+  }
+
+  nsCOMPtr<nsISupports> callbacks;
+  self->mConnection->GetSecurityInfo(getter_AddRefs(callbacks));
+  nsCOMPtr<nsIInterfaceRequestor> irCallbacks = do_QueryInterface(callbacks);
+
+  nsRefPtr<UpdateAltSvcEvent> event = new UpdateAltSvcEvent(
+    hostRoute, portRoute, protocol, NowInSeconds() + maxAge,
+    specifiedOrigin, ci, irCallbacks);
+  NS_DispatchToMainThread(event);
+
+  LOG3(("Http2Session::RecvAltSvc %p processed location=%s:%u protocol=%s "
+        "maxAge=%u origin=%s", self, hostRoute.get(), portRoute,
+        protocol.get(), maxAge, specifiedOrigin.get()));
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsAHttpTransaction. It is expected that nsHttpConnection is the caller
 // of these methods
 //-----------------------------------------------------------------------------
 
 void
 Http2Session::OnTransportStatus(nsITransport* aTransport,
                                 nsresult aStatus, uint64_t aProgress)
@@ -1978,20 +2182,27 @@ Http2Session::ReadSegments(nsAHttpSegmen
     } else {
       rv = NS_BASE_STREAM_WOULD_BLOCK;
     }
     SetWriteCallbacks();
     return rv;
   }
 
   if (NS_FAILED(rv)) {
-    LOG3(("Http2Session::ReadSegments %p returning FAIL code %X",
+    LOG3(("Http2Session::ReadSegments %p may return FAIL code %X",
           this, rv));
-    if (rv != NS_BASE_STREAM_WOULD_BLOCK)
-      CleanupStream(stream, rv, CANCEL_ERROR);
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      return rv;
+    }
+
+    CleanupStream(stream, rv, CANCEL_ERROR);
+    if (SoftStreamError(rv)) {
+      LOG3(("Http2Session::ReadSegments %p soft error override\n", this));
+      rv = NS_OK;
+    }
     return rv;
   }
 
   if (*countRead > 0) {
     LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d",
           this, stream, *countRead));
     mReadyForWrite.Push(stream);
     SetWriteCallbacks();
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -70,27 +70,28 @@ public:
   +-+-------------+---------------+-------------------------------+
   |R|                 Stream Identifier (31)                      |
   +-+-------------------------------------------------------------+
   |                     Frame Data (0...)                       ...
   +---------------------------------------------------------------+
 */
 
   enum frameType {
-    FRAME_TYPE_DATA = 0,
-    FRAME_TYPE_HEADERS = 1,
-    FRAME_TYPE_PRIORITY = 2,
-    FRAME_TYPE_RST_STREAM = 3,
-    FRAME_TYPE_SETTINGS = 4,
-    FRAME_TYPE_PUSH_PROMISE = 5,
-    FRAME_TYPE_PING = 6,
-    FRAME_TYPE_GOAWAY = 7,
-    FRAME_TYPE_WINDOW_UPDATE = 8,
-    FRAME_TYPE_CONTINUATION = 9,
-    FRAME_TYPE_LAST = 10
+    FRAME_TYPE_DATA          = 0x0,
+    FRAME_TYPE_HEADERS       = 0x1,
+    FRAME_TYPE_PRIORITY      = 0x2,
+    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
   };
 
   // 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,
@@ -163,16 +164,17 @@ public:
   static nsresult RecvPriority(Http2Session *);
   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 *);
 
   char       *EnsureOutputBuffer(uint32_t needed);
 
   template<typename charType>
   void CreateFrameHeader(charType dest, uint16_t frameLength,
                          uint8_t frameType, uint8_t frameFlags,
                          uint32_t streamID);
 
--- a/netwerk/protocol/http/NullHttpTransaction.cpp
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -18,20 +18,20 @@ namespace net {
 NS_IMPL_ISUPPORTS(NullHttpTransaction, NullHttpTransaction, nsISupportsWeakReference)
 
 NullHttpTransaction::NullHttpTransaction(nsHttpConnectionInfo *ci,
                                          nsIInterfaceRequestor *callbacks,
                                          uint32_t caps)
   : mStatus(NS_OK)
   , mCaps(caps | NS_HTTP_ALLOW_KEEPALIVE)
   , mCapsToClear(0)
+  , mRequestHead(nullptr)
+  , mIsDone(false)
   , mCallbacks(callbacks)
   , mConnectionInfo(ci)
-  , mRequestHead(nullptr)
-  , mIsDone(false)
 {
 }
 
 NullHttpTransaction::~NullHttpTransaction()
 {
   mCallbacks = nullptr;
   delete mRequestHead;
 }
--- a/netwerk/protocol/http/NullHttpTransaction.h
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -44,27 +44,31 @@ public:
     return PR_SecondsToInterval(15);
   }
 
 protected:
   virtual ~NullHttpTransaction();
 
 private:
   nsresult mStatus;
+protected:
   uint32_t mCaps;
+private:
   // mCapsToClear holds flags that should be cleared in mCaps, e.g. unset
   // NS_HTTP_REFRESH_DNS when DNS refresh request has completed to avoid
   // redundant requests on the network. To deal with raciness, only unsetting
   // bitfields should be allowed: 'lost races' will thus err on the
   // conservative side, e.g. by going ahead with a 2nd DNS refresh.
   uint32_t mCapsToClear;
+  nsHttpRequestHead *mRequestHead;
+  bool mIsDone;
+
+protected:
   nsRefPtr<nsAHttpConnection> mConnection;
   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
   nsRefPtr<nsHttpConnectionInfo> mConnectionInfo;
-  nsHttpRequestHead *mRequestHead;
-  bool mIsDone;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(NullHttpTransaction, NS_NULLHTTPTRANSACTION_IID)
 
 }} // namespace mozilla::net
 
 #endif // mozilla_net_NullHttpTransaction_h
--- a/netwerk/protocol/http/SpdySession3.cpp
+++ b/netwerk/protocol/http/SpdySession3.cpp
@@ -381,37 +381,40 @@ SpdySession3::AddStream(nsAHttpTransacti
           this, stream));
     ActivateStream(stream);
   }
   else {
     LOG3(("SpdySession3::AddStream %p stream %p queued.", this, stream));
     mQueuedStreams.Push(stream);
   }
 
-  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) {
+  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
+      !aHttpTransaction->IsNullTransaction()) {
     LOG3(("SpdySession3::AddStream %p transaction %p forces keep-alive off.\n",
           this, aHttpTransaction));
     DontReuse();
   }
   return true;
 }
 
 void
 SpdySession3::ActivateStream(SpdyStream3 *stream)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
              "Do not activate pushed streams");
 
-  ++mConcurrent;
-  if (mConcurrent > mConcurrentHighWater)
-    mConcurrentHighWater = mConcurrent;
-  LOG3(("SpdySession3::AddStream %p activating stream %p Currently %d "
-        "streams in session, high water mark is %d",
-        this, stream, mConcurrent, mConcurrentHighWater));
+  if (!(stream->Transaction() && stream->Transaction()->IsNullTransaction())) {
+    ++mConcurrent;
+    if (mConcurrent > mConcurrentHighWater)
+      mConcurrentHighWater = mConcurrent;
+    LOG3(("SpdySession3::AddStream %p activating stream %p Currently %d "
+          "streams in session, high water mark is %d",
+          this, stream, mConcurrent, mConcurrentHighWater));
+  }
 
   mReadyForWrite.Push(stream);
   SetWriteCallbacks();
 
   // Kick off the SYN transmit without waiting for the poll loop
   // This won't work for stream id=1 because there is no segment reader
   // yet.
   if (mSegmentReader) {
@@ -1756,20 +1759,27 @@ SpdySession3::ReadSegments(nsAHttpSegmen
       rv = NS_OK;
     else
       rv = NS_BASE_STREAM_WOULD_BLOCK;
     SetWriteCallbacks();
     return rv;
   }
 
   if (NS_FAILED(rv)) {
-    LOG3(("SpdySession3::ReadSegments %p returning FAIL code %X",
+    LOG3(("SpdySession3::ReadSegments %p may return FAIL code %X",
           this, rv));
-    if (rv != NS_BASE_STREAM_WOULD_BLOCK)
-      CleanupStream(stream, rv, RST_CANCEL);
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      return rv;
+    }
+
+    CleanupStream(stream, rv, RST_CANCEL);
+    if (SoftStreamError(rv)) {
+      LOG3(("SpdySession3::ReadSegments %p soft error override\n", this));
+      rv = NS_OK;
+    }
     return rv;
   }
 
   if (*countRead > 0) {
     LOG3(("SpdySession3::ReadSegments %p stream=%p countread=%d",
           this, stream, *countRead));
     mReadyForWrite.Push(stream);
     SetWriteCallbacks();
--- a/netwerk/protocol/http/SpdySession31.cpp
+++ b/netwerk/protocol/http/SpdySession31.cpp
@@ -384,38 +384,42 @@ SpdySession31::AddStream(nsAHttpTransact
           this, stream));
     ActivateStream(stream);
   }
   else {
     LOG3(("SpdySession31::AddStream %p stream %p queued.", this, stream));
     mQueuedStreams.Push(stream);
   }
 
-  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) {
+  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
+      !aHttpTransaction->IsNullTransaction()) {
     LOG3(("SpdySession31::AddStream %p transaction %p forces keep-alive off.\n",
           this, aHttpTransaction));
     DontReuse();
   }
 
   return true;
 }
 
 void
 SpdySession31::ActivateStream(SpdyStream31 *stream)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
              "Do not activate pushed streams");
 
-  ++mConcurrent;
-  if (mConcurrent > mConcurrentHighWater)
-    mConcurrentHighWater = mConcurrent;
-  LOG3(("SpdySession31::AddStream %p activating stream %p Currently %d "
-        "streams in session, high water mark is %d",
-        this, stream, mConcurrent, mConcurrentHighWater));
+  if (!(stream->Transaction() && stream->Transaction()->IsNullTransaction())) {
+    ++mConcurrent;
+    if (mConcurrent > mConcurrentHighWater) {
+      mConcurrentHighWater = mConcurrent;
+    }
+    LOG3(("SpdySession31::AddStream %p activating stream %p Currently %d "
+          "streams in session, high water mark is %d",
+          this, stream, mConcurrent, mConcurrentHighWater));
+  }
 
   mReadyForWrite.Push(stream);
   SetWriteCallbacks();
 
   // Kick off the SYN transmit without waiting for the poll loop
   // This won't work for stream id=1 because there is no segment reader
   // yet.
   if (mSegmentReader) {
@@ -1822,20 +1826,27 @@ SpdySession31::ReadSegments(nsAHttpSegme
       rv = NS_OK;
     else
       rv = NS_BASE_STREAM_WOULD_BLOCK;
     SetWriteCallbacks();
     return rv;
   }
 
   if (NS_FAILED(rv)) {
-    LOG3(("SpdySession31::ReadSegments %p returning FAIL code %X",
+    LOG3(("SpdySession31::ReadSegments %p may return FAIL code %X",
           this, rv));
-    if (rv != NS_BASE_STREAM_WOULD_BLOCK)
-      CleanupStream(stream, rv, RST_CANCEL);
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      return rv;
+    }
+
+    CleanupStream(stream, rv, RST_CANCEL);
+    if (SoftStreamError(rv)) {
+      LOG3(("SpdySession31::ReadSegments %p soft error override\n", this));
+      rv = NS_OK;
+    }
     return rv;
   }
 
   if (*countRead > 0) {
     LOG3(("SpdySession31::ReadSegments %p stream=%p countread=%d",
           this, stream, *countRead));
     mReadyForWrite.Push(stream);
     SetWriteCallbacks();
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -36,16 +36,17 @@ EXPORTS.mozilla.net += [
     'PSpdyPush.h',
 ]
 
 # ASpdySession.cpp and nsHttpAuthCache cannot be built in unified mode because
 # they use plarena.h.
 # The rest of these files cannot be built in unified mode because they want to
 # force NSPR logging.
 SOURCES += [
+    'AlternateServices.cpp',
     'ASpdySession.cpp',
     'ConnectionDiagnostics.cpp',
     'Http2Compression.cpp',
     'Http2Push.cpp',
     'Http2Session.cpp',
     'Http2Stream.cpp',
     'HttpBaseChannel.cpp',
     'HttpChannelChild.cpp',
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -135,16 +135,19 @@ public:
 
     // The number of transaction bytes written out on this HTTP Connection, does
     // not count CONNECT tunnel setup
     virtual int64_t BytesWritten() = 0;
 
     // Update the callbacks used to provide security info. May be called on
     // any thread.
     virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
+
+    // nsHttp.h version
+    virtual uint32_t Version() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
 
 #define NS_DECL_NSAHTTPCONNECTION(fwdObject)                    \
     nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset); \
     void CloseTransaction(nsAHttpTransaction *, nsresult); \
     nsresult TakeTransport(nsISocketTransport **,    \
@@ -202,16 +205,22 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpCon
         return (fwdObject)->ForceRecv();   \
     }                                      \
     nsISocketTransport *Transport()        \
     {                                      \
         if (!(fwdObject))                  \
             return nullptr;                 \
         return (fwdObject)->Transport();   \
     }                                      \
+    uint32_t Version()                     \
+    {                                      \
+        return (fwdObject) ?               \
+            (fwdObject)->Version() :       \
+            NS_HTTP_VERSION_UNKNOWN;       \
+    }                                      \
     bool IsProxyConnectInProgress()                         \
     {                                                       \
         return (fwdObject)->IsProxyConnectInProgress();     \
     }                                                       \
     bool LastTransactionExpectedNoContent()                 \
     {                                                       \
         return (fwdObject)->LastTransactionExpectedNoContent(); \
     }                                                       \
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -198,17 +198,17 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpTra
                            nsresult status, uint64_t progress); \
     bool     IsDone(); \
     nsresult Status(); \
     uint32_t Caps();   \
     void     SetDNSWasRefreshed(); \
     uint64_t Available(); \
     virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *); \
     virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *); \
-    void     Close(nsresult reason);                                    \
+    virtual void Close(nsresult reason);                                \
     nsHttpConnectionInfo *ConnectionInfo();                             \
     void     SetProxyConnectFailed();                                   \
     virtual nsHttpRequestHead *RequestHead();                                   \
     uint32_t Http1xTransactionCount();                                  \
     nsresult TakeSubTransactions(nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions); \
     nsresult AddTransaction(nsAHttpTransaction *);                      \
     uint32_t PipelineDepth();                                           \
     nsresult SetPipelinePosition(int32_t);                              \
--- a/netwerk/protocol/http/nsHttp.cpp
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -341,11 +341,133 @@ void EnsureBuffer(nsAutoArrayPtr<char> &
     localEnsureBuffer<char> (buf, newSize, preserve, objSize);
 }
 
 void EnsureBuffer(nsAutoArrayPtr<uint8_t> &buf, uint32_t newSize,
                   uint32_t preserve, uint32_t &objSize)
 {
     localEnsureBuffer<uint8_t> (buf, newSize, preserve, objSize);
 }
+///
+
+void
+ParsedHeaderValueList::Tokenize(char *input, uint32_t inputLen, char **token,
+                                uint32_t *tokenLen, bool *foundEquals, char **next)
+{
+    if (foundEquals) {
+        *foundEquals = false;
+    }
+    if (next) {
+        *next = nullptr;
+    }
+    if (inputLen < 1 || !input || !token) {
+        return;
+    }
+
+    bool foundFirst = false;
+    bool inQuote = false;
+    bool foundToken = false;
+    *token = input;
+    *tokenLen = inputLen;
+
+    for (uint32_t index = 0; !foundToken && index < inputLen; ++index) {
+        // strip leading cruft
+        if (!foundFirst &&
+            (input[index] == ' ' || input[index] == '"' || input[index] == '\t')) {
+            (*token)++;
+        } else {
+            foundFirst = true;
+        }
+
+        if (input[index] == '"') {
+            inQuote = !inQuote;
+            continue;
+        }
+
+        if (inQuote) {
+            continue;
+        }
+
+        if (input[index] == '=' || input[index] == ';') {
+            *tokenLen = (input + index) - *token;
+            if (next && ((index + 1) < inputLen)) {
+                *next = input + index + 1;
+            }
+            foundToken = true;
+            if (foundEquals && input[index] == '=') {
+                *foundEquals = true;
+            }
+            break;
+        }
+    }
+
+    if (!foundToken) {
+        *tokenLen = (input + inputLen) - *token;
+    }
+
+    // strip trailing cruft
+    for (char *index = *token + *tokenLen - 1; index >= *token; --index) {
+        if (*index != ' ' && *index != '\t' && *index != '"') {
+            break;
+        }
+        --(*tokenLen);
+        if (*index == '"') {
+            break;
+        }
+    }
+}
+
+ParsedHeaderValueList::ParsedHeaderValueList(char *t, uint32_t len)
+{
+    char *name = nullptr;
+    uint32_t nameLen = 0;
+    char *value = nullptr;
+    uint32_t valueLen = 0;
+    char *next = nullptr;
+    bool foundEquals;
+
+    while (t) {
+        Tokenize(t, len, &name, &nameLen, &foundEquals, &next);
+        if (next) {
+            len -= next - t;
+        }
+        t = next;
+        if (foundEquals && t) {
+            Tokenize(t, len, &value, &valueLen, nullptr, &next);
+            if (next) {
+                len -= next - t;
+            }
+            t = next;
+        }
+        mValues.AppendElement(ParsedHeaderPair(name, nameLen, value, valueLen));
+        value = name = nullptr;
+        valueLen = nameLen = 0;
+        next = nullptr;
+    }
+}
+
+ParsedHeaderValueListList::ParsedHeaderValueListList(const nsCString &fullHeader)
+    : mFull(fullHeader)
+{
+    char *t = mFull.BeginWriting();
+    uint32_t len = mFull.Length();
+    char *last = t;
+    bool inQuote = false;
+    for (uint32_t index = 0; index < len; ++index) {
+        if (t[index] == '"') {
+            inQuote = !inQuote;
+            continue;
+        }
+        if (inQuote) {
+            continue;
+        }
+        if (t[index] == ',') {
+            mValues.AppendElement(ParsedHeaderValueList(last, (t + index) - last));
+            last = t + index + 1;
+        }
+    }
+    if (!inQuote) {
+        mValues.AppendElement(ParsedHeaderValueList(last, (t + len) - last));
+    }
+}
 
 } // namespace mozilla::net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -7,16 +7,17 @@
 #ifndef nsHttp_h__
 #define nsHttp_h__
 
 #include <stdint.h>
 #include "prtime.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include "nsError.h"
+#include "nsTArray.h"
 
 // http version codes
 #define NS_HTTP_VERSION_UNKNOWN  0
 #define NS_HTTP_VERSION_0_9      9
 #define NS_HTTP_VERSION_1_0     10
 #define NS_HTTP_VERSION_1_1     11
 #define NS_HTTP_VERSION_2_0     20
 
@@ -198,12 +199,62 @@ PRTimeToSeconds(PRTime t_usec)
 #define HTTP_LWS " \t"
 #define HTTP_HEADER_VALUE_SEPS HTTP_LWS ","
 
 void EnsureBuffer(nsAutoArrayPtr<char> &buf, uint32_t newSize,
                   uint32_t preserve, uint32_t &objSize);
 void EnsureBuffer(nsAutoArrayPtr<uint8_t> &buf, uint32_t newSize,
                   uint32_t preserve, uint32_t &objSize);
 
+// h2=":443"; ma=60; single
+// results in 3 mValues = {{h2, :443}, {ma, 60}, {single}}
+
+class ParsedHeaderPair
+{
+public:
+    ParsedHeaderPair(const char *name, int32_t nameLen,
+                     const char *val, int32_t valLen)
+    {
+        if (nameLen > 0) {
+            mName.Rebind(name, name + nameLen);
+        }
+        if (valLen > 0) {
+            mValue.Rebind(val, val + valLen);
+        }
+    }
+
+    ParsedHeaderPair(ParsedHeaderPair const &copy)
+        : mName(copy.mName)
+        , mValue(copy.mValue)
+    {
+    }
+
+    nsDependentCSubstring mName;
+    nsDependentCSubstring mValue;
+};
+
+class ParsedHeaderValueList
+{
+public:
+    ParsedHeaderValueList(char *t, uint32_t len);
+    nsTArray<ParsedHeaderPair> mValues;
+
+private:
+    void ParsePair(char *t, uint32_t len);
+    void Tokenize(char *input, uint32_t inputLen, char **token,
+                  uint32_t *tokenLen, bool *foundEquals, char **next);
+};
+
+class ParsedHeaderValueListList
+{
+public:
+    explicit ParsedHeaderValueListList(const nsCString &txt);
+    nsTArray<ParsedHeaderValueList> mValues;
+
+private:
+    nsCString mFull;
+};
+
+
 } // namespace mozilla::net
 } // namespace mozilla
 
 #endif // nsHttp_h__
--- a/netwerk/protocol/http/nsHttpAtomList.h
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -18,16 +18,18 @@
  ******/
 
 HTTP_ATOM(Accept,                    "Accept")
 HTTP_ATOM(Accept_Encoding,           "Accept-Encoding")
 HTTP_ATOM(Accept_Language,           "Accept-Language")
 HTTP_ATOM(Accept_Ranges,             "Accept-Ranges")
 HTTP_ATOM(Age,                       "Age")
 HTTP_ATOM(Allow,                     "Allow")
+HTTP_ATOM(Alternate_Service,         "Alt-Svc")
+HTTP_ATOM(Alternate_Service_Used,    "Alt-Svc-Used")
 HTTP_ATOM(Assoc_Req,                 "Assoc-Req")
 HTTP_ATOM(Authentication,            "Authentication")
 HTTP_ATOM(Authorization,             "Authorization")
 HTTP_ATOM(Cache_Control,             "Cache-Control")
 HTTP_ATOM(Connection,                "Connection")
 HTTP_ATOM(Content_Disposition,       "Content-Disposition")
 HTTP_ATOM(Content_Encoding,          "Content-Encoding")
 HTTP_ATOM(Content_Language,          "Content-Language")
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -60,16 +60,17 @@
 #include "nsISocketTransport.h"
 #include "nsIStreamConverterService.h"
 #include "nsISiteSecurityService.h"
 #include "nsCRT.h"
 #include "nsPIDOMWindow.h"
 #include "nsPerformance.h"
 #include "CacheObserver.h"
 #include "mozilla/Telemetry.h"
+#include "AlternateServices.h"
 
 namespace mozilla { namespace net {
 
 namespace {
 
 // True if the local cache should be bypassed when processing a request.
 #define BYPASS_LOCAL_CACHE(loadFlags) \
         (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
@@ -273,21 +274,21 @@ nsHttpChannel::Connect()
     nsresult rv;
 
     LOG(("nsHttpChannel::Connect [this=%p]\n", this));
 
     // Even if we're in private browsing mode, we still enforce existing STS
     // data (it is read-only).
     // if the connection is not using SSL and either the exact host matches or
     // a superdomain wants to force HTTPS, do it.
-    bool usingSSL = false;
-    rv = mURI->SchemeIs("https", &usingSSL);
+    bool isHttps = false;
+    rv = mURI->SchemeIs("https", &isHttps);
     NS_ENSURE_SUCCESS(rv,rv);
 
-    if (mAllowSTS && !usingSSL) {
+    if (mAllowSTS && !isHttps) {
         // enforce Strict-Transport-Security
         nsISiteSecurityService* sss = gHttpHandler->GetSSService();
         NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
 
         bool isStsHost = false;
         uint32_t flags = mPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
         rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags,
                               &isStsHost);
@@ -321,17 +322,17 @@ nsHttpChannel::Connect()
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
     if (!gHttpHandler->UseCache()) {
         return ContinueConnect();
     }
 
     // open a cache entry for this channel...
-    rv = OpenCacheEntry(usingSSL);
+    rv = OpenCacheEntry(isHttps);
 
     // do not continue if asyncOpenCacheEntry is in progress
     if (mCacheEntriesToWaitFor) {
         MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");
         return NS_OK;
     }
 
     if (NS_FAILED(rv)) {
@@ -1225,16 +1226,137 @@ nsHttpChannel::ProcessSSLInformation()
                                   expireTime);
         LOG(("nsHttpChannel::ProcessSSLInformation [this=%p] "
              "falsestart-rsa permission granted for this host\n", this));
     } else {
         permMgr->RemoveFromPrincipal(principal, "falsestart-rsa");
     }
 }
 
+void
+nsHttpChannel::ProcessAltService()
+{
+    // e.g. Alt-Svc: h2=":443"; ma=60
+    // e.g. Alt-Svc: h2="otherhost:443"
+    // Alt-Svc       = 1#( alternative *( OWS ";" OWS parameter ) )
+    // alternative   = protocol-id "=" alt-authority
+    // protocol-id   = token ; percent-encoded ALPN protocol identifier
+    // alt-authority = quoted-string ;  containing [ uri-host ] ":" port
+
+    if (!gHttpHandler->AllowAltSvc()) {
+        return;
+    }
+
+    nsAutoCString scheme;
+    mURI->GetScheme(scheme);
+    bool isHttp = scheme.Equals(NS_LITERAL_CSTRING("http"));
+    if (!isHttp && !scheme.Equals(NS_LITERAL_CSTRING("https"))) {
+        return;
+    }
+
+    if (isHttp && !gHttpHandler->AllowAltSvcOE()) {
+        return;
+    }
+
+    const char *altSvc;
+    if (!(altSvc = mResponseHead->PeekHeader(nsHttp::Alternate_Service))) {
+        return;
+    }
+
+    LOG(("nsHttpChannel %p Alt-Svc Response Header %s\n", this, altSvc));
+
+    nsCString buf(altSvc);
+    if (!nsHttp::IsReasonableHeaderValue(buf)) {
+        LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
+        return;
+    }
+
+    ParsedHeaderValueListList parsedAltSvc(buf);
+    nsRefPtr<AltSvcMapping> mapping;
+
+    nsAutoCString originHost;
+    int32_t originPort = 80;
+    mURI->GetPort(&originPort);
+    if (NS_FAILED(mURI->GetHost(originHost))) {
+        return;
+    }
+    uint32_t now = NowInSeconds(), currentAge = 0;
+    mResponseHead->ComputeCurrentAge(now, mRequestTime, &currentAge);
+
+    for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) {
+        uint32_t maxage = 86400; // default
+        nsAutoCString hostname; // Always empty in the header form
+        nsAutoCString npnToken;
+        int32_t portno = originPort;
+
+        for (uint32_t pairIndex = 0;
+             pairIndex < parsedAltSvc.mValues[index].mValues.Length();
+             ++pairIndex) {
+            nsDependentCSubstring &currentName =
+                parsedAltSvc.mValues[index].mValues[pairIndex].mName;
+            nsDependentCSubstring &currentValue =
+                parsedAltSvc.mValues[index].mValues[pairIndex].mValue;
+
+            if (!pairIndex) {
+                // h2=:443
+                npnToken = currentName;
+                int32_t colonIndex = currentValue.FindChar(':');
+                if (colonIndex >= 0) {
+                    portno =
+                        atoi(PromiseFlatCString(currentValue).get() + colonIndex + 1);
+                } else {
+                    colonIndex = 0;
+                }
+                hostname.Assign(currentValue.BeginReading(), colonIndex);
+            } else if (currentName.Equals(NS_LITERAL_CSTRING("ma"))) {
+                maxage = atoi(PromiseFlatCString(currentValue).get());
+                break;
+            }
+        }
+
+        // unescape modifies a c string in place, so afterwards
+        // update nsCString length
+        nsUnescape(npnToken.BeginWriting());
+        npnToken.SetLength(strlen(npnToken.BeginReading()));
+
+        uint32_t spdyIndex;
+        SpdyInformation *spdyInfo = gHttpHandler->SpdyInfo();
+        if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
+              spdyInfo->ProtocolEnabled(spdyIndex))) {
+            LOG(("Alt Svc %p unknown protocol %s, ignoring", this, npnToken.get()));
+            continue;
+        }
+
+        mapping = new AltSvcMapping(scheme,
+                                    originHost, originPort,
+                                    mUsername, mPrivateBrowsing,
+                                    NowInSeconds() + maxage,
+                                    hostname, portno, npnToken);
+        if (!mapping) {
+            continue;
+        }
+
+        nsCOMPtr<nsIInterfaceRequestor> callbacks;
+        NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+                                               getter_AddRefs(callbacks));
+        if (!callbacks) {
+            return;
+        }
+
+        nsCOMPtr<nsProxyInfo> proxyInfo;
+        if (mProxyInfo) {
+            proxyInfo = do_QueryInterface(mProxyInfo);
+        }
+
+        gHttpHandler->
+            UpdateAltServiceMapping(mapping, proxyInfo, callbacks,
+                                    mCaps & (NS_HTTP_ALLOW_RSA_FALSESTART | NS_HTTP_DISALLOW_SPDY));
+    }
+}
+
 nsresult
 nsHttpChannel::ProcessResponse()
 {
     nsresult rv;
     uint32_t httpStatus = mResponseHead->Status();
 
     // Gather data on whether the transaction and page (if this is
     // the initial page load) is being loaded with SSL.
@@ -1279,16 +1401,20 @@ nsHttpChannel::ProcessResponse()
 
         // reset the authentication's current continuation state because our
         // last authentication attempt has been completed successfully
         mAuthProvider->Disconnect(NS_ERROR_ABORT);
         mAuthProvider = nullptr;
         LOG(("  continuation state has been reset"));
     }
 
+    if (httpStatus < 500) {
+        ProcessAltService();
+    }
+
     bool successfulReval = false;
 
     // handle different server response categories.  Note that we handle
     // caching or not caching of error pages in
     // nsHttpResponseHead::MustValidate; if you change this switch, update that
     // one
     switch (httpStatus) {
     case 200:
@@ -2508,17 +2634,17 @@ IsSubRangeRequest(nsHttpRequestHead &aRe
     if (!aRequestHead.PeekHeader(nsHttp::Range))
         return false;
     nsAutoCString byteRange;
     aRequestHead.GetHeader(nsHttp::Range, byteRange);
     return !byteRange.EqualsLiteral("bytes=0-");
 }
 
 nsresult
-nsHttpChannel::OpenCacheEntry(bool usingSSL)
+nsHttpChannel::OpenCacheEntry(bool isHttps)
 {
     MOZ_EVENT_TRACER_EXEC(this, "net::http::OpenCacheEntry");
 
     // Handle correctly mCacheEntriesToWaitFor
     AutoCacheWaitFlags waitFlags(this);
 
     // Drop this flag here
     mConcurentCacheAccess = 0;
@@ -2835,18 +2961,18 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
                 // also set.
                 MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
             } else {
                 return rv;
             }
         }
     }
 
-    bool usingSSL = false;
-    rv = mURI->SchemeIs("https", &usingSSL);
+    bool isHttps = false;
+    rv = mURI->SchemeIs("https", &isHttps);
     NS_ENSURE_SUCCESS(rv,rv);
 
     bool doValidation = false;
     bool canAddImsHeader = true;
 
     bool isForcedValid = false;
     entry->GetIsForcedValid(&isForcedValid);
 
@@ -2875,17 +3001,17 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
     }
     // Even if the VALIDATE_NEVER flag is set, there are still some cases in
     // which we must validate the cached response with the server.
     else if (mLoadFlags & nsIRequest::VALIDATE_NEVER) {
         LOG(("VALIDATE_NEVER set\n"));
         // if no-store or if no-cache and ssl, validate cached response (see
         // bug 112564 for an explanation of this logic)
         if (mCachedResponseHead->NoStore() ||
-           (mCachedResponseHead->NoCache() && usingSSL)) {
+           (mCachedResponseHead->NoCache() && isHttps)) {
             LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
             doValidation = true;
         }
         else {
             LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
             doValidation = false;
         }
     }
@@ -3430,21 +3556,21 @@ nsHttpChannel::ShouldUpdateOfflineCacheE
 }
 
 nsresult
 nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering,
                                     bool checkingAppCacheEntry)
 {
     nsresult rv;
 
-    bool usingSSL = false;
-    rv = mURI->SchemeIs("https", &usingSSL);
+    bool isHttps = false;
+    rv = mURI->SchemeIs("https", &isHttps);
     NS_ENSURE_SUCCESS(rv,rv);
 
-    if (usingSSL) {
+    if (isHttps) {
         rv = cacheEntry->GetSecurityInfo(
                                       getter_AddRefs(mCachedSecurityInfo));
         if (NS_FAILED(rv)) {
             LOG(("failed to parse security-info [channel=%p, entry=%p]",
                  this, cacheEntry));
             NS_WARNING("failed to parse security-info");
             return rv;
         }
@@ -3788,19 +3914,21 @@ void
 nsHttpChannel::UpdateInhibitPersistentCachingFlag()
 {
     // The no-store directive within the 'Cache-Control:' header indicates
     // that we must not store the response in a persistent cache.
     if (mResponseHead->NoStore())
         mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
 
     // Only cache SSL content on disk if the pref is set
+    bool isHttps;
     if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
-        mConnectionInfo->EndToEndSSL())
+        NS_SUCCEEDED(mURI->SchemeIs("https", &isHttps)) && isHttps) {
         mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
+    }
 }
 
 nsresult
 nsHttpChannel::InitOfflineCacheEntry()
 {
     // This function can be called even when we fail to connect (bug 551990)
 
     if (!mOfflineCacheEntry) {
@@ -4561,44 +4689,85 @@ nsHttpChannel::AsyncOpen(nsIStreamListen
 nsresult
 nsHttpChannel::BeginConnect()
 {
     LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
     nsresult rv;
 
     // Construct connection info object
     nsAutoCString host;
+    nsAutoCString scheme;
     int32_t port = -1;
-    nsAutoCString username;
-    bool usingSSL = false;
-
-    rv = mURI->SchemeIs("https", &usingSSL);
+    bool isHttps = false;
+
+    rv = mURI->GetScheme(scheme);
+    if (NS_SUCCEEDED(rv))
+        rv = mURI->SchemeIs("https", &isHttps);
     if (NS_SUCCEEDED(rv))
         rv = mURI->GetAsciiHost(host);
     if (NS_SUCCEEDED(rv))
         rv = mURI->GetPort(&port);
     if (NS_SUCCEEDED(rv))
-        mURI->GetUsername(username);
+        mURI->GetUsername(mUsername);
     if (NS_SUCCEEDED(rv))
         rv = mURI->GetAsciiSpec(mSpec);
     if (NS_FAILED(rv))
         return rv;
 
     // Reject the URL if it doesn't specify a host
     if (host.IsEmpty())
         return NS_ERROR_MALFORMED_URI;
     LOG(("host=%s port=%d\n", host.get(), port));
     LOG(("uri=%s\n", mSpec.get()));
 
     nsCOMPtr<nsProxyInfo> proxyInfo;
     if (mProxyInfo)
         proxyInfo = do_QueryInterface(mProxyInfo);
 
-    mConnectionInfo = new nsHttpConnectionInfo(host, port, username, proxyInfo, usingSSL);
-    mRequestHead.SetHTTPS(usingSSL);
+    mRequestHead.SetHTTPS(isHttps);
+    mRequestHead.SetOrigin(scheme, host, port);
+
+    nsRefPtr<AltSvcMapping> mapping;
+    if ((scheme.Equals(NS_LITERAL_CSTRING("http")) ||
+         scheme.Equals(NS_LITERAL_CSTRING("https"))) &&
+        (mapping = gHttpHandler->GetAltServiceMapping(scheme,
+                                                      host, port,
+                                                      mPrivateBrowsing))) {
+        LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d\n", this,
+             scheme.get(), mapping->AlternateHost().get(),
+             mapping->AlternatePort()));
+        mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, NS_LITERAL_CSTRING("1"));
+
+        nsCOMPtr<nsIConsoleService> consoleService =
+            do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+        if (consoleService) {
+            nsAutoString message(NS_LITERAL_STRING("Alternate Service Mapping found: "));
+            AppendASCIItoUTF16(scheme.get(), message);
+            message.Append(NS_LITERAL_STRING("://"));
+            AppendASCIItoUTF16(host.get(), message);
+            message.Append(NS_LITERAL_STRING(":"));
+            message.AppendInt(port);
+            message.Append(NS_LITERAL_STRING(" to "));
+            AppendASCIItoUTF16(scheme.get(), message);
+            message.Append(NS_LITERAL_STRING("://"));
+            AppendASCIItoUTF16(mapping->AlternateHost().get(), message);
+            message.Append(NS_LITERAL_STRING(":"));
+            message.AppendInt(mapping->AlternatePort());
+            consoleService->LogStringMessage(message.get());
+        }
+
+        LOG(("nsHttpChannel %p Using connection info from altsvc mapping", this));
+        mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo);
+        Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
+        Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
+    } else {
+        LOG(("nsHttpChannel %p Using default connection info", this));
+        mConnectionInfo = new nsHttpConnectionInfo(host, port, EmptyCString(), mUsername, proxyInfo, isHttps);
+        Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+    }
 
     mAuthProvider =
         do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1",
                           &rv);
     if (NS_SUCCEEDED(rv))
         rv = mAuthProvider->Init(this);
     if (NS_FAILED(rv))
         return rv;
@@ -4863,18 +5032,20 @@ nsHttpChannel::GetResponseEnd(TimeStamp*
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIHttpAuthenticableChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::GetIsSSL(bool *aIsSSL)
 {
-    *aIsSSL = mConnectionInfo->EndToEndSSL();
-    return NS_OK;
+    // this attribute is really misnamed - it wants to know if
+    // https:// is being used. SSL might be used to cover http://
+    // in some circumstances (proxies, http/2, etc..)
+    return mURI->SchemeIs("https", aIsSSL);
 }
 
 NS_IMETHODIMP
 nsHttpChannel::GetProxyMethodIsConnect(bool *aProxyMethodIsConnect)
 {
     *aProxyMethodIsConnect = mConnectionInfo->UsingConnect();
     return NS_OK;
 }
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -200,16 +200,17 @@ private:
     void     SpeculativeConnect();
     nsresult SetupTransaction();
     void     SetupTransactionLoadGroupInfo();
     nsresult CallOnStartRequest();
     nsresult ProcessResponse();
     nsresult ContinueProcessResponse(nsresult);
     nsresult ProcessNormal();
     nsresult ContinueProcessNormal(nsresult);
+    void     ProcessAltService();
     nsresult ProcessNotModified();
     nsresult AsyncProcessRedirection(uint32_t httpStatus);
     nsresult ContinueProcessRedirection(nsresult);
     nsresult ContinueProcessRedirectionAfterFallback(nsresult);
     nsresult ProcessFailedProxyConnect(uint32_t httpStatus);
     nsresult ProcessFallback(bool *waitingForRedirectCallback);
     nsresult ContinueProcessFallback(nsresult);
     void     HandleAsyncAbort();
@@ -422,16 +423,18 @@ private:
 
     // Needed for accurate DNS timing
     nsRefPtr<nsDNSPrefetch>           mDNSPrefetch;
 
     nsresult WaitForRedirectCallback();
     void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
     void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
 
+    nsCString mUsername;
+
 protected:
     virtual void DoNotifyListenerCleanup();
     nsPerformance* GetPerformance();
 
 private: // cache telemetry
     bool mDidReval;
 };
 
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -478,35 +478,58 @@ nsHttpConnection::SetupSSL()
 // offer list for both NPN and ALPN. ALPN validation callbacks are made
 // now before the handshake is complete, and NPN validation callbacks
 // are made during the handshake.
 nsresult
 nsHttpConnection::SetupNPNList(nsISSLSocketControl *ssl, uint32_t caps)
 {
     nsTArray<nsCString> protocolArray;
 
-    // The first protocol is used as the fallback if none of the
-    // protocols supported overlap with the server's list.
-    // When using ALPN the advertised preferences are protocolArray indicies
-    // {1, .., N, 0} in decreasing order.
-    // For NPN, In the case of overlap, matching priority is driven by
-    // the order of the server's advertisement - with index 0 used when
-    // there is no match.
-    protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
+    nsCString npnToken = mConnInfo->GetNPNToken();
+    if (npnToken.IsEmpty()) {
+        // The first protocol is used as the fallback if none of the
+        // protocols supported overlap with the server's list.
+        // When using ALPN the advertised preferences are protocolArray indicies
+        // {1, .., N, 0} in decreasing order.
+        // For NPN, In the case of overlap, matching priority is driven by
+        // the order of the server's advertisement - with index 0 used when
+        // there is no match.
+        protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
 
-    if (gHttpHandler->IsSpdyEnabled() &&
-        !(caps & NS_HTTP_DISALLOW_SPDY)) {
-        LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
-        const SpdyInformation *info = gHttpHandler->SpdyInfo();
-        for (uint32_t index = SpdyInformation::kCount; index > 0; --index) {
-            if (info->ProtocolEnabled(index - 1) &&
-                info->ALPNCallbacks[index - 1](ssl)) {
-                protocolArray.AppendElement(info->VersionString[index - 1]);
+        if (gHttpHandler->IsSpdyEnabled() &&
+            !(caps & NS_HTTP_DISALLOW_SPDY)) {
+            LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
+            const SpdyInformation *info = gHttpHandler->SpdyInfo();
+            for (uint32_t index = SpdyInformation::kCount; index > 0; --index) {
+                if (info->ProtocolEnabled(index - 1) &&
+                    info->ALPNCallbacks[index - 1](ssl)) {
+                    protocolArray.AppendElement(info->VersionString[index - 1]);
+                }
             }
         }
+    } else {
+        LOG(("nsHttpConnection::SetupSSL limiting NPN selection to %s",
+             npnToken.get()));
+        protocolArray.AppendElement(npnToken);
+    }
+
+    nsCString authHost = mConnInfo->GetAuthenticationHost();
+    int32_t   authPort = mConnInfo->GetAuthenticationPort();
+
+    if (!authHost.IsEmpty()) {
+        ssl->SetAuthenticationName(authHost);
+        ssl->SetAuthenticationPort(authPort);
+    }
+
+    if (mConnInfo->GetRelaxed()) { // http:// over tls
+        if (authHost.IsEmpty() || authHost.Equals(mConnInfo->GetHost())) {
+            LOG(("nsHttpConnection::SetupSSL %p TLS-Relaxed "
+                 "with Same Host Auth Bypass", this));
+            ssl->SetBypassAuthentication(true);
+        }
     }
 
     nsresult rv = ssl->SetNPNList(protocolArray);
     LOG(("nsHttpConnection::SetupNPNList %p %x\n",this, rv));
     return rv;
 }
 
 nsresult
@@ -526,16 +549,24 @@ nsHttpConnection::AddTransaction(nsAHttp
     bool needTunnel = transCI->UsingHttpsProxy();
     needTunnel = needTunnel && !mTLSFilter;
     needTunnel = needTunnel && transCI->UsingConnect();
     needTunnel = needTunnel && httpTransaction->QueryHttpTransaction();
 
     LOG(("nsHttpConnection::AddTransaction for SPDY%s",
          needTunnel ? " over tunnel" : ""));
 
+    // do a runtime check here just for defense in depth
+    if (transCI->GetRelaxed() &&
+        httpTransaction->RequestHead() && httpTransaction->RequestHead()->IsHTTPS()) {
+        LOG(("This Cannot happen - https on relaxed tls stream\n"));
+        MOZ_ASSERT(false, "https:// on tls relaxed");
+        return NS_ERROR_FAILURE;
+    }
+
     if (!mSpdySession->AddStream(httpTransaction, priority,
                                  needTunnel, mCallbacks)) {
         MOZ_ASSERT(false); // this cannot happen!
         httpTransaction->Close(NS_ERROR_ABORT);
         return NS_ERROR_FAILURE;
     }
 
     ResumeSend();
@@ -1402,16 +1433,22 @@ nsHttpConnection::EndIdleMonitoring()
     if (mIdleMonitoring) {
         LOG(("Leaving Idle Monitoring Mode [this=%p]", this));
         mIdleMonitoring = false;
         if (mSocketIn)
             mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
     }
 }
 
+uint32_t
+nsHttpConnection::Version()
+{
+    return mUsingSpdyVersion  ? mUsingSpdyVersion : mLastHttpResponseVersion;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpConnection <private>
 //-----------------------------------------------------------------------------
 
 void
 nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
 {
     LOG(("nsHttpConnection::CloseTransaction[this=%p trans=%p reason=%x]\n",
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -206,16 +206,18 @@ public:
     void CheckForTraffic(bool check);
 
     // NoTraffic() returns true if there's been no traffic on the (non-spdy)
     // connection since CheckForTraffic() was called.
     bool NoTraffic() {
         return mTrafficStamp &&
             (mTrafficCount == (mTotalBytesWritten + mTotalBytesRead));
     }
+    // override of nsAHttpConnection
+    virtual uint32_t Version();
 
 private:
     // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
     enum TCPKeepaliveConfig {
       kTCPKeepaliveDisabled = 0,
       kTCPKeepaliveShortLivedConfig,
       kTCPKeepaliveLongLivedConfig
     };
--- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -15,26 +15,59 @@
 
 #include "nsHttpConnectionInfo.h"
 #include "mozilla/net/DNS.h"
 #include "prnetdb.h"
 
 namespace mozilla {
 namespace net {
 
-nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &host, int32_t port,
+nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &physicalHost,
+                                           int32_t physicalPort,
+                                           const nsACString &npnToken,
                                            const nsACString &username,
-                                           nsProxyInfo* proxyInfo,
+                                           nsProxyInfo *proxyInfo,
                                            bool endToEndSSL)
-    : mUsername(username)
-    , mProxyInfo(proxyInfo)
-    , mEndToEndSSL(endToEndSSL)
-    , mUsingConnect(false)
+    : mAuthenticationPort(443)
+{
+    Init(physicalHost, physicalPort, npnToken, username, proxyInfo, endToEndSSL);
+}
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &physicalHost,
+                                           int32_t physicalPort,
+                                           const nsACString &npnToken,
+                                           const nsACString &username,
+                                           nsProxyInfo *proxyInfo,
+                                           const nsACString &logicalHost,
+                                           int32_t logicalPort)
+
 {
-    LOG(("Creating nsHttpConnectionInfo @%x\n", this));
+    mEndToEndSSL = true; // so DefaultPort() works
+    mAuthenticationPort = logicalPort == -1 ? DefaultPort() : logicalPort;
+
+    if (!physicalHost.Equals(logicalHost) || (physicalPort != logicalPort)) {
+        mAuthenticationHost = logicalHost;
+    }
+    Init(physicalHost, physicalPort, npnToken, username, proxyInfo, true);
+}
+
+void
+nsHttpConnectionInfo::Init(const nsACString &host, int32_t port,
+                           const nsACString &npnToken,
+                           const nsACString &username,
+                           nsProxyInfo* proxyInfo,
+                           bool e2eSSL)
+{
+    LOG(("Init nsHttpConnectionInfo @%p\n", this));
+
+    mUsername = username;
+    mProxyInfo = proxyInfo;
+    mEndToEndSSL = e2eSSL;
+    mUsingConnect = false;
+    mNPNToken = npnToken;
 
     mUsingHttpsProxy = (proxyInfo && proxyInfo->IsHTTPS());
     mUsingHttpProxy = mUsingHttpsProxy || (proxyInfo && proxyInfo->IsHTTP());
 
     if (mUsingHttpProxy) {
         mUsingConnect = mEndToEndSSL;  // SSL always uses CONNECT
         uint32_t resolveFlags = 0;
         if (NS_SUCCEEDED(mProxyInfo->GetResolveFlags(&resolveFlags)) &&
@@ -73,18 +106,19 @@ nsHttpConnectionInfo::SetOriginServer(co
         keyPort = Port();
     }
 
     // The hashkey has 4 fields followed by host connection info
     // byte 0 is P/T/. {P,T} for Plaintext/TLS Proxy over HTTP
     // byte 1 is S/. S is for end to end ssl such as https:// uris
     // byte 2 is A/. A is for an anonymous channel (no cookies, etc..)
     // byte 3 is P/. P is for a private browising channel
-    mHashKey.AssignLiteral("....");
+    // byte 4 is R/. R is for 'relaxed' unauthed TLS for http:// uris
 
+    mHashKey.AssignLiteral(".....");
     mHashKey.Append(keyHost);
     mHashKey.Append(':');
     mHashKey.AppendInt(keyPort);
     if (!mUsername.IsEmpty()) {
         mHashKey.Append('[');
         mHashKey.Append(mUsername);
         mHashKey.Append(']');
     }
@@ -113,44 +147,86 @@ nsHttpConnectionInfo::SetOriginServer(co
         mHashKey.AppendLiteral(" (");
         mHashKey.Append(ProxyType());
         mHashKey.Append(':');
         mHashKey.Append(ProxyHost());
         mHashKey.Append(':');
         mHashKey.AppendInt(ProxyPort());
         mHashKey.Append(')');
     }
+
+    if(!mAuthenticationHost.IsEmpty()) {
+        mHashKey.AppendLiteral(" <TLS-LOGIC ");
+        mHashKey.Append(mAuthenticationHost);
+        mHashKey.Append(':');
+        mHashKey.AppendInt(mAuthenticationPort);
+        mHashKey.Append('>');
+    }
+
+    if (!mNPNToken.IsEmpty()) {
+        mHashKey.AppendLiteral(" {NPN-TOKEN ");
+        mHashKey.Append(mNPNToken);
+        mHashKey.AppendLiteral("}");
+    }
 }
 
 nsHttpConnectionInfo*
 nsHttpConnectionInfo::Clone() const
 {
-    nsHttpConnectionInfo* clone = new nsHttpConnectionInfo(mHost, mPort, mUsername, mProxyInfo, mEndToEndSSL);
+    nsHttpConnectionInfo *clone;
+    if (mAuthenticationHost.IsEmpty()) {
+        clone = new nsHttpConnectionInfo(mHost, mPort, mNPNToken, mUsername, mProxyInfo, mEndToEndSSL);
+    } else {
+        MOZ_ASSERT(mEndToEndSSL);
+        clone = new nsHttpConnectionInfo(mHost, mPort, mNPNToken, mUsername, mProxyInfo,
+                                         mAuthenticationHost,
+                                         mAuthenticationPort);
+    }
 
-    // Make sure the anonymous and private flags are transferred!
+    // Make sure the anonymous, relaxed, and private flags are transferred
     clone->SetAnonymous(GetAnonymous());
     clone->SetPrivate(GetPrivate());
+    clone->SetRelaxed(GetRelaxed());
     MOZ_ASSERT(clone->Equals(this));
+
     return clone;
 }
 
+void
+nsHttpConnectionInfo::CloneAsDirectRoute(nsHttpConnectionInfo **outCI)
+{
+    if (mAuthenticationHost.IsEmpty()) {
+        *outCI = Clone();
+        return;
+    }
+
+    nsRefPtr<nsHttpConnectionInfo> clone =
+        new nsHttpConnectionInfo(mAuthenticationHost, mAuthenticationPort,
+                                 EmptyCString(), mUsername, mProxyInfo, mEndToEndSSL);
+    // Make sure the anonymous, relaxed, and private flags are transferred
+    clone->SetAnonymous(GetAnonymous());
+    clone->SetPrivate(GetPrivate());
+    clone->SetRelaxed(GetRelaxed());
+    clone.forget(outCI);
+}
+
 nsresult
 nsHttpConnectionInfo::CreateWildCard(nsHttpConnectionInfo **outParam)
 {
     // T???mozilla.org:443 (https:proxy.ducksong.com:3128) [specifc form]
     // TS??*:0 (https:proxy.ducksong.com:3128)   [wildcard form]
 
     if (!mUsingHttpsProxy) {
         MOZ_ASSERT(false);
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     nsRefPtr<nsHttpConnectionInfo> clone;
     clone = new nsHttpConnectionInfo(NS_LITERAL_CSTRING("*"), 0,
-                                     mUsername, mProxyInfo, true);
+                                     mNPNToken, mUsername, mProxyInfo, true);
     // Make sure the anonymous and private flags are transferred!
     clone->SetAnonymous(GetAnonymous());
     clone->SetPrivate(GetPrivate());
     clone.forget(outParam);
     return NS_OK;
 }
 
 bool
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -27,39 +27,48 @@ extern PRLogModuleInfo *gHttpLog;
 // and multiplex non tunneled transactions at the same time, so they have a
 // special wildcard CI that accepts all origins through that proxy.
 
 namespace mozilla { namespace net {
 
 class nsHttpConnectionInfo
 {
 public:
-    nsHttpConnectionInfo(const nsACString &host, int32_t port,
+    nsHttpConnectionInfo(const nsACString &physicalHost,
+                         int32_t physicalPort,
+                         const nsACString &npnToken,
                          const nsACString &username,
-                         nsProxyInfo* proxyInfo,
+                         nsProxyInfo *proxyInfo,
                          bool endToEndSSL = false);
 
+    // this version must use TLS and you may supply the domain
+    // information to be validated
+    nsHttpConnectionInfo(const nsACString &physicalHost,
+                         int32_t physicalPort,
+                         const nsACString &npnToken,
+                         const nsACString &username,
+                         nsProxyInfo *proxyInfo,
+                         const nsACString &logicalHost,
+                         int32_t logicalPort);
+
 private:
     virtual ~nsHttpConnectionInfo()
     {
         PR_LOG(gHttpLog, 4, ("Destroying nsHttpConnectionInfo @%x\n", this));
     }
 
 public:
     const nsAFlatCString &HashKey() const { return mHashKey; }
 
-    void SetOriginServer(const nsACString &host, int32_t port);
-
-    void SetOriginServer(const char *host, int32_t port)
-    {
-        SetOriginServer(nsDependentCString(host), port);
-    }
+    const nsCString &GetAuthenticationHost() const { return mAuthenticationHost; }
+    int32_t GetAuthenticationPort() const { return mAuthenticationPort; }
 
     // OK to treat these as an infalible allocation
     nsHttpConnectionInfo* Clone() const;
+    void CloneAsDirectRoute(nsHttpConnectionInfo **outParam);
     nsresult CreateWildCard(nsHttpConnectionInfo **outParam);
 
     const char *ProxyHost() const { return mProxyInfo ? mProxyInfo->Host().get() : nullptr; }
     int32_t     ProxyPort() const { return mProxyInfo ? mProxyInfo->Port() : -1; }
     const char *ProxyType() const { return mProxyInfo ? mProxyInfo->Type() : nullptr; }
 
     // Compare this connection info to another...
     // Two connections are 'equal' if they end up talking the same
@@ -78,18 +87,22 @@ public:
     const char   *Username() const       { return mUsername.get(); }
     nsProxyInfo  *ProxyInfo()            { return mProxyInfo; }
     int32_t       DefaultPort() const    { return mEndToEndSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT; }
     void          SetAnonymous(bool anon)
                                          { mHashKey.SetCharAt(anon ? 'A' : '.', 2); }
     bool          GetAnonymous() const   { return mHashKey.CharAt(2) == 'A'; }
     void          SetPrivate(bool priv)  { mHashKey.SetCharAt(priv ? 'P' : '.', 3); }
     bool          GetPrivate() const     { return mHashKey.CharAt(3) == 'P'; }
+    void          SetRelaxed(bool relaxed)
+                                       { mHashKey.SetCharAt(relaxed ? 'R' : '.', 4); }
+    bool          GetRelaxed() const   { return mHashKey.CharAt(4) == 'R'; }
 
     const nsCString &GetHost() { return mHost; }
+    const nsCString &GetNPNToken() { return mNPNToken; }
 
     // Returns true for any kind of proxy (http, socks, https, etc..)
     bool UsingProxy();
 
     // Returns true when proxying over HTTP or HTTPS
     bool UsingHttpProxy() const { return mUsingHttpProxy || mUsingHttpsProxy; }
 
     // Returns true when proxying over HTTPS
@@ -103,25 +116,36 @@ public:
 
     // Returns true when CONNECT is used to tunnel through the proxy (e.g. https:// or ws://)
     bool UsingConnect() const { return mUsingConnect; }
 
     // Returns true when mHost is an RFC1918 literal.
     bool HostIsLocalIPLiteral() const;
 
 private:
+    void Init(const nsACString &host,
+              int32_t port,
+              const nsACString &npnToken,
+              const nsACString &username,
+              nsProxyInfo* proxyInfo,
+              bool EndToEndSSL);
+    void SetOriginServer(const nsACString &host, int32_t port);
+
     nsCString              mHashKey;
     nsCString              mHost;
     int32_t                mPort;
     nsCString              mUsername;
+    nsCString              mAuthenticationHost;
+    int32_t                mAuthenticationPort;
     nsCOMPtr<nsProxyInfo>  mProxyInfo;
     bool                   mUsingHttpProxy;
     bool                   mUsingHttpsProxy;
     bool                   mEndToEndSSL;
     bool                   mUsingConnect;  // if will use CONNECT with http proxy
+    nsCString              mNPNToken;
 
 // for nsRefPtr
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo)
 };
 
 }} // namespace mozilla::net
 
 #endif // nsHttpConnectionInfo_h__
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -377,65 +377,75 @@ public:
 public: // intentional!
     nsRefPtr<NullHttpTransaction> mTrans;
 
     bool mOverridesOK;
     uint32_t mParallelSpeculativeConnectLimit;
     bool mIgnoreIdle;
     bool mIgnorePossibleSpdyConnections;
     bool mIsFromPredictor;
+    bool mAllow1918;
 
     // As above, added manually so we can use nsRefPtr without inheriting from
     // nsISupports
 protected:
     ThreadSafeAutoRefCnt mRefCnt;
     NS_DECL_OWNINGTHREAD
 };
 
 NS_IMPL_ADDREF(SpeculativeConnectArgs)
 NS_IMPL_RELEASE(SpeculativeConnectArgs)
 
 nsresult
 nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
                                         nsIInterfaceRequestor *callbacks,
-                                        uint32_t caps)
+                                        uint32_t caps,
+                                        NullHttpTransaction *nullTransaction)
 {
     MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!");
 
     LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
          ci->HashKey().get()));
 
+    nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
+        do_GetInterface(callbacks);
+
+    bool allow1918 = false;
+    if (overrider) {
+        overrider->GetAllow1918(&allow1918);
+    }
+
     // Hosts that are Local IP Literals should not be speculatively
     // connected - Bug 853423.
-    if (ci && ci->HostIsLocalIPLiteral()) {
+    if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
         LOG(("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
              "address [%s]", ci->Host()));
         return NS_OK;
     }
 
     nsRefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
 
     // Wrap up the callbacks and the target to ensure they're released on the target
     // thread properly.
     nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
     NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks));
 
     caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
-    args->mTrans = new NullHttpTransaction(ci, wrappedCallbacks, caps);
-
-    nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
-        do_GetInterface(callbacks);
+    args->mTrans =
+        nullTransaction ? nullTransaction : new NullHttpTransaction(ci, wrappedCallbacks, caps);
+
     if (overrider) {
         args->mOverridesOK = true;
         overrider->GetParallelSpeculativeConnectLimit(
             &args->mParallelSpeculativeConnectLimit);
         overrider->GetIgnoreIdle(&args->mIgnoreIdle);
         overrider->GetIgnorePossibleSpdyConnections(
             &args->mIgnorePossibleSpdyConnections);
         overrider->GetIsFromPredictor(&args->mIsFromPredictor);
+        overrider->GetAllow1918(&args->mAllow1918);
     }
 
     nsresult rv =
         PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
     if (NS_SUCCEEDED(rv))
         unused << args.forget();
     return rv;
 }
@@ -1292,17 +1302,17 @@ nsHttpConnectionMgr::ReportFailedToProce
     if (NS_SUCCEEDED(rv))
         uri->GetUsername(username);
     if (NS_FAILED(rv) || !isHttp || host.IsEmpty())
         return;
 
     // report the event for all the permutations of anonymous and
     // private versions of this host
     nsRefPtr<nsHttpConnectionInfo> ci =
-        new nsHttpConnectionInfo(host, port, username, nullptr, usingSSL);
+        new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr, usingSSL);
     ci->SetAnonymous(false);
     ci->SetPrivate(false);
     PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
 
     ci = ci->Clone();
     ci->SetAnonymous(false);
     ci->SetPrivate(true);
     PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
@@ -1513,17 +1523,17 @@ nsHttpConnectionMgr::MakeNewConnection(n
 
     if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) &&
         mNumActiveConns && gHttpHandler->IsSpdyEnabled())
         mCT.Enumerate(PurgeExcessSpdyConnectionsCB, this);
 
     if (AtActiveConnectionLimit(ent, trans->Caps()))
         return NS_ERROR_NOT_AVAILABLE;
 
-    nsresult rv = CreateTransport(ent, trans, trans->Caps(), false);
+    nsresult rv = CreateTransport(ent, trans, trans->Caps(), false, false, true);
     if (NS_FAILED(rv)) {
         /* hard failure */
         LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
              "CreateTransport() hard failure.\n",
              ent->mConnInfo->HashKey().get(), trans));
         trans->Close(rv);
         if (rv == NS_ERROR_NOT_AVAILABLE)
             rv = NS_ERROR_FAILURE;
@@ -2140,23 +2150,25 @@ nsHttpConnectionMgr::RecvdConnect()
     ConditionallyStopTimeoutTick();
 }
 
 nsresult
 nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
                                      nsAHttpTransaction *trans,
                                      uint32_t caps,
                                      bool speculative,
-                                     bool isFromPredictor)
+                                     bool isFromPredictor,
+                                     bool allow1918)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     nsRefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps);
     if (speculative) {
         sock->SetSpeculative(true);
+        sock->SetAllow1918(allow1918);
         Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN> totalSpeculativeConn;
         ++totalSpeculativeConn;
 
         if (isFromPredictor) {
           sock->SetIsFromPredictor(true);
           Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED> totalPreconnectsCreated;
           ++totalPreconnectsCreated;
         }
@@ -2931,30 +2943,33 @@ nsHttpConnectionMgr::OnMsgSpeculativeCon
     if (preferredEntry)
         ent = preferredEntry;
 
     uint32_t parallelSpeculativeConnectLimit =
         gHttpHandler->ParallelSpeculativeConnectLimit();
     bool ignorePossibleSpdyConnections = false;
     bool ignoreIdle = false;
     bool isFromPredictor = false;
+    bool allow1918 = false;
 
     if (args->mOverridesOK) {
         parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
         ignorePossibleSpdyConnections = args->mIgnorePossibleSpdyConnections;
         ignoreIdle = args->mIgnoreIdle;
         isFromPredictor = args->mIsFromPredictor;
+        allow1918 = args->mAllow1918;
     }
 
+    bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
     if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
         ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
          !ent->mIdleConns.Length()) &&
-        !RestrictConnections(ent, ignorePossibleSpdyConnections) &&
+        !(keepAlive && RestrictConnections(ent, ignorePossibleSpdyConnections)) &&
         !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
-        CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor);
+        CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918);
     }
     else {
         LOG(("  Transport not created due to existing connection count\n"));
     }
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
@@ -2978,32 +2993,32 @@ nsresult
 nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
 {
     return mConn->PushBack(buf, bufLen);
 }
 
 
 //////////////////////// nsHalfOpenSocket
 
-
 NS_IMPL_ISUPPORTS(nsHttpConnectionMgr::nsHalfOpenSocket,
                   nsIOutputStreamCallback,
                   nsITransportEventSink,
                   nsIInterfaceRequestor,
                   nsITimerCallback)
 
 nsHttpConnectionMgr::
 nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent,
                                    nsAHttpTransaction *trans,
                                    uint32_t caps)
     : mEnt(ent)
     , mTransaction(trans)
     , mCaps(caps)
     , mSpeculative(false)
     , mIsFromPredictor(false)
+    , mAllow1918(true)
     , mHasConnected(false)
     , mPrimaryConnectedOK(false)
     , mBackupConnectedOK(false)
 {
     MOZ_ASSERT(ent && trans, "constructor with null arguments");
     LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s key=%s]\n",
          this, trans, ent->mConnInfo->Host(), ent->mConnInfo->HashKey().get()));
 }
@@ -3069,17 +3084,17 @@ nsHalfOpenSocket::SetupStreams(nsISocket
     if (mEnt->mPreferIPv6) {
         tmpFlags |= nsISocketTransport::DISABLE_IPV4;
     }
     else if (mEnt->mPreferIPv4 ||
              (isBackup && gHttpHandler->FastFallbackToIPv4())) {
         tmpFlags |= nsISocketTransport::DISABLE_IPV6;
     }
 
-    if (IsSpeculative()) {
+    if (!Allow1918()) {
         tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
     }
 
     socketTransport->SetConnectionFlags(tmpFlags);
 
     socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
 
     rv = socketTransport->SetEventSink(this, nullptr);
@@ -3133,16 +3148,18 @@ nsHttpConnectionMgr::nsHalfOpenSocket::S
         mSocketTransport = nullptr;
     }
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
 {
+    MOZ_ASSERT(mTransaction && !mTransaction->IsNullTransaction());
+
     mBackupSynStarted = TimeStamp::Now();
     nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
                                getter_AddRefs(mBackupStreamIn),
                                getter_AddRefs(mBackupStreamOut),
                                true);
     LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
          this, mEnt->mConnInfo->Host(), rv));
     if (NS_FAILED(rv)) {
@@ -3155,18 +3172,18 @@ nsHttpConnectionMgr::nsHalfOpenSocket::S
     return rv;
 }
 
 void
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
 {
     uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
     MOZ_ASSERT(!mSynTimer, "timer already initd");
-
-    if (timeout && !mTransaction->IsDone()) {
+    if (timeout && !mTransaction->IsDone() &&
+        !mTransaction->IsNullTransaction()) {
         // Setup the timer that will establish a backup socket
         // if we do not get a writable event on the main one.
         // We do this because a lost SYN takes a very long time
         // to repair at the TCP level.
         //
         // Failure to setup the timer is something we can live with,
         // so don't return an error in that case.
         nsresult rv;
@@ -3342,18 +3359,17 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
     index = mEnt->mPendingQ.IndexOf(mTransaction);
     if (index != -1) {
         MOZ_ASSERT(!mSpeculative,
                    "Speculative Half Open found mTransaction");
         nsRefPtr<nsHttpTransaction> temp = dont_AddRef(mEnt->mPendingQ[index]);
         mEnt->mPendingQ.RemoveElementAt(index);
         gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
         rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn);
-    }
-    else {
+    } else {
         // this transaction was dispatched off the pending q before all the
         // sockets established themselves.
 
         // After about 1 second allow for the possibility of restarting a
         // transaction due to server close. Keep at sub 1 second as that is the
         // minimum granularity we can expect a server to be timing out with.
         conn->SetIsReusedAfter(950);
 
@@ -3361,27 +3377,32 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
         // then form a null transaction to drive the SSL handshake to
         // completion. Afterwards the connection will be 100% ready for the next
         // transaction to use it. Make an exception for SSL tunneled HTTP proxy as the
         // NullHttpTransaction does not know how to drive Connect
         if (mEnt->mConnInfo->FirstHopSSL() && !mEnt->mPendingQ.Length() &&
             !mEnt->mConnInfo->UsingConnect()) {
             LOG(("nsHalfOpenSocket::OnOutputStreamReady null transaction will "
                  "be used to finish SSL handshake on conn %p\n", conn.get()));
-            nsRefPtr<NullHttpTransaction>  trans =
-                new NullHttpTransaction(mEnt->mConnInfo,
-                                        callbacks,
-                                        mCaps & ~NS_HTTP_ALLOW_PIPELINING);
+            nsRefPtr<nsAHttpTransaction> trans;
+            if (mTransaction->IsNullTransaction()) {
+                // null transactions cannot be put in the entry queue, so that
+                // explains why it is not present.
+                trans = mTransaction;
+            } else {
+                trans = new NullHttpTransaction(mEnt->mConnInfo,
+                                                callbacks,
+                                                mCaps & ~NS_HTTP_ALLOW_PIPELINING);
+            }
 
             gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
             conn->Classify(nsAHttpTransaction::CLASS_SOLO);
             rv = gHttpHandler->ConnMgr()->
                 DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
-        }
-        else {
+        } else {
             // otherwise just put this in the persistent connection pool
             LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match "
                  "returning conn %p to pool\n", conn.get()));
             nsRefPtr<nsHttpConnection> copy(conn);
             // forget() to effectively addref because onmsg*() will drop a ref
             gHttpHandler->ConnMgr()->OnMsgReclaimConnection(
                 0, conn.forget().take());
         }
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -11,30 +11,33 @@
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Attributes.h"
+#include "AlternateServices.h"
 
 #include "nsIObserver.h"
 #include "nsITimer.h"
 
 class nsIHttpUpgradeListener;
 
 namespace mozilla {
 namespace net {
 class EventTokenBucket;
+class NullHttpTransaction;
 struct HttpRetParams;
 
 //-----------------------------------------------------------------------------
 
 class nsHttpConnectionMgr : public nsIObserver
+                          , public AltSvcCache
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIOBSERVER
 
     // parameter names
     enum nsParamName {
         MAX_CONNECTIONS,
@@ -110,17 +113,18 @@ public:
     // called to indicate a transaction for the connectionInfo is likely coming
     // soon. The connection manager may use this information to start a TCP
     // and/or SSL level handshake for that resource immediately so that it is
     // ready when the transaction is submitted. No obligation is taken on by the
     // connection manager, nor is the submitter obligated to actually submit a
     // real transaction for this connectionInfo.
     nsresult SpeculativeConnect(nsHttpConnectionInfo *,
                                 nsIInterfaceRequestor *,
-                                uint32_t caps = 0);
+                                uint32_t caps = 0,
+                                NullHttpTransaction * = nullptr);
 
     // called when a connection is done processing a transaction.  if the
     // connection can be reused then it will be added to the idle list, else
     // it will be closed.
     nsresult ReclaimConnection(nsHttpConnection *conn);
 
     // called by the main thread to execute the taketransport() logic on the
     // socket thread after a 101 response has been received and the socket
@@ -460,16 +464,19 @@ private:
         nsAHttpTransaction *Transaction() { return mTransaction; }
 
         bool IsSpeculative() { return mSpeculative; }
         void SetSpeculative(bool val) { mSpeculative = val; }
 
         bool IsFromPredictor() { return mIsFromPredictor; }
         void SetIsFromPredictor(bool val) { mIsFromPredictor = val; }
 
+        bool Allow1918() { return mAllow1918; }
+        void SetAllow1918(bool val) { mAllow1918 = val; }
+
         bool HasConnected() { return mHasConnected; }
 
         void PrintDiagnostics(nsCString &log);
     private:
         nsConnectionEntry              *mEnt;
         nsRefPtr<nsAHttpTransaction>   mTransaction;
         nsCOMPtr<nsISocketTransport>   mSocketTransport;
         nsCOMPtr<nsIAsyncOutputStream> mStreamOut;
@@ -485,16 +492,18 @@ private:
         // more connections that are needed.)
         bool                           mSpeculative;
 
         // mIsFromPredictor is set if the socket originated from the network
         // Predictor. It is used to gather telemetry data on used speculative
         // connections from the predictor.
         bool                           mIsFromPredictor;
 
+        bool                           mAllow1918;
+
         TimeStamp             mPrimarySynStarted;
         TimeStamp             mBackupSynStarted;
 
         // for syn retry
         nsCOMPtr<nsITimer>             mSynTimer;
         nsCOMPtr<nsISocketTransport>   mBackupTransport;
         nsCOMPtr<nsIAsyncOutputStream> mBackupStreamOut;
         nsCOMPtr<nsIAsyncInputStream>  mBackupStreamIn;
@@ -557,17 +566,17 @@ private:
                            nsAHttpTransaction *,
                            nsHttpPipeline **);
     bool     RestrictConnections(nsConnectionEntry *, bool = false);
     nsresult ProcessNewTransaction(nsHttpTransaction *);
     nsresult EnsureSocketThreadTarget();
     void     ClosePersistentConnections(nsConnectionEntry *ent);
     void     ReportProxyTelemetry(nsConnectionEntry *ent);
     nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *,
-                             uint32_t, bool, bool = false);
+                             uint32_t, bool, bool, bool);
     void     AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
     void     DecrementActiveConnCount(nsHttpConnection *);
     void     StartedConnect();
     void     RecvdConnect();
 
     nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *,
                                                   bool allowWildCard);
 
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -185,16 +185,18 @@ nsHttpHandler::nsHttpHandler()
     , mSpdyV3(true)
     , mSpdyV31(true)
     , mHttp2DraftEnabled(true)
     , mHttp2Enabled(true)
     , mEnforceHttp2TlsProfile(true)
     , mCoalesceSpdy(true)
     , mSpdyPersistentSettings(false)
     , mAllowPush(true)
+    , mEnableAltSvc(true)
+    , mEnableAltSvcOE(true)
     , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
     , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize)
     , mSpdyPushAllowance(32768)
     , mSpdyPingThreshold(PR_SecondsToInterval(58))
     , mSpdyPingTimeout(PR_SecondsToInterval(8))
     , mConnectTimeout(90000)
     , mParallelSpeculativeConnectLimit(6)
     , mRequestTokenBucketEnabled(true)
@@ -1225,16 +1227,31 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
 
     if (PREF_CHANGED(HTTP_PREF("spdy.allow-push"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("spdy.allow-push"),
                                 &cVar);
         if (NS_SUCCEEDED(rv))
             mAllowPush = cVar;
     }
 
+    if (PREF_CHANGED(HTTP_PREF("altsvc.enabled"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("atsvc.enabled"),
+                                &cVar);
+        if (NS_SUCCEEDED(rv))
+            mEnableAltSvc = cVar;
+    }
+
+
+    if (PREF_CHANGED(HTTP_PREF("altsvc.oe"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("atsvc.oe"),
+                                &cVar);
+        if (NS_SUCCEEDED(rv))
+            mEnableAltSvcOE = 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)));
         }
     }
@@ -1829,21 +1846,28 @@ nsHttpHandler::Observe(nsISupports *subj
         }
     } else if (!strcmp(topic, "net:failed-to-process-uri-content")) {
         nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
         if (uri && mConnMgr) {
             mConnMgr->ReportFailedToProcess(uri);
         }
     } else if (!strcmp(topic, "last-pb-context-exited")) {
         mPrivateAuthCache.ClearAll();
+        if (mConnMgr) {
+            mConnMgr->ClearAltServiceMappings();
+        }
     } else if (!strcmp(topic, "browser:purge-session-history")) {
-        if (mConnMgr && gSocketTransportService) {
-            nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mConnMgr,
-                &nsHttpConnectionMgr::ClearConnectionHistory);
-            gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+        if (mConnMgr) {
+            if (gSocketTransportService) {
+                nsCOMPtr<nsIRunnable> event =
+                    NS_NewRunnableMethod(mConnMgr,
+                                         &nsHttpConnectionMgr::ClearConnectionHistory);
+                gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+            }
+            mConnMgr->ClearAltServiceMappings();
         }
     } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
         nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
         if (!strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) {
             if (mConnMgr) {
                 mConnMgr->PruneDeadConnections();
                 mConnMgr->VerifyTraffic();
             }
@@ -1912,17 +1936,17 @@ nsHttpHandler::SpeculativeConnect(nsIURI
     rv = aURI->GetPort(&port);
     if (NS_FAILED(rv))
         return rv;
 
     nsAutoCString username;
     aURI->GetUsername(username);
 
     nsHttpConnectionInfo *ci =
-        new nsHttpConnectionInfo(host, port, username, nullptr, usingSSL);
+        new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr, usingSSL);
 
     return SpeculativeConnect(ci, aCallbacks);
 }
 
 void
 nsHttpHandler::TickleWifi(nsIInterfaceRequestor *cb)
 {
     if (!cb || !mWifiTickler)
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -32,16 +32,17 @@ class nsITimer;
 namespace mozilla {
 namespace net {
 class ATokenBucketEvent;
 class EventTokenBucket;
 class Tickler;
 class nsHttpConnection;
 class nsHttpConnectionInfo;
 class nsHttpTransaction;
+class AltSvcMapping;
 
 //-----------------------------------------------------------------------------
 // nsHttpHandler - protocol handler for HTTP and HTTPS
 //-----------------------------------------------------------------------------
 
 class nsHttpHandler MOZ_FINAL : public nsIHttpProtocolHandler
                               , public nsIObserver
                               , public nsSupportsWeakReference
@@ -103,16 +104,18 @@ public:
     bool           CoalesceSpdy() { return mCoalesceSpdy; }
     bool           UseSpdyPersistentSettings() { return mSpdyPersistentSettings; }
     uint32_t       SpdySendingChunkSize() { return mSpdySendingChunkSize; }
     uint32_t       SpdySendBufferSize()      { return mSpdySendBufferSize; }
     uint32_t       SpdyPushAllowance()       { return mSpdyPushAllowance; }
     PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
     PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
     bool           AllowPush()   { return mAllowPush; }
+    bool           AllowAltSvc() { return mEnableAltSvc; }
+    bool           AllowAltSvcOE() { return mEnableAltSvcOE; }
     uint32_t       ConnectTimeout()  { return mConnectTimeout; }
     uint32_t       ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
     bool           CriticalRequestPrioritization() { return mCriticalRequestPrioritization; }
 
     uint32_t       MaxConnectionsPerOrigin() { return mMaxPersistentConnectionsPerServer; }
     bool           UseRequestTokenBucket() { return mRequestTokenBucketEnabled; }
     uint16_t       RequestTokenBucketMinParallelism() { return mRequestTokenBucketMinParallelism; }
     uint32_t       RequestTokenBucketHz() { return mRequestTokenBucketHz; }
@@ -214,16 +217,32 @@ public:
     nsresult SpeculativeConnect(nsHttpConnectionInfo *ci,
                                 nsIInterfaceRequestor *callbacks,
                                 uint32_t caps = 0)
     {
         TickleWifi(callbacks);
         return mConnMgr->SpeculativeConnect(ci, callbacks, caps);
     }
 
+    // Alternate Services Maps are main thread only
+    void UpdateAltServiceMapping(AltSvcMapping *map,
+                                 nsProxyInfo *proxyInfo,
+                                 nsIInterfaceRequestor *callbacks,
+                                 uint32_t caps)
+    {
+        mConnMgr->UpdateAltServiceMapping(map, proxyInfo, callbacks, caps);
+    }
+
+    AltSvcMapping *GetAltServiceMapping(const nsACString &scheme,
+                                        const nsACString &host,
+                                        int32_t port, bool pb)
+    {
+        return mConnMgr->GetAltServiceMapping(scheme, host, port, pb);
+    }
+
     //
     // The HTTP handler caches pointers to specific XPCOM services, and
     // provides the following helper routines for accessing those services:
     //
     nsresult GetStreamConverterService(nsIStreamConverterService **);
     nsresult GetIOService(nsIIOService** service);
     nsICookieService * GetCookieService(); // not addrefed
     nsISiteSecurityService * GetSSService();
@@ -449,16 +468,18 @@ private:
     uint32_t           mSpdyV3 : 1;
     uint32_t           mSpdyV31 : 1;
     uint32_t           mHttp2DraftEnabled : 1;
     uint32_t           mHttp2Enabled : 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;
 
     // Try to use SPDY features instead of HTTP/1.1 over SSL
     SpdyInformation    mSpdyInfo;
 
     uint32_t       mSpdySendingChunkSize;
     uint32_t       mSpdySendBufferSize;
     uint32_t       mSpdyPushAllowance;
     PRIntervalTime mSpdyPingThreshold;
--- a/netwerk/protocol/http/nsHttpRequestHead.cpp
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -46,16 +46,28 @@ nsHttpRequestHead::SetMethod(const nsACS
         mParsedMethod = kMethod_Head;
     } else if (!strcmp(mMethod.get(), "PUT")) {
         mParsedMethod = kMethod_Put;
     } else if (!strcmp(mMethod.get(), "TRACE")) {
         mParsedMethod = kMethod_Trace;
     }
 }
 
+void
+nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host, int32_t port)
+{
+    mOrigin.Assign(scheme);
+    mOrigin.Append(NS_LITERAL_CSTRING("://"));
+    mOrigin.Append(host);
+    if (port >= 0) {
+        mOrigin.Append(NS_LITERAL_CSTRING(":"));
+        mOrigin.AppendInt(port);
+    }
+}
+
 bool
 nsHttpRequestHead::IsSafeMethod() const
 {
   // This code will need to be extended for new safe methods, otherwise
   // they'll default to "not safe".
     if (IsGet() || IsHead() || IsOptions() || IsTrace()) {
         return true;
     }
--- a/netwerk/protocol/http/nsHttpRequestHead.h
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -31,16 +31,19 @@ public:
     nsHttpHeaderArray & Headers()          { return mHeaders; }
     const nsCString &Method()        const { return mMethod; }
     nsHttpVersion       Version()    const { return mVersion; }
     const nsCSubstring &RequestURI() const { return mRequestURI; }
 
     void SetHTTPS(bool val) { mHTTPS = val; }
     bool IsHTTPS() const { return mHTTPS; }
 
+    void SetOrigin(const nsACString &scheme, const nsACString &host, int32_t port);
+    const nsCString &Origin() const { return mOrigin; }
+
     const char *PeekHeader(nsHttpAtom h) const
     {
         return mHeaders.PeekHeader(h);
     }
     nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false) { return mHeaders.SetHeader(h, v, m); }
     nsresult GetHeader(nsHttpAtom h, nsACString &v) const
     {
         return mHeaders.GetHeader(h, v);
@@ -92,15 +95,16 @@ public:
     bool IsTrace() const { return EqualsMethod(kMethod_Trace); }
 
 private:
     // All members must be copy-constructable and assignable
     nsHttpHeaderArray mHeaders;
     nsCString         mMethod;
     nsHttpVersion     mVersion;
     nsCString         mRequestURI;
+    nsCString         mOrigin;
     ParsedMethodType  mParsedMethod;
     bool              mHTTPS;
 };
 
 }} // namespace mozilla::net
 
 #endif // nsHttpRequestHead_h__
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -86,17 +86,16 @@ LogHeaders(const char *lineStart)
 //-----------------------------------------------------------------------------
 // nsHttpTransaction <public>
 //-----------------------------------------------------------------------------
 
 nsHttpTransaction::nsHttpTransaction()
     : mLock("transaction lock")
     , mRequestSize(0)
     , mConnection(nullptr)
-    , mConnInfo(nullptr)
     , mRequestHead(nullptr)
     , mResponseHead(nullptr)
     , mContentLength(-1)
     , mContentRead(0)
     , mInvalidResponseBytesRead(0)
     , mChunkedDecoder(nullptr)
     , mStatus(NS_OK)
     , mPriority(0)
@@ -119,16 +118,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mStatusEventPending(false)
     , mHasRequestBody(false)
     , mProxyConnectFailed(false)
     , mHttpResponseMatched(false)
     , mPreserveStream(false)
     , mDispatchedAsBlocking(false)
     , mResponseTimeoutEnabled(true)
     , mDontRouteViaWildCard(false)
+    , mForceRestart(false)
     , mReportedStart(false)
     , mReportedResponseHeader(false)
     , mForTakeResponseHead(nullptr)
     , mResponseHeadTaken(false)
     , mSubmittedRatePacing(false)
     , mPassedRatePacing(false)
     , mSynchronousRatePaceRequest(false)
     , mCountRecv(0)
@@ -843,16 +843,21 @@ nsHttpTransaction::Close(nsresult reason
     //
     // NOTE: because of the way SSL proxy CONNECT is implemented, it is
     // possible that the transaction may have received data without having
     // sent any data.  for this reason, mSendData == FALSE does not imply
     // mReceivedData == FALSE.  (see bug 203057 for more info.)
     //
     if (reason == NS_ERROR_NET_RESET || reason == NS_OK) {
 
+        if (mForceRestart && NS_SUCCEEDED(Restart())) {
+            LOG(("transaction force restarted\n"));
+            return;
+        }
+
         // reallySentData is meant to separate the instances where data has
         // been sent by this transaction but buffered at a higher level while
         // a TLS session (perhaps via a tunnel) is setup.
         bool reallySentData =
             mSentData && (!mConnection || mConnection->BytesWritten());
 
         if (!mReceivedData &&
             (!reallySentData || connReused || mPipelinePosition)) {
@@ -1105,16 +1110,27 @@ nsHttpTransaction::Restart()
     }
 
     // disable pipelining for the next attempt in case pipelining caused the
     // reset.  this is being overly cautious since we don't know if pipelining
     // was the problem here.
     mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
     SetPipelinePosition(0);
 
+    if (!mConnInfo->GetAuthenticationHost().IsEmpty()) {
+        MutexAutoLock lock(*nsHttp::GetLock());
+        nsRefPtr<nsHttpConnectionInfo> ci;
+         mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+         mConnInfo = ci;
+        if (mRequestHead) {
+            mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, NS_LITERAL_CSTRING("0"));
+        }
+    }
+    mForceRestart = false;
+
     return gHttpHandler->InitiateTransaction(this, mPriority);
 }
 
 char *
 nsHttpTransaction::LocateHttpStart(char *buf, uint32_t len,
                                    bool aAllowPartialMatch)
 {
     MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty());
@@ -1376,21 +1392,21 @@ nsHttpTransaction::ParseHead(char *buf,
             return NS_OK;
         rv = ParseLineSegment(buf, len);
         if (NS_FAILED(rv))
             return rv;
     }
     return NS_OK;
 }
 
-// called on the socket thread
 nsresult
 nsHttpTransaction::HandleContentStart()
 {
     LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this));
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     if (mResponseHead) {
 #if defined(PR_LOGGING)
         if (LOG3_ENABLED()) {
             LOG3(("http response [\n"));
             nsAutoCString headers;
             mResponseHead->Flatten(headers, false);
             LogHeaders(headers.get());
@@ -1424,16 +1440,24 @@ nsHttpTransaction::HandleContentStart()
         case 101:
             mPreserveStream = true;    // fall through to other no content
         case 204:
         case 205:
         case 304:
             mNoContent = true;
             LOG(("this response should not contain a body.\n"));
             break;
+        case 421:
+            if (!mConnInfo->GetAuthenticationHost().IsEmpty()) {
+                LOG(("Not Authoritative.\n"));
+                gHttpHandler->ConnMgr()->
+                    ClearHostMapping(mConnInfo->GetHost(), mConnInfo->Port());
+                mForceRestart = true;
+            }
+            break;
         }
 
         if (mResponseHead->Status() == 200 &&
             mConnection->IsProxyConnectInProgress()) {
             // successful CONNECTs do not have response bodies
             mNoContent = true;
         }
         mConnection->SetLastTransactionExpectedNoContent(mNoContent);
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -259,16 +259,17 @@ private:
     bool                            mStatusEventPending;
     bool                            mHasRequestBody;
     bool                            mProxyConnectFailed;
     bool                            mHttpResponseMatched;
     bool                            mPreserveStream;
     bool                            mDispatchedAsBlocking;
     bool                            mResponseTimeoutEnabled;
     bool                            mDontRouteViaWildCard;
+    bool                            mForceRestart;
 
     // mClosed           := transaction has been explicitly closed
     // mTransactionDone  := transaction ran to completion or was interrupted
     // mResponseComplete := transaction ran to completion
 
     // For Restart-In-Progress Functionality
     bool                            mReportedStart;
     bool                            mReportedResponseHeader;
--- a/netwerk/test/unit/test_http2.js
+++ b/netwerk/test/unit/test_http2.js
@@ -330,27 +330,75 @@ function test_http2_post() {
 
 // Make sure we can do a POST that covers more than 2 frames
 function test_http2_post_big() {
   var chan = makeChan("https://localhost:6944/post");
   var listener = new Http2PostListener(md5s[1]);
   do_post(posts[1], chan, listener);
 }
 
+Cu.import("resource://testing-common/httpd.js");
+var httpserv = null;
+var ios = Components.classes["@mozilla.org/network/io-service;1"]
+                    .getService(Components.interfaces.nsIIOService);
+
+var altsvcClientListener = {
+  onStartRequest: function test_onStartR(request, ctx) {
+    do_check_eq(request.status, Components.results.NS_OK);
+  },
+
+  onDataAvailable: function test_ODA(request, cx, stream, offset, cnt) {
+   read_stream(stream, cnt);
+  },
+
+  onStopRequest: function test_onStopR(request, ctx, status) {
+    var isHttp2Connection = checkIsHttp2(request);
+    if (!isHttp2Connection) {
+	// not over tls yet - retry. It's all async and transparent to client
+	var chan = ios.newChannel("http://localhost:" + httpserv.identity.primaryPort + "/altsvc1",
+				  null, null).QueryInterface(Components.interfaces.nsIHttpChannel);
+	chan.asyncOpen(altsvcClientListener, null);
+    } else {
+        do_check_true(isHttp2Connection);
+	httpserv.stop(do_test_finished);
+	run_next_test();
+    }
+  }
+};
+
+function altsvcHttp1Server(metadata, response) {
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setHeader("Alt-Svc", 'h2=":6944"; ma=3200, h2-14=":6944"', false);
+  var body = "this is where a cool kid would write something neat.\n";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+function test_http2_altsvc() {
+  httpserv = new HttpServer();
+  httpserv.registerPathHandler("/altsvc1", altsvcHttp1Server);
+  httpserv.start(-1);
+
+  var chan = ios.newChannel("http://localhost:" + httpserv.identity.primaryPort + "/altsvc1",
+			    null, null).QueryInterface(Components.interfaces.nsIHttpChannel);
+  chan.asyncOpen(altsvcClientListener, null);
+}
+
 // hack - the header test resets the multiplex object on the server,
 // so make sure header is always run before the multiplex test.
 //
 // make sure post_big runs first to test race condition in restarting
 // a stalled stream when a SETTINGS frame arrives
 var tests = [ test_http2_post_big
             , test_http2_basic
             , test_http2_push1
             , test_http2_push2
             , test_http2_push3
             , test_http2_push4
+	    , test_http2_altsvc
             , test_http2_doubleheader
             , test_http2_xhr
             , test_http2_header
             , test_http2_cookie_crumbling
             , test_http2_multiplex
             , test_http2_big
             , test_http2_post
             ];
@@ -427,16 +475,18 @@ var tlspref;
 var loadGroup;
 
 function resetPrefs() {
   prefs.setBoolPref("network.http.spdy.enabled", spdypref);
   prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref);
   prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
   prefs.setBoolPref("network.http.spdy.enabled.http2draft", http2pref);
   prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlspref);
+  prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
+  prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
 }
 
 function run_test() {
   // Set to allow the cert presented by our SPDY server
   do_get_profile();
   var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
   prefs.setIntPref("network.http.speculative-parallel-limit", 0);
@@ -449,19 +499,24 @@ function run_test() {
   prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
 
   // Enable all versions of spdy to see that we auto negotiate http/2
   spdypref = prefs.getBoolPref("network.http.spdy.enabled");
   spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3");
   spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
   http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2draft");
   tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
+  altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
+  altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
+
   prefs.setBoolPref("network.http.spdy.enabled", true);
   prefs.setBoolPref("network.http.spdy.enabled.v3", true);
   prefs.setBoolPref("network.http.spdy.allow-push", true);
   prefs.setBoolPref("network.http.spdy.enabled.http2draft", true);
   prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
+  prefs.setBoolPref("network.http.altsvc.enabled", true);
+  prefs.setBoolPref("network.http.altsvc.oe", true);
 
   loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
 
   // And make go!
   run_next_test();
 }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -1188,16 +1188,26 @@
     "kind": "boolean",
     "description": "Whether a HTTP transaction was over SSL or not."
   },
   "HTTP_PAGELOAD_IS_SSL": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether a HTTP base page load was over SSL or not."
   },
+  "HTTP_TRANSACTION_USE_ALTSVC": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "description": "Whether a HTTP transaction was routed via Alt-Svc or not."
+  },
+  "HTTP_TRANSACTION_USE_ALTSVC_OE": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "description": "Whether a HTTP transaction routed via Alt-Svc was scheme=http"
+  },
   "SSL_HANDSHAKE_VERSION": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 16,
     "description": "SSL Version (0=ssl3, 1=tls1, 2=tls1.1, 3=tls1.2)"
   },
   "SSL_TIME_UNTIL_READY": {
     "expires_in_version": "never",