Bug 1220810 - Hardcode localhost to loopback, r=ckerschb,necko-reviewers,dragana
authorFrédéric Wang <fwang@igalia.com>
Thu, 22 Oct 2020 07:36:15 +0000
changeset 554016 dfcb025567da9e33bf724520e0146fef3d776d5f
parent 554015 0abf4e0c4d227e757b0149dd72f35cc03ae54e85
child 554017 7e223284a9225c66b590aaad671c7448d1ff0b57
push id129181
push userrmaries@mozilla.com
push dateThu, 22 Oct 2020 08:41:52 +0000
treeherderautoland@dfcb025567da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, necko-reviewers, dragana
bugs1220810
milestone84.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,necko-reviewers,dragana This patch make localhost addresses resolve to a loopback address, thereby ensuring that we can safely treat http://localhost/ and http://*.localhost/ as "Potentially Trustworthy". This addresses various bug reports from developers and aligns with specifications. See https://groups.google.com/g/mozilla.dev.platform/c/sZdEYTiEBdE Differential Revision: https://phabricator.services.mozilla.com/D92716
docshell/test/browser/browser_badCertDomainFixup.js
dom/base/test/unit/test_error_codes.js
dom/media/webrtc/tests/mochitests/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/httpserver/test/test_seizepower.js
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_http3.js
netwerk/test/unit/test_http3_421.js
netwerk/test/unit/test_http3_alt_svc.js
netwerk/test/unit/test_http3_perf.js
netwerk/test/unit/test_ping_aboutnetworking.js
netwerk/test/unit/xpcshell.ini
testing/web-platform/meta/css/css-font-loading/fontface-override-descriptor-getter-setter.sub.html.ini
--- a/docshell/test/browser/browser_badCertDomainFixup.js
+++ b/docshell/test/browser/browser_badCertDomainFixup.js
@@ -4,16 +4,18 @@
 "use strict";
 
 // This test checks if we are correctly fixing https URLs by prefixing
 // with www. when we encounter a SSL_ERROR_BAD_CERT_DOMAIN error.
 // For example, https://example.com -> https://www.example.com.
 
 const PREF_BAD_CERT_DOMAIN_FIX_ENABLED =
   "security.bad_cert_domain_error.url_fix_enabled";
+const PREF_ALLOW_HIJACKING_LOCALHOST =
+  "network.proxy.allow_hijacking_localhost";
 
 const BAD_CERT_DOMAIN_ERROR_URL = "https://badcertdomain.example.com:443";
 const FIXED_URL = "https://www.badcertdomain.example.com/";
 
 const BAD_CERT_DOMAIN_ERROR_URL2 =
   "https://mismatch.badcertdomain.example.com:443";
 const IPV4_ADDRESS = "https://127.0.0.3:433";
 const BAD_CERT_DOMAIN_ERROR_PORT = "https://badcertdomain.example.com:82";
@@ -72,17 +74,19 @@ add_task(async function ignoreBadCertDom
   Services.prefs.setBoolPref(PREF_BAD_CERT_DOMAIN_FIX_ENABLED, true);
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
   // Test for when "www." form is not present in the certificate.
   await verifyErrorPage(BAD_CERT_DOMAIN_ERROR_URL2);
   info("Certificate error was shown as expected");
 
   // Test that urls with IP addresses are not fixed.
+  Services.prefs.setBoolPref(PREF_ALLOW_HIJACKING_LOCALHOST, true);
   await verifyErrorPage(IPV4_ADDRESS);
+  Services.prefs.clearUserPref(PREF_ALLOW_HIJACKING_LOCALHOST);
   info("Certificate error was shown as expected for an IP address");
 
   // Test that urls with ports are not fixed.
   await verifyErrorPage(BAD_CERT_DOMAIN_ERROR_PORT);
   info("Certificate error was shown as expected for a host with port");
 
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- 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/webrtc/tests/mochitests/mochitest.ini
+++ b/dom/media/webrtc/tests/mochitests/mochitest.ini
@@ -22,17 +22,19 @@ support-files =
   !/dom/canvas/test/captureStream_common.js
   !/dom/canvas/test/webgl-mochitest/webgl-util.js
   !/dom/media/test/manifest.js
   !/dom/media/test/320x240.ogv
   !/dom/media/test/r11025_s16_c1.wav
   !/dom/media/test/bug461281.ogg
   !/dom/media/test/seek.webm
   !/dom/media/test/gizmo.mp4
-prefs = privacy.partition.network_state=false
+prefs =
+  privacy.partition.network_state=false
+  network.proxy.allow_hijacking_localhost=true
 
 [test_1488832.html]
 [test_a_noOp.html]
 scheme=http
 [test_dataChannel_basicAudio.html]
 [test_dataChannel_basicAudioVideo.html]
 [test_dataChannel_basicAudioVideoNoBundle.html]
 [test_dataChannel_basicAudioVideoCombined.html]
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -224,18 +224,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) {
@@ -244,19 +243,18 @@ bool nsMixedContentBlocker::IsPotentiall
 
   using namespace mozilla::net;
   NetAddr addr(&tempAddr);
 
   // 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 addr.IsIPAddrV4() && addr.IsLoopbackAddr();
+  // trustworthy.
+  return addr.IsLoopBackAddressWithoutIPv6Mapping();
 }
 
 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,26 +16,28 @@ 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"
   );
   Services.prefs.clearUserPref("security.webauth.webauthn_enable_softtoken");
   Services.prefs.clearUserPref("security.webauth.webauthn_enable_usbtoken");
+  Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
 });
 
 add_task(async function test_appid() {
   // 127.0.0.1 triggers special cases in ssltunnel, so let's use .2!
   const TEST_URL = "https://127.0.0.2/";
 
   // Open a new tab.
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -8208,16 +8208,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
@@ -1942,18 +1942,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 {
@@ -135,33 +137,51 @@ bool NetAddr::ToStringBuffer(char* buf, 
     memcpy(buf, addr->local.path, sizeof(addr->local.path));
     return true;
   }
 #endif
   return false;
 }
 
 bool NetAddr::IsLoopbackAddr() const {
+  if (IsLoopBackAddressWithoutIPv6Mapping()) {
+    return true;
+  }
+  const NetAddr* addr = this;
+  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 NetAddr::IsLoopBackAddressWithoutIPv6Mapping() const {
   const NetAddr* addr = this;
   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;
-    }
+
+  return addr->raw.family == AF_INET6 && IPv6ADDR_IS_LOOPBACK(&addr->inet6.ip);
+}
+
+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;
   }
-  return false;
+
+  nsAutoCString host;
+  nsContentUtils::ASCIIToLower(aAsciiHost, host);
+
+  return host.EqualsLiteral("localhost") ||
+         StringEndsWith(host, ".localhost"_ns);
 }
 
 bool NetAddr::IsIPAddrAny() const {
   if (this->raw.family == AF_INET) {
     if (this->inet.ip == htonl(INADDR_ANY)) {
       return true;
     }
   } else if (this->raw.family == AF_INET6) {
--- a/netwerk/dns/DNS.h
+++ b/netwerk/dns/DNS.h
@@ -132,16 +132,17 @@ union NetAddr {
     return *this;
   }
 
   NetAddr() { memset(this, 0, sizeof(NetAddr)); }
   explicit NetAddr(const PRNetAddr* prAddr);
 
   bool IsIPAddrAny() const;
   bool IsLoopbackAddr() const;
+  bool IsLoopBackAddressWithoutIPv6Mapping() const;
   bool IsIPAddrV4() const;
   bool IsIPAddrV4Mapped() const;
   bool IsIPAddrLocal() const;
   bool IsIPAddrShared() const;
   nsresult GetPort(uint16_t* aResult) const;
   bool ToStringBuffer(char* buf, uint32_t bufSize) const;
 };
 
@@ -228,12 +229,14 @@ class AddrInfo {
 // Copies the contents of a PRNetAddr to a NetAddr.
 // Does not do a ptr safety check!
 void PRNetAddrToNetAddr(const PRNetAddr* prAddr, NetAddr* addr);
 
 // Copies the contents of a NetAddr to a PRNetAddr.
 // Does not do a ptr safety check!
 void NetAddrToPRNetAddr(const NetAddr* addr, PRNetAddr* prAddr);
 
+bool IsLoopbackHostname(const nsACString& aAsciiHost);
+
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // DNS_h_
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -911,21 +911,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;
     }
@@ -934,16 +930,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);
@@ -983,282 +1021,292 @@ 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.
+
+    bool excludedFromTRR = false;
+
+    if (gTRRService && gTRRService->IsExcludedFromTRR(host)) {
+      flags |= RES_DISABLE_TRR;
+      excludedFromTRR = true;
+
+      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;
+    }
 
-      bool excludedFromTRR = false;
-      if (gTRRService && gTRRService->IsExcludedFromTRR(host)) {
-        flags |= RES_DISABLE_TRR;
-        excludedFromTRR = true;
+    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 (excludedFromTRR) {
+      rec->RecordReason(nsHostRecord::TRR_EXCLUDED);
+    }
 
-        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()));
 
-      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()));
+      // 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;
 
-      if (excludedFromTRR) {
-        rec->RecordReason(nsHostRecord::TRR_EXCLUDED);
+      // 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;
 
-      // 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);
-        }
+      // 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);
 
-        // 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) {
+            MutexAutoLock lock(addrUnspecRec->addr_info_lock);
+            if (addrUnspecRec->addr_info) {
+              // Search for any valid address in the AF_UNSPEC entry
+              // in the cache (not blocklisted and from the right
+              // family).
+              nsTArray<NetAddr> addresses;
+              for (const auto& addr : addrUnspecRec->addr_info->Addresses()) {
+                if ((af == addr.inet.family) &&
+                    !addrUnspecRec->Blocklisted(&addr)) {
+                  addresses.AppendElement(addr);
+                }
+              }
+              if (!addresses.IsEmpty()) {
+                addrRec->addr_info = new AddrInfo(
+                    addrUnspecRec->addr_info->Hostname(),
+                    addrUnspecRec->addr_info->CanonicalHostname(),
+                    addrUnspecRec->addr_info->IsTRR(), std::move(addresses));
+                addrRec->addr_info_gencnt++;
+                rec->CopyExpirationTimesAndFlagsFrom(unspecRec);
+              }
+            }
+          }
+          // 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>(&tempAddr);
-        // 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 {
-              MutexAutoLock lock(addrUnspecRec->addr_info_lock);
-              if (addrUnspecRec->addr_info) {
-                // Search for any valid address in the AF_UNSPEC entry
-                // in the cache (not blocklisted and from the right
-                // family).
-                nsTArray<NetAddr> addresses;
-                for (const auto& addr : addrUnspecRec->addr_info->Addresses()) {
-                  if ((af == addr.inet.family) &&
-                      !addrUnspecRec->Blocklisted(&addr)) {
-                    addresses.AppendElement(addr);
-                  }
-                }
-                if (!addresses.IsEmpty()) {
-                  addrRec->addr_info = new AddrInfo(
-                      addrUnspecRec->addr_info->Hostname(),
-                      addrUnspecRec->addr_info->CanonicalHostname(),
-                      addrUnspecRec->addr_info->IsTRR(), std::move(addresses));
-                  addrRec->addr_info_gencnt++;
-                  rec->CopyExpirationTimesAndFlagsFrom(unspecRec);
-                }
-              }
-            }
-            // 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
@@ -489,16 +489,24 @@ class nsHostResolver : public nsISupport
    * having the callback implementation return without doing anything).
    */
   nsresult ResolveHost(const nsACString& aHost, 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/httpserver/test/test_seizepower.js
+++ b/netwerk/test/httpserver/test/test_seizepower.js
@@ -10,27 +10,31 @@
 
 XPCOMUtils.defineLazyGetter(this, "PORT", function() {
   return srv.identity.primaryPort;
 });
 
 var srv;
 
 function run_test() {
+  Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
   srv = createServer();
 
   srv.registerPathHandler("/raw-data", handleRawData);
   srv.registerPathHandler("/called-too-late", handleTooLate);
   srv.registerPathHandler("/exceptions", handleExceptions);
   srv.registerPathHandler("/async-seizure", handleAsyncSeizure);
   srv.registerPathHandler("/seize-after-async", handleSeizeAfterAsync);
 
   srv.start(-1);
 
-  runRawTests(tests, testComplete(srv));
+  runRawTests(tests, function() {
+    Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+    testComplete(srv)();
+  });
 }
 
 function checkException(fun, err, msg) {
   try {
     fun();
   } catch (e) {
     if (e !== err && e.result !== err) {
       do_throw(msg);
--- 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
@@ -42,16 +42,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",
       Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
       0,
       null, // resolverInfo
       listener1,
@@ -102,9 +104,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);
     inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
     var answer = inRecord.getNextAddrAsString();
     Assert.ok(answer == "127.0.0.1" || answer == "::1");
@@ -26,26 +29,28 @@ var listener2 = {
     test3();
     do_test_finished();
   },
 };
 
 var listener3 = {
   onLookupComplete(inRequest, inRecord, inStatus) {
     Assert.equal(inStatus, Cr.NS_ERROR_OFFLINE);
+    cleanup();
     do_test_finished();
   },
 };
 
 const firstOriginAttributes = { userContextId: 1 };
 const secondOriginAttributes = { userContextId: 2 };
 
 // First, we resolve the address normally for first originAttributes.
 function run_test() {
   do_test_pending();
+  prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
   dns.asyncResolve(
     "localhost",
     Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
     0,
     null, // resolverInfo
     listener1,
     mainThread,
     firstOriginAttributes
@@ -79,11 +84,16 @@ function test3() {
       Ci.nsIDNSService.RESOLVE_OFFLINE,
       null, // resolverInfo
       listener3,
       mainThread,
       secondOriginAttributes
     );
   } catch (e) {
     Assert.equal(e.result, Cr.NS_ERROR_OFFLINE);
+    cleanup();
     do_test_finished();
   }
 }
+
+function cleanup() {
+  prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+}
--- a/netwerk/test/unit/test_http3.js
+++ b/netwerk/test/unit/test_http3.js
@@ -66,16 +66,19 @@ function run_test() {
   h3AltSvc = ":" + h3Port;
 
   h3Route = "foo.example.com:" + h3Port;
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
   prefs.setBoolPref("network.http.http3.enabled", true);
   prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+  // We always resolve elements of localDomains as it's hardcoded without the
+  // following pref:
+  prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
 
   // The certificate for the http3server server is for foo.example.com and
   // is signed by http2-ca.pem so add that cert to the trust list as a
   // signing cert.
   let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     Ci.nsIX509CertDB
   );
   addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
@@ -548,12 +551,13 @@ function test_version_fallback() {
   let listener = new CheckFallbackListener();
   chan.asyncOpen(listener);
   do_test_pending();
 }
 
 function testsDone() {
   prefs.clearUserPref("network.http.http3.enabled");
   prefs.clearUserPref("network.dns.localDomains");
+  prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
   dump("testDone\n");
   do_test_pending();
   h1Server.stop(do_test_finished);
 }
--- a/netwerk/test/unit/test_http3_421.js
+++ b/netwerk/test/unit/test_http3_421.js
@@ -29,16 +29,19 @@ function run_test() {
   h3AltSvc = ":" + h3Port;
 
   h3Route = "foo.example.com:" + h3Port;
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
   prefs.setBoolPref("network.http.http3.enabled", true);
   prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+  // We always resolve elements of localDomains as it's hardcoded without the
+  // following pref:
+  prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
 
   // The certificate for the http3server server is for foo.example.com and
   // is signed by http2-ca.pem so add that cert to the trust list as a
   // signing cert.
   let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     Ci.nsIX509CertDB
   );
   addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
@@ -160,12 +163,13 @@ function test_response_421() {
   let chan = makeChan(httpsOrigin + "Response421");
   chan.asyncOpen(listener);
   do_test_pending();
 }
 
 function testsDone() {
   prefs.clearUserPref("network.http.http3.enabled");
   prefs.clearUserPref("network.dns.localDomains");
+  prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
   dump("testDone\n");
   do_test_pending();
   do_test_finished();
 }
--- a/netwerk/test/unit/test_http3_alt_svc.js
+++ b/netwerk/test/unit/test_http3_alt_svc.js
@@ -31,16 +31,19 @@ function run_test() {
   h3AltSvc = ":" + h3Port;
 
   h3Route = "foo.example.com:" + h3Port;
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
   prefs.setBoolPref("network.http.http3.enabled", true);
   prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+  // We always resolve elements of localDomains as it's hardcoded without the
+  // following pref:
+  prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
 
   // The certificate for the http3server server is for foo.example.com and
   // is signed by http2-ca.pem so add that cert to the trust list as a
   // signing cert.
   let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     Ci.nsIX509CertDB
   );
   addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
@@ -116,10 +119,11 @@ function test_https_alt_svc() {
   dump("test_https_alt_svc()\n");
   do_test_pending();
   doTest(httpsOrigin + "http3-test2", h3Route, h3AltSvc);
 }
 
 function testsDone() {
   prefs.clearUserPref("network.http.http3.enabled");
   prefs.clearUserPref("network.dns.localDomains");
+  prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
   dump("testDone\n");
 }
--- a/netwerk/test/unit/test_http3_perf.js
+++ b/netwerk/test/unit/test_http3_perf.js
@@ -71,16 +71,19 @@ function run_test() {
   h3AltSvc = ":" + h3Port;
 
   h3Route = "foo.example.com:" + h3Port;
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
   prefs.setBoolPref("network.http.http3.enabled", true);
   prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+  // We always resolve elements of localDomains as it's hardcoded without the
+  // following pref:
+  prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
 
   // The certificate for the http3server server is for foo.example.com and
   // is signed by http2-ca.pem so add that cert to the trust list as a
   // signing cert.
   let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     Ci.nsIX509CertDB
   );
   addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
@@ -247,12 +250,13 @@ function test_download() {
   let chan = makeChan(httpsOrigin + listener.amount.toString());
   chan.asyncOpen(listener);
   do_test_pending();
 }
 
 function testsDone() {
   prefs.clearUserPref("network.http.http3.enabled");
   prefs.clearUserPref("network.dns.localDomains");
+  prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
   dump("testDone\n");
   do_test_pending();
   do_test_finished();
 }
--- a/netwerk/test/unit/test_ping_aboutnetworking.js
+++ b/netwerk/test/unit/test_ping_aboutnetworking.js
@@ -53,19 +53,22 @@ 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");
+    ps.clearUserPref("network.proxy.allow_hijacking_localhost");
   });
 
   let serverSocket = Cc["@mozilla.org/network/server-socket;1"].createInstance(
     Ci.nsIServerSocket
   );
   serverSocket.init(-1, true, -1);
 
   do_test_pending();
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -196,18 +196,20 @@ skip-if = true # Bug 863738
 [test_cookies_sync_failure.js]
 [test_cookies_thirdparty.js]
 [test_cookies_thirdparty_session.js]
 [test_cookies_upgrade_10.js]
 [test_dns_cancel.js]
 [test_data_protocol.js]
 [test_dns_service.js]
 [test_dns_offline.js]
+skip-if = socketprocess_networking # Bug 1640105
 [test_dns_onion.js]
 [test_dns_originAttributes.js]
+skip-if = socketprocess_networking # Bug 1640105
 [test_dns_localredirect.js]
 [test_dns_proxy_bypass.js]
 [test_dns_disabled.js]
 [test_domain_eviction.js]
 [test_duplicate_headers.js]
 [test_chunked_responses.js]
 [test_content_length_underrun.js]
 [test_event_sink.js]
--- a/testing/web-platform/meta/css/css-font-loading/fontface-override-descriptor-getter-setter.sub.html.ini
+++ b/testing/web-platform/meta/css/css-font-loading/fontface-override-descriptor-getter-setter.sub.html.ini
@@ -1,14 +1,14 @@
 [fontface-override-descriptor-getter-setter.sub.html]
   expected:
     if (os == "win") and ccov: [OK, TIMEOUT]
     if (os == "win") and debug and not webrender and (processor == "x86"): ["OK", "TIMEOUT"]
     if (os == "win") and debug and not webrender and (processor == "x86_64"): ["OK", "TIMEOUT"]
-    if (os == "win") and not debug and webrender and not fission: ["OK", "TIMEOUT"]
+    if (os == "win") and not debug and webrender: ["OK", "TIMEOUT"]
     if (os == "win") and debug and webrender: ["TIMEOUT", "OK"]
   [Initialize lineGapOverride with 'normal' should succeed]
     expected: FAIL
 
   [Initial value of lineGapOverride should be 'normal']
     expected: FAIL
 
   [Initialize ascentOverride with a non-percentage should fail]
@@ -65,15 +65,15 @@
   [Changing descentOverride to invalid value should fail]
     expected: FAIL
 
   [Initialize lineGapOverride with a non-percentage should fail]
     expected:
       if (os == "win") and ccov: [PASS, NOTRUN]
       if (os == "win") and debug and not webrender and (processor == "x86_64"): ["FAIL", "NOTRUN"]
       if (os == "win") and debug and not webrender and (processor == "x86"): ["FAIL", "NOTRUN"]
-      if (os == "win") and not debug and webrender and not fission: ["FAIL", "TIMEOUT"]
+      if (os == "win") and not debug and webrender: ["FAIL", "TIMEOUT"]
       if (os == "win") and debug and webrender: ["NOTRUN", "FAIL"]
       FAIL
 
   [Initialize ascentOverride with a percentage should succeed]
     expected: FAIL