Bug 1220810 - Hardcode localhost to loopback, r=ckerschb,dragana
☠☠ backed out by b34436389d46 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 10 Jul 2020 12:23:46 +0000
changeset 539864 1b44f13206d03a1f4113a8d1472f87b7d1afd488
parent 539863 f05d3259ce178f9042c3e26478f3a2bf2b2693a4
child 539865 57fd9fa7bf4984ec8d01407c2fcbbc2c3aecf421
push id37586
push userccoroiu@mozilla.com
push dateFri, 10 Jul 2020 16:06:24 +0000
treeherdermozilla-central@4e9d6619c9d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, dragana
bugs1220810
milestone80.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1220810 - Hardcode localhost to loopback, r=ckerschb,dragana Differential Revision: https://phabricator.services.mozilla.com/D64586
dom/base/test/unit/test_error_codes.js
dom/media/tests/mochitest/mochitest.ini
dom/security/nsMixedContentBlocker.cpp
dom/security/nsMixedContentBlocker.h
dom/security/test/gtest/TestSecureContext.cpp
dom/security/test/unit/test_isOriginPotentiallyTrustworthy.js
dom/webauthn/tests/browser/browser_webauthn_ipaddress.js
modules/libpref/init/StaticPrefList.yaml
modules/libpref/init/all.js
netwerk/base/nsProtocolProxyService.cpp
netwerk/base/nsProtocolProxyService.h
netwerk/dns/DNS.cpp
netwerk/dns/DNS.h
netwerk/dns/nsHostResolver.cpp
netwerk/dns/nsHostResolver.h
netwerk/test/unit/test_about_networking.js
netwerk/test/unit/test_dns_offline.js
netwerk/test/unit/test_dns_originAttributes.js
netwerk/test/unit/test_ping_aboutnetworking.js
netwerk/test/unit/test_trr.js
--- a/dom/base/test/unit/test_error_codes.js
+++ b/dom/base/test/unit/test_error_codes.js
@@ -33,27 +33,30 @@ function run_test() {
 
 // network offline
 function run_test_pt1() {
   try {
     Services.io.manageOfflineStatus = false;
   } catch (e) {}
   Services.io.offline = true;
   prefs.setBoolPref("network.dns.offline-localhost", false);
+  // We always resolve localhost as it's hardcoded without the following pref:
+  prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
 
   gExpectedStatus = Cr.NS_ERROR_OFFLINE;
   gNextTestFunc = run_test_pt2;
   dump("Testing error returned by async XHR when the network is offline\n");
   asyncXHR.load();
 }
 
 // connection refused
 function run_test_pt2() {
   Services.io.offline = false;
   prefs.clearUserPref("network.dns.offline-localhost");
+  prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
 
   gExpectedStatus = Cr.NS_ERROR_CONNECTION_REFUSED;
   gNextTestFunc = end_test;
   dump("Testing error returned by aync XHR when the connection is refused\n");
   asyncXHR.load();
 }
 
 function end_test() {
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -1,12 +1,14 @@
 [DEFAULT]
 tags = mtg webrtc
 subsuite = media
 scheme = https
+prefs =
+  network.proxy.allow_hijacking_localhost=true
 support-files =
   head.js
   dataChannel.js
   mediaStreamPlayback.js
   network.js
   nonTrickleIce.js
   pc.js
   templates.js
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -222,18 +222,17 @@ nsMixedContentBlocker::ShouldLoad(nsIURI
                                 nsILoadInfo::BLOCKING_REASON_MIXED_BLOCKED);
   }
 
   return rv;
 }
 
 bool nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
     const nsACString& aAsciiHost) {
-  if (aAsciiHost.EqualsLiteral("::1") ||
-      aAsciiHost.EqualsLiteral("localhost")) {
+  if (mozilla::net::IsLoopbackHostname(aAsciiHost)) {
     return true;
   }
 
   PRNetAddr tempAddr;
   memset(&tempAddr, 0, sizeof(PRNetAddr));
 
   if (PR_StringToNetAddr(PromiseFlatCString(aAsciiHost).get(), &tempAddr) !=
       PR_SUCCESS) {
@@ -243,19 +242,18 @@ bool nsMixedContentBlocker::IsPotentiall
   using namespace mozilla::net;
   NetAddr addr;
   PRNetAddrToNetAddr(&tempAddr, &addr);
 
   // Step 4 of
   // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy says
   // we should only consider [::1]/128 as a potentially trustworthy IPv6
   // address, whereas for IPv4 127.0.0.1/8 are considered as potentially
-  // trustworthy.  We already handled "[::1]" above, so all that's remained to
-  // handle here are IPv4 loopback addresses.
-  return IsIPAddrV4(&addr) && IsLoopBackAddress(&addr);
+  // trustworthy.
+  return IsLoopBackAddressWithoutIPv6Mapping(&addr);
 }
 
 bool nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(nsIURI* aURL) {
   nsAutoCString asciiHost;
   nsresult rv = aURL->GetAsciiHost(asciiHost);
   NS_ENSURE_SUCCESS(rv, false);
   return IsPotentiallyTrustworthyLoopbackHost(asciiHost);
 }
--- a/dom/security/nsMixedContentBlocker.h
+++ b/dom/security/nsMixedContentBlocker.h
@@ -29,16 +29,21 @@ enum MixedContentTypes {
 #include "nsIContentPolicy.h"
 #include "nsIChannel.h"
 #include "nsIChannelEventSink.h"
 #include "imgRequest.h"
 
 using mozilla::OriginAttributes;
 
 class nsILoadInfo;  // forward declaration
+namespace mozilla {
+namespace net {
+class nsProtocolProxyService;  // forward declaration
+}
+}  // namespace mozilla
 
 class nsMixedContentBlocker : public nsIContentPolicy,
                               public nsIChannelEventSink {
  private:
   virtual ~nsMixedContentBlocker();
 
  public:
   NS_DECL_ISUPPORTS
--- a/dom/security/test/gtest/TestSecureContext.cpp
+++ b/dom/security/test/gtest/TestSecureContext.cpp
@@ -19,32 +19,51 @@ using namespace mozilla;
 
 static const uint32_t kURIMaxLength = 64;
 
 struct TestExpectations {
   char uri[kURIMaxLength];
   bool expectedResult;
 };
 
+class MOZ_RAII AutoRestoreBoolPref final {
+ public:
+  AutoRestoreBoolPref(const char* aPref, bool aValue) : mPref(aPref) {
+    Preferences::GetBool(mPref, &mOldValue);
+    Preferences::SetBool(mPref, aValue);
+  }
+
+  ~AutoRestoreBoolPref() { Preferences::SetBool(mPref, mOldValue); }
+
+ private:
+  const char* mPref = nullptr;
+  bool mOldValue = false;
+};
+
 // ============================= TestDirectives ========================
 
 TEST(SecureContext, IsOriginPotentiallyTrustworthyWithContentPrincipal)
 {
   // boolean isOriginPotentiallyTrustworthy(in nsIPrincipal aPrincipal);
 
+  AutoRestoreBoolPref savedPref("network.proxy.allow_hijacking_localhost",
+                                false);
+
   static const TestExpectations uris[] = {
       {"http://example.com/", false},
       {"https://example.com/", true},
       {"ws://example.com/", false},
       {"wss://example.com/", true},
       {"file:///xyzzy", true},
       {"ftp://example.com", false},
       {"about:config", false},
       {"http://localhost", true},
-      {"http://xyzzy.localhost", false},
+      {"http://localhost.localhost", true},
+      {"http://a.b.c.d.e.localhost", true},
+      {"http://xyzzy.localhost", true},
       {"http://127.0.0.1", true},
       {"http://127.0.0.2", true},
       {"http://127.1.0.1", true},
       {"http://128.0.0.1", false},
       {"http://[::1]", true},
       {"http://[::ffff:127.0.0.1]", false},
       {"http://[::ffff:127.0.0.2]", false},
       {"http://[::ffff:7f00:1]", false},
@@ -66,17 +85,18 @@ TEST(SecureContext, IsOriginPotentiallyT
   nsresult rv;
   for (uint32_t i = 0; i < numExpectations; i++) {
     nsCOMPtr<nsIPrincipal> prin;
     nsAutoCString uri(uris[i].uri);
     rv = nsScriptSecurityManager::GetScriptSecurityManager()
              ->CreateContentPrincipalFromOrigin(uri, getter_AddRefs(prin));
     ASSERT_EQ(rv, NS_OK);
     bool isPotentiallyTrustworthy = prin->GetIsOriginPotentiallyTrustworthy();
-    ASSERT_EQ(isPotentiallyTrustworthy, uris[i].expectedResult);
+    ASSERT_EQ(isPotentiallyTrustworthy, uris[i].expectedResult)
+        << uris[i].uri << uris[i].expectedResult;
   }
 }
 
 TEST(SecureContext, IsOriginPotentiallyTrustworthyWithSystemPrincipal)
 {
   RefPtr<nsScriptSecurityManager> ssManager =
       nsScriptSecurityManager::GetScriptSecurityManager();
   ASSERT_TRUE(!!ssManager);
--- a/dom/security/test/unit/test_isOriginPotentiallyTrustworthy.js
+++ b/dom/security/test/unit/test_isOriginPotentiallyTrustworthy.js
@@ -33,16 +33,17 @@ Services.prefs.setCharPref(
 
 Services.prefs.setBoolPref("dom.securecontext.whitelist_onions", false);
 
 add_task(async function test_isOriginPotentiallyTrustworthy() {
   for (let [uriSpec, expectedResult] of [
     ["http://example.com/", false],
     ["https://example.com/", true],
     ["http://localhost/", true],
+    ["http://localhost.localhost/", true],
     ["http://127.0.0.1/", true],
     ["file:///", true],
     ["resource:///", true],
     ["moz-extension://", true],
     ["wss://example.com/", true],
     ["about:config", false],
     ["http://example.net/", true],
     ["ws://example.org/", true],
--- a/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js
+++ b/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js
@@ -16,16 +16,17 @@ add_task(function test_setup() {
   Services.prefs.setBoolPref(
     "security.webauth.webauthn_enable_softtoken",
     true
   );
   Services.prefs.setBoolPref(
     "security.webauth.webauthn_enable_usbtoken",
     false
   );
+  Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
 });
 
 registerCleanupFunction(async function() {
   Services.prefs.clearUserPref("security.webauth.u2f");
   Services.prefs.clearUserPref("security.webauth.webauthn");
   Services.prefs.clearUserPref(
     "security.webauth.webauthn_enable_android_fido2"
   );
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -7890,16 +7890,22 @@
   mirror: always
 
 # Enables the predictive service.
 - name: network.predictor.enabled
   type: bool
   value: true
   mirror: always
 
+# Set true to allow resolving proxy for localhost
+- name: network.proxy.allow_hijacking_localhost
+  type: RelaxedAtomicBool
+  value: false
+  mirror: always
+
 # Allow CookieJarSettings to be unblocked for channels without a document.
 # This is for testing only.
 - name: network.cookieJarSettings.unblocked_for_testing
   type: bool
   value: false
   mirror: always
 
 - name: network.predictor.enable-hover-on-ssl
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1907,18 +1907,16 @@ pref("network.proxy.http",              
 pref("network.proxy.http_port",             0);
 pref("network.proxy.ssl",                   "");
 pref("network.proxy.ssl_port",              0);
 pref("network.proxy.socks",                 "");
 pref("network.proxy.socks_port",            0);
 pref("network.proxy.socks_version",         5);
 pref("network.proxy.proxy_over_tls",        true);
 pref("network.proxy.no_proxies_on",         "");
-// Set true to allow resolving proxy for localhost
-pref("network.proxy.allow_hijacking_localhost", false);
 pref("network.proxy.failover_timeout",      1800); // 30 minutes
 pref("network.online",                      true); //online/offline
 
 // The interval in seconds to move the cookies in the child process.
 // Set to 0 to disable moving the cookies.
 pref("network.cookie.move.interval_sec",    10);
 
 // This pref contains the list of hostnames (such as
--- a/netwerk/base/nsProtocolProxyService.cpp
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -31,17 +31,19 @@
 #include "prnetdb.h"
 #include "nsPACMan.h"
 #include "nsProxyRelease.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/CondVar.h"
 #include "nsISystemProxySettings.h"
 #include "nsINetworkLinkService.h"
 #include "nsIHttpChannelInternal.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_network.h"
 #include "mozilla/Tokenizer.h"
 #include "mozilla/Unused.h"
 
 //----------------------------------------------------------------------------
 
 namespace mozilla {
 namespace net {
 
@@ -766,17 +768,16 @@ nsProtocolProxyService::nsProtocolProxyS
       mHTTPProxyPort(-1),
       mFTPProxyPort(-1),
       mHTTPSProxyPort(-1),
       mSOCKSProxyPort(-1),
       mSOCKSProxyVersion(4),
       mSOCKSProxyRemoteDNS(false),
       mProxyOverTLS(true),
       mWPADOverDHCPEnabled(false),
-      mAllowHijackingLocalhost(false),
       mPACMan(nullptr),
       mSessionStart(PR_Now()),
       mFailedProxyTimeout(30 * 60)  // 30 minute default
       ,
       mIsShutdown(false) {}
 
 nsProtocolProxyService::~nsProtocolProxyService() {
   // These should have been cleaned up in our Observe method.
@@ -1014,21 +1015,16 @@ void nsProtocolProxyService::PrefsChange
   }
 
   if (!pref || !strcmp(pref, PROXY_PREF("enable_wpad_over_dhcp"))) {
     proxy_GetBoolPref(prefBranch, PROXY_PREF("enable_wpad_over_dhcp"),
                       mWPADOverDHCPEnabled);
     reloadPAC = reloadPAC || mProxyConfig == PROXYCONFIG_WPAD;
   }
 
-  if (!pref || !strcmp(pref, PROXY_PREF("allow_hijacking_localhost"))) {
-    proxy_GetBoolPref(prefBranch, PROXY_PREF("allow_hijacking_localhost"),
-                      mAllowHijackingLocalhost);
-  }
-
   if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout")))
     proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"),
                      mFailedProxyTimeout);
 
   if (!pref || !strcmp(pref, PROXY_PREF("no_proxies_on"))) {
     rv = prefBranch->GetCharPref(PROXY_PREF("no_proxies_on"), tempString);
     if (NS_SUCCEEDED(rv)) LoadHostFilters(tempString);
   }
@@ -1092,19 +1088,22 @@ bool nsProtocolProxyService::CanUseProxy
     } else {
       NS_WARNING("unknown address family");
       return true;  // allow proxying
     }
   }
 
   // Don't use proxy for local hosts (plain hostname, no dots)
   if ((!is_ipaddr && mFilterLocalHosts && !host.Contains('.')) ||
-      (!mAllowHijackingLocalhost &&
-       (host.EqualsLiteral("127.0.0.1") || host.EqualsLiteral("::1") ||
-        host.EqualsLiteral("localhost")))) {
+      // This method detects if we have network.proxy.allow_hijacking_localhost
+      // pref enabled. If it's true then this method will always return false
+      // otherwise it returns true if the host matches an address that's
+      // hardcoded to the loopback address.
+      (!StaticPrefs::network_proxy_allow_hijacking_localhost() &&
+       nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host))) {
     LOG(("Not using proxy for this local host [%s]!\n", host.get()));
     return false;  // don't allow proxying
   }
 
   int32_t index = -1;
   while (++index < int32_t(mHostFiltersArray.Length())) {
     const auto& hinfo = mHostFiltersArray[index];
 
--- a/netwerk/base/nsProtocolProxyService.h
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -383,17 +383,16 @@ class nsProtocolProxyService final : pub
   // mSOCKSProxyTarget could be a host, a domain socket path,
   // or a named-pipe name.
   nsCString mSOCKSProxyTarget;
   int32_t mSOCKSProxyPort;
   int32_t mSOCKSProxyVersion;
   bool mSOCKSProxyRemoteDNS;
   bool mProxyOverTLS;
   bool mWPADOverDHCPEnabled;
-  bool mAllowHijackingLocalhost;
 
   RefPtr<nsPACMan> mPACMan;  // non-null if we are using PAC
   nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
 
   PRTime mSessionStart;
   nsFailedProxyTable mFailedProxies;
   int32_t mFailedProxyTimeout;
 
--- a/netwerk/dns/DNS.cpp
+++ b/netwerk/dns/DNS.cpp
@@ -1,19 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=4 sw=2 sts=2 et cin: */
 /* 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 "mozilla/net/DNS.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/mozalloc.h"
-#include "mozilla/ArrayUtils.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsContentUtils.h"
 #include "nsString.h"
 #include <string.h>
 
 #ifdef XP_WIN
 #  include "ws2tcpip.h"
 #endif
 
 namespace mozilla {
@@ -134,31 +136,56 @@ bool NetAddrToString(const NetAddr* addr
     memcpy(buf, addr->local.path, sizeof(addr->local.path));
     return true;
   }
 #endif
   return false;
 }
 
 bool IsLoopBackAddress(const NetAddr* addr) {
+  if (IsLoopBackAddressWithoutIPv6Mapping(addr)) {
+    return true;
+  }
+  if (addr->raw.family != AF_INET6) {
+    return false;
+  }
+
+  return IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip) &&
+         IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip) == htonl(INADDR_LOOPBACK);
+}
+
+bool IsLoopBackAddressWithoutIPv6Mapping(const NetAddr* addr) {
   if (addr->raw.family == AF_INET) {
     // Consider 127.0.0.1/8 as loopback
     uint32_t ipv4Addr = ntohl(addr->inet.ip);
     return (ipv4Addr >> 24) == 127;
   }
-  if (addr->raw.family == AF_INET6) {
-    if (IPv6ADDR_IS_LOOPBACK(&addr->inet6.ip)) {
-      return true;
-    }
-    if (IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip) &&
-        IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip) ==
-            htonl(INADDR_LOOPBACK)) {
-      return true;
-    }
+
+  if (addr->raw.family == AF_INET6 && IPv6ADDR_IS_LOOPBACK(&addr->inet6.ip)) {
+    return true;
   }
+
+  return false;
+}
+
+bool IsLoopbackHostname(const nsACString& aAsciiHost) {
+  // If the user has configured to proxy localhost addresses don't consider them
+  // to be secure
+  if (StaticPrefs::network_proxy_allow_hijacking_localhost()) {
+    return false;
+  }
+
+  nsAutoCString host;
+  nsContentUtils::ASCIIToLower(aAsciiHost, host);
+
+  if (host.EqualsLiteral("localhost") ||
+      StringEndsWith(host, NS_LITERAL_CSTRING(".localhost"))) {
+    return true;
+  }
+
   return false;
 }
 
 bool IsIPAddrAny(const NetAddr* addr) {
   if (addr->raw.family == AF_INET) {
     if (addr->inet.ip == htonl(INADDR_ANY)) {
       return true;
     }
--- a/netwerk/dns/DNS.h
+++ b/netwerk/dns/DNS.h
@@ -177,16 +177,20 @@ void PRNetAddrToNetAddr(const PRNetAddr*
 // Copies the contents of a NetAddr to a PRNetAddr.
 // Does not do a ptr safety check!
 void NetAddrToPRNetAddr(const NetAddr* addr, PRNetAddr* prAddr);
 
 bool NetAddrToString(const NetAddr* addr, char* buf, uint32_t bufSize);
 
 bool IsLoopBackAddress(const NetAddr* addr);
 
+bool IsLoopBackAddressWithoutIPv6Mapping(const NetAddr* addr);
+
+bool IsLoopbackHostname(const nsACString& aAsciiHost);
+
 bool IsIPAddrAny(const NetAddr* addr);
 
 bool IsIPAddrV4(const NetAddr* addr);
 
 bool IsIPAddrV4Mapped(const NetAddr* addr);
 
 bool IsIPAddrLocal(const NetAddr* addr);
 
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -840,21 +840,17 @@ nsresult nsHostResolver::GetHostRecord(c
                                        uint16_t af, bool pb,
                                        const nsCString& originSuffix,
                                        nsHostRecord** result) {
   MutexAutoLock lock(mLock);
   nsHostKey key(host, aTrrServer, type, flags, af, pb, originSuffix);
 
   RefPtr<nsHostRecord>& entry = mRecordDB.GetOrInsert(key);
   if (!entry) {
-    if (IS_ADDR_TYPE(type)) {
-      entry = new AddrHostRecord(key);
-    } else {
-      entry = new TypeHostRecord(key);
-    }
+    entry = InitRecord(key);
   }
 
   RefPtr<nsHostRecord> rec = entry;
   if (rec->IsAddrRecord()) {
     RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
     if (addrRec->addr) {
       return NS_ERROR_FAILURE;
     }
@@ -863,16 +859,58 @@ nsresult nsHostResolver::GetHostRecord(c
   if (rec->mResolving) {
     return NS_ERROR_FAILURE;
   }
 
   *result = rec.forget().take();
   return NS_OK;
 }
 
+nsHostRecord* nsHostResolver::InitRecord(const nsHostKey& key) {
+  if (IS_ADDR_TYPE(key.type)) {
+    return new AddrHostRecord(key);
+  }
+  return new TypeHostRecord(key);
+}
+
+already_AddRefed<nsHostRecord> nsHostResolver::InitLoopbackRecord(
+    const nsHostKey& key, nsresult* aRv) {
+  MOZ_ASSERT(aRv);
+  MOZ_ASSERT(IS_ADDR_TYPE(key.type));
+
+  *aRv = NS_ERROR_FAILURE;
+  RefPtr<nsHostRecord> rec = InitRecord(key);
+
+  RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+  MutexAutoLock lock(addrRec->addr_info_lock);
+
+  PRNetAddr prAddr;
+
+  if (key.af == PR_AF_INET) {
+    MOZ_RELEASE_ASSERT(PR_StringToNetAddr("127.0.0.1", &prAddr) == PR_SUCCESS);
+  } else {
+    MOZ_RELEASE_ASSERT(PR_StringToNetAddr("::1", &prAddr) == PR_SUCCESS);
+  }
+
+  RefPtr<AddrInfo> ai;
+  *aRv = GetAddrInfo(rec->host, rec->af, addrRec->flags, getter_AddRefs(ai),
+                     addrRec->mGetTtl);
+  if (NS_WARN_IF(NS_FAILED(*aRv))) {
+    return nullptr;
+  }
+
+  addrRec->addr_info = ai;
+  addrRec->SetExpiration(TimeStamp::NowLoRes(), mDefaultCacheLifetime,
+                         mDefaultGracePeriod);
+  addrRec->negative = false;
+
+  *aRv = NS_OK;
+  return rec.forget();
+}
+
 nsresult nsHostResolver::ResolveHost(const nsACString& aHost,
                                      const nsACString& aTrrServer,
                                      uint16_t type,
                                      const OriginAttributes& aOriginAttributes,
                                      uint16_t flags, uint16_t af,
                                      nsResolveHostCallback* aCallback) {
   nsAutoCString host(aHost);
   NS_ENSURE_TRUE(!host.IsEmpty(), NS_ERROR_UNEXPECTED);
@@ -910,276 +948,284 @@ nsresult nsHostResolver::ResolveHost(con
   // if result is set inside the lock, then we need to issue the
   // callback before returning.
   RefPtr<nsHostRecord> result;
   nsresult status = NS_OK, rv = NS_OK;
   {
     MutexAutoLock lock(mLock);
 
     if (mShutdown) {
-      rv = NS_ERROR_NOT_INITIALIZED;
-    } else {
-      // check to see if there is already an entry for this |host|
-      // in the hash table.  if so, then check to see if we can't
-      // just reuse the lookup result.  otherwise, if there are
-      // any pending callbacks, then add to pending callbacks queue,
-      // and return.  otherwise, add ourselves as first pending
-      // callback, and proceed to do the lookup.
+      return NS_ERROR_NOT_INITIALIZED;
+    }
+
+    // check to see if there is already an entry for this |host|
+    // in the hash table.  if so, then check to see if we can't
+    // just reuse the lookup result.  otherwise, if there are
+    // any pending callbacks, then add to pending callbacks queue,
+    // and return.  otherwise, add ourselves as first pending
+    // callback, and proceed to do the lookup.
+
+    if (gTRRService && gTRRService->IsExcludedFromTRR(host)) {
+      flags |= RES_DISABLE_TRR;
+
+      if (!aTrrServer.IsEmpty()) {
+        return NS_ERROR_UNKNOWN_HOST;
+      }
+    }
+
+    nsHostKey key(host, aTrrServer, type, flags, af,
+                  (aOriginAttributes.mPrivateBrowsingId > 0), originSuffix);
+
+    // Check if we have a localhost domain, if so hardcode to loopback
+    if (IS_ADDR_TYPE(type) && IsLoopbackHostname(host)) {
+      nsresult rv;
+      RefPtr<nsHostRecord> result = InitLoopbackRecord(key, &rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      MOZ_ASSERT(result);
+      aCallback->OnResolveHostComplete(this, result, NS_OK);
+      return NS_OK;
+    }
 
-      if (gTRRService && gTRRService->IsExcludedFromTRR(host)) {
-        flags |= RES_DISABLE_TRR;
+    RefPtr<nsHostRecord>& entry = mRecordDB.GetOrInsert(key);
+    if (!entry) {
+      entry = InitRecord(key);
+    }
+
+    RefPtr<nsHostRecord> rec = entry;
+    RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+    MOZ_ASSERT(rec, "Record should not be null");
+    MOZ_ASSERT((IS_ADDR_TYPE(type) && rec->IsAddrRecord() && addrRec) ||
+               (IS_OTHER_TYPE(type) && !rec->IsAddrRecord()));
 
-        if (!aTrrServer.IsEmpty()) {
-          return NS_ERROR_UNKNOWN_HOST;
+    if (!(flags & RES_BYPASS_CACHE) &&
+        rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
+      LOG(("  Using cached record for host [%s].\n", host.get()));
+      // put reference to host record on stack...
+      result = rec;
+      if (IS_ADDR_TYPE(type)) {
+        Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
+      }
+
+      // For entries that are in the grace period
+      // or all cached negative entries, use the cache but start a new
+      // lookup in the background
+      ConditionallyRefreshRecord(rec, host);
+
+      if (rec->negative) {
+        LOG(("  Negative cache entry for host [%s].\n", host.get()));
+        if (IS_ADDR_TYPE(type)) {
+          Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+                                METHOD_NEGATIVE_HIT);
         }
+        status = NS_ERROR_UNKNOWN_HOST;
       }
 
-      nsHostKey key(host, aTrrServer, type, flags, af,
-                    (aOriginAttributes.mPrivateBrowsingId > 0), originSuffix);
-      RefPtr<nsHostRecord>& entry = mRecordDB.GetOrInsert(key);
-      if (!entry) {
-        if (IS_ADDR_TYPE(type)) {
-          entry = new AddrHostRecord(key);
-        } else {
-          entry = new TypeHostRecord(key);
-        }
+      // Check whether host is a IP address for A/AAAA queries.
+      // For by-type records we have already checked at the beginning of
+      // this function.
+    } else if (addrRec && addrRec->addr) {
+      // if the host name is an IP address literal and has been
+      // parsed, go ahead and use it.
+      LOG(("  Using cached address for IP Literal [%s].\n", host.get()));
+      Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL);
+      result = rec;
+    } else if (addrRec &&
+               PR_StringToNetAddr(host.get(), &tempAddr) == PR_SUCCESS) {
+      // try parsing the host name as an IP address literal to short
+      // circuit full host resolution.  (this is necessary on some
+      // platforms like Win9x.  see bug 219376 for more details.)
+      LOG(("  Host is IP Literal [%s].\n", host.get()));
+
+      // ok, just copy the result into the host record, and be
+      // done with it! ;-)
+      addrRec->addr = MakeUnique<NetAddr>();
+      PRNetAddrToNetAddr(&tempAddr, addrRec->addr.get());
+      // put reference to host record on stack...
+      Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL);
+      result = rec;
+
+      // Check if we have received too many requests.
+    } else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
+               !IsHighPriority(flags) && !rec->mResolving) {
+      LOG(
+          ("  Lookup queue full: dropping %s priority request for "
+           "host [%s].\n",
+           IsMediumPriority(flags) ? "medium" : "low", host.get()));
+      if (IS_ADDR_TYPE(type)) {
+        Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_OVERFLOW);
       }
+      // This is a lower priority request and we are swamped, so refuse it.
+      rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
 
-      RefPtr<nsHostRecord> rec = entry;
-      RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
-      MOZ_ASSERT(rec, "Record should not be null");
-      MOZ_ASSERT((IS_ADDR_TYPE(type) && rec->IsAddrRecord() && addrRec) ||
-                 (IS_OTHER_TYPE(type) && !rec->IsAddrRecord()));
+      // Check if the offline flag is set.
+    } else if (flags & RES_OFFLINE) {
+      LOG(("  Offline request for host [%s]; ignoring.\n", host.get()));
+      rv = NS_ERROR_OFFLINE;
+
+      // We do not have a valid result till here.
+      // A/AAAA request can check for an alternative entry like AF_UNSPEC.
+      // Otherwise we need to start a new query.
+    } else if (!rec->mResolving) {
+      // If this is an IPV4 or IPV6 specific request, check if there is
+      // an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
+      if (addrRec && !(flags & RES_BYPASS_CACHE) &&
+          ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
+        // Check for an AF_UNSPEC entry.
+
+        const nsHostKey unspecKey(
+            host, aTrrServer, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags,
+            PR_AF_UNSPEC, (aOriginAttributes.mPrivateBrowsingId > 0),
+            originSuffix);
+        RefPtr<nsHostRecord> unspecRec = mRecordDB.Get(unspecKey);
 
-      // Check if the entry is vaild.
-      if (!(flags & RES_BYPASS_CACHE) &&
-          rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
-        LOG(("  Using cached record for host [%s].\n", host.get()));
-        // put reference to host record on stack...
-        result = rec;
-        if (IS_ADDR_TYPE(type)) {
-          Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
-        }
+        TimeStamp now = TimeStamp::NowLoRes();
+        if (unspecRec && unspecRec->HasUsableResult(now, flags)) {
+          MOZ_ASSERT(unspecRec->IsAddrRecord());
+
+          RefPtr<AddrHostRecord> addrUnspecRec = do_QueryObject(unspecRec);
+          MOZ_ASSERT(addrUnspecRec);
+          MOZ_ASSERT(addrUnspecRec->addr_info || addrUnspecRec->negative,
+                     "Entry should be resolved or negative.");
+
+          LOG(("  Trying AF_UNSPEC entry for host [%s] af: %s.\n", host.get(),
+               (af == PR_AF_INET) ? "AF_INET" : "AF_INET6"));
+
+          // We need to lock in case any other thread is reading
+          // addr_info.
+          MutexAutoLock lock(addrRec->addr_info_lock);
 
-        // For entries that are in the grace period
-        // or all cached negative entries, use the cache but start a new
-        // lookup in the background
-        ConditionallyRefreshRecord(rec, host);
-
-        if (rec->negative) {
-          LOG(("  Negative cache entry for host [%s].\n", host.get()));
-          if (IS_ADDR_TYPE(type)) {
+          addrRec->addr_info = nullptr;
+          addrRec->addr_info_gencnt++;
+          if (unspecRec->negative) {
+            rec->negative = unspecRec->negative;
+            rec->CopyExpirationTimesAndFlagsFrom(unspecRec);
+          } else if (addrUnspecRec->addr_info) {
+            // Search for any valid address in the AF_UNSPEC entry
+            // in the cache (not blacklisted and from the right
+            // family).
+            NetAddrElement* addrIter =
+                addrUnspecRec->addr_info->mAddresses.getFirst();
+            while (addrIter) {
+              if ((af == addrIter->mAddress.inet.family) &&
+                  !addrUnspecRec->Blacklisted(&addrIter->mAddress)) {
+                if (!addrRec->addr_info) {
+                  addrRec->addr_info =
+                      new AddrInfo(addrUnspecRec->addr_info->mHostName,
+                                   addrUnspecRec->addr_info->mCanonicalName,
+                                   addrUnspecRec->addr_info->IsTRR());
+                  addrRec->addr_info_gencnt++;
+                  rec->CopyExpirationTimesAndFlagsFrom(unspecRec);
+                }
+                addrRec->addr_info->AddAddress(new NetAddrElement(*addrIter));
+              }
+              addrIter = addrIter->getNext();
+            }
+          }
+          // Now check if we have a new record.
+          if (rec->HasUsableResult(now, flags)) {
+            result = rec;
+            if (rec->negative) {
+              status = NS_ERROR_UNKNOWN_HOST;
+            }
+            Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
+            ConditionallyRefreshRecord(rec, host);
+          } else if (af == PR_AF_INET6) {
+            // For AF_INET6, a new lookup means another AF_UNSPEC
+            // lookup. We have already iterated through the
+            // AF_UNSPEC addresses, so we mark this record as
+            // negative.
+            LOG(
+                ("  No AF_INET6 in AF_UNSPEC entry: "
+                 "host [%s] unknown host.",
+                 host.get()));
+            result = rec;
+            rec->negative = true;
+            status = NS_ERROR_UNKNOWN_HOST;
             Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
                                   METHOD_NEGATIVE_HIT);
           }
-          status = NS_ERROR_UNKNOWN_HOST;
+        }
+      }
+
+      // If this is a by-type request or if no valid record was found
+      // in the cache or this is an AF_UNSPEC request, then start a
+      // new lookup.
+      if (!result) {
+        LOG(("  No usable record in cache for host [%s] type %d.", host.get(),
+             type));
+
+        if (flags & RES_REFRESH_CACHE) {
+          rec->Invalidate();
         }
 
-        // Check whether host is a IP address for A/AAAA queries.
-        // For by-type records we have already checked at the beginning of
-        // this function.
-      } else if (addrRec && addrRec->addr) {
-        // if the host name is an IP address literal and has been
-        // parsed, go ahead and use it.
-        LOG(("  Using cached address for IP Literal [%s].\n", host.get()));
-        Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL);
-        result = rec;
-      } else if (addrRec &&
-                 PR_StringToNetAddr(host.get(), &tempAddr) == PR_SUCCESS) {
-        // try parsing the host name as an IP address literal to short
-        // circuit full host resolution.  (this is necessary on some
-        // platforms like Win9x.  see bug 219376 for more details.)
-        LOG(("  Host is IP Literal [%s].\n", host.get()));
-
-        // ok, just copy the result into the host record, and be
-        // done with it! ;-)
-        addrRec->addr = MakeUnique<NetAddr>();
-        PRNetAddrToNetAddr(&tempAddr, addrRec->addr.get());
-        // put reference to host record on stack...
-        Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL);
-        result = rec;
-
-        // Check if we have received too many requests.
-      } else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
-                 !IsHighPriority(flags) && !rec->mResolving) {
-        LOG(
-            ("  Lookup queue full: dropping %s priority request for "
-             "host [%s].\n",
-             IsMediumPriority(flags) ? "medium" : "low", host.get()));
+        // Add callback to the list of pending callbacks.
+        rec->mCallbacks.insertBack(callback);
+        rec->flags = flags;
+        rv = NameLookup(rec);
         if (IS_ADDR_TYPE(type)) {
-          Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_OVERFLOW);
+          Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+                                METHOD_NETWORK_FIRST);
         }
-        // This is a lower priority request and we are swamped, so refuse it.
-        rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+        if (NS_FAILED(rv) && callback->isInList()) {
+          callback->remove();
+        } else {
+          LOG(
+              ("  DNS lookup for host [%s] blocking "
+               "pending 'getaddrinfo' or trr query: "
+               "callback [%p]",
+               host.get(), callback.get()));
+        }
+      }
 
-        // Check if the offline flag is set.
-      } else if (flags & RES_OFFLINE) {
-        LOG(("  Offline request for host [%s]; ignoring.\n", host.get()));
-        rv = NS_ERROR_OFFLINE;
-
-        // We do not have a valid result till here.
-        // A/AAAA request can check for an alternative entry like AF_UNSPEC.
-        // Otherwise we need to start a new query.
-      } else if (!rec->mResolving) {
-        // If this is an IPV4 or IPV6 specific request, check if there is
-        // an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
-        if (addrRec && !(flags & RES_BYPASS_CACHE) &&
-            ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
-          // Check for an AF_UNSPEC entry.
-
-          const nsHostKey unspecKey(
-              host, aTrrServer, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags,
-              PR_AF_UNSPEC, (aOriginAttributes.mPrivateBrowsingId > 0),
-              originSuffix);
-          RefPtr<nsHostRecord> unspecRec = mRecordDB.Get(unspecKey);
-
-          TimeStamp now = TimeStamp::NowLoRes();
-          if (unspecRec && unspecRec->HasUsableResult(now, flags)) {
-            MOZ_ASSERT(unspecRec->IsAddrRecord());
-
-            RefPtr<AddrHostRecord> addrUnspecRec = do_QueryObject(unspecRec);
-            MOZ_ASSERT(addrUnspecRec);
-            MOZ_ASSERT(addrUnspecRec->addr_info || addrUnspecRec->negative,
-                       "Entry should be resolved or negative.");
-
-            LOG(("  Trying AF_UNSPEC entry for host [%s] af: %s.\n", host.get(),
-                 (af == PR_AF_INET) ? "AF_INET" : "AF_INET6"));
-
-            // We need to lock in case any other thread is reading
-            // addr_info.
-            MutexAutoLock lock(addrRec->addr_info_lock);
+    } else if (addrRec && addrRec->mDidCallbacks) {
+      // This is only for A/AAAA query.
+      // record is still pending more (TRR) data; make the callback
+      // at once
+      result = rec;
+      // make it count as a hit
+      Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
 
-            addrRec->addr_info = nullptr;
-            addrRec->addr_info_gencnt++;
-            if (unspecRec->negative) {
-              rec->negative = unspecRec->negative;
-              rec->CopyExpirationTimesAndFlagsFrom(unspecRec);
-            } else if (addrUnspecRec->addr_info) {
-              // Search for any valid address in the AF_UNSPEC entry
-              // in the cache (not blacklisted and from the right
-              // family).
-              NetAddrElement* addrIter =
-                  addrUnspecRec->addr_info->mAddresses.getFirst();
-              while (addrIter) {
-                if ((af == addrIter->mAddress.inet.family) &&
-                    !addrUnspecRec->Blacklisted(&addrIter->mAddress)) {
-                  if (!addrRec->addr_info) {
-                    addrRec->addr_info =
-                        new AddrInfo(addrUnspecRec->addr_info->mHostName,
-                                     addrUnspecRec->addr_info->mCanonicalName,
-                                     addrUnspecRec->addr_info->IsTRR());
-                    addrRec->addr_info_gencnt++;
-                    rec->CopyExpirationTimesAndFlagsFrom(unspecRec);
-                  }
-                  addrRec->addr_info->AddAddress(new NetAddrElement(*addrIter));
-                }
-                addrIter = addrIter->getNext();
-              }
-            }
-            // Now check if we have a new record.
-            if (rec->HasUsableResult(now, flags)) {
-              result = rec;
-              if (rec->negative) {
-                status = NS_ERROR_UNKNOWN_HOST;
-              }
-              Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
-              ConditionallyRefreshRecord(rec, host);
-            } else if (af == PR_AF_INET6) {
-              // For AF_INET6, a new lookup means another AF_UNSPEC
-              // lookup. We have already iterated through the
-              // AF_UNSPEC addresses, so we mark this record as
-              // negative.
-              LOG(
-                  ("  No AF_INET6 in AF_UNSPEC entry: "
-                   "host [%s] unknown host.",
-                   host.get()));
-              result = rec;
-              rec->negative = true;
-              status = NS_ERROR_UNKNOWN_HOST;
-              Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
-                                    METHOD_NEGATIVE_HIT);
-            }
-          }
-        }
+      LOG(("  Host [%s] re-using early TRR resolve data\n", host.get()));
+    } else {
+      LOG(
+          ("  Host [%s] is being resolved. Appending callback "
+           "[%p].",
+           host.get(), callback.get()));
 
-        // If this is a by-type request or if no valid record was found
-        // in the cache or this is an AF_UNSPEC request, then start a
-        // new lookup.
-        if (!result) {
-          LOG(("  No usable record in cache for host [%s] type %d.", host.get(),
-               type));
+      rec->mCallbacks.insertBack(callback);
 
-          if (flags & RES_REFRESH_CACHE) {
-            rec->Invalidate();
-          }
+      // Only A/AAAA records are place in a queue. The queues are for
+      // the native resolver, therefore by-type request are never put
+      // into a queue.
+      if (addrRec && addrRec->onQueue) {
+        Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+                              METHOD_NETWORK_SHARED);
 
-          // Add callback to the list of pending callbacks.
-          rec->mCallbacks.insertBack(callback);
-          rec->flags = flags;
-          rv = NameLookup(rec);
-          if (IS_ADDR_TYPE(type)) {
-            Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
-                                  METHOD_NETWORK_FIRST);
-          }
-          if (NS_FAILED(rv) && callback->isInList()) {
-            callback->remove();
-          } else {
-            LOG(
-                ("  DNS lookup for host [%s] blocking "
-                 "pending 'getaddrinfo' or trr query: "
-                 "callback [%p]",
-                 host.get(), callback.get()));
-          }
-        }
-
-      } else if (addrRec && addrRec->mDidCallbacks) {
-        // This is only for A/AAAA query.
-        // record is still pending more (TRR) data; make the callback
-        // at once
-        result = rec;
-        // make it count as a hit
-        Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
+        // Consider the case where we are on a pending queue of
+        // lower priority than the request is being made at.
+        // In that case we should upgrade to the higher queue.
 
-        LOG(("  Host [%s] re-using early TRR resolve data\n", host.get()));
-      } else {
-        LOG(
-            ("  Host [%s] is being resolved. Appending callback "
-             "[%p].",
-             host.get(), callback.get()));
-
-        rec->mCallbacks.insertBack(callback);
-
-        // Only A/AAAA records are place in a queue. The queues are for
-        // the native resolver, therefore by-type request are never put
-        // into a queue.
-        if (addrRec && addrRec->onQueue) {
-          Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
-                                METHOD_NETWORK_SHARED);
-
-          // Consider the case where we are on a pending queue of
-          // lower priority than the request is being made at.
-          // In that case we should upgrade to the higher queue.
-
-          if (IsHighPriority(flags) && !IsHighPriority(rec->flags)) {
-            // Move from (low|med) to high.
-            NS_ASSERTION(addrRec->onQueue,
-                         "Moving Host Record Not Currently Queued");
-            rec->remove();
-            mHighQ.insertBack(rec);
-            rec->flags = flags;
-            ConditionallyCreateThread(rec);
-          } else if (IsMediumPriority(flags) && IsLowPriority(rec->flags)) {
-            // Move from low to med.
-            NS_ASSERTION(addrRec->onQueue,
-                         "Moving Host Record Not Currently Queued");
-            rec->remove();
-            mMediumQ.insertBack(rec);
-            rec->flags = flags;
-            mIdleTaskCV.Notify();
-          }
+        if (IsHighPriority(flags) && !IsHighPriority(rec->flags)) {
+          // Move from (low|med) to high.
+          NS_ASSERTION(addrRec->onQueue,
+                       "Moving Host Record Not Currently Queued");
+          rec->remove();
+          mHighQ.insertBack(rec);
+          rec->flags = flags;
+          ConditionallyCreateThread(rec);
+        } else if (IsMediumPriority(flags) && IsLowPriority(rec->flags)) {
+          // Move from low to med.
+          NS_ASSERTION(addrRec->onQueue,
+                       "Moving Host Record Not Currently Queued");
+          rec->remove();
+          mMediumQ.insertBack(rec);
+          rec->flags = flags;
+          mIdleTaskCV.Notify();
         }
       }
     }
 
     if (result && callback->isInList()) {
       callback->remove();
     }
   }  // lock
--- a/netwerk/dns/nsHostResolver.h
+++ b/netwerk/dns/nsHostResolver.h
@@ -440,16 +440,24 @@ class nsHostResolver : public nsISupport
    * having the callback implementation return without doing anything).
    */
   nsresult ResolveHost(const nsACString& hostname, const nsACString& trrServer,
                        uint16_t type,
                        const mozilla::OriginAttributes& aOriginAttributes,
                        uint16_t flags, uint16_t af,
                        nsResolveHostCallback* callback);
 
+  nsHostRecord* InitRecord(const nsHostKey& key);
+
+  /**
+   * return a resolved hard coded loopback dns record for the specified key
+   */
+  already_AddRefed<nsHostRecord> InitLoopbackRecord(const nsHostKey& key,
+                                                    nsresult* aRv);
+
   /**
    * removes the specified callback from the nsHostRecord for the given
    * hostname, originAttributes, flags, and address family.  these parameters
    * should correspond to the parameters passed to ResolveHost.  this function
    * executes the callback if the callback is still pending with the given
    * status.
    */
   void DetachCallback(const nsACString& hostname, const nsACString& trrServer,
--- a/netwerk/test/unit/test_about_networking.js
+++ b/netwerk/test/unit/test_about_networking.js
@@ -93,25 +93,29 @@ add_test(function test_sockets() {
 });
 
 function run_test() {
   Services.prefs.setBoolPref(
     "network.cookieJarSettings.unblocked_for_testing",
     true
   );
 
+  // We always resolve localhost as it's hardcoded without the following pref:
+  Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
   let ioService = Cc["@mozilla.org/network/io-service;1"].getService(
     Ci.nsIIOService
   );
 
   gHttpServer.start(-1);
 
   let uri = ioService.newURI(
     "http://localhost:" + gHttpServer.identity.primaryPort
   );
   let channel = NetUtil.newChannel({ uri, loadUsingSystemPrincipal: true });
 
   channel.open();
 
   gServerSocket.init(-1, true, -1);
+  Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
 
   run_next_test();
 }
--- a/netwerk/test/unit/test_dns_offline.js
+++ b/netwerk/test/unit/test_dns_offline.js
@@ -40,16 +40,18 @@ var listener3 = {
   },
 };
 
 const defaultOriginAttributes = {};
 
 function run_test() {
   do_test_pending();
   prefs.setBoolPref("network.dns.offline-localhost", false);
+  // We always resolve localhost as it's hardcoded without the following pref:
+  prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
   ioService.offline = true;
   try {
     dns.asyncResolve(
       "localhost",
       0,
       listener1,
       mainThread,
       defaultOriginAttributes
@@ -94,9 +96,10 @@ function test3Continued() {
     listener3,
     mainThread,
     defaultOriginAttributes
   );
 }
 
 function cleanup() {
   prefs.clearUserPref("network.dns.offline-localhost");
+  prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
 }
--- a/netwerk/test/unit/test_dns_originAttributes.js
+++ b/netwerk/test/unit/test_dns_originAttributes.js
@@ -1,14 +1,17 @@
 "use strict";
 
 var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
 var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
   Ci.nsIThreadManager
 );
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+  Ci.nsIPrefBranch
+);
 var mainThread = threadManager.currentThread;
 
 var listener1 = {
   onLookupComplete(inRequest, inRecord, inStatus) {
     Assert.equal(inStatus, Cr.NS_OK);
     var answer = inRecord.getNextAddrAsString();
     Assert.ok(answer == "127.0.0.1" || answer == "::1");
     test2();
@@ -61,21 +64,23 @@ function test2() {
   );
 }
 
 // Third, we resolve the same address offline again with different originAttributes.
 // This resolving should fail since the DNS cache of the given address is not exist
 // for this originAttributes.
 function test3() {
   do_test_pending();
+  prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
   try {
     dns.asyncResolve(
       "localhost",
       Ci.nsIDNSService.RESOLVE_OFFLINE,
       listener3,
       mainThread,
       secondOriginAttributes
     );
   } catch (e) {
     Assert.equal(e.result, Cr.NS_ERROR_OFFLINE);
+    prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
     do_test_finished();
   }
 }
--- a/netwerk/test/unit/test_ping_aboutnetworking.js
+++ b/netwerk/test/unit/test_ping_aboutnetworking.js
@@ -53,16 +53,18 @@ function test_sockets(serverSocket) {
 
 function run_test() {
   var ps = Cc["@mozilla.org/preferences-service;1"].getService(
     Ci.nsIPrefBranch
   );
   // disable network changed events to avoid the the risk of having the dns
   // cache getting flushed behind our back
   ps.setBoolPref("network.notify.changed", false);
+  // Localhost is hardcoded to loopback and isn't cached, disable that with this pref
+  ps.setBoolPref("network.proxy.allow_hijacking_localhost", true);
 
   registerCleanupFunction(function() {
     ps.clearUserPref("network.notify.changed");
   });
 
   let serverSocket = Cc["@mozilla.org/network/server-socket;1"].createInstance(
     Ci.nsIServerSocket
   );
--- a/netwerk/test/unit/test_trr.js
+++ b/netwerk/test/unit/test_trr.js
@@ -1005,25 +1005,27 @@ add_task(async function test24k() {
   );
   await new DNSListener("bar.example.com", "127.0.0.1");
 });
 
 // TRR-only that resolving localhost with TRR-only mode will use the remote
 // resolver if it's not in the excluded domains
 add_task(async function test25() {
   dns.clearCache(true);
+  Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); // Disable localhost hardcoding
   Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
   Services.prefs.setCharPref("network.trr.excluded-domains", "");
   Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
   Services.prefs.setCharPref(
     "network.trr.uri",
     `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
   );
 
   await new DNSListener("localhost", "192.192.192.192", true);
+  Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
 });
 
 // TRR-only check that localhost goes directly to native lookup when in the excluded-domains
 add_task(async function test25b() {
   dns.clearCache(true);
   Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
   Services.prefs.setCharPref("network.trr.excluded-domains", "localhost");
   Services.prefs.setCharPref(